create-ej-app 26.0.0 → 28.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 +47 -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 +50 -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,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
|
+
}
|
|
@@ -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,10 +60,12 @@ 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>
|
|
@@ -49,5 +73,5 @@ export default function RootLayout({
|
|
|
49
73
|
)
|
|
50
74
|
}
|
|
51
75
|
|
|
52
|
-
/**
|
|
76
|
+
/** Renders every request dynamically, so env-driven metadata stays current. */
|
|
53
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>
|
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
import type { MetadataRoute } from 'next'
|
|
2
2
|
import { env } from '~/env'
|
|
3
3
|
|
|
4
|
+
/** Renders every request dynamically, so the environment check is live. */
|
|
4
5
|
export const dynamic = 'force-dynamic'
|
|
5
6
|
|
|
7
|
+
/**
|
|
8
|
+
* robots.txt. Staging blocks every crawler; production keeps signed-in
|
|
9
|
+
* surfaces and the API out of search results.
|
|
10
|
+
*/
|
|
6
11
|
export default function robots(): MetadataRoute.Robots {
|
|
7
|
-
// Block all crawlers in staging to prevent accidental indexing
|
|
8
12
|
if (env.APP_ENV === 'staging') {
|
|
9
|
-
return {
|
|
10
|
-
rules: [
|
|
11
|
-
{
|
|
12
|
-
userAgent: '*',
|
|
13
|
-
disallow: '/'
|
|
14
|
-
}
|
|
15
|
-
]
|
|
16
|
-
}
|
|
13
|
+
return { rules: [{ userAgent: '*', disallow: '/' }] }
|
|
17
14
|
}
|
|
18
15
|
|
|
19
16
|
const baseUrl = env.BETTER_AUTH_URL
|
|
@@ -23,7 +20,14 @@ export default function robots(): MetadataRoute.Robots {
|
|
|
23
20
|
{
|
|
24
21
|
userAgent: '*',
|
|
25
22
|
allow: '/',
|
|
26
|
-
disallow: [
|
|
23
|
+
disallow: [
|
|
24
|
+
'/api/',
|
|
25
|
+
'/admin',
|
|
26
|
+
'/app',
|
|
27
|
+
'/accept-invitation',
|
|
28
|
+
'/profile',
|
|
29
|
+
'/support'
|
|
30
|
+
]
|
|
27
31
|
}
|
|
28
32
|
],
|
|
29
33
|
sitemap: `${baseUrl}/sitemap.xml`,
|
|
@@ -3,13 +3,14 @@ import { env } from '~/env'
|
|
|
3
3
|
|
|
4
4
|
export const dynamic = 'force-dynamic'
|
|
5
5
|
|
|
6
|
+
/** Public pages only. Staging returns an empty sitemap. */
|
|
6
7
|
export default function sitemap(): MetadataRoute.Sitemap {
|
|
7
8
|
if (env.APP_ENV === 'staging') return []
|
|
8
9
|
|
|
9
10
|
const baseUrl = env.BETTER_AUTH_URL
|
|
10
11
|
const lastModified = new Date()
|
|
11
12
|
|
|
12
|
-
const routes = ['/', '/privacy', '/terms', '/login']
|
|
13
|
+
const routes = ['/', '/contact-us', '/privacy', '/terms', '/login']
|
|
13
14
|
|
|
14
15
|
return routes.map(path => ({
|
|
15
16
|
url: `${baseUrl}${path === '/' ? '' : path}`,
|
|
@@ -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
|
+
}
|
|
@@ -20,6 +20,8 @@ type DashboardHeaderProps = {
|
|
|
20
20
|
actions?: ReactNode
|
|
21
21
|
fallbackName?: string
|
|
22
22
|
fallbackInitials?: string
|
|
23
|
+
isSuperAdmin?: boolean
|
|
24
|
+
isImpersonating?: boolean
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
export function DashboardHeader({
|
|
@@ -27,7 +29,9 @@ export function DashboardHeader({
|
|
|
27
29
|
search,
|
|
28
30
|
actions,
|
|
29
31
|
fallbackName,
|
|
30
|
-
fallbackInitials
|
|
32
|
+
fallbackInitials,
|
|
33
|
+
isSuperAdmin,
|
|
34
|
+
isImpersonating
|
|
31
35
|
}: DashboardHeaderProps) {
|
|
32
36
|
return (
|
|
33
37
|
<header className="bg-background sticky top-0 z-50 flex h-14 shrink-0 items-center gap-2 border-b px-4">
|
|
@@ -57,6 +61,8 @@ export function DashboardHeader({
|
|
|
57
61
|
user={user}
|
|
58
62
|
fallbackName={fallbackName}
|
|
59
63
|
fallbackInitials={fallbackInitials}
|
|
64
|
+
isSuperAdmin={isSuperAdmin}
|
|
65
|
+
isImpersonating={isImpersonating}
|
|
60
66
|
/>
|
|
61
67
|
</div>
|
|
62
68
|
</header>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared TanStack Table v9 feature set for the app DataTable.
|
|
3
|
+
*
|
|
4
|
+
* Defined once outside components so feature types stay stable and tree-shaken.
|
|
5
|
+
*/
|
|
6
|
+
import {
|
|
7
|
+
type ColumnDef,
|
|
8
|
+
type RowData,
|
|
9
|
+
columnVisibilityFeature,
|
|
10
|
+
createPaginatedRowModel,
|
|
11
|
+
createSortedRowModel,
|
|
12
|
+
rowPaginationFeature,
|
|
13
|
+
rowSortingFeature,
|
|
14
|
+
sortFn_alphanumeric,
|
|
15
|
+
sortFn_alphanumericCaseSensitive,
|
|
16
|
+
sortFn_basic,
|
|
17
|
+
sortFn_datetime,
|
|
18
|
+
sortFn_text,
|
|
19
|
+
sortFn_textCaseSensitive,
|
|
20
|
+
tableFeatures
|
|
21
|
+
} from '@tanstack/react-table'
|
|
22
|
+
|
|
23
|
+
/** Features registered by every DataTable instance. */
|
|
24
|
+
export const dataTableFeatures = tableFeatures({
|
|
25
|
+
rowSortingFeature,
|
|
26
|
+
rowPaginationFeature,
|
|
27
|
+
columnVisibilityFeature,
|
|
28
|
+
sortedRowModel: createSortedRowModel(),
|
|
29
|
+
paginatedRowModel: createPaginatedRowModel(),
|
|
30
|
+
sortFns: {
|
|
31
|
+
alphanumeric: sortFn_alphanumeric,
|
|
32
|
+
alphanumericCaseSensitive: sortFn_alphanumericCaseSensitive,
|
|
33
|
+
text: sortFn_text,
|
|
34
|
+
textCaseSensitive: sortFn_textCaseSensitive,
|
|
35
|
+
datetime: sortFn_datetime,
|
|
36
|
+
basic: sortFn_basic
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
type DataTableFeatures = typeof dataTableFeatures
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Column definition typed to the DataTable feature set.
|
|
44
|
+
* Prefer this over raw `ColumnDef` from `@tanstack/react-table`.
|
|
45
|
+
*/
|
|
46
|
+
export type DataTableColumnDef<
|
|
47
|
+
TData extends RowData,
|
|
48
|
+
TValue = unknown
|
|
49
|
+
> = ColumnDef<DataTableFeatures, TData, TValue>
|