From f5cfe0a22ee6e6e2d36e3c12743c168677e343b4 Mon Sep 17 00:00:00 2001 From: gunshiz Date: Sat, 3 Oct 2026 14:51:40 +0700 Subject: [PATCH] feat : update --- README.md | 6 +++--- app/admin/[character]/[page]/page.tsx | 2 +- app/admin/[character]/page.tsx | 2 +- app/admin/[character]/preview/[[...page]]/page.tsx | 2 +- app/admin/activity/page.tsx | 2 +- app/admin/commission/page.tsx | 2 +- app/admin/create/page.tsx | 2 +- app/admin/glossary/page.tsx | 2 +- app/admin/image/layout.tsx | 2 +- app/admin/layout.tsx | 10 +++++++++- app/admin/loading.tsx | 7 ------- app/admin/page.tsx | 2 +- app/admin/stygian/page.tsx | 2 +- app/admin/templates/page.tsx | 5 ----- app/{ => auth}/check-email/page.tsx | 2 +- app/{ => auth}/forgot-password/page.tsx | 0 app/{ => auth}/login/page.tsx | 2 +- app/{ => auth}/password-reset/page.tsx | 2 +- app/{ => auth}/register/page.tsx | 2 +- app/commission/login/page.tsx | 5 ----- app/commission/page.tsx | 6 +++--- app/commission/pay/[id]/page.tsx | 2 +- app/commission/tickets/page.tsx | 2 +- app/profile/page.tsx | 4 ++-- app/robots.ts | 2 +- components/admin/guide-section-editor.tsx | 7 +------ components/admin/guide-visibility-control.tsx | 4 ---- components/auth/account-form.tsx | 8 ++++---- components/auth/account-menu.tsx | 2 +- components/auth/email-action-form.tsx | 6 +++--- components/auth/registration-otp-form.tsx | 2 +- lib/auth/return-path.test.ts | 2 +- lib/auth/return-path.ts | 2 +- lib/guides/queries.ts | 1 - next.config.ts | 4 ---- tests/public-performance.test.ts | 2 +- 36 files changed, 47 insertions(+), 70 deletions(-) delete mode 100644 app/admin/loading.tsx delete mode 100644 app/admin/templates/page.tsx rename app/{ => auth}/check-email/page.tsx (95%) rename app/{ => auth}/forgot-password/page.tsx (100%) rename app/{ => auth}/login/page.tsx (95%) rename app/{ => auth}/password-reset/page.tsx (93%) rename app/{ => auth}/register/page.tsx (91%) delete mode 100644 app/commission/login/page.tsx diff --git a/README.md b/README.md index f0a04ec..48a9317 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,8 @@ formula fixtures. Their guide text and media are not imported or published. | `/` | Searchable public character directory | | `/[character]` | Redirect to the first visible page | | `/[character]/[page]` | Render a public guide page | -| `/login` | Public account sign-in | -| `/register` | Public account registration | +| `/auth/login` | Public account sign-in | +| `/auth/register` | Public account registration | | `/admin` | Character and page overview | | `/admin/[character]/[page]` | Visual page editor | | `/admin/create` | Create a structured guide from the synced character catalog | @@ -66,7 +66,7 @@ cp .env.example .env `.env.example` contains placeholders only. Configure `.env` yourself; it is ignored by Git and must never be committed. `BETTER_AUTH_URL` must exactly match the application origin. Set `BASE_URL` to the public site origin for SEO -metadata, sitemap, and robots.txt. Visitors can create public accounts at `/register`. +metadata, sitemap, and robots.txt. Visitors can create public accounts at `/auth/register`. Prepare the database and start the application: diff --git a/app/admin/[character]/[page]/page.tsx b/app/admin/[character]/[page]/page.tsx index 59e57c2..a0941ed 100644 --- a/app/admin/[character]/[page]/page.tsx +++ b/app/admin/[character]/[page]/page.tsx @@ -31,7 +31,7 @@ export default async function StructuredEditorPage({ params: Promise<{ character: string; page: string }>; }) { await connection(); - if (!(await getAdminSession())) redirect("/login?next=%2Fadmin"); + if (!(await getAdminSession())) redirect("/auth/login?next=%2Fadmin"); const { character, page } = await params; const guide = await getGuideEditorHeader(character); if (!guide) notFound(); diff --git a/app/admin/[character]/page.tsx b/app/admin/[character]/page.tsx index 1d2c1a5..b9de28d 100644 --- a/app/admin/[character]/page.tsx +++ b/app/admin/[character]/page.tsx @@ -27,7 +27,7 @@ export const instant = false; export default async function GuideOverviewPage({ params }: { params: Promise<{ character: string }> }) { await connection(); - if (!(await getAdminSession())) redirect("/login?next=%2Fadmin"); + if (!(await getAdminSession())) redirect("/auth/login?next=%2Fadmin"); const { character } = await params; const guide = await getGuideByCharacterKey(character); if (!guide) notFound(); diff --git a/app/admin/[character]/preview/[[...page]]/page.tsx b/app/admin/[character]/preview/[[...page]]/page.tsx index eea10db..17e7bfe 100644 --- a/app/admin/[character]/preview/[[...page]]/page.tsx +++ b/app/admin/[character]/preview/[[...page]]/page.tsx @@ -14,7 +14,7 @@ export default async function GuidePreviewPage({ params: Promise<{ character: string; page?: string[] }>; }) { await connection(); - if (!(await getAdminSession())) redirect("/login?next=%2Fadmin"); + if (!(await getAdminSession())) redirect("/auth/login?next=%2Fadmin"); const { character, page } = await params; if (page && page.length > 1) notFound(); const header = await getGuideEditorHeader(character); diff --git a/app/admin/activity/page.tsx b/app/admin/activity/page.tsx index da7e7a3..1981611 100644 --- a/app/admin/activity/page.tsx +++ b/app/admin/activity/page.tsx @@ -21,7 +21,7 @@ export default async function AuditLogPage({ searchParams: Promise>; }) { await connection(); - if (!(await getAdminSession())) redirect("/login?next=%2Fadmin"); + if (!(await getAdminSession())) redirect("/auth/login?next=%2Fadmin"); const query = await searchParams; const rawPage = Array.isArray(query.page) ? query.page[0] : query.page; diff --git a/app/admin/commission/page.tsx b/app/admin/commission/page.tsx index a345c5b..07989b8 100644 --- a/app/admin/commission/page.tsx +++ b/app/admin/commission/page.tsx @@ -19,7 +19,7 @@ export const instant = false; export default async function AdminCommissionPage({ searchParams }: PageProps<"/admin/commission">) { await connection(); const session = await getAdminSession(); - if (!session) redirect("/login?next=%2Fadmin"); + if (!session) redirect("/auth/login?next=%2Fadmin"); const query = await searchParams; const rawPage = Number(query.page ?? 1); const page = Number.isInteger(rawPage) && rawPage > 0 ? Math.min(rawPage, 10000) : 1; diff --git a/app/admin/create/page.tsx b/app/admin/create/page.tsx index 178c304..82bafa3 100644 --- a/app/admin/create/page.tsx +++ b/app/admin/create/page.tsx @@ -11,7 +11,7 @@ import { getActiveCatalog, listGuideCards } from "@/lib/guides/queries"; export default async function CreateGuidePage() { await connection(); - if (!(await getAdminSession())) redirect("/login?next=%2Fadmin"); + if (!(await getAdminSession())) redirect("/auth/login?next=%2Fadmin"); const [catalog, guides] = await Promise.all([getActiveCatalog(), listGuideCards()]); if (!catalog) redirect("/admin?sync=required"); return ( diff --git a/app/admin/glossary/page.tsx b/app/admin/glossary/page.tsx index c1c9e21..34620be 100644 --- a/app/admin/glossary/page.tsx +++ b/app/admin/glossary/page.tsx @@ -9,7 +9,7 @@ import { getCatalogOptions } from "@/lib/guides/queries"; export default async function GlossaryPage() { await connection(); - if (!(await getAdminSession())) redirect("/login?next=%2Fadmin"); + if (!(await getAdminSession())) redirect("/auth/login?next=%2Fadmin"); const [catalog, aliases] = await Promise.all([ getCatalogOptions(), listGlossaryAliases(), diff --git a/app/admin/image/layout.tsx b/app/admin/image/layout.tsx index b942657..354fcdb 100644 --- a/app/admin/image/layout.tsx +++ b/app/admin/image/layout.tsx @@ -9,7 +9,7 @@ export default async function ImageGeneratorLayout({ children, }: LayoutProps<"/admin/image">) { await connection(); - if (!(await getAdminSession())) redirect("/login?next=%2Fadmin"); + if (!(await getAdminSession())) redirect("/auth/login?next=%2Fadmin"); return (
diff --git a/app/admin/layout.tsx b/app/admin/layout.tsx index 53f9d9f..7ec6001 100644 --- a/app/admin/layout.tsx +++ b/app/admin/layout.tsx @@ -5,7 +5,15 @@ import { TooltipProvider } from "@/components/ui/tooltip"; export default function AdminLayout({ children }: LayoutProps<"/admin">) { return ( - {children} + +

กำลังโหลด…

+ + } + > + {children} +
); } diff --git a/app/admin/loading.tsx b/app/admin/loading.tsx deleted file mode 100644 index 7291489..0000000 --- a/app/admin/loading.tsx +++ /dev/null @@ -1,7 +0,0 @@ -export default function AdminLoading() { - return ( -
-

กำลังโหลด…

-
- ); -} diff --git a/app/admin/page.tsx b/app/admin/page.tsx index 91671c1..2a9d4b5 100644 --- a/app/admin/page.tsx +++ b/app/admin/page.tsx @@ -12,7 +12,7 @@ export const instant = false; export default async function AdminPage() { await connection(); - if (!(await getAdminSession())) redirect("/login?next=%2Fadmin"); + if (!(await getAdminSession())) redirect("/auth/login?next=%2Fadmin"); const [guides, trash, catalogVersion] = await Promise.all([ listGuideCards(), listGuideCards(true), diff --git a/app/admin/stygian/page.tsx b/app/admin/stygian/page.tsx index 01b6161..46f6708 100644 --- a/app/admin/stygian/page.tsx +++ b/app/admin/stygian/page.tsx @@ -11,7 +11,7 @@ import { getStygianSchedule, listStygianSchedules } from "@/lib/stygian/reposito export default async function AdminStygianPage({ searchParams }: { searchParams: Promise> }) { await connection(); - if (!(await getAdminSession())) redirect("/login?next=%2Fadmin"); + if (!(await getAdminSession())) redirect("/auth/login?next=%2Fadmin"); const query = await searchParams; const schedules = await listStygianSchedules(); const raw = Array.isArray(query.schedule) ? query.schedule[0] : query.schedule; diff --git a/app/admin/templates/page.tsx b/app/admin/templates/page.tsx deleted file mode 100644 index 9a370c3..0000000 --- a/app/admin/templates/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { redirect } from "next/navigation"; - -export default async function TemplatesPage() { - redirect("/admin/create"); -} diff --git a/app/check-email/page.tsx b/app/auth/check-email/page.tsx similarity index 95% rename from app/check-email/page.tsx rename to app/auth/check-email/page.tsx index ee88b15..d157a4f 100644 --- a/app/check-email/page.tsx +++ b/app/auth/check-email/page.tsx @@ -5,7 +5,7 @@ import { safeAuthReturnPath } from "@/lib/auth/return-path"; export const instant = false; -export default async function CheckEmailPage({ searchParams }: PageProps<"/check-email">) { +export default async function CheckEmailPage({ searchParams }: PageProps<"/auth/check-email">) { await connection(); const { email, next, setup } = await searchParams; return
diff --git a/app/forgot-password/page.tsx b/app/auth/forgot-password/page.tsx similarity index 100% rename from app/forgot-password/page.tsx rename to app/auth/forgot-password/page.tsx diff --git a/app/login/page.tsx b/app/auth/login/page.tsx similarity index 95% rename from app/login/page.tsx rename to app/auth/login/page.tsx index 272ab1d..f138bb3 100644 --- a/app/login/page.tsx +++ b/app/auth/login/page.tsx @@ -3,7 +3,7 @@ import { AccountPage } from "@/components/auth/account-page"; export const instant = false; -export default async function LoginPage({ searchParams }: PageProps<"/login">) { +export default async function LoginPage({ searchParams }: PageProps<"/auth/login">) { await connection(); const { next, verified, error } = await searchParams; return ) { +export default async function ResetPasswordPage({ searchParams }: PageProps<"/auth/password-reset">) { await connection(); const { token, error } = await searchParams; return
diff --git a/app/register/page.tsx b/app/auth/register/page.tsx similarity index 91% rename from app/register/page.tsx rename to app/auth/register/page.tsx index 3984f2a..2ec4155 100644 --- a/app/register/page.tsx +++ b/app/auth/register/page.tsx @@ -3,7 +3,7 @@ import { AccountPage } from "@/components/auth/account-page"; export const instant = false; -export default async function RegisterPage({ searchParams }: PageProps<"/register">) { +export default async function RegisterPage({ searchParams }: PageProps<"/auth/register">) { await connection(); const { next } = await searchParams; return ; diff --git a/app/commission/login/page.tsx b/app/commission/login/page.tsx deleted file mode 100644 index d3c7e11..0000000 --- a/app/commission/login/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { redirect } from "next/navigation"; - -export default function LegacyCommissionLoginPage() { - redirect("/login?next=%2Fcommission"); -} diff --git a/app/commission/page.tsx b/app/commission/page.tsx index 830ae3f..5b5df1f 100644 --- a/app/commission/page.tsx +++ b/app/commission/page.tsx @@ -25,8 +25,8 @@ async function CommissionContent() { if (!session) return

เข้าสู่ระบบหรือสมัครสมาชิกก่อนเริ่มคำขอ

- - + +
; const catalog = await getCatalogOptions(); @@ -43,7 +43,7 @@ export default function CommissionPage() {

Commission

ขอทีม เปรียบเทียบอาวุธหรือกลุ่มดาวตามที่ต้องการ

- กำลังโหลด...

}>
+
; } diff --git a/app/commission/pay/[id]/page.tsx b/app/commission/pay/[id]/page.tsx index 0e7187a..9acbf0e 100644 --- a/app/commission/pay/[id]/page.tsx +++ b/app/commission/pay/[id]/page.tsx @@ -21,7 +21,7 @@ export default async function CommissionPayPage({ params }: PageProps<"/commissi await connection(); const { id } = await params; const session = await getCustomerSession(); - if (!session) redirect(`/login?next=${encodeURIComponent(`/commission/pay/${id}`)}`); + if (!session) redirect(`/auth/login?next=${encodeURIComponent(`/commission/pay/${id}`)}`); const [checkout] = await getDb().select().from(commissionCheckouts) .where(and(eq(commissionCheckouts.id, id), eq(commissionCheckouts.userId, session.user.id))).limit(1); if (!checkout) notFound(); diff --git a/app/commission/tickets/page.tsx b/app/commission/tickets/page.tsx index 2e7173a..db477c0 100644 --- a/app/commission/tickets/page.tsx +++ b/app/commission/tickets/page.tsx @@ -16,7 +16,7 @@ export const instant = false; export default async function CommissionTicketsPage() { await connection(); const session = await getCustomerSession(); - if (!session) redirect("/login?next=%2Fcommission%2Ftickets"); + if (!session) redirect("/auth/login?next=%2Fcommission%2Ftickets"); const [tickets, pending] = await Promise.all([ getDb().select({ ticket: commissionTickets, amount: commissionCheckouts.amountBaht }) .from(commissionTickets).innerJoin(commissionCheckouts, eq(commissionTickets.checkoutId, commissionCheckouts.id)) diff --git a/app/profile/page.tsx b/app/profile/page.tsx index 710c250..80e2f47 100644 --- a/app/profile/page.tsx +++ b/app/profile/page.tsx @@ -20,11 +20,11 @@ export default async function ProfilePage({ searchParams }: PageProps<"/profile" const nextPath = safeAuthReturnPath(next); const setupNextPath = nextPath === "/profile" || nextPath.startsWith("/profile?") ? "/" : nextPath; const session = await getCustomerSession(); - if (!session) redirect("/login?next=%2Fprofile"); + if (!session) redirect("/auth/login?next=%2Fprofile"); const [user] = await getDb().select({ name: users.name, email: users.email, emailVerified: users.emailVerified, image: users.image }) .from(users).where(eq(users.id, session.user.id)).limit(1); - if (!user) redirect("/login?next=%2Fprofile"); + if (!user) redirect("/auth/login?next=%2Fprofile"); return
{isAuthorizedAdmin(session.user) ? : }

โปรไฟล์ของฉัน

diff --git a/app/robots.ts b/app/robots.ts index 7fc54b5..496facd 100644 --- a/app/robots.ts +++ b/app/robots.ts @@ -7,7 +7,7 @@ export default function robots(): MetadataRoute.Robots { rules: { userAgent: "*", allow: "/", - disallow: ["/admin", "/api", "/login", "/register"], + disallow: ["/admin", "/api", "/auth", "/login", "/register"], }, sitemap: `${siteUrl}/sitemap.xml`, }; diff --git a/components/admin/guide-section-editor.tsx b/components/admin/guide-section-editor.tsx index e409a0c..dbbeb53 100644 --- a/components/admin/guide-section-editor.tsx +++ b/components/admin/guide-section-editor.tsx @@ -2579,7 +2579,7 @@ function TeamEditor({ const [importOpen, setImportOpen] = useState(false); const [importFiles, setImportFiles] = useState([]); const [guideImportOpen, setGuideImportOpen] = useState(false); - const [importGuides, setImportGuides] = useState | null>(null); + const [importGuides, setImportGuides] = useState | null>(null); const [activeImportGuide, setActiveImportGuide] = useState(null); const [selectedImports, setSelectedImports] = useState>>({}); const [guideImportLoading, setGuideImportLoading] = useState<"guides" | "teams" | null>(null); @@ -3998,11 +3998,6 @@ function TeamEditor({ {sourceGuide.name} {sourceGuide.characterKey} - - - {sourceGuide.isPublic ? "Published" : "Private"} - - :
} diff --git a/components/auth/account-menu.tsx b/components/auth/account-menu.tsx index f1cca65..0ca195c 100644 --- a/components/auth/account-menu.tsx +++ b/components/auth/account-menu.tsx @@ -17,7 +17,7 @@ export function AccountMenu({ admin = false }: { admin?: boolean }) { async function signOut() { await unsubscribeCommissionPush().catch(() => undefined); await authClient.signOut(); - router.push(admin ? "/login?next=%2Fadmin" : "/commission"); + router.push(admin ? "/auth/login?next=%2Fadmin" : "/commission"); router.refresh(); } diff --git a/components/auth/email-action-form.tsx b/components/auth/email-action-form.tsx index d9570a1..96a546e 100644 --- a/components/auth/email-action-form.tsx +++ b/components/auth/email-action-form.tsx @@ -27,7 +27,7 @@ export function EmailActionForm({ mode, token = "", invalidToken = false }: { setError(""); try { if (mode === "forgot") { - const result = await authClient.requestPasswordReset({ email: address, redirectTo: "/reset-password" }); + const result = await authClient.requestPasswordReset({ email: address, redirectTo: "/auth/password-reset" }); if (result.error) throw new Error("ส่งลิงก์รีเซ็ตไม่สำเร็จ กรุณาลองอีกครั้ง"); } else { const password = String(form.get("password") ?? ""); @@ -74,10 +74,10 @@ export function EmailActionForm({ mode, token = "", invalidToken = false }: { mode === "forgot" ? "ส่งลิงก์รีเซ็ต" : "ตั้งรหัสผ่าน"} } - {mode === "reset" && !token && } - + ; } diff --git a/components/auth/registration-otp-form.tsx b/components/auth/registration-otp-form.tsx index be54956..f5a2c06 100644 --- a/components/auth/registration-otp-form.tsx +++ b/components/auth/registration-otp-form.tsx @@ -75,7 +75,7 @@ export function RegistrationOtpForm({ email, nextPath, setup }: { - + ; } diff --git a/lib/auth/return-path.test.ts b/lib/auth/return-path.test.ts index c1cb5fc..b48f1d9 100644 --- a/lib/auth/return-path.test.ts +++ b/lib/auth/return-path.test.ts @@ -7,7 +7,7 @@ describe("public auth return path", () => { }); it("rejects external destinations and auth loops", () => { - for (const value of ["https://example.com", "//example.com", "/\\example.com", "/login", "/lo%67in", "/register", "/admin/login", 4]) + for (const value of ["https://example.com", "//example.com", "/\\example.com", "/login", "/lo%67in", "/register", "/auth/login", "/auth/register", "/auth/check-email", "/auth/password-reset", "/admin/login", 4]) expect(safeAuthReturnPath(value)).toBe("/"); }); }); diff --git a/lib/auth/return-path.ts b/lib/auth/return-path.ts index 8eb78f8..1b75e42 100644 --- a/lib/auth/return-path.ts +++ b/lib/auth/return-path.ts @@ -3,7 +3,7 @@ export function safeAuthReturnPath(value: unknown): string { try { const url = new URL(value, "https://auth.local"); const path = decodeURIComponent(url.pathname); - if (url.origin !== "https://auth.local" || ["/login", "/register", "/commission/login", "/admin/login"].includes(path)) + if (url.origin !== "https://auth.local" || path === "/auth" || path.startsWith("/auth/") || ["/login", "/register", "/commission/login", "/admin/login"].includes(path)) return "/"; return `${url.pathname}${url.search}${url.hash}`; } catch { diff --git a/lib/guides/queries.ts b/lib/guides/queries.ts index 9826fb9..9445299 100644 --- a/lib/guides/queries.ts +++ b/lib/guides/queries.ts @@ -187,7 +187,6 @@ export async function listAdminTeamImportGuides(excludeGuideId: string) { id: guides.id, name: guides.name, characterKey: guides.characterKey, - isPublic: guides.isPublic, coverObjectKey: media.objectKey, }) .from(guides) diff --git a/next.config.ts b/next.config.ts index a26d9b8..0b0bc95 100644 --- a/next.config.ts +++ b/next.config.ts @@ -7,10 +7,6 @@ const nextConfig: NextConfig = { async headers() { return [{ source: "/:path*", headers: securityHeaders(process.env.NODE_ENV === "production", "https://cdn.jsdelivr.net") }]; }, - async rewrites() { - // The reset-password segment resolves to the dynamic guide route in this Next version. - return { beforeFiles: [{ source: "/reset-password", destination: "/password-reset" }] }; - }, cacheComponents: true, cacheHandlers: { remote: resolve(process.cwd(), "cache-handlers/redis-handler.cjs"), diff --git a/tests/public-performance.test.ts b/tests/public-performance.test.ts index 67e1341..726ec03 100644 --- a/tests/public-performance.test.ts +++ b/tests/public-performance.test.ts @@ -53,7 +53,7 @@ describe("public rendering performance", () => { rules: { userAgent: "*", allow: "/", - disallow: ["/admin", "/api", "/login", "/register"], + disallow: ["/admin", "/api", "/auth", "/login", "/register"], }, sitemap: "https://guide.example.test/sitemap.xml", });