feat(i18n) : add personal languages and compact weapon exports
CI / Verify (push) Successful in 1m18s
CI / Build immutable images and deploy (push) Successful in 1m57s

This commit is contained in:
2026-09-14 16:44:44 +07:00 Unverified
parent f214d330f0
commit e0794822b1
72 changed files with 4199 additions and 1011 deletions
+15 -13
View File
@@ -1,5 +1,6 @@
"use client";
import { useTranslation } from "@/components/i18n/locale-provider";
import { searchItems } from "@/lib/search";
import Image from "next/image";
@@ -23,6 +24,7 @@ import { cn } from "@/lib/utils";
type Character = { key: string; name: string; aliases: string[]; element: string | null; rarity: number | null; imageUrl: string };
export function CreateGuideFlow({ characters, existingKeys }: { characters: Character[]; existingKeys: string[] }) {
const { t } = useTranslation();
const router = useRouter();
const [query, setQuery] = useState("");
const [selected, setSelected] = useState<Character | null>(null);
@@ -39,13 +41,13 @@ export function CreateGuideFlow({ characters, existingKeys }: { characters: Char
return (
<Card className="mx-auto w-full max-w-4xl">
<CardHeader>
<CardTitle>ตั้งค่า Guide ของ {selected.name}</CardTitle>
<CardDescription>เพิ่มภาพหน้าปก และใส่ Overview ได้หากต้องการ</CardDescription>
<CardTitle>{t("ตั้งค่า Guide ของ")}{" "}{selected.name}</CardTitle>
<CardDescription>{t("เพิ่มภาพหน้าปก และใส่ Overview ได้หากต้องการ")}</CardDescription>
</CardHeader>
<CardContent>
<FieldGroup>
<Field>
<FieldLabel>Guide Image</FieldLabel>
<FieldLabel>{t("Guide Image")}</FieldLabel>
{cover ? (
<div className="relative aspect-square max-w-md overflow-hidden rounded-lg border">
<Image src={cover.url} alt={`Guide cover ${selected.name}`} fill className="object-cover" />
@@ -54,20 +56,20 @@ export function CreateGuideFlow({ characters, existingKeys }: { characters: Char
<MediaUploader onReady={setCover} />
</Field>
<Field>
<FieldLabel htmlFor="overview">Overview (optional)</FieldLabel>
<Textarea id="overview" value={overview} maxLength={500} onChange={(event) => setOverview(event.target.value)} placeholder="สรุปแนวทางเล่น จุดเด่น และสิ่งที่ผู้ชมจะพบใน Guide นี้" />
<FieldDescription>{overview.length}/500 ตัวอักษร</FieldDescription>
<FieldLabel htmlFor="overview">{t("Overview (optional)")}</FieldLabel>
<Textarea id="overview" value={overview} maxLength={500} onChange={(event) => setOverview(event.target.value)} placeholder={t("สรุปแนวทางเล่น จุดเด่น และสิ่งที่ผู้ชมจะพบใน Guide นี้")} />
<FieldDescription>{overview.length}{t("/500 ตัวอักษร")}</FieldDescription>
</Field>
</FieldGroup>
</CardContent>
<CardFooter className="justify-between">
<Button type="button" variant="outline" onClick={() => setSelected(null)}><ArrowLeftIcon data-icon="inline-start" />เลือกใหม่</Button>
<Button type="button" variant="outline" onClick={() => setSelected(null)}><ArrowLeftIcon data-icon="inline-start" />{t("เลือกใหม่")}</Button>
<Button disabled={pending || !cover} onClick={() => startTransition(async () => {
const result = await createStructuredGuide({ characterKey: selected.key, coverMediaId: cover?.id, overview });
if (result.status === "error") { toast.error(result.message); return; }
toast.success("สร้าง Guide แล้ว");
if (result.status === "error") { toast.error(t(result.message)); return; }
toast.success(t("สร้าง Guide แล้ว"));
router.push(result.location!);
})}>{pending ? <Spinner data-icon="inline-start" /> : <CheckIcon data-icon="inline-start" />}สร้าง Guide</Button>
})}>{pending ? <Spinner data-icon="inline-start" /> : <CheckIcon data-icon="inline-start" />}{t("สร้าง Guide")}</Button>
</CardFooter>
</Card>
);
@@ -77,7 +79,7 @@ export function CreateGuideFlow({ characters, existingKeys }: { characters: Char
<div className="flex flex-col gap-6">
<InputGroup className="max-w-xl">
<InputGroupAddon><SearchIcon /></InputGroupAddon>
<InputGroupInput value={query} onChange={(event) => setQuery(event.target.value)} placeholder="ค้นหาตัวละคร…" aria-label="ค้นหาตัวละคร" />
<InputGroupInput value={query} onChange={(event) => setQuery(event.target.value)} placeholder={t("ค้นหาตัวละคร…")} aria-label={t("ค้นหาตัวละคร")} />
</InputGroup>
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
@@ -90,8 +92,8 @@ export function CreateGuideFlow({ characters, existingKeys }: { characters: Char
<CardTitle>{character.name}</CardTitle>
<CardDescription>{character.element ?? "Unknown element"}</CardDescription>
</CardHeader>
<CardContent className="flex gap-2">{character.rarity ? <Badge variant="secondary">{character.rarity}★</Badge> : null}{disabled ? <Badge variant="outline">มี Guide แล้ว</Badge> : null}</CardContent>
<CardFooter className="pb-4"><Button className="w-full" disabled={disabled} onClick={() => setSelected(character)}>เลือกตัวละคร</Button></CardFooter>
<CardContent className="flex gap-2">{character.rarity ? <Badge variant="secondary">{character.rarity}★</Badge> : null}{disabled ? <Badge variant="outline">{t("มี Guide แล้ว")}</Badge> : null}</CardContent>
<CardFooter className="pb-4"><Button className="w-full" disabled={disabled} onClick={() => setSelected(character)}>{t("เลือกตัวละคร")}</Button></CardFooter>
</Card>
);
})}