feat(ui) : improve guide navigation, signup, and commissions

This commit is contained in:
2026-10-07 04:39:57 +07:00 Unverified
parent b35c1a4471
commit 22f5f96231
31 changed files with 737 additions and 400 deletions
+12 -13
View File
@@ -10,7 +10,6 @@ import {
BookOpenIcon,
ClipboardListIcon,
MessagesSquareIcon,
PlusIcon,
SwordsIcon,
} from "lucide-react";
@@ -22,7 +21,7 @@ export function AdminHeader() {
const pathname = usePathname();
return (<>
<CommissionPushEntryPrompt scope="admin" />
{pathname.startsWith("/admin/commission") && <CommissionPushEntryPrompt scope="admin" />}
<header className="border-b bg-background/95 supports-backdrop-filter:backdrop-blur">
<div className="mx-auto flex min-h-16 w-full max-w-7xl flex-nowrap items-center gap-2 px-4 py-2 sm:px-6">
<Link
@@ -33,10 +32,10 @@ export function AdminHeader() {
<Image src="/icon.png" alt="" width={36} height={36} className="size-9 object-contain" />
<span className="hidden lg:inline">ไกด์เกนชินไม่ใช่เกมมือถือ</span>
</Link>
<nav aria-label="Admin navigation" className="flex min-w-0 flex-1 items-center gap-1 overflow-x-auto sm:justify-end">
<nav aria-label="เมนูแอดมิน" className="flex min-w-0 flex-1 items-center gap-1 overflow-x-auto sm:justify-end">
<Button variant={pathname.startsWith("/admin/commission") ? "secondary" : "ghost"}
size="sm" nativeButton={false} render={<Link href="/admin/commission" />} aria-label="Commission">
<MessagesSquareIcon data-icon="inline-start" /><span className="hidden xl:inline">Commission</span>
size="sm" nativeButton={false} render={<Link href="/admin/commission" />} aria-label="คำขอคำนวณทีม">
<MessagesSquareIcon data-icon="inline-start" /><span className="hidden xl:inline">คำขอคำนวณทีม</span>
</Button>
<Button
variant={pathname === "/admin/stygian" ? "secondary" : "ghost"}
@@ -53,20 +52,20 @@ export function AdminHeader() {
size="sm"
nativeButton={false}
render={<Link href="/admin/activity" />}
aria-label="Activity"
aria-label="ประวัติการแก้ไข"
>
<ClipboardListIcon data-icon="inline-start" />
<span className="hidden xl:inline">Activity</span>
<span className="hidden xl:inline">ประวัติการแก้ไข</span>
</Button>
<Button
variant={pathname === "/admin/glossary" ? "secondary" : "ghost"}
size="sm"
nativeButton={false}
render={<Link href="/admin/glossary" />}
aria-label="Glossary"
aria-label="คำศัพท์"
>
<BookOpenIcon data-icon="inline-start" />
<span className="hidden xl:inline">Glossary</span>
<span className="hidden xl:inline">คำศัพท์</span>
</Button>
<Button
variant={
@@ -75,20 +74,20 @@ export function AdminHeader() {
size="sm"
nativeButton={false}
render={<Link href="/admin/image/overview" />}
aria-label="Image Generator"
aria-label="ส่งออกภาพ"
>
<ImageIcon data-icon="inline-start" />
<span className="hidden xl:inline">Image Generator</span>
<span className="hidden xl:inline">ส่งออกภาพ</span>
</Button>
<Button
variant={pathname === "/admin" ? "secondary" : "ghost"}
size="sm"
nativeButton={false}
render={<Link href="/admin" />}
aria-label="Dashboard"
aria-label="ภาพรวม"
>
<LayoutDashboardIcon data-icon="inline-start" />
<span className="hidden xl:inline">Dashboard</span>
<span className="hidden xl:inline">ภาพรวม</span>
</Button>
<Button
+5 -5
View File
@@ -30,14 +30,14 @@ export function AutosaveStatus({
return (
<Badge variant="secondary">
<Spinner data-icon="inline-start" />
Saving…
กำลังบันทึก…
</Badge>
);
if (state === "unsaved")
return (
<Badge variant="outline">
<ClockIcon data-icon="inline-start" />
Waiting to save…
รอบันทึก…
</Badge>
);
if (state === "incomplete")
@@ -123,11 +123,11 @@ export function AutosaveStatus({
<div className="flex items-center gap-2">
<Badge variant="destructive">
<AlertCircleIcon data-icon="inline-start" />
Autosave failed
บันทึกอัตโนมัติไม่สำเร็จ
</Badge>
{onSave ? (
<Button type="button" variant="outline" size="sm" onClick={onSave}>
Save now
ลองบันทึกอีกครั้ง
</Button>
) : null}
</div>
@@ -135,7 +135,7 @@ export function AutosaveStatus({
return (
<Badge variant="secondary">
<CheckIcon data-icon="inline-start" />
Saved
บันทึกแล้ว
</Badge>
);
}
+9 -5
View File
@@ -10,10 +10,10 @@ import { navigationIconPath } from "@/lib/navigation";
import { cn } from "@/lib/utils";
const sections = [
{ slug: "weapon", label: "Weapons" },
{ slug: "artifact", label: "Artifacts & Stats" },
{ slug: "constellations", label: "Constellations" },
{ slug: "team", label: "Team Calc" },
{ slug: "weapon", label: "อาวุธ" },
{ slug: "artifact", label: "อาร์ติแฟกต์และค่าสถานะ" },
{ slug: "constellations", label: "กลุ่มดาว" },
{ slug: "team", label: "คำนวณทีม" },
] as const;
export function GuideSectionNav({
@@ -37,19 +37,21 @@ export function GuideSectionNav({
return (
<ScrollableNav
ariaLabel="เมนูแก้ไข Guide"
activeKey={current}
className="w-full rounded-xl border bg-card p-2"
>
<Link
href={`/admin/${characterKey}`}
prefetch={false}
onClick={confirmNavigation}
aria-current={current === "overview" ? "page" : undefined}
className={cn(
"flex min-h-8 shrink-0 items-center gap-2 rounded-md px-3 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring",
current === "overview" ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted hover:text-foreground",
)}
>
<Image src={navigationIconPath("overview")!} alt="" width={20} height={20} className="size-5 object-contain" />
Overview
ภาพรวม
</Link>
{sections.map((section) => {
const iconPath = navigationIconPath(section.slug);
@@ -59,6 +61,7 @@ export function GuideSectionNav({
href={`/admin/${characterKey}/${section.slug}`}
prefetch={false}
onClick={confirmNavigation}
aria-current={current === section.slug ? "page" : undefined}
className={cn(
"flex min-h-8 shrink-0 items-center gap-2 rounded-md px-3 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring",
current === section.slug ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted hover:text-foreground",
@@ -75,6 +78,7 @@ export function GuideSectionNav({
href={`/admin/${characterKey}/${extra.slug}`}
prefetch={false}
onClick={confirmNavigation}
aria-current={current === extra.slug ? "page" : undefined}
className={cn(
"flex min-h-8 shrink-0 items-center gap-2 rounded-md px-3 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring",
current === extra.slug ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted hover:text-foreground",
@@ -52,7 +52,7 @@ export function GuideVisibilityControl({
toast.error(message);
return;
}
toast.success(nextPublic ? "เผยแพร่ Guide แล้ว" : "เปลี่ยน Guide เป็น Private แล้ว");
toast.success(nextPublic ? "เผยแพร่ไกด์แล้ว" : "ซ่อนไกด์จากเว็บไซต์แล้ว");
setOpen(false);
router.refresh();
});
@@ -70,13 +70,13 @@ export function GuideVisibilityControl({
onClick={() => setOpen(true)}
>
{pending ? <Spinner data-icon="inline-start" /> : isPublic ? <EyeOffIcon data-icon="inline-start" /> : <EyeIcon data-icon="inline-start" />}
{isPublic ? "Make private" : "Make Publish"}
{isPublic ? "ซ่อนจากเว็บไซต์" : "เผยแพร่ไกด์"}
</Button>
<AlertDialog open={open} onOpenChange={setOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
{isPublic ? `เปลี่ยน ${guideName} เป็น Private?` : `เผยแพร่ ${guideName} เป็น Public?`}
{isPublic ? `ซ่อน ${guideName} จากเว็บไซต์?` : `เผยแพร่ ${guideName} ให้ทุกคนดู?`}
</AlertDialogTitle>
<AlertDialogDescription>
{isPublic
@@ -101,7 +101,7 @@ export function GuideVisibilityControl({
}}
>
{pending && <Spinner data-icon="inline-start" />}
{isPublic ? "ยืนยัน Private" : "ยืนยัน Public"}
{isPublic ? "ยืนยันการซ่อน" : "ยืนยันการเผยแพร่"}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>