"use client";
import { useState, useEffect } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import {
Home,
ChevronRight,
ArrowLeft,
Edit3,
Trash2,
Save,
X,
AlertCircle,
Loader2,
Download,
LogOut,
} from "lucide-react";
import FileViewer from "./FileViewer";
import { isText, isPDF, isImage } from "./FileIcon";
export default function ViewClient({
filePath,
fileData,
crumbs,
backHref,
error,
}) {
const router = useRouter();
const [adminToken, setAdminToken] = useState(null);
const [editing, setEditing] = useState(false);
const [editContent, setEditContent] = useState("");
const [saving, setSaving] = useState(false);
const [deleting, setDeleting] = useState(false);
const [confirmDelete, setConfirmDelete] = useState(false);
const [err, setErr] = useState(error);
const fileName = filePath.split("/").pop();
const canEdit = fileData && isText(fileName);
const isAdmin = !!adminToken;
useEffect(() => {
setAdminToken(sessionStorage.getItem("adminToken"));
}, []);
function startEdit() {
setEditContent(fileData?.content || "");
setEditing(true);
}
function cancelEdit() {
setEditing(false);
setEditContent("");
setErr(null);
}
async function handleSave() {
setSaving(true);
setErr(null);
try {
const res = await fetch("/api/github/file", {
method: "PUT",
headers: {
"Content-Type": "application/json",
"x-admin-token": adminToken,
},
body: JSON.stringify({
path: filePath,
content: editContent,
sha: fileData.sha,
message: `Edit ${fileName}`,
}),
});
if (!res.ok) {
const d = await res.json();
throw new Error(d.error || "Save failed");
}
setEditing(false);
router.refresh();
} catch (e) {
setErr(e.message);
} finally {
setSaving(false);
}
}
async function handleDelete() {
setDeleting(true);
setConfirmDelete(false);
try {
const res = await fetch("/api/github/file", {
method: "DELETE",
headers: {
"Content-Type": "application/json",
"x-admin-token": adminToken,
},
body: JSON.stringify({
path: filePath,
sha: fileData.sha,
message: `Delete ${fileName}`,
}),
});
if (!res.ok) {
const d = await res.json();
throw new Error(d.error || "Delete failed");
}
router.push(backHref);
} catch (e) {
setErr(e.message);
setDeleting(false);
}
}
function logout() {
sessionStorage.removeItem("adminToken");
setAdminToken(null);
}
return (
<div className="min-h-screen flex flex-col bg-gray-50">
{/* Top bar */}
<div className="flex items-center justify-between px-4 py-2 bg-white border-b border-gray-200 shadow-sm">
{/* Back + Breadcrumb */}
<nav className="flex items-center gap-1 text-sm flex-wrap text-gray-500">
<Link
href={backHref}
className="flex items-center gap-1 hover:text-indigo-600 transition-colors mr-1"
>
<ArrowLeft size={13} />
</Link>
{crumbs.map((c, i) => (
<span key={i} className="flex items-center gap-1">
{i > 0 && <ChevronRight size={12} />}
{c.href ? (
<Link
href={c.href}
className="hover:text-indigo-600 transition-colors flex items-center gap-1"
>
{i === 0 && <Home size={12} />}
{c.label}
</Link>
) : (
<span className="text-gray-800 font-medium">{c.label}</span>
)}
</span>
))}
</nav>
{/* Action buttons */}
<div className="flex items-center gap-2">
{/* Download */}
{fileData?.download_url && (
<a
href={fileData.download_url}
download={fileName}
className="flex items-center gap-1 text-xs px-2 py-1 rounded hover:bg-gray-100 transition-colors text-gray-500"
title="Download"
>
<Download size={13} />
</a>
)}
{isAdmin && (
<>
<span className="text-xs px-2 py-0.5 rounded bg-indigo-100 text-indigo-700 font-medium">
Admin
</span>
{canEdit && !editing && (
<button
onClick={startEdit}
className="flex items-center gap-1 text-xs px-2 py-1 rounded hover:bg-green-50 transition-colors text-green-600"
>
<Edit3 size={13} />
Edit
</button>
)}
{editing && (
<>
<button
onClick={handleSave}
disabled={saving}
className="flex items-center gap-1 text-xs px-2 py-1 rounded font-semibold transition-colors disabled:opacity-50 bg-green-600 text-white"
>
{saving ? (
<Loader2 size={13} className="animate-spin" />
) : (
<Save size={13} />
)}
Save
</button>
<button
onClick={cancelEdit}
className="flex items-center gap-1 text-xs px-2 py-1 rounded hover:bg-gray-100 transition-colors text-gray-500"
>
<X size={13} />
Cancel
</button>
</>
)}
{!editing && fileData && (
<button
onClick={() => setConfirmDelete(true)}
disabled={deleting}
className="flex items-center gap-1 text-xs px-2 py-1 rounded hover:bg-red-50 transition-colors text-red-500 disabled:opacity-50"
>
{deleting ? (
<Loader2 size={13} className="animate-spin" />
) : (
<Trash2 size={13} />
)}
Delete
</button>
)}
<button
onClick={logout}
title="Logout"
className="flex items-center gap-1 text-xs px-2 py-1 rounded hover:bg-red-50 transition-colors text-red-500"
>
<LogOut size={13} />
</button>
</>
)}
{!isAdmin && (
<Link
href="/admin"
className="text-xs px-2 py-1 rounded transition-colors text-gray-400 hover:text-indigo-600"
>
Login
</Link>
)}
</div>
</div>
{}
{err && (
<div className="flex items-center gap-2 px-4 py-2 text-sm bg-red-50 text-red-700 border-b border-red-200">
<AlertCircle size={14} />
{err}
<button onClick={() => setErr(null)} className="ml-auto">
<X size={14} />
</button>
</div>
)}
{}
{confirmDelete && (
<div
className="fixed inset-0 flex items-center justify-center z-50"
style={{ background: "rgba(0,0,0,0.6)" }}
>
<div className="bg-white rounded-xl shadow-2xl p-6 max-w-sm w-full mx-4">
<h2 className="font-bold text-gray-900 text-lg mb-2">
Delete file?
</h2>
<p className="text-sm text-gray-600 mb-4">
<strong>{fileName}</strong> will be permanently deleted from the
repository.
</p>
<div className="flex gap-3 justify-end">
<button
onClick={() => setConfirmDelete(false)}
className="px-4 py-1.5 rounded text-sm border border-gray-200 text-gray-700 hover:bg-gray-50"
>
Cancel
</button>
<button
onClick={handleDelete}
className="px-4 py-1.5 rounded text-sm font-semibold bg-red-600 text-white hover:bg-red-700"
>
Delete
</button>
</div>
</div>
</div>
)}
<div className="flex-1">
{}
{editing ? (
<div className="min-h-screen flex flex-col bg-white">
<div className="px-4 py-2 text-xs border-b bg-gray-50 border-gray-200 text-gray-500">
Editing: <strong>{fileName}</strong>
<span className="ml-2 opacity-60">
Changes will be committed to GitHub
</span>
</div>
<textarea
value={editContent}
onChange={(e) => setEditContent(e.target.value)}
className="flex-1 w-full p-6 resize-none focus:outline-none text-gray-800 text-sm bg-white"
style={{
fontFamily: '"Consolas", "Courier New", monospace',
lineHeight: "1.6",
minHeight: "calc(100vh - 6rem)",
}}
/>
</div>
) : fileData ? (
<FileViewer
name={fileName}
content={fileData.content}
downloadUrl={fileData.download_url}
/>
) : (
<div className="min-h-screen p-8 flex items-center justify-center bg-white">
<p className="text-gray-400 italic">
File not found or could not be loaded.
</p>
</div>
)}
</div>
</div>
);
}