-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add protected application shell #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
45 commits
Select commit
Hold shift + click to select a range
3b13f8e
feat(auth): classify Clerk configuration states
DoMinhHHung e5dd5ff
feat(auth): read Clerk configuration centrally
DoMinhHHung b5234b1
test(auth): cover Clerk configuration states
DoMinhHHung eb7e0bd
feat(auth): define explicit application routes
DoMinhHHung 2720dc6
test(auth): cover protected route matching
DoMinhHHung 312df03
feat(auth): centralize server session state
DoMinhHHung 9c7f5cc
feat(auth): fail closed for protected application routes
DoMinhHHung 2417d1b
refactor(auth): use centralized Clerk provider configuration
DoMinhHHung 1878a37
feat(auth): add safe authentication state presentation
DoMinhHHung f46bd97
feat(auth): add responsive authentication page layout
DoMinhHHung 6000339
feat(shell): add keyboard accessible mobile navigation
DoMinhHHung efc163b
feat(shell): add responsive authenticated application shell
DoMinhHHung 912c379
feat(auth): add Clerk sign-in route
DoMinhHHung a34e6a0
feat(auth): add Clerk sign-up route
DoMinhHHung a4e61ea
feat(shell): protect and render authenticated application layout
DoMinhHHung 6d22fdf
feat(shell): add authenticated overview foundation page
DoMinhHHung f670932
feat(shell): add stable application loading state
DoMinhHHung c95daa3
feat(shell): add redacted application error recovery
DoMinhHHung 32adca4
style(a11y): respect reduced motion globally
DoMinhHHung 5bc63f7
test(shell): cover rendering and keyboard navigation
DoMinhHHung ddbfdf7
test(storybook): add authenticated shell stories
DoMinhHHung 7b1d51f
test(storybook): add authentication unavailable stories
DoMinhHHung ce07aa7
test: configure DOM component test environment
DoMinhHHung 9386d29
test: add unit and Storybook Vitest projects
DoMinhHHung 7eba616
test: run the dedicated unit project
DoMinhHHung a795e10
test(e2e): cover public and fail-closed authentication states
DoMinhHHung 26a82e9
feat(auth): link the public foundation to sign-in
DoMinhHHung 3f58eeb
docs(auth): document protected application shell
DoMinhHHung 44e9f90
ci: name expanded browser validation accurately
DoMinhHHung 45a15eb
fix(auth): narrow invalid Clerk key matches
DoMinhHHung 44b8377
fix(auth): render sign-in per request
DoMinhHHung 6d6868d
fix(auth): render sign-up per request
DoMinhHHung 83b9ba9
fix(auth): render protected shell per request
DoMinhHHung b3dc281
fix(auth): verify protected page per request
DoMinhHHung 0834d35
fix(shell): use the Next.js error boundary contract
DoMinhHHung 1802f55
fix(storybook): wait for mobile sheet transitions
DoMinhHHung e9d0351
harden server-only clerk config boundary
DoMinhHHung 177afa9
harden server-only auth session boundary
DoMinhHHung f53ba55
align clerk key validation with documented formats
DoMinhHHung 32036b8
harden clerk configuration fixtures and coverage
DoMinhHHung c3024ec
document authenticated smoke hardening
DoMinhHHung 7699056
verify server-only client import rejection
DoMinhHHung 968c3cf
remove ignored server-only verification fixture
DoMinhHHung cf14d08
verify routable server-only client import rejection
DoMinhHHung 3c1f3e9
remove server-only verification fixture
DoMinhHHung File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| import { SignIn } from "@clerk/nextjs"; | ||
| import type { Metadata } from "next"; | ||
| import { redirect } from "next/navigation"; | ||
|
|
||
| import { | ||
| AuthComponentFallback, | ||
| AuthPage, | ||
| } from "@/components/layout/auth-page"; | ||
| import { | ||
| AuthState, | ||
| configurationStateKind, | ||
| } from "@/components/layout/auth-state"; | ||
| import { getClerkSessionState } from "@/lib/auth-session.server"; | ||
| import { | ||
| APP_ROUTE, | ||
| SIGN_IN_ROUTE, | ||
| SIGN_UP_ROUTE, | ||
| } from "@/lib/auth-routes"; | ||
| import { getClerkConfiguration } from "@/lib/clerk-config.server"; | ||
|
|
||
| export const dynamic = "force-dynamic"; | ||
|
|
||
| export const metadata: Metadata = { | ||
| title: "Sign in", | ||
| description: "Sign in to the protected BridgeWorks application.", | ||
| }; | ||
|
|
||
| export default async function SignInPage() { | ||
| const configuration = getClerkConfiguration(); | ||
|
|
||
| if (configuration.status !== "configured") { | ||
| return ( | ||
| <AuthPage | ||
| title="Sign in to BridgeWorks" | ||
| description="Continue to the secure application workspace after your account is authenticated." | ||
| > | ||
| <AuthState kind={configurationStateKind(configuration)} /> | ||
| </AuthPage> | ||
| ); | ||
| } | ||
|
|
||
| const session = await getClerkSessionState(); | ||
| if (session.status === "signed-in") { | ||
| redirect(APP_ROUTE); | ||
| } | ||
| if (session.status === "session-unavailable") { | ||
| return ( | ||
| <AuthPage | ||
| title="Sign in to BridgeWorks" | ||
| description="Continue to the secure application workspace after your account is authenticated." | ||
| > | ||
| <AuthState kind="session-unavailable" /> | ||
| </AuthPage> | ||
| ); | ||
| } | ||
| if (session.status === "unexpected") { | ||
| return ( | ||
| <AuthPage | ||
| title="Sign in to BridgeWorks" | ||
| description="Continue to the secure application workspace after your account is authenticated." | ||
| > | ||
| <AuthState kind="unexpected" /> | ||
| </AuthPage> | ||
| ); | ||
| } | ||
|
|
||
| return ( | ||
| <AuthPage | ||
| title="Sign in to BridgeWorks" | ||
| description="Continue to the secure application workspace after your account is authenticated." | ||
| > | ||
| <SignIn | ||
| path={SIGN_IN_ROUTE} | ||
| routing="path" | ||
| signUpUrl={SIGN_UP_ROUTE} | ||
| fallbackRedirectUrl={APP_ROUTE} | ||
| signUpFallbackRedirectUrl={APP_ROUTE} | ||
| fallback={<AuthComponentFallback />} | ||
| /> | ||
| </AuthPage> | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| import { SignUp } from "@clerk/nextjs"; | ||
| import type { Metadata } from "next"; | ||
| import { redirect } from "next/navigation"; | ||
|
|
||
| import { | ||
| AuthComponentFallback, | ||
| AuthPage, | ||
| } from "@/components/layout/auth-page"; | ||
| import { | ||
| AuthState, | ||
| configurationStateKind, | ||
| } from "@/components/layout/auth-state"; | ||
| import { getClerkSessionState } from "@/lib/auth-session.server"; | ||
| import { | ||
| APP_ROUTE, | ||
| SIGN_IN_ROUTE, | ||
| SIGN_UP_ROUTE, | ||
| } from "@/lib/auth-routes"; | ||
| import { getClerkConfiguration } from "@/lib/clerk-config.server"; | ||
|
|
||
| export const dynamic = "force-dynamic"; | ||
|
|
||
| export const metadata: Metadata = { | ||
| title: "Create account", | ||
| description: "Create an account for the protected BridgeWorks application.", | ||
| }; | ||
|
|
||
| export default async function SignUpPage() { | ||
| const configuration = getClerkConfiguration(); | ||
|
|
||
| if (configuration.status !== "configured") { | ||
| return ( | ||
| <AuthPage | ||
| title="Create your BridgeWorks account" | ||
| description="Create a secure account before entering the BridgeWorks application workspace." | ||
| > | ||
| <AuthState kind={configurationStateKind(configuration)} /> | ||
| </AuthPage> | ||
| ); | ||
| } | ||
|
|
||
| const session = await getClerkSessionState(); | ||
| if (session.status === "signed-in") { | ||
| redirect(APP_ROUTE); | ||
| } | ||
| if (session.status === "session-unavailable") { | ||
| return ( | ||
| <AuthPage | ||
| title="Create your BridgeWorks account" | ||
| description="Create a secure account before entering the BridgeWorks application workspace." | ||
| > | ||
| <AuthState kind="session-unavailable" /> | ||
| </AuthPage> | ||
| ); | ||
| } | ||
| if (session.status === "unexpected") { | ||
| return ( | ||
| <AuthPage | ||
| title="Create your BridgeWorks account" | ||
| description="Create a secure account before entering the BridgeWorks application workspace." | ||
| > | ||
| <AuthState kind="unexpected" /> | ||
| </AuthPage> | ||
| ); | ||
| } | ||
|
|
||
| return ( | ||
| <AuthPage | ||
| title="Create your BridgeWorks account" | ||
| description="Create a secure account before entering the BridgeWorks application workspace." | ||
| > | ||
| <SignUp | ||
| path={SIGN_UP_ROUTE} | ||
| routing="path" | ||
| signInUrl={SIGN_IN_ROUTE} | ||
| fallbackRedirectUrl={APP_ROUTE} | ||
| signInFallbackRedirectUrl={APP_ROUTE} | ||
| fallback={<AuthComponentFallback />} | ||
| /> | ||
| </AuthPage> | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| "use client"; | ||
|
|
||
| import { AlertTriangle } from "lucide-react"; | ||
| import Link from "next/link"; | ||
|
|
||
| import { Button } from "@/components/ui/button"; | ||
| import { HOME_ROUTE } from "@/lib/auth-routes"; | ||
|
|
||
| type AppErrorProps = { | ||
| error: Error & { digest?: string }; | ||
| reset: () => void; | ||
| }; | ||
|
|
||
| export default function AppError({ reset }: AppErrorProps) { | ||
| return ( | ||
| <section | ||
| aria-labelledby="application-error-title" | ||
| className="max-w-xl rounded-xl border border-border bg-card p-6 shadow-sm sm:p-8" | ||
| > | ||
| <div className="flex size-11 items-center justify-center rounded-lg bg-muted text-foreground"> | ||
| <AlertTriangle aria-hidden="true" className="size-5" /> | ||
| </div> | ||
| <h1 | ||
| id="application-error-title" | ||
| className="mt-5 text-2xl font-semibold tracking-tight" | ||
| > | ||
| The application could not finish loading | ||
| </h1> | ||
| <p className="mt-3 max-w-prose text-base leading-7 text-muted-foreground"> | ||
| BridgeWorks stopped before rendering incomplete protected content. Try | ||
| the request again, or return to the public site. | ||
| </p> | ||
| <div className="mt-6 flex flex-col gap-3 sm:flex-row"> | ||
| <Button type="button" size="lg" onClick={reset}> | ||
| Try again | ||
| </Button> | ||
| <Button asChild variant="outline" size="lg"> | ||
| <Link href={HOME_ROUTE}>Return to BridgeWorks</Link> | ||
| </Button> | ||
| </div> | ||
| </section> | ||
| ); | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Không che giấu lỗi không phát hiện kiểm thử.
Repository đã có unit test trong
src/.--passWithNoTestslàm bước CI thành công nếu globsrc/**/*.test.{ts,tsx}bị sai hoặc không còn tìm thấy kiểm thử. Hãy để Vitest thất bại trong trường hợp đó.Đề xuất sửa
📝 Committable suggestion
🤖 Prompt for AI Agents