From e3692fe75bbd57aaac3c1b6c45549d0571feb1ec Mon Sep 17 00:00:00 2001 From: gunshiz Date: Mon, 31 Aug 2026 00:48:33 +0700 Subject: [PATCH] feat : better ui --- app/[character]/[page]/page.tsx | 2 +- app/[character]/page.tsx | 4 +- .../[guideId]/artifacts/image/route.tsx | 52 +++-- .../guides/[guideId]/weapons/image/route.tsx | 6 +- app/api/teams/[teamId]/image/route.tsx | 8 +- app/layout.tsx | 2 + app/page.tsx | 4 +- components/admin/autosave-status.tsx | 19 +- components/admin/create-guide-flow.tsx | 4 +- components/admin/guide-overview-form.tsx | 6 +- components/admin/guide-section-editor.tsx | 184 +++++++++++++----- components/page-progress.tsx | 90 +++++++++ components/public/structured-directory.tsx | 8 +- components/public/structured-guide.tsx | 70 +++++-- components/public/team-search.tsx | 21 +- components/ui/accordion.tsx | 5 +- components/ui/progress.tsx | 83 ++++++++ lib/guides/schemas.ts | 2 +- lib/guides/stats.ts | 9 + 19 files changed, 465 insertions(+), 114 deletions(-) create mode 100644 components/page-progress.tsx create mode 100644 components/ui/progress.tsx diff --git a/app/[character]/[page]/page.tsx b/app/[character]/[page]/page.tsx index a9cab81..04262e7 100644 --- a/app/[character]/[page]/page.tsx +++ b/app/[character]/[page]/page.tsx @@ -11,7 +11,7 @@ export async function generateMetadata({ params }: { params: Params }): Promise< await connection(); const { character, page } = await params; const guide = await getCachedPublicGuide(character); - return guide ? { title: `${page} — ${guide.name} Build Guide`, description: guide.overview } : { title: "ไม่พบ Guide" }; + return guide ? { title: `${page} — ${guide.name} — คู่มือ`, description: guide.overview } : { title: "ไม่พบไกด์" }; } export default async function GuideSectionPage({ params }: { params: Params }) { diff --git a/app/[character]/page.tsx b/app/[character]/page.tsx index fa11f6c..4020ad6 100644 --- a/app/[character]/page.tsx +++ b/app/[character]/page.tsx @@ -15,8 +15,8 @@ export async function generateMetadata({ await connection(); const guide = await getCachedPublicGuide((await params).character); return guide - ? { title: `${guide.name} Build Guide`, description: guide.overview } - : { title: "ไม่พบ Guide" }; + ? { title: `${guide.name} — คู่มือ`, description: guide.overview } + : { title: "ไม่พบไกด์" }; } export default async function GuideOverviewPage({ diff --git a/app/api/guides/[guideId]/artifacts/image/route.tsx b/app/api/guides/[guideId]/artifacts/image/route.tsx index d696eb3..2a9b5b4 100644 --- a/app/api/guides/[guideId]/artifacts/image/route.tsx +++ b/app/api/guides/[guideId]/artifacts/image/route.tsx @@ -36,11 +36,29 @@ function metric(value: string): string { } function statLabel(value: string): string { - return value === "EM" - ? "Elemental Mastery" - : value === "ER" - ? "Energy Recharge" - : value; + const labels: Record = { + HP: "พลังชีวิต", + ATK: "พลังโจมตี", + DEF: "พลังป้องกัน", + EM: "ความชำนาญธาตุ", + ER: "ฟื้นฟูพลังงาน", + "CRIT RATE": "อัตราคริ", + "CRIT DAMAGE": "ความแรงคริ", + "ATK%": "พลังโจมตี%", + "HP%": "พลังชีวิต%", + "DEF%": "พลังป้องกัน%", + "CRIT Rate%/CRIT DMG%": "อัตราคริ/ความแรงคริ", + "Pyro DMG%": "ความเสียหายธาตุไฟ%", + "Hydro DMG%": "ความเสียหายธาตุน้ำ%", + "Cryo DMG%": "ความเสียหายธาตุน้ำแข็ง%", + "Electro DMG%": "ความเสียหายธาตุไฟฟ้า%", + "Anemo DMG%": "ความเสียหายธาตุลม%", + "Geo DMG%": "ความเสียหายธาตุดิน%", + "Dendro DMG%": "ความเสียหายธาตุไม้%", + "Physical DMG%": "ความเสียหายกายภาพ%", + "Healing Bonus%": "โบนัสการรักษา%", + }; + return labels[value] ?? value; } async function pngDataUrl(url: string): Promise { @@ -72,7 +90,7 @@ export async function GET( ) { const { guideId } = await context.params; const guide = await getCachedPublicArtifactExport(guideId); - if (!guide) return new Response("Guide not found", { status: 404 }); + if (!guide) return new Response("ไม่พบไกด์", { status: 404 }); const talents = normalizeTalentPriorities(guide.profile?.talents ?? []); const [fonts, groupAssets, statIconAssets, talentAssets, targetStatAssets] = await Promise.all([ @@ -131,7 +149,7 @@ export async function GET( อาร์ติแฟกต์และสเตตัสที่แนะนำ
- Artifacts & Stats + อาร์ติแฟกต์และค่าสถานะ
@@ -150,7 +168,7 @@ export async function GET( }} >
- {group.name} + {group.name === "Late Game" ? "เลทเกม" : group.name}
{group.rows.map((row, rowIndex) => ( @@ -188,15 +206,15 @@ export async function GET(
- Artifact Stats + ออฟหลัก
{[ - { label: "Sands", values: profile.sands, icon: statIconAssets[0] }, - { label: "Goblet", values: profile.goblet, icon: statIconAssets[1] }, - { label: "Circlet", values: profile.circlet, icon: statIconAssets[2] }, + { label: "นาฬิกา", values: profile.sands, icon: statIconAssets[0] }, + { label: "ถ้วย", values: profile.goblet, icon: statIconAssets[1] }, + { label: "หัว", values: profile.circlet, icon: statIconAssets[2] }, ].map((stat) => ( -
+
{stat.label}
@@ -210,7 +228,7 @@ export async function GET( {profile.substats.length ? (
- Stat Priority: + ลำดับค่าสถานะ:
{profile.substats.map((stat, index) => {statLabel(stat)})}
@@ -222,7 +240,7 @@ export async function GET(
รายละเอียดเพิ่มเติม
{profile.targets.length ? (
-
Target Stats
+
ค่าสถานะเป้าหมาย
{profile.targets.map((target, index) => { const iconIndex = targetStatOptions.findIndex((stat) => stat === normalizeTargetStat(target.stat)); return ( @@ -241,7 +259,7 @@ export async function GET(
{talentAssets[index] ? : null} - {talent.type}{talent.level ? ` Lv.${talent.level}+` : ""} + {talent.type}{talent.level ? ` เลเวล ${talent.level}+` : ""} {talentTypeLabel(talent.type)} {index < talents.length - 1 ? {talent.relationToNext === "gte" ? ">=" : ">"} : null} @@ -269,7 +287,7 @@ export async function GET( fonts: fonts.length ? fonts : undefined, headers: { "Cache-Control": "no-store", - "Content-Disposition": imageContentDisposition(`${guide.guideName} Artifacts`, "artifacts"), + "Content-Disposition": imageContentDisposition(`${guide.guideName} อาร์ติแฟกต์`, "artifacts"), "Content-Type": "image/png", }, }, diff --git a/app/api/guides/[guideId]/weapons/image/route.tsx b/app/api/guides/[guideId]/weapons/image/route.tsx index 4a4dae9..5e3fe0e 100644 --- a/app/api/guides/[guideId]/weapons/image/route.tsx +++ b/app/api/guides/[guideId]/weapons/image/route.tsx @@ -50,7 +50,7 @@ export async function GET( ) { const { guideId } = await context.params; const guide = await getCachedPublicWeaponExport(guideId); - if (!guide) return new Response("Guide not found", { status: 404 }); + if (!guide) return new Response("ไม่พบไกด์", { status: 404 }); const [fonts, weaponImages] = await Promise.all([ anuphanFonts, @@ -77,7 +77,7 @@ export async function GET(
อาวุธที่แนะนำ
-
Weapons
+
อาวุธ
{guide.weapons.map((row, index) => (
sum + Number(member.damage), @@ -135,7 +135,7 @@ export async function GET(
-
C{member.constellation} · {member.character.name}
+
C{member.constellation} {member.character.name}
{amount(damage)}
@@ -161,11 +161,11 @@ export async function GET(
-
Team Rotation
+
Rotation
{team.combo}
- DPS: {amount(dps)} + DPS): {amount(dps)} | DPR: {amount(total)}
diff --git a/app/layout.tsx b/app/layout.tsx index 3d0786f..1a5fc21 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -2,6 +2,7 @@ import type { Metadata } from "next"; import { Anuphan, Geist_Mono } from "next/font/google"; import { DeploymentUpdateNotifier } from "@/components/deployment-update-notifier"; +import { PageProgress } from "@/components/page-progress"; import { Toaster } from "@/components/ui/sonner"; import { TooltipProvider } from "@/components/ui/tooltip"; @@ -37,6 +38,7 @@ export default function RootLayout({ children }: LayoutProps<"/">) { > + {children} diff --git a/app/page.tsx b/app/page.tsx index 4cf926c..1dea880 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -15,13 +15,13 @@ export default async function HomePage() {

- Genshin Impact Build Guides + คู่มือการจัดตัวละครเกนชินอิมแพกต์

เลือกตัวละครแล้วส่องดูได้เลย

- ไกด์ตัวละคร Weapons, Artifacts, Constellations และ Team Calc + ไกด์ตัวละคร อาวุธ อาร์ติแฟกต์ กลุ่มดาว และคำนวณทีม

diff --git a/components/admin/autosave-status.tsx b/components/admin/autosave-status.tsx index 3c20a83..8c5aabf 100644 --- a/components/admin/autosave-status.tsx +++ b/components/admin/autosave-status.tsx @@ -1,13 +1,28 @@ import { AlertCircleIcon, CheckIcon, ClockIcon } from "lucide-react"; +import { useState } from "react"; +import { Alert, AlertDescription } from "@/components/ui/alert"; import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; import { Spinner } from "@/components/ui/spinner"; import type { AutosaveState } from "@/hooks/use-autosave"; -export function AutosaveStatus({ state }: { state: AutosaveState }) { +export function AutosaveStatus({ state, issues = [] }: { state: AutosaveState; issues?: readonly string[] }) { + const [showIssues, setShowIssues] = useState(false); + if (state === "saving") return Saving…; if (state === "unsaved") return Waiting to save…; - if (state === "incomplete") return Complete required fields; + if (state === "incomplete") return
+ + {showIssues ? + : null} +
; if (state === "error") return Autosave failed; return Saved; } diff --git a/components/admin/create-guide-flow.tsx b/components/admin/create-guide-flow.tsx index d90a91e..687459a 100644 --- a/components/admin/create-guide-flow.tsx +++ b/components/admin/create-guide-flow.tsx @@ -15,6 +15,8 @@ import { Field, FieldDescription, FieldGroup, FieldLabel } from "@/components/ui import { InputGroup, InputGroupAddon, InputGroupInput } from "@/components/ui/input-group"; import { Spinner } from "@/components/ui/spinner"; import { Textarea } from "@/components/ui/textarea"; +import { rarityGradientClass } from "@/lib/catalog/rarity"; +import { cn } from "@/lib/utils"; type Character = { key: string; name: string; element: string | null; rarity: number | null; imageUrl: string }; @@ -77,7 +79,7 @@ export function CreateGuideFlow({ characters, existingKeys }: { characters: Char const disabled = existing.has(character.key); return ( -
{character.name}
+
{character.name}
{character.name} {character.element ?? "Unknown element"} diff --git a/components/admin/guide-overview-form.tsx b/components/admin/guide-overview-form.tsx index cb2519c..3a6f3b2 100644 --- a/components/admin/guide-overview-form.tsx +++ b/components/admin/guide-overview-form.tsx @@ -15,11 +15,15 @@ export function GuideOverviewForm({ guide }: { guide: { id: string; version: num const [overview, setOverview] = useState(guide.overview); const [cover, setCover] = useState({ id: guide.coverMediaId, url: guide.coverUrl }); const value = { coverMediaId: cover.id, overview }; + const incompleteIssues = [ + !cover.id ? "Guide Image is required" : null, + !overview.trim() ? "Overview is required" : null, + ].filter((issue): issue is string => Boolean(issue)); const autosave = useAutosave({ value, valid: Boolean(cover.id && overview.trim()), initialVersion: guide.version, persist: (next, expectedVersion) => saveGuideOverview(guide.id, { ...next, expectedVersion }), }); - return Overviewภาพและข้อความนี้แสดงบนหน้าแรกและส่วนแนะนำของ GuideGuide Image
{`Guide
setCover(asset)} />
Overview