Skip to content

Commit 1b80809

Browse files
committed
refactor: reduce dynamic routes
1 parent 0d98dad commit 1b80809

11 files changed

Lines changed: 188 additions & 156 deletions

File tree

app/(admin)/layout.tsx

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { AppSidebar } from "@/components/app-sidebar";
22
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
3+
import AuthorizedApolloWrapper from "@/providers/use-apollo.rsc";
34
import ProtectedRoute from "@/providers/use-protected-route";
45
import { unstable_ViewTransition as ViewTransition } from "react";
56

@@ -10,21 +11,23 @@ export default function AdminLayout({
1011
}>) {
1112
return (
1213
<ProtectedRoute>
13-
<SidebarProvider
14-
style={{
15-
"--sidebar-width": "calc(var(--spacing) * 72)",
16-
"--header-height": "calc(var(--spacing) * 12)",
17-
} as React.CSSProperties}
18-
>
19-
<AppSidebar variant="inset" />
20-
<SidebarInset>
21-
<ViewTransition>
22-
<div suppressHydrationWarning>
23-
{children}
24-
</div>
25-
</ViewTransition>
26-
</SidebarInset>
27-
</SidebarProvider>
14+
<AuthorizedApolloWrapper>
15+
<SidebarProvider
16+
style={{
17+
"--sidebar-width": "calc(var(--spacing) * 72)",
18+
"--header-height": "calc(var(--spacing) * 12)",
19+
} as React.CSSProperties}
20+
>
21+
<AppSidebar variant="inset" />
22+
<SidebarInset>
23+
<ViewTransition>
24+
<div suppressHydrationWarning>
25+
{children}
26+
</div>
27+
</ViewTransition>
28+
</SidebarInset>
29+
</SidebarProvider>
30+
</AuthorizedApolloWrapper>
2831
</ProtectedRoute>
2932
);
3033
}

app/forbidden.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import type { Metadata } from "next";
33

44
export const metadata: Metadata = {
55
title: "權限不足",
6-
}
6+
};
77

88
export default function ForbiddenPage() {
99
return <ForbiddenLayout />;
10-
}
10+
}

app/layout.tsx

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ import type { Metadata } from "next";
22
import { Geist, Geist_Mono } from "next/font/google";
33
import "./globals.css";
44
import { Toaster } from "@/components/ui/sonner";
5-
import { getAuthToken } from "@/lib/auth";
6-
import { ApolloWrapper } from "@/providers/use-apollo";
75
import { ProgressProvider } from "@/providers/use-progress-provider";
86
import { PreloadResources } from "./preload-resources";
97

@@ -18,7 +16,10 @@ const geistMono = Geist_Mono({
1816
});
1917

2018
export const metadata: Metadata = {
21-
title: { template: "%s | 管理介面 | 資料庫練功坊", default: "管理介面 | 資料庫練功坊" },
19+
title: {
20+
template: "%s | 管理介面 | 資料庫練功坊",
21+
default: "管理介面 | 資料庫練功坊",
22+
},
2223
description: "管理資料庫練功坊的題目、使用者、做題記錄等。",
2324
};
2425

@@ -29,8 +30,6 @@ export default async function RootLayout({
2930
}: Readonly<{
3031
children: React.ReactNode;
3132
}>) {
32-
const token = await getAuthToken();
33-
3433
return (
3534
<html lang="zh-hant-tw">
3635
<head>
@@ -48,9 +47,7 @@ export default async function RootLayout({
4847
font-sans antialiased
4948
`}
5049
>
51-
<ApolloWrapper token={token}>
52-
<ProgressProvider delay={500}>{children}</ProgressProvider>
53-
</ApolloWrapper>
50+
<ProgressProvider delay={500}>{children}</ProgressProvider>
5451
<Toaster />
5552
</body>
5653
</html>

app/login/page.tsx

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,13 @@
11
import { LoginForm } from "@/components/login-form";
22
import { Logo } from "@/components/logo";
3-
import { redirectIfAuthenticated } from "@/lib/auth.rsc";
43
import type { Metadata } from "next";
54
import Link from "next/link";
65

7-
interface LoginPageProps {
8-
searchParams: Promise<{
9-
error?: string;
10-
error_description?: string;
11-
message?: string;
12-
redirect?: string;
13-
}>;
14-
}
15-
166
export const metadata: Metadata = {
177
title: "登入",
188
};
199

20-
export default async function LoginPage({ searchParams }: LoginPageProps) {
21-
// Redirect if already authenticated
22-
await redirectIfAuthenticated();
23-
24-
const params = await searchParams;
25-
10+
export default function LoginPage() {
2611
return (
2712
<div
2813
className={`
@@ -46,11 +31,7 @@ export default async function LoginPage({ searchParams }: LoginPageProps) {
4631
</div>
4732
Database Playground
4833
</Link>
49-
<LoginForm
50-
error={params.error}
51-
errorDescription={params.error_description}
52-
message={params.message}
53-
/>
34+
<LoginForm />
5435
</div>
5536
</div>
5637
);

components/app-sidebar.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ const buildNavbar = (
8484
title: "題庫管理",
8585
url: "/questions",
8686
icon: LibrarySquare,
87-
isActive: pathname.startsWith("/questions") || pathname.startsWith("/database"),
87+
isActive: pathname.startsWith("/questions")
88+
|| pathname.startsWith("/database"),
8889
items: [
8990
{
9091
title: "題庫",
@@ -167,7 +168,13 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
167168
</SidebarMenu>
168169
</SidebarHeader>
169170
<SidebarContent>
170-
{data.navMain.map((group) => <NavMain key={group.group} items={group.items} groupLabel={group.group} />)}
171+
{data.navMain.map((group) => (
172+
<NavMain
173+
key={group.group}
174+
items={group.items}
175+
groupLabel={group.group}
176+
/>
177+
))}
171178
<NavSecondary items={data.navSecondary} className="mt-auto" />
172179
</SidebarContent>
173180
<SidebarFooter>

components/forbidden-layout/page.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { Logo } from "@/components/logo";
22
import { Button } from "@/components/ui/button";
33
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
4+
import AuthorizedApolloWrapper from "@/providers/use-apollo.rsc";
45
import { AlertTriangle } from "lucide-react";
56
import Link from "next/link";
7+
import { Suspense } from "react";
68
import { UserInfo } from "./user-info";
79

810
export default async function ForbiddenLayout() {
@@ -44,7 +46,11 @@ export default async function ForbiddenLayout() {
4446
<CardFooter
4547
className={`justify-center text-center text-xs text-muted-foreground`}
4648
>
47-
<UserInfo />
49+
<Suspense>
50+
<AuthorizedApolloWrapper>
51+
<UserInfo />
52+
</AuthorizedApolloWrapper>
53+
</Suspense>
4854
</CardFooter>
4955
</Card>
5056
</div>

components/login-form.tsx

Lines changed: 0 additions & 107 deletions
This file was deleted.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
"use client";
2+
3+
import { AlertCircle } from "lucide-react";
4+
import { useSearchParams } from "next/navigation";
5+
import { Alert, AlertDescription } from "../ui/alert";
6+
7+
export function ErrorAlert() {
8+
const searchParams = useSearchParams();
9+
const error = searchParams.get("error");
10+
const errorDescription = searchParams.get("error_description");
11+
12+
if (!error || !errorDescription) {
13+
return null;
14+
}
15+
16+
return (
17+
<Alert variant="destructive">
18+
<AlertCircle className="h-4 w-4" />
19+
<AlertDescription>
20+
{getErrorMessage(error, errorDescription)}
21+
</AlertDescription>
22+
</Alert>
23+
);
24+
}
25+
26+
function getErrorMessage(error: string, description?: string | null): string {
27+
if (description) return description;
28+
29+
switch (error) {
30+
case "invalid_request":
31+
return "登入請求無效,請重試。";
32+
case "unauthorized":
33+
return "您沒有權限存取此應用程式。";
34+
case "access_denied":
35+
return "登入已取消或拒絕。";
36+
case "server_error":
37+
return "伺服器發生錯誤,請稍後再試。";
38+
case "temporarily_unavailable":
39+
return "服務暫時無法使用,請稍後再試。";
40+
case "auth_error":
41+
return "認證過程中發生錯誤,請重新登入。";
42+
case "logout_failed":
43+
return "登出時發生錯誤,但您的本地工作階段已清除。";
44+
case "forbidden":
45+
return "您沒有權限存取此應用程式。";
46+
default:
47+
return "登入時發生未知錯誤,請重試。";
48+
}
49+
}

components/login-form/index.tsx

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { Button } from "@/components/ui/button";
2+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
3+
import { cn } from "@/lib/utils";
4+
import { Suspense } from "react";
5+
import { ErrorAlert } from "./error-alert";
6+
import { MessageAlert } from "./message-alert";
7+
8+
export function LoginForm({
9+
className,
10+
...props
11+
}: React.ComponentPropsWithoutRef<"div">) {
12+
return (
13+
<div className={cn("flex flex-col gap-6", className)} {...props}>
14+
<Suspense>
15+
<ErrorAlert />
16+
<MessageAlert />
17+
</Suspense>
18+
19+
<Card>
20+
<CardHeader className="text-center">
21+
<CardTitle className="text-xl">登入管理介面</CardTitle>
22+
<CardDescription>
23+
使用具有管理員權限的帳號登入資料庫練功坊。
24+
</CardDescription>
25+
</CardHeader>
26+
<CardContent>
27+
<div className="grid gap-6">
28+
<div className="flex flex-col gap-4">
29+
<a href="/api/auth/login">
30+
<Button variant="outline" className="w-full">
31+
<svg
32+
xmlns="http://www.w3.org/2000/svg"
33+
viewBox="0 0 24 24"
34+
className={`mr-2 h-4 w-4`}
35+
>
36+
<path
37+
d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z"
38+
fill="currentColor"
39+
/>
40+
</svg>
41+
使用 Google 登入
42+
</Button>
43+
</a>
44+
</div>
45+
</div>
46+
</CardContent>
47+
</Card>
48+
</div>
49+
);
50+
}

0 commit comments

Comments
 (0)