feat(public): render responsive character guides
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
import { SiteHeader } from "@/components/public/site-header";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
|
||||
export default function GuideLoading() {
|
||||
return (
|
||||
<div className="min-h-svh">
|
||||
<SiteHeader />
|
||||
<div className="border-b p-4"><Skeleton className="mx-auto h-16 max-w-7xl" /></div>
|
||||
<main className="mx-auto max-w-5xl space-y-5 px-4 py-10">
|
||||
<Skeleton className="h-72 w-full" />
|
||||
<Skeleton className="h-8 w-1/3" />
|
||||
<Skeleton className="h-28 w-full" />
|
||||
<Skeleton className="h-80 w-full" />
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import type { Metadata } from "next";
|
||||
import { notFound, permanentRedirect } from "next/navigation";
|
||||
import { connection } from "next/server";
|
||||
|
||||
import { GuideShell } from "@/components/public/guide-shell";
|
||||
import { loadPublicPage } from "@/lib/content/loaders";
|
||||
|
||||
type RouteParams = Promise<{ character: string; page: string }>;
|
||||
|
||||
export async function generateMetadata({ params }: { params: RouteParams }): Promise<Metadata> {
|
||||
await connection();
|
||||
const { character, page } = await params;
|
||||
const result = await loadPublicPage(character, page);
|
||||
if (result.type !== "page") return { title: "ไม่พบคู่มือ" };
|
||||
return {
|
||||
title: `${result.snapshot.page.title} — ${result.snapshot.character.nameTh}`,
|
||||
description: result.snapshot.character.description ?? undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export default async function GuidePage({ params }: { params: RouteParams }) {
|
||||
await connection();
|
||||
const { character, page } = await params;
|
||||
const result = await loadPublicPage(character, page);
|
||||
if (result.type === "redirect") permanentRedirect(result.location);
|
||||
if (result.type === "not-found") notFound();
|
||||
return <GuideShell snapshot={result.snapshot} />;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { notFound, permanentRedirect } from "next/navigation";
|
||||
import { connection } from "next/server";
|
||||
|
||||
import { loadPublicPage } from "@/lib/content/loaders";
|
||||
|
||||
export default async function CharacterPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ character: string }>;
|
||||
}) {
|
||||
await connection();
|
||||
const { character } = await params;
|
||||
const result = await loadPublicPage(character);
|
||||
if (result.type === "redirect") permanentRedirect(result.location);
|
||||
notFound();
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { DirectorySkeleton } from "@/components/public/directory-skeleton";
|
||||
import { SiteHeader } from "@/components/public/site-header";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
|
||||
export default function Loading() {
|
||||
return (
|
||||
<div className="min-h-svh">
|
||||
<SiteHeader />
|
||||
<main className="mx-auto w-full max-w-7xl space-y-8 px-4 py-10 sm:px-6 sm:py-14">
|
||||
<div className="space-y-3">
|
||||
<Skeleton className="h-4 w-32" />
|
||||
<Skeleton className="h-10 w-full max-w-2xl" />
|
||||
<Skeleton className="h-6 w-full max-w-xl" />
|
||||
</div>
|
||||
<DirectorySkeleton />
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import Link from "next/link";
|
||||
import { MapPinOffIcon } from "lucide-react";
|
||||
|
||||
import { SiteHeader } from "@/components/public/site-header";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Empty,
|
||||
EmptyContent,
|
||||
EmptyDescription,
|
||||
EmptyHeader,
|
||||
EmptyMedia,
|
||||
EmptyTitle,
|
||||
} from "@/components/ui/empty";
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<div className="min-h-svh">
|
||||
<SiteHeader />
|
||||
<main className="mx-auto grid min-h-[calc(100svh-4rem)] max-w-3xl place-items-center px-4 py-16">
|
||||
<Empty>
|
||||
<EmptyHeader>
|
||||
<EmptyMedia variant="icon"><MapPinOffIcon aria-hidden="true" /></EmptyMedia>
|
||||
<EmptyTitle>ไม่พบคู่มือนี้</EmptyTitle>
|
||||
<EmptyDescription>
|
||||
หน้านี้อาจถูกซ่อน เปลี่ยนชื่อ หรือยังไม่มีเนื้อหาที่เผยแพร่
|
||||
</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
<EmptyContent>
|
||||
<Button render={<Link href="/" />}>กลับไปหน้ารวมตัวละคร</Button>
|
||||
</EmptyContent>
|
||||
</Empty>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+20
-61
@@ -1,68 +1,27 @@
|
||||
import Image from "next/image";
|
||||
import { connection } from "next/server";
|
||||
|
||||
import { CharacterDirectory } from "@/components/public/character-directory";
|
||||
import { SiteHeader } from "@/components/public/site-header";
|
||||
import { loadPublicDirectory } from "@/lib/content/loaders";
|
||||
|
||||
export default async function HomePage() {
|
||||
await connection();
|
||||
const characters = await loadPublicDirectory();
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="flex flex-col flex-1 items-center justify-center bg-zinc-50 font-sans dark:bg-black">
|
||||
<main className="flex flex-1 w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
|
||||
<Image
|
||||
className="dark:invert h-5 w-[100px]"
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={100}
|
||||
height={20}
|
||||
priority
|
||||
/>
|
||||
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
|
||||
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
|
||||
To get started, edit the{" "}
|
||||
<code className="rounded bg-black/[.06] px-1.5 py-0.5 font-mono text-[0.9em] dark:bg-white/[.08]">
|
||||
page.tsx
|
||||
</code>{" "}
|
||||
file.
|
||||
<div className="min-h-svh bg-background">
|
||||
<SiteHeader />
|
||||
<main className="mx-auto w-full max-w-7xl px-4 py-10 sm:px-6 sm:py-14">
|
||||
<header className="mb-8 max-w-3xl space-y-3">
|
||||
<p className="text-sm font-medium text-primary">คลังคู่มือตัวละคร</p>
|
||||
<h1 className="font-heading text-3xl font-semibold tracking-tight sm:text-4xl">
|
||||
หาคู่มือที่ต้องการ แล้วเริ่มจัดทีมได้เลย
|
||||
</h1>
|
||||
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
|
||||
Looking for a starting point or more instructions? Head over to{" "}
|
||||
<a
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
||||
>
|
||||
Templates
|
||||
</a>{" "}
|
||||
or the{" "}
|
||||
<a
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
||||
>
|
||||
Learning
|
||||
</a>{" "}
|
||||
center.
|
||||
<p className="text-base leading-7 text-muted-foreground sm:text-lg">
|
||||
ค้นหาตามชื่อ ธาตุ หรือบทบาท ทุกหน้าจัดรูปแบบมาให้อ่านง่ายทั้งมือถือและเดสก์ท็อป
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
|
||||
<a
|
||||
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
className="dark:invert h-[14px] w-4"
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={16}
|
||||
height={14}
|
||||
/>
|
||||
Deploy Now
|
||||
</a>
|
||||
<a
|
||||
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Documentation
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
<CharacterDirectory characters={characters} />
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user