create-ej-app 26.0.0 → 27.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +125 -226
- package/cli.ts +79 -29
- package/package.json +2 -2
- package/template/api/.vscode/settings.json +1 -1
- package/template/api/{.github/copilot-instructions.md → AGENTS.md} +0 -2
- package/template/api/CLAUDE.md +1 -0
- package/template/api/package.json +10 -10
- package/template/nextjs/.dockerignore +1 -0
- package/template/nextjs/AGENTS.md +260 -0
- package/template/nextjs/CLAUDE.md +1 -0
- package/template/nextjs/README.md +109 -0
- package/template/nextjs/bunfig.toml +3 -0
- package/template/nextjs/eslint.config.mjs +6 -5
- package/template/nextjs/gitignore +6 -1
- package/template/nextjs/next.config.ts +8 -1
- package/template/nextjs/package.json +46 -39
- package/template/nextjs/public/logo-dark.svg +4 -0
- package/template/nextjs/public/logo.svg +4 -0
- package/template/nextjs/src/app/(admin)/actions/users.ts +183 -0
- package/template/nextjs/src/app/(admin)/admin/_components/admin-header.tsx +24 -0
- package/template/nextjs/src/app/(admin)/admin/_components/admin-search.tsx +16 -0
- package/template/nextjs/src/app/(admin)/admin/_components/admin-sidebar.tsx +42 -0
- package/template/nextjs/src/app/(admin)/admin/layout.tsx +53 -0
- package/template/nextjs/src/app/(admin)/admin/page.tsx +11 -2
- package/template/nextjs/src/app/(admin)/admin/settings/layout.tsx +24 -0
- package/template/nextjs/src/app/(admin)/admin/settings/users/[userId]/page.tsx +43 -0
- package/template/nextjs/src/app/(admin)/admin/settings/users/new/page.tsx +16 -0
- package/template/nextjs/src/app/(admin)/admin/settings/users/page.tsx +377 -0
- package/template/nextjs/src/app/(admin)/admin/settings/users/user-form.tsx +138 -0
- package/template/nextjs/src/app/(admin)/admin/settings/users/validation.ts +23 -0
- package/template/nextjs/src/app/(app)/app/layout.tsx +44 -0
- package/template/nextjs/src/app/(app)/app/page.tsx +14 -2
- package/template/nextjs/src/app/(auth)/(account)/layout.tsx +64 -0
- package/template/nextjs/src/app/(auth)/(account)/profile/page.tsx +16 -0
- package/template/nextjs/src/app/(auth)/(account)/profile/profile-form.tsx +247 -0
- package/template/nextjs/src/app/(auth)/login/email-login-form.tsx +56 -29
- package/template/nextjs/src/app/(auth)/login/page.tsx +45 -12
- package/template/nextjs/src/app/(auth)/login/social-login-buttons.tsx +85 -17
- package/template/nextjs/src/app/(marketing)/layout.tsx +35 -4
- package/template/nextjs/src/app/(marketing)/legal.tsx +56 -0
- package/template/nextjs/src/app/(marketing)/page.tsx +20 -2
- package/template/nextjs/src/app/(marketing)/privacy/page.tsx +43 -2
- package/template/nextjs/src/app/(marketing)/terms/page.tsx +33 -2
- package/template/nextjs/src/app/error.tsx +37 -0
- package/template/nextjs/src/app/fonts/geist-mono/GeistMono-Variable.woff2 +0 -0
- package/template/nextjs/src/app/fonts/geist-sans/Geist-Variable.woff2 +0 -0
- package/template/nextjs/src/app/fonts/index.ts +60 -0
- package/template/nextjs/src/app/global-error.tsx +38 -0
- package/template/nextjs/src/app/globals.css +29 -0
- package/template/nextjs/src/app/layout.tsx +45 -18
- package/template/nextjs/src/app/not-found.tsx +25 -3
- package/template/nextjs/src/app/robots.ts +29 -0
- package/template/nextjs/src/app/sitemap.ts +21 -0
- package/template/nextjs/src/components/confirm-dialog.tsx +79 -0
- package/template/nextjs/src/components/dashboard-header.tsx +70 -0
- package/template/nextjs/src/components/data-table/enum.ts +5 -0
- package/template/nextjs/src/components/data-table/features.ts +49 -0
- package/template/nextjs/src/components/data-table/index.tsx +961 -0
- package/template/nextjs/src/components/data-table/types.ts +76 -0
- package/template/nextjs/src/components/error.tsx +71 -0
- package/template/nextjs/src/components/loader.tsx +18 -2
- package/template/nextjs/src/components/logo.tsx +49 -0
- package/template/nextjs/src/components/navigation-search.tsx +140 -0
- package/template/nextjs/src/components/navigation-sidebar.tsx +241 -0
- package/template/nextjs/src/components/page-heading.tsx +38 -0
- package/template/nextjs/src/components/react-query-provider.tsx +41 -2
- package/template/nextjs/src/components/route-error.tsx +55 -0
- package/template/nextjs/src/components/screen-size.tsx +36 -0
- package/template/nextjs/src/components/site-footer.tsx +20 -4
- package/template/nextjs/src/components/theme-provider.tsx +48 -0
- package/template/nextjs/src/components/theme-toggle.tsx +58 -5
- package/template/nextjs/src/components/ui/alert-dialog.tsx +199 -0
- package/template/nextjs/src/components/ui/avatar.tsx +112 -0
- package/template/nextjs/src/components/ui/badge.tsx +54 -0
- package/template/nextjs/src/components/ui/button.tsx +3 -1
- package/template/nextjs/src/components/ui/card.tsx +4 -4
- package/template/nextjs/src/components/ui/collapsible.tsx +33 -0
- package/template/nextjs/src/components/ui/command.tsx +192 -0
- package/template/nextjs/src/components/ui/dialog.tsx +164 -0
- package/template/nextjs/src/components/ui/dropdown-menu.tsx +273 -0
- package/template/nextjs/src/components/ui/input-group.tsx +156 -0
- package/template/nextjs/src/components/ui/select.tsx +195 -0
- package/template/nextjs/src/components/ui/sheet.tsx +143 -0
- package/template/nextjs/src/components/ui/sidebar.tsx +702 -0
- package/template/nextjs/src/components/ui/skeleton.tsx +13 -0
- package/template/nextjs/src/components/ui/table.tsx +116 -0
- package/template/nextjs/src/components/ui/textarea.tsx +18 -0
- package/template/nextjs/src/components/ui/tooltip.tsx +57 -0
- package/template/nextjs/src/components/user-menu.tsx +206 -0
- package/template/nextjs/src/db/common.ts +10 -0
- package/template/nextjs/src/db/index.ts +16 -6
- package/template/nextjs/src/db/relations.ts +21 -0
- package/template/nextjs/src/db/schema.ts +85 -46
- package/template/nextjs/src/emails/components/email-layout.tsx +27 -11
- package/template/nextjs/src/emails/magic-link.tsx +15 -13
- package/template/nextjs/src/emails/test.tsx +11 -9
- package/template/nextjs/src/emails/welcome.tsx +12 -9
- package/template/nextjs/src/env.ts +1 -1
- package/template/nextjs/src/hooks/use-mobile.ts +14 -12
- package/template/nextjs/src/instrumentation.ts +20 -2
- package/template/nextjs/src/lib/actions.ts +55 -0
- package/template/nextjs/src/lib/admin-navigation.ts +43 -0
- package/template/nextjs/src/lib/auth-client.ts +20 -9
- package/template/nextjs/src/lib/auth-permissions.ts +61 -0
- package/template/nextjs/src/lib/auth.ts +33 -3
- package/template/nextjs/src/lib/callback-url.ts +28 -0
- package/template/nextjs/src/lib/constants.ts +21 -0
- package/template/nextjs/src/lib/delay.ts +10 -0
- package/template/nextjs/src/lib/email-service.ts +21 -0
- package/template/nextjs/src/lib/errors.ts +74 -0
- package/template/nextjs/src/lib/format-currency.ts +22 -24
- package/template/nextjs/src/lib/format-date.ts +69 -6
- package/template/nextjs/src/lib/nodemailer.ts +27 -3
- package/template/nextjs/src/lib/otel.ts +92 -2
- package/template/nextjs/src/lib/safe-action-client.ts +60 -9
- package/template/nextjs/src/lib/safe-action.ts +152 -16
- package/template/nextjs/src/lib/search.ts +16 -0
- package/template/nextjs/src/lib/seo.ts +7 -0
- package/template/nextjs/src/lib/siteConfig.ts +17 -1
- package/template/nextjs/src/lib/toast-message.ts +87 -1
- package/template/nextjs/src/lib/validations.ts +30 -0
- package/template/nextjs/src/proxy.ts +48 -0
- package/template/nextjs/src/scripts/reset-db.ts +22 -0
- package/template/nextjs/src/scripts/seed.ts +59 -8
- package/template/nextjs/src/scripts/test-mail.ts +28 -20
- package/template/nextjs/tests/admin/users.test.ts +64 -0
- package/template/nextjs/tests/db/fk-indexes.test.ts +62 -0
- package/template/nextjs/tests/db/seed.test.ts +49 -0
- package/template/nextjs/tests/helpers/action.ts +71 -0
- package/template/nextjs/tests/helpers/fixtures.ts +78 -0
- package/template/nextjs/tests/helpers/index.ts +2 -0
- package/template/nextjs/tests/lib/callback-url.test.ts +48 -0
- package/template/nextjs/tests/lib/client-server-boundary.test.ts +57 -0
- package/template/nextjs/tests/lib/errors.test.ts +10 -0
- package/template/nextjs/tests/lib/search.test.ts +23 -0
- package/template/nextjs/tests/setup/pglite-db.ts +63 -0
- package/template/nextjs/tests/setup/preload.ts +95 -0
- package/template/nextjs/tsconfig.json +1 -0
- package/template/saas-starter/.dockerignore +1 -0
- package/template/saas-starter/.env.example +15 -15
- package/template/saas-starter/AGENTS.md +302 -0
- package/template/saas-starter/CLAUDE.md +1 -0
- package/template/saas-starter/README.md +124 -2
- package/template/saas-starter/bunfig.toml +3 -0
- package/template/saas-starter/eslint.config.mjs +6 -5
- package/template/saas-starter/gitignore +6 -1
- package/template/saas-starter/next.config.ts +19 -1
- package/template/saas-starter/package.json +49 -44
- package/template/saas-starter/public/logo-dark.svg +4 -0
- package/template/saas-starter/public/logo.svg +4 -0
- package/template/saas-starter/src/app/(admin)/actions/contact-leads.ts +51 -0
- package/template/saas-starter/src/app/(admin)/actions/feedbacks.ts +91 -0
- package/template/saas-starter/src/app/(admin)/actions/users.ts +145 -59
- package/template/saas-starter/src/app/(admin)/admin/_components/admin-header.tsx +4 -1
- package/template/saas-starter/src/app/(admin)/admin/_components/admin-sidebar.tsx +12 -10
- package/template/saas-starter/src/app/(admin)/admin/contact-leads/page.tsx +123 -0
- package/template/saas-starter/src/app/(admin)/admin/feedbacks/page.tsx +161 -0
- package/template/saas-starter/src/app/(admin)/admin/layout.tsx +24 -28
- package/template/saas-starter/src/app/(admin)/admin/page.tsx +59 -2
- package/template/saas-starter/src/app/(admin)/admin/settings/layout.tsx +24 -0
- package/template/saas-starter/src/app/(admin)/admin/settings/users/[userId]/page.tsx +43 -0
- package/template/saas-starter/src/app/(admin)/admin/settings/users/new/page.tsx +16 -0
- package/template/saas-starter/src/app/(admin)/admin/settings/users/page.tsx +372 -10
- package/template/saas-starter/src/app/(admin)/admin/settings/users/user-form.tsx +138 -0
- package/template/saas-starter/src/app/(admin)/admin/settings/users/validation.ts +23 -0
- package/template/saas-starter/src/app/(admin)/admin/support-tickets/[ticketId]/page.tsx +17 -0
- package/template/saas-starter/src/app/(admin)/admin/support-tickets/page.tsx +5 -0
- package/template/saas-starter/src/app/(app)/actions/billing.ts +171 -107
- package/template/saas-starter/src/app/(app)/actions/feedbacks.ts +28 -0
- package/template/saas-starter/src/app/(app)/actions/feedbacks.validation.ts +12 -0
- package/template/saas-starter/src/app/(app)/actions/members.ts +149 -123
- package/template/saas-starter/src/app/(app)/actions/organizations.ts +230 -0
- package/template/saas-starter/src/app/(app)/actions/organizations.validation.ts +17 -0
- package/template/saas-starter/src/app/(app)/app/[orgId]/dashboard/page.tsx +6 -9
- package/template/saas-starter/src/app/(app)/app/[orgId]/layout.tsx +6 -9
- package/template/saas-starter/src/app/(app)/app/[orgId]/settings/_components/billing-email-form.tsx +45 -53
- package/template/saas-starter/src/app/(app)/app/[orgId]/settings/_components/danger-zone.tsx +40 -47
- package/template/saas-starter/src/app/(app)/app/[orgId]/settings/_components/general-settings-form.tsx +80 -105
- package/template/saas-starter/src/app/(app)/app/[orgId]/settings/_components/plans-section.tsx +72 -82
- package/template/saas-starter/src/app/(app)/app/[orgId]/settings/billing/page.tsx +51 -52
- package/template/saas-starter/src/app/(app)/app/[orgId]/settings/layout.tsx +24 -0
- package/template/saas-starter/src/app/(app)/app/[orgId]/settings/members/_components/invite-member-dialog.tsx +15 -16
- package/template/saas-starter/src/app/(app)/app/[orgId]/settings/members/_components/members-tabs.tsx +77 -44
- package/template/saas-starter/src/app/(app)/app/[orgId]/settings/members/page.tsx +11 -18
- package/template/saas-starter/src/app/(app)/app/[orgId]/settings/page.tsx +14 -28
- package/template/saas-starter/src/app/(app)/app/[orgId]/support-tickets/[ticketId]/page.tsx +25 -0
- package/template/saas-starter/src/app/(app)/app/[orgId]/support-tickets/page.tsx +25 -0
- package/template/saas-starter/src/app/(app)/app/_components/app-header.tsx +15 -13
- package/template/saas-starter/src/app/(app)/app/_components/app-search.tsx +15 -6
- package/template/saas-starter/src/app/(app)/app/_components/app-sidebar.tsx +6 -3
- package/template/saas-starter/src/app/(app)/app/_components/feedback-dialog.tsx +108 -0
- package/template/saas-starter/src/app/(app)/app/_components/organization-context.tsx +5 -9
- package/template/saas-starter/src/app/(app)/app/_components/organization-switcher.tsx +68 -8
- package/template/saas-starter/src/app/(app)/app/layout.tsx +44 -32
- package/template/saas-starter/src/app/(app)/app/onboarding/_components/create-organization-form.tsx +75 -87
- package/template/saas-starter/src/app/(app)/app/onboarding/page.tsx +10 -12
- package/template/saas-starter/src/app/(app)/app/page.tsx +16 -21
- package/template/saas-starter/src/app/(auth)/(account)/layout.tsx +64 -0
- package/template/saas-starter/src/app/(auth)/(account)/profile/page.tsx +16 -0
- package/template/saas-starter/src/app/(auth)/(account)/profile/profile-form.tsx +247 -0
- package/template/saas-starter/src/app/(auth)/(account)/support/[ticketId]/page.tsx +19 -0
- package/template/saas-starter/src/app/(auth)/(account)/support/page.tsx +9 -0
- package/template/saas-starter/src/app/(auth)/login/email-login-form.tsx +49 -27
- package/template/saas-starter/src/app/(auth)/login/page.tsx +39 -21
- package/template/saas-starter/src/app/(auth)/login/social-login-buttons.tsx +80 -18
- package/template/saas-starter/src/app/(invitation)/accept-invitation/[id]/accept-invitation-form.tsx +16 -10
- package/template/saas-starter/src/app/(invitation)/accept-invitation/[id]/page.tsx +28 -42
- package/template/saas-starter/src/app/(invitation)/actions/invitations.ts +32 -0
- package/template/saas-starter/src/app/(marketing)/contact-us/actions.ts +23 -0
- package/template/saas-starter/src/app/(marketing)/contact-us/contact-form.tsx +118 -0
- package/template/saas-starter/src/app/(marketing)/contact-us/page.tsx +20 -0
- package/template/saas-starter/src/app/(marketing)/contact-us/validation.ts +22 -0
- package/template/saas-starter/src/app/(marketing)/layout.tsx +38 -4
- package/template/saas-starter/src/app/(marketing)/legal.tsx +66 -0
- package/template/saas-starter/src/app/(marketing)/page.tsx +23 -2
- package/template/saas-starter/src/app/(marketing)/privacy/page.tsx +62 -2
- package/template/saas-starter/src/app/(marketing)/terms/page.tsx +40 -2
- package/template/saas-starter/src/app/actions/support-tickets.ts +510 -0
- package/template/saas-starter/src/app/actions/support-tickets.validation.ts +112 -0
- package/template/saas-starter/src/app/actions/uploads.ts +36 -0
- package/template/saas-starter/src/app/actions/uploads.validation.ts +24 -0
- package/template/saas-starter/src/app/error.tsx +37 -0
- package/template/saas-starter/src/app/fonts/geist-mono/GeistMono-Variable.woff2 +0 -0
- package/template/saas-starter/src/app/fonts/geist-sans/Geist-Variable.woff2 +0 -0
- package/template/saas-starter/src/app/fonts/index.ts +60 -0
- package/template/saas-starter/src/app/global-error.tsx +38 -0
- package/template/saas-starter/src/app/globals.css +29 -0
- package/template/saas-starter/src/app/layout.tsx +43 -19
- package/template/saas-starter/src/app/not-found.tsx +25 -3
- package/template/saas-starter/src/app/robots.ts +14 -10
- package/template/saas-starter/src/app/sitemap.ts +2 -1
- package/template/saas-starter/src/components/confirm-dialog.tsx +79 -0
- package/template/saas-starter/src/components/dashboard-header.tsx +7 -1
- package/template/saas-starter/src/components/data-table/features.ts +49 -0
- package/template/saas-starter/src/components/data-table/index.tsx +65 -64
- package/template/saas-starter/src/components/data-table/types.ts +5 -4
- package/template/saas-starter/src/components/error.tsx +45 -0
- package/template/saas-starter/src/components/file-upload.tsx +65 -26
- package/template/saas-starter/src/components/loader.tsx +18 -2
- package/template/saas-starter/src/components/logo.tsx +49 -0
- package/template/saas-starter/src/components/navigation-search.tsx +2 -2
- package/template/saas-starter/src/components/page-heading.tsx +6 -0
- package/template/saas-starter/src/components/react-query-provider.tsx +41 -2
- package/template/saas-starter/src/components/route-error.tsx +55 -0
- package/template/saas-starter/src/components/screen-size.tsx +36 -0
- package/template/saas-starter/src/components/site-footer.tsx +20 -4
- package/template/saas-starter/src/components/support/create-support-ticket-dialog.tsx +122 -0
- package/template/saas-starter/src/components/support/support-ticket-conversation.tsx +236 -0
- package/template/saas-starter/src/components/support/support-ticket-shared.tsx +58 -0
- package/template/saas-starter/src/components/support/support-tickets-table.tsx +281 -0
- package/template/saas-starter/src/components/theme-provider.tsx +48 -0
- package/template/saas-starter/src/components/theme-toggle.tsx +58 -5
- package/template/saas-starter/src/components/ui/alert-dialog.tsx +199 -0
- package/template/saas-starter/src/components/ui/badge.tsx +6 -1
- package/template/saas-starter/src/components/ui/button.tsx +2 -0
- package/template/saas-starter/src/components/ui/command.tsx +1 -1
- package/template/saas-starter/src/components/user-menu.tsx +147 -24
- package/template/saas-starter/src/db/common.ts +33 -1
- package/template/saas-starter/src/db/enums.ts +24 -0
- package/template/saas-starter/src/db/index.ts +14 -5
- package/template/saas-starter/src/db/relations.ts +40 -3
- package/template/saas-starter/src/db/schema.ts +186 -35
- package/template/saas-starter/src/emails/components/email-layout.tsx +27 -11
- package/template/saas-starter/src/emails/invitation.tsx +34 -21
- package/template/saas-starter/src/emails/magic-link.tsx +15 -13
- package/template/saas-starter/src/emails/test.tsx +11 -9
- package/template/saas-starter/src/emails/welcome.tsx +12 -9
- package/template/saas-starter/src/env.ts +1 -1
- package/template/saas-starter/src/hooks/use-mobile.ts +14 -12
- package/template/saas-starter/src/instrumentation.ts +20 -2
- package/template/saas-starter/src/lib/actions.ts +34 -0
- package/template/saas-starter/src/lib/admin-navigation.ts +33 -1
- package/template/saas-starter/src/lib/app-navigation.ts +45 -41
- package/template/saas-starter/src/lib/auth-client.ts +8 -3
- package/template/saas-starter/src/lib/auth-permissions.ts +50 -11
- package/template/saas-starter/src/lib/auth.ts +122 -21
- package/template/saas-starter/src/lib/billing.ts +59 -24
- package/template/saas-starter/src/lib/callback-url.ts +28 -0
- package/template/saas-starter/src/lib/constants.ts +78 -9
- package/template/saas-starter/src/lib/delay.ts +10 -0
- package/template/saas-starter/src/lib/email-service.ts +30 -1
- package/template/saas-starter/src/lib/errors.ts +74 -0
- package/template/saas-starter/src/lib/format-currency.ts +22 -24
- package/template/saas-starter/src/lib/format-date.ts +69 -6
- package/template/saas-starter/src/lib/image-url.ts +54 -0
- package/template/saas-starter/src/lib/nodemailer.ts +27 -3
- package/template/saas-starter/src/lib/organization-access.ts +56 -76
- package/template/saas-starter/src/lib/otel.ts +92 -2
- package/template/saas-starter/src/lib/plan-limits.ts +37 -0
- package/template/saas-starter/src/lib/plans.ts +40 -13
- package/template/saas-starter/src/lib/rbac.ts +44 -0
- package/template/saas-starter/src/lib/safe-action-client.ts +60 -9
- package/template/saas-starter/src/lib/safe-action.ts +137 -17
- package/template/saas-starter/src/lib/search.ts +16 -0
- package/template/saas-starter/src/lib/seo.ts +7 -0
- package/template/saas-starter/src/lib/siteConfig.ts +17 -1
- package/template/saas-starter/src/lib/storage.ts +176 -46
- package/template/saas-starter/src/lib/stripe.ts +4 -1
- package/template/saas-starter/src/lib/toast-message.ts +87 -1
- package/template/saas-starter/src/lib/validations.ts +85 -2
- package/template/saas-starter/src/proxy.ts +48 -0
- package/template/saas-starter/src/scripts/cleanup-temp-uploads.ts +24 -0
- package/template/saas-starter/src/scripts/seed.ts +82 -8
- package/template/saas-starter/src/scripts/setup-db.ts +72 -0
- package/template/saas-starter/src/scripts/setup-stripe-plans.ts +13 -7
- package/template/saas-starter/src/scripts/test-mail.ts +28 -20
- package/template/saas-starter/tests/admin/users.test.ts +64 -0
- package/template/saas-starter/tests/db/fk-indexes.test.ts +62 -0
- package/template/saas-starter/tests/db/seed.test.ts +57 -0
- package/template/saas-starter/tests/helpers/action.ts +71 -0
- package/template/saas-starter/tests/helpers/fixtures.ts +151 -0
- package/template/saas-starter/tests/helpers/index.ts +2 -0
- package/template/saas-starter/tests/lib/callback-url.test.ts +48 -0
- package/template/saas-starter/tests/lib/client-server-boundary.test.ts +57 -0
- package/template/saas-starter/tests/lib/errors.test.ts +10 -0
- package/template/saas-starter/tests/lib/image-url.test.ts +31 -0
- package/template/saas-starter/tests/lib/search.test.ts +23 -0
- package/template/saas-starter/tests/marketing/contact-and-feedback.test.ts +86 -0
- package/template/saas-starter/tests/organizations/organizations.test.ts +175 -0
- package/template/saas-starter/tests/setup/pglite-db.ts +67 -0
- package/template/saas-starter/tests/setup/preload.ts +108 -0
- package/template/saas-starter/tests/support/support-tickets.test.ts +145 -0
- package/template/saas-starter/tsconfig.json +1 -0
- package/cli.js +0 -147
- package/template/nextjs/src/app/(admin)/layout.tsx +0 -20
- package/template/nextjs/src/app/(app)/layout.tsx +0 -20
- package/template/nextjs/terraform/artifact-registry.tf +0 -5
- package/template/nextjs/terraform/backend-service.tf +0 -13
- package/template/nextjs/terraform/cleanup.sh +0 -9
- package/template/nextjs/terraform/cloudbuild.tf +0 -70
- package/template/nextjs/terraform/cloudrun.tf +0 -68
- package/template/nextjs/terraform/database.tf +0 -60
- package/template/nextjs/terraform/forwarding-rule.tf +0 -43
- package/template/nextjs/terraform/gitignore +0 -4
- package/template/nextjs/terraform/ip-address.tf +0 -11
- package/template/nextjs/terraform/network-endpoint-group.tf +0 -11
- package/template/nextjs/terraform/network.tf +0 -21
- package/template/nextjs/terraform/pre.sh +0 -3
- package/template/nextjs/terraform/providers.tf +0 -18
- package/template/nextjs/terraform/secret.txt +0 -11
- package/template/nextjs/terraform/setup.sh +0 -25
- package/template/nextjs/terraform/ssl-certs.tf +0 -32
- package/template/nextjs/terraform/target-https-proxy copy.tf +0 -14
- package/template/nextjs/terraform/target-https-proxy.tf +0 -14
- package/template/nextjs/terraform/terraform.tfvars +0 -4
- package/template/nextjs/terraform/url-map.tf +0 -23
- package/template/nextjs/terraform/variables.tf +0 -21
- package/template/saas-starter/next-env.d.ts +0 -6
- package/template/saas-starter/src/actions/uploads.ts +0 -28
- package/template/saas-starter/src/actions/uploads.validation.ts +0 -14
- package/template/saas-starter/src/app/(admin)/admin/settings/users/_components/create-user-dialog.tsx +0 -183
- package/template/saas-starter/src/app/(admin)/admin/settings/users/_components/edit-name-dialog.tsx +0 -129
- package/template/saas-starter/src/app/(admin)/admin/settings/users/_components/users-table.tsx +0 -415
- package/template/saas-starter/src/app/(app)/actions/organization.ts +0 -92
- package/template/saas-starter/src/app/proxy.ts +0 -28
- package/template/saas-starter/src/components/impersonation-banner.tsx +0 -44
- package/template/saas-starter/src/lib/organization-plan-limits.ts +0 -38
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
import { siteConfig } from '~/lib/siteConfig'
|
|
3
|
+
|
|
4
|
+
/** Page shell shared by the privacy policy and the terms of service. */
|
|
5
|
+
export function LegalPage({
|
|
6
|
+
title,
|
|
7
|
+
children
|
|
8
|
+
}: {
|
|
9
|
+
title: string
|
|
10
|
+
children: ReactNode
|
|
11
|
+
}) {
|
|
12
|
+
return (
|
|
13
|
+
<article className="container mx-auto max-w-4xl px-4 py-12 md:px-0">
|
|
14
|
+
<h1 className="mb-8 text-4xl font-bold">{title}</h1>
|
|
15
|
+
<div className="text-muted-foreground space-y-6 leading-relaxed">
|
|
16
|
+
<p className="text-foreground font-medium">
|
|
17
|
+
Last updated: {siteConfig.legal.lastUpdated}
|
|
18
|
+
</p>
|
|
19
|
+
{children}
|
|
20
|
+
</div>
|
|
21
|
+
</article>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** One numbered section of a legal page. Number the title yourself. */
|
|
26
|
+
export function LegalSection({
|
|
27
|
+
title,
|
|
28
|
+
children
|
|
29
|
+
}: {
|
|
30
|
+
title: string
|
|
31
|
+
children: ReactNode
|
|
32
|
+
}) {
|
|
33
|
+
return (
|
|
34
|
+
<section className="space-y-4">
|
|
35
|
+
<h2 className="text-foreground mt-8 text-2xl font-semibold">{title}</h2>
|
|
36
|
+
{children}
|
|
37
|
+
</section>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Bulleted list styled for legal page body copy. */
|
|
42
|
+
export function LegalList({ children }: { children: ReactNode }) {
|
|
43
|
+
return <ul className="list-disc space-y-2 pl-6">{children}</ul>
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const linkClassName =
|
|
47
|
+
'text-primary focus-visible:ring-ring/50 rounded-sm underline underline-offset-4 focus-visible:ring-3 focus-visible:outline-none'
|
|
48
|
+
|
|
49
|
+
/** Inline mailto link to the support address. */
|
|
50
|
+
export function ContactEmailLink() {
|
|
51
|
+
return (
|
|
52
|
+
<a href={`mailto:${siteConfig.contact.email}`} className={linkClassName}>
|
|
53
|
+
{siteConfig.contact.email}
|
|
54
|
+
</a>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
@@ -1,3 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import Link from 'next/link'
|
|
2
|
+
import { Button } from '~/components/ui/button'
|
|
3
|
+
import { siteConfig } from '~/lib/siteConfig'
|
|
4
|
+
|
|
5
|
+
export default function HomePage() {
|
|
6
|
+
return (
|
|
7
|
+
<section className="container flex flex-col items-center gap-6 py-24 text-center">
|
|
8
|
+
<h1 className="max-w-2xl text-4xl font-bold tracking-tight sm:text-5xl">
|
|
9
|
+
{siteConfig.name}
|
|
10
|
+
</h1>
|
|
11
|
+
<p className="text-muted-foreground max-w-xl text-lg">
|
|
12
|
+
{siteConfig.description}
|
|
13
|
+
</p>
|
|
14
|
+
<div className="flex flex-wrap justify-center gap-3">
|
|
15
|
+
<Button asChild size="lg">
|
|
16
|
+
<Link href="/login">Get Started</Link>
|
|
17
|
+
</Button>
|
|
18
|
+
</div>
|
|
19
|
+
</section>
|
|
20
|
+
)
|
|
3
21
|
}
|
|
@@ -1,3 +1,44 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import type { Metadata } from 'next'
|
|
2
|
+
import { siteConfig } from '~/lib/siteConfig'
|
|
3
|
+
import { ContactEmailLink, LegalList, LegalPage, LegalSection } from '../legal'
|
|
4
|
+
|
|
5
|
+
export const metadata: Metadata = {
|
|
6
|
+
title: 'Privacy Policy',
|
|
7
|
+
description: `How ${siteConfig.name} collects, uses, and protects your data.`
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Starting point only. Have this reviewed before you launch, and update it
|
|
11
|
+
// with `siteConfig.legal.lastUpdated` whenever the product's data use changes.
|
|
12
|
+
export default function PrivacyPage() {
|
|
13
|
+
return (
|
|
14
|
+
<LegalPage title="Privacy Policy">
|
|
15
|
+
<p>
|
|
16
|
+
This policy explains what {siteConfig.name} collects, why, and what you
|
|
17
|
+
can do about it.
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
<LegalSection title="1. Information You Give Us">
|
|
21
|
+
<LegalList>
|
|
22
|
+
<li>
|
|
23
|
+
<strong>Account details</strong>: your name and email address. When
|
|
24
|
+
you sign in with Google or GitHub, that provider also shares your
|
|
25
|
+
profile picture.
|
|
26
|
+
</li>
|
|
27
|
+
</LegalList>
|
|
28
|
+
</LegalSection>
|
|
29
|
+
|
|
30
|
+
<LegalSection title="2. How We Use It">
|
|
31
|
+
<p>
|
|
32
|
+
We use this information to run your account and send the emails you
|
|
33
|
+
ask for.
|
|
34
|
+
</p>
|
|
35
|
+
</LegalSection>
|
|
36
|
+
|
|
37
|
+
<LegalSection title="3. Contact">
|
|
38
|
+
<p>
|
|
39
|
+
Email <ContactEmailLink /> with questions about your data.
|
|
40
|
+
</p>
|
|
41
|
+
</LegalSection>
|
|
42
|
+
</LegalPage>
|
|
43
|
+
)
|
|
3
44
|
}
|
|
@@ -1,3 +1,34 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import type { Metadata } from 'next'
|
|
2
|
+
import { siteConfig } from '~/lib/siteConfig'
|
|
3
|
+
import { ContactEmailLink, LegalPage, LegalSection } from '../legal'
|
|
4
|
+
|
|
5
|
+
export const metadata: Metadata = {
|
|
6
|
+
title: 'Terms of Service',
|
|
7
|
+
description: `The terms that apply when you use ${siteConfig.name}.`
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Starting point only. Have this reviewed before you launch, and update it
|
|
11
|
+
// with `siteConfig.legal.lastUpdated` whenever the product's behavior changes.
|
|
12
|
+
export default function TermsPage() {
|
|
13
|
+
return (
|
|
14
|
+
<LegalPage title="Terms of Service">
|
|
15
|
+
<p>
|
|
16
|
+
These terms apply when you use {siteConfig.name}. By creating an
|
|
17
|
+
account, you agree to them.
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
<LegalSection title="1. Your Account">
|
|
21
|
+
<p>
|
|
22
|
+
Keep access to your email inbox secure: anyone who can read it can
|
|
23
|
+
sign in as you.
|
|
24
|
+
</p>
|
|
25
|
+
</LegalSection>
|
|
26
|
+
|
|
27
|
+
<LegalSection title="2. Contact">
|
|
28
|
+
<p>
|
|
29
|
+
Email <ContactEmailLink /> with questions about these terms.
|
|
30
|
+
</p>
|
|
31
|
+
</LegalSection>
|
|
32
|
+
</LegalPage>
|
|
33
|
+
)
|
|
3
34
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { usePathname } from 'next/navigation'
|
|
4
|
+
import { useEffect } from 'react'
|
|
5
|
+
import { RouteError } from '~/components/route-error'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Root segment error boundary.
|
|
9
|
+
*
|
|
10
|
+
* Catches runtime errors in pages and nested layouts under the root layout.
|
|
11
|
+
* Does not catch errors thrown in the root layout itself; see `global-error.tsx`.
|
|
12
|
+
*/
|
|
13
|
+
export default function Error({
|
|
14
|
+
error,
|
|
15
|
+
unstable_retry
|
|
16
|
+
}: {
|
|
17
|
+
error: Error & { digest?: string }
|
|
18
|
+
unstable_retry: () => void
|
|
19
|
+
}) {
|
|
20
|
+
const pathname = usePathname()
|
|
21
|
+
const homeHref =
|
|
22
|
+
pathname?.startsWith('/admin') && pathname !== '/admin' ? '/admin' : '/'
|
|
23
|
+
const homeLabel = homeHref === '/admin' ? 'Back to Admin' : 'Go Home'
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
console.error('Route error boundary:', error)
|
|
27
|
+
}, [error])
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<RouteError
|
|
31
|
+
error={error}
|
|
32
|
+
onRetry={unstable_retry}
|
|
33
|
+
homeHref={homeHref}
|
|
34
|
+
homeLabel={homeLabel}
|
|
35
|
+
/>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Global font definitions shared across all route groups.
|
|
3
|
+
*
|
|
4
|
+
* Fonts are defined here in a single module so that `next/font/local` generates
|
|
5
|
+
* a stable CSS module hash regardless of which layout imports them. Defining
|
|
6
|
+
* fonts inside individual route-group layouts (e.g. `(marketing)` or `(admin)`)
|
|
7
|
+
* causes the server and client to produce different class name hashes, leading
|
|
8
|
+
* to React hydration mismatches.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* import { geistMono, geistSans } from '~/app/fonts'
|
|
12
|
+
* <body className={`${geistSans.variable} ${geistMono.variable}`}>
|
|
13
|
+
*
|
|
14
|
+
* @module app/fonts/index
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import localFont from 'next/font/local'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Geist Sans - primary variable font for body and UI text.
|
|
21
|
+
*
|
|
22
|
+
* Exposed as the CSS custom property `--font-geist-sans` so it can be
|
|
23
|
+
* referenced in Tailwind via `font-sans`.
|
|
24
|
+
*
|
|
25
|
+
* @see https://vercel.com/font/sans
|
|
26
|
+
*/
|
|
27
|
+
export const geistSans = localFont({
|
|
28
|
+
src: '../fonts/geist-sans/Geist-Variable.woff2',
|
|
29
|
+
variable: '--font-geist-sans',
|
|
30
|
+
weight: '100 900'
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Geist Mono - monospace variable font for code blocks, badges, and
|
|
35
|
+
* any UI element that benefits from fixed-width rendering.
|
|
36
|
+
*
|
|
37
|
+
* Exposed as the CSS custom property `--font-geist-mono`.
|
|
38
|
+
*
|
|
39
|
+
* `adjustFontFallback` is disabled because the automatic metric adjustment
|
|
40
|
+
* produces undesirable layout shifts with the custom fallback stack below.
|
|
41
|
+
*
|
|
42
|
+
* @see https://vercel.com/font/mono
|
|
43
|
+
*/
|
|
44
|
+
export const geistMono = localFont({
|
|
45
|
+
src: '../fonts/geist-mono/GeistMono-Variable.woff2',
|
|
46
|
+
variable: '--font-geist-mono',
|
|
47
|
+
adjustFontFallback: false,
|
|
48
|
+
fallback: [
|
|
49
|
+
'ui-monospace',
|
|
50
|
+
'SFMono-Regular',
|
|
51
|
+
'Roboto Mono',
|
|
52
|
+
'Menlo',
|
|
53
|
+
'Monaco',
|
|
54
|
+
'Liberation Mono',
|
|
55
|
+
'DejaVu Sans Mono',
|
|
56
|
+
'Courier New',
|
|
57
|
+
'monospace'
|
|
58
|
+
],
|
|
59
|
+
weight: '100 900'
|
|
60
|
+
})
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect } from 'react'
|
|
4
|
+
import { RouteError } from '~/components/route-error'
|
|
5
|
+
import { siteConfig } from '~/lib/siteConfig'
|
|
6
|
+
import { geistMono, geistSans } from './fonts'
|
|
7
|
+
import './globals.css'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Root layout error boundary.
|
|
11
|
+
*
|
|
12
|
+
* Replaces the entire root layout when an error is thrown there.
|
|
13
|
+
* Must define its own document shell (`html` / `body`) and styles.
|
|
14
|
+
*/
|
|
15
|
+
export default function GlobalError({
|
|
16
|
+
error,
|
|
17
|
+
unstable_retry
|
|
18
|
+
}: {
|
|
19
|
+
error: Error & { digest?: string }
|
|
20
|
+
unstable_retry: () => void
|
|
21
|
+
}) {
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
console.error('Global error boundary:', error)
|
|
24
|
+
}, [error])
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<html
|
|
28
|
+
lang="en"
|
|
29
|
+
suppressHydrationWarning
|
|
30
|
+
className={`${geistSans.variable} ${geistMono.variable}`}
|
|
31
|
+
>
|
|
32
|
+
<body>
|
|
33
|
+
<title>This Page Failed to Load | {siteConfig.name}</title>
|
|
34
|
+
<RouteError error={error} onRetry={unstable_retry} />
|
|
35
|
+
</body>
|
|
36
|
+
</html>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
@@ -28,6 +28,14 @@
|
|
|
28
28
|
--color-chart-3: var(--chart-3);
|
|
29
29
|
--color-chart-4: var(--chart-4);
|
|
30
30
|
--color-chart-5: var(--chart-5);
|
|
31
|
+
|
|
32
|
+
--color-success: var(--success);
|
|
33
|
+
--color-success-foreground: var(--success-foreground);
|
|
34
|
+
--color-warning: var(--warning);
|
|
35
|
+
--color-warning-foreground: var(--warning-foreground);
|
|
36
|
+
--color-info: var(--info);
|
|
37
|
+
--color-info-foreground: var(--info-foreground);
|
|
38
|
+
|
|
31
39
|
--color-sidebar: var(--sidebar);
|
|
32
40
|
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
33
41
|
--color-sidebar-primary: var(--sidebar-primary);
|
|
@@ -84,6 +92,13 @@
|
|
|
84
92
|
--sidebar-accent-foreground: oklch(0.205 0 0);
|
|
85
93
|
--sidebar-border: oklch(0.922 0 0);
|
|
86
94
|
--sidebar-ring: oklch(0.708 0 0);
|
|
95
|
+
|
|
96
|
+
--success: oklch(0.552 0.152 149.579);
|
|
97
|
+
--success-foreground: oklch(1 0 0);
|
|
98
|
+
--warning: oklch(0.574 0.188 70.08);
|
|
99
|
+
--warning-foreground: oklch(1 0 0);
|
|
100
|
+
--info: oklch(0.569 0.165 254.624);
|
|
101
|
+
--info-foreground: oklch(1 0 0);
|
|
87
102
|
}
|
|
88
103
|
|
|
89
104
|
.dark {
|
|
@@ -119,6 +134,13 @@
|
|
|
119
134
|
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
120
135
|
--sidebar-border: oklch(1 0 0 / 10%);
|
|
121
136
|
--sidebar-ring: oklch(0.556 0 0);
|
|
137
|
+
|
|
138
|
+
--success: oklch(0.723 0.192 149.579);
|
|
139
|
+
--success-foreground: oklch(0.145 0 0);
|
|
140
|
+
--warning: oklch(0.769 0.188 70.08);
|
|
141
|
+
--warning-foreground: oklch(0.205 0 0);
|
|
142
|
+
--info: oklch(0.707 0.165 254.624);
|
|
143
|
+
--info-foreground: oklch(0.205 0 0);
|
|
122
144
|
}
|
|
123
145
|
|
|
124
146
|
@layer base {
|
|
@@ -128,6 +150,7 @@
|
|
|
128
150
|
|
|
129
151
|
html {
|
|
130
152
|
@apply font-sans antialiased;
|
|
153
|
+
scroll-behavior: smooth;
|
|
131
154
|
}
|
|
132
155
|
|
|
133
156
|
body {
|
|
@@ -138,6 +161,12 @@
|
|
|
138
161
|
[role='button']:not([disabled]) {
|
|
139
162
|
cursor: pointer;
|
|
140
163
|
}
|
|
164
|
+
|
|
165
|
+
@media (prefers-reduced-motion: reduce) {
|
|
166
|
+
html {
|
|
167
|
+
scroll-behavior: auto;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
141
170
|
}
|
|
142
171
|
|
|
143
172
|
@theme inline {
|
|
@@ -1,27 +1,49 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Root layout: document shell, fonts, metadata, and the
|
|
3
|
+
* client providers every page shares (TanStack Query, theme, tooltips,
|
|
4
|
+
* toasts, and the navigation progress bar).
|
|
5
|
+
*
|
|
6
|
+
* @module app/layout
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Metadata, Viewport } from 'next'
|
|
3
10
|
import { Loader } from '~/components/loader'
|
|
4
11
|
import { ReactQueryProvider } from '~/components/react-query-provider'
|
|
5
12
|
import { ScreenSize } from '~/components/screen-size'
|
|
6
13
|
import { ThemeProvider } from '~/components/theme-provider'
|
|
7
14
|
import { Toaster } from '~/components/ui/sonner'
|
|
15
|
+
import { TooltipProvider } from '~/components/ui/tooltip'
|
|
8
16
|
import { env } from '~/env'
|
|
9
17
|
import { siteConfig } from '~/lib/siteConfig'
|
|
18
|
+
import { geistMono, geistSans } from './fonts'
|
|
10
19
|
import './globals.css'
|
|
11
20
|
|
|
12
|
-
const geistSans = Geist({
|
|
13
|
-
variable: '--font-geist-sans',
|
|
14
|
-
subsets: ['latin']
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
const geistMono = Geist_Mono({
|
|
18
|
-
variable: '--font-geist-mono',
|
|
19
|
-
subsets: ['latin']
|
|
20
|
-
})
|
|
21
|
-
|
|
22
21
|
export const metadata: Metadata = {
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
metadataBase: new URL(env.BETTER_AUTH_URL),
|
|
23
|
+
title: {
|
|
24
|
+
default: siteConfig.name,
|
|
25
|
+
template: `%s | ${siteConfig.name}`
|
|
26
|
+
},
|
|
27
|
+
description: siteConfig.description,
|
|
28
|
+
applicationName: siteConfig.name,
|
|
29
|
+
openGraph: {
|
|
30
|
+
type: 'website',
|
|
31
|
+
locale: 'en_US',
|
|
32
|
+
siteName: siteConfig.name,
|
|
33
|
+
description: siteConfig.description
|
|
34
|
+
},
|
|
35
|
+
twitter: {
|
|
36
|
+
card: 'summary_large_image',
|
|
37
|
+
description: siteConfig.description
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Browser chrome colors, matching `--background` in globals.css. */
|
|
42
|
+
export const viewport: Viewport = {
|
|
43
|
+
themeColor: [
|
|
44
|
+
{ media: '(prefers-color-scheme: light)', color: '#ffffff' },
|
|
45
|
+
{ media: '(prefers-color-scheme: dark)', color: '#0a0a0a' }
|
|
46
|
+
]
|
|
25
47
|
}
|
|
26
48
|
|
|
27
49
|
export default function RootLayout({
|
|
@@ -38,13 +60,18 @@ export default function RootLayout({
|
|
|
38
60
|
<body>
|
|
39
61
|
<ReactQueryProvider>
|
|
40
62
|
<ThemeProvider>
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
63
|
+
<TooltipProvider>
|
|
64
|
+
<Loader />
|
|
65
|
+
{children}
|
|
66
|
+
{env.APP_ENV === 'development' ? <ScreenSize /> : null}
|
|
67
|
+
<Toaster />
|
|
68
|
+
</TooltipProvider>
|
|
45
69
|
</ThemeProvider>
|
|
46
70
|
</ReactQueryProvider>
|
|
47
71
|
</body>
|
|
48
72
|
</html>
|
|
49
73
|
)
|
|
50
74
|
}
|
|
75
|
+
|
|
76
|
+
/** Renders every request dynamically, so env-driven metadata stays current. */
|
|
77
|
+
export const dynamic = 'force-dynamic'
|
|
@@ -1,12 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview 404 Not Found Page
|
|
3
|
+
*
|
|
4
|
+
* Global catch-all page displayed when a route is not found.
|
|
5
|
+
* Redirects users back to the appropriate home page based on the current route context.
|
|
6
|
+
*
|
|
7
|
+
* **Behavior:**
|
|
8
|
+
* - Admin routes (/admin/*) redirect to /admin
|
|
9
|
+
* - All other routes redirect to /
|
|
10
|
+
*
|
|
11
|
+
* @module app/not-found
|
|
12
|
+
*/
|
|
13
|
+
|
|
1
14
|
'use client'
|
|
2
15
|
|
|
3
16
|
import Link from 'next/link'
|
|
4
17
|
import { usePathname } from 'next/navigation'
|
|
5
18
|
import { Button } from '~/components/ui/button'
|
|
6
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Not Found Page Component
|
|
22
|
+
*
|
|
23
|
+
* Displays a 404 error message with context-aware home button.
|
|
24
|
+
* Uses the current pathname to determine the appropriate home route.
|
|
25
|
+
*
|
|
26
|
+
* @returns {JSX.Element} 404 error page with navigation button
|
|
27
|
+
*/
|
|
7
28
|
export default function NotFound() {
|
|
8
29
|
const pathname = usePathname()
|
|
9
|
-
const homeRoute =
|
|
30
|
+
const homeRoute =
|
|
31
|
+
pathname?.startsWith('/admin') && pathname !== '/admin' ? '/admin' : '/'
|
|
10
32
|
|
|
11
33
|
return (
|
|
12
34
|
<main className="bg-background text-foreground flex min-h-screen flex-col">
|
|
@@ -15,10 +37,10 @@ export default function NotFound() {
|
|
|
15
37
|
<h1 className="text-foreground mb-2 text-6xl font-bold">404</h1>
|
|
16
38
|
<p className="text-muted-foreground mb-8 text-xl">Page not found</p>
|
|
17
39
|
<p className="text-muted-foreground mb-8 max-w-md text-base">
|
|
18
|
-
The page you
|
|
40
|
+
The page you're looking for doesn't exist.
|
|
19
41
|
</p>
|
|
20
42
|
<Button asChild>
|
|
21
|
-
<Link href={homeRoute}>Return
|
|
43
|
+
<Link href={homeRoute}>Return Home</Link>
|
|
22
44
|
</Button>
|
|
23
45
|
</div>
|
|
24
46
|
</section>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { MetadataRoute } from 'next'
|
|
2
|
+
import { env } from '~/env'
|
|
3
|
+
|
|
4
|
+
/** Renders every request dynamically, so the environment check is live. */
|
|
5
|
+
export const dynamic = 'force-dynamic'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* robots.txt. Staging blocks every crawler; production keeps signed-in
|
|
9
|
+
* surfaces and the API out of search results.
|
|
10
|
+
*/
|
|
11
|
+
export default function robots(): MetadataRoute.Robots {
|
|
12
|
+
if (env.APP_ENV === 'staging') {
|
|
13
|
+
return { rules: [{ userAgent: '*', disallow: '/' }] }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const baseUrl = env.BETTER_AUTH_URL
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
rules: [
|
|
20
|
+
{
|
|
21
|
+
userAgent: '*',
|
|
22
|
+
allow: '/',
|
|
23
|
+
disallow: ['/api/', '/admin', '/app', '/profile']
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
sitemap: `${baseUrl}/sitemap.xml`,
|
|
27
|
+
host: baseUrl
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { MetadataRoute } from 'next'
|
|
2
|
+
import { env } from '~/env'
|
|
3
|
+
|
|
4
|
+
export const dynamic = 'force-dynamic'
|
|
5
|
+
|
|
6
|
+
/** Public pages only. Staging returns an empty sitemap. */
|
|
7
|
+
export default function sitemap(): MetadataRoute.Sitemap {
|
|
8
|
+
if (env.APP_ENV === 'staging') return []
|
|
9
|
+
|
|
10
|
+
const baseUrl = env.BETTER_AUTH_URL
|
|
11
|
+
const lastModified = new Date()
|
|
12
|
+
|
|
13
|
+
const routes = ['/', '/privacy', '/terms', '/login']
|
|
14
|
+
|
|
15
|
+
return routes.map(path => ({
|
|
16
|
+
url: `${baseUrl}${path === '/' ? '' : path}`,
|
|
17
|
+
lastModified,
|
|
18
|
+
changeFrequency: path === '/' ? 'weekly' : 'monthly',
|
|
19
|
+
priority: path === '/' ? 1 : 0.7
|
|
20
|
+
}))
|
|
21
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Loader2 } from 'lucide-react'
|
|
4
|
+
import type { ReactNode } from 'react'
|
|
5
|
+
import {
|
|
6
|
+
AlertDialog,
|
|
7
|
+
AlertDialogCancel,
|
|
8
|
+
AlertDialogContent,
|
|
9
|
+
AlertDialogDescription,
|
|
10
|
+
AlertDialogFooter,
|
|
11
|
+
AlertDialogHeader,
|
|
12
|
+
AlertDialogTitle
|
|
13
|
+
} from '~/components/ui/alert-dialog'
|
|
14
|
+
import { Button } from '~/components/ui/button'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Controlled confirmation for destructive or hard-to-undo actions. Use it
|
|
18
|
+
* instead of `window.confirm`.
|
|
19
|
+
*
|
|
20
|
+
* The dialog stays open while `pending` is true; close it from the
|
|
21
|
+
* caller's success handler by clearing whatever drives `open`.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* <ConfirmDialog
|
|
25
|
+
* open={target !== null}
|
|
26
|
+
* onOpenChange={open => !open && setTarget(null)}
|
|
27
|
+
* title="Delete User"
|
|
28
|
+
* description="This permanently deletes the account."
|
|
29
|
+
* confirmLabel="Delete User"
|
|
30
|
+
* pending={isDeleting}
|
|
31
|
+
* onConfirm={() => deleteUser(target)}
|
|
32
|
+
* />
|
|
33
|
+
*/
|
|
34
|
+
export function ConfirmDialog({
|
|
35
|
+
open,
|
|
36
|
+
onOpenChange,
|
|
37
|
+
title,
|
|
38
|
+
description,
|
|
39
|
+
confirmLabel,
|
|
40
|
+
pending = false,
|
|
41
|
+
destructive = true,
|
|
42
|
+
onConfirm
|
|
43
|
+
}: {
|
|
44
|
+
open: boolean
|
|
45
|
+
onOpenChange: (open: boolean) => void
|
|
46
|
+
title: string
|
|
47
|
+
description: ReactNode
|
|
48
|
+
confirmLabel: string
|
|
49
|
+
pending?: boolean
|
|
50
|
+
destructive?: boolean
|
|
51
|
+
onConfirm: () => void
|
|
52
|
+
}) {
|
|
53
|
+
return (
|
|
54
|
+
<AlertDialog
|
|
55
|
+
open={open}
|
|
56
|
+
onOpenChange={next => {
|
|
57
|
+
if (!pending) onOpenChange(next)
|
|
58
|
+
}}
|
|
59
|
+
>
|
|
60
|
+
<AlertDialogContent>
|
|
61
|
+
<AlertDialogHeader>
|
|
62
|
+
<AlertDialogTitle>{title}</AlertDialogTitle>
|
|
63
|
+
<AlertDialogDescription>{description}</AlertDialogDescription>
|
|
64
|
+
</AlertDialogHeader>
|
|
65
|
+
<AlertDialogFooter>
|
|
66
|
+
<AlertDialogCancel disabled={pending}>Cancel</AlertDialogCancel>
|
|
67
|
+
<Button
|
|
68
|
+
variant={destructive ? 'destructive' : 'default'}
|
|
69
|
+
disabled={pending}
|
|
70
|
+
onClick={onConfirm}
|
|
71
|
+
>
|
|
72
|
+
{pending ? <Loader2 className="animate-spin" /> : null}
|
|
73
|
+
{confirmLabel}
|
|
74
|
+
</Button>
|
|
75
|
+
</AlertDialogFooter>
|
|
76
|
+
</AlertDialogContent>
|
|
77
|
+
</AlertDialog>
|
|
78
|
+
)
|
|
79
|
+
}
|