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 @@
|
|
|
1
|
+
@AGENTS.md
|
|
@@ -7,25 +7,25 @@
|
|
|
7
7
|
"dev": "bun --watch .",
|
|
8
8
|
"build": "bun type-check && bun build --compile --target bun --outfile server .",
|
|
9
9
|
"build:docker": "docker build -t my-api .",
|
|
10
|
-
"type-check": "
|
|
10
|
+
"type-check": "tsc --incremental false",
|
|
11
11
|
"format": "prettier --write \"./src/**/*.{js,cjs,mjs,jsx,ts,tsx}\"",
|
|
12
12
|
"db:push": "drizzle-kit push",
|
|
13
13
|
"db:studio": "drizzle-kit studio",
|
|
14
14
|
"db:seed": "bun ./src/scripts/seed.ts"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@elysiajs/openapi": "1.4.
|
|
17
|
+
"@elysiajs/openapi": "1.4.16",
|
|
18
18
|
"@t3-oss/env-core": "0.13.11",
|
|
19
|
-
"drizzle-orm": "0.45.
|
|
20
|
-
"elysia": "1.4.
|
|
21
|
-
"zod": "4.
|
|
19
|
+
"drizzle-orm": "0.45.3",
|
|
20
|
+
"elysia": "1.4.30",
|
|
21
|
+
"zod": "4.6.5"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@types/bun": "1.
|
|
25
|
-
"
|
|
26
|
-
"drizzle-kit": "0.31.10",
|
|
24
|
+
"@types/bun": "1.4.2",
|
|
25
|
+
"drizzle-kit": "0.31.11",
|
|
27
26
|
"postgres": "3.4.9",
|
|
28
|
-
"prettier": "3.
|
|
29
|
-
"prettier-plugin-organize-imports": "4.3.0"
|
|
27
|
+
"prettier": "3.9.9",
|
|
28
|
+
"prettier-plugin-organize-imports": "4.3.0",
|
|
29
|
+
"typescript": "7.0.2"
|
|
30
30
|
}
|
|
31
31
|
}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
Shared guidance for agentic coding tools working in this repository.
|
|
4
|
+
|
|
5
|
+
## What is this project
|
|
6
|
+
|
|
7
|
+
{{PROJECT_DESCRIPTION}}
|
|
8
|
+
|
|
9
|
+
It is a full-stack Next.js app built on the `nextjs` template from `create-ej-app`. People sign in with a magic link or OAuth, and platform admins manage users from an admin panel.
|
|
10
|
+
|
|
11
|
+
## Coding preferences
|
|
12
|
+
|
|
13
|
+
- Keep things simple. Channel "yagni" energy unless told otherwise.
|
|
14
|
+
- Don't export a function or type, or define a type, that nothing uses.
|
|
15
|
+
- Type safety is useful; take advantage of it.
|
|
16
|
+
- Don't write code that introduces security vulnerabilities. Include authorization checks and input validation, and follow security best practices.
|
|
17
|
+
- Don't be scared to propose bold ideas or breaking changes if they can meaningfully benefit our work.
|
|
18
|
+
- Be careful with destructive actions I haven't explicitly requested.
|
|
19
|
+
- Write performant, optimized code.
|
|
20
|
+
- Don't hardcode limits, labels, or repeated values. Put them in `src/lib/constants.ts` or the matching registry, and read them from there in the UI, validation, tests and docs so one change updates every place.
|
|
21
|
+
- Tests are good! Endless smoke tests and "regression tests" for feature deletions are much less good. Tests should be focused, not slop.
|
|
22
|
+
- Comments are a great way to clarify functionality and how code is used. Don't comment every line. Concisely describe how functions and classes are used above their definitions, and explain only the tricky lines.
|
|
23
|
+
- If you need a paragraph-long comment to justify why a workaround is OK, the code is wrong. Fix the code unless I tell you otherwise.
|
|
24
|
+
- When designing or building something new, ask me about any design or flow decision you're unsure about, unless the answer is obvious. I'd rather clarify upfront than redo work built on an assumption. When you ask, give your recommendation and suggested answers so I can confirm, redirect, or decide.
|
|
25
|
+
- Report any bug, problem, or security issue you notice anywhere in the codebase, even if it's unrelated to the current work. Don't silently fix or ignore it.
|
|
26
|
+
|
|
27
|
+
## Writing guidelines
|
|
28
|
+
|
|
29
|
+
These cover every word that ships: UI copy, dialog and page titles, toasts, validation and error messages, emails, code comments, commit messages, PR descriptions, and Markdown in the repo.
|
|
30
|
+
|
|
31
|
+
**Fix the English you notice.** Typos, awkward phrasing, and copy that breaks these rules get fixed on sight, even outside the task you were given. This is the one exception to the reporting rule above. Bugs and security issues are still reported rather than silently changed; wording is not.
|
|
32
|
+
|
|
33
|
+
### Casing
|
|
34
|
+
|
|
35
|
+
Title Case for headings, page and section titles, dialog titles, nav labels, button labels, and table column headers: `Edit User`, `Save Changes`, not `Edit user` or `Save changes`. Sentence case for body copy, descriptions, field labels, helper text, validation messages, and Markdown headings in repo docs.
|
|
36
|
+
|
|
37
|
+
### Voice
|
|
38
|
+
|
|
39
|
+
- Active voice, present tense. Append "by monkeys" as a test: if the sentence still parses, rewrite it.
|
|
40
|
+
- Address the reader as `you`, never "the user".
|
|
41
|
+
- Imperative for actions: "Select a role", not "You will need to select a role".
|
|
42
|
+
- Aim under 20 words per sentence. Paragraphs of 2 to 4 sentences, one idea each. Contractions are welcome.
|
|
43
|
+
- No rhetorical questions.
|
|
44
|
+
- Don't open a paragraph by recapping the previous one: "With that in place…".
|
|
45
|
+
- Don't have machines perform human actions: "the browser fetches the URL", not "hand the browser a URL".
|
|
46
|
+
- Commit subjects are lowercase imperative sentences.
|
|
47
|
+
- Don't include `Co-authored-by` or similar attribution trailers in commit messages or descriptions.
|
|
48
|
+
- Avoid repeating the same information across UI copy on a page. Give each heading, description, and helper text a distinct purpose.
|
|
49
|
+
|
|
50
|
+
### Words to cut
|
|
51
|
+
|
|
52
|
+
- `easy`, `simple`, `quick`: name the concrete thing instead, such as "one command" or "default settings".
|
|
53
|
+
- `very`, `just`, `really`, `simply`: filler.
|
|
54
|
+
- Vague quantifiers such as `many`, `often`, `typically`, `significantly`, `near-zero`, and `sub-second`: give the number or the specific claim.
|
|
55
|
+
- Metaphor verbs such as `lands`, `carries`, `hits`, and `flows through`: name the literal action.
|
|
56
|
+
- Spec-sheet phrasing: "provides", "is configurable", "is explicitly labeled".
|
|
57
|
+
|
|
58
|
+
### Punctuation and units
|
|
59
|
+
|
|
60
|
+
- No em dashes or curly quotes. Use a colon, a comma, or a period, or rewrite the sentence.
|
|
61
|
+
- Ellipsis character `…`, never three dots. Loading states end with it: `Saving…`.
|
|
62
|
+
- Units take a space and an uppercase symbol: `64 KB`, `200 ms`. Seconds stay bare: `30s`.
|
|
63
|
+
|
|
64
|
+
### Emphasis and code
|
|
65
|
+
|
|
66
|
+
- Bold marks a UI element or a critical fact, never emphasis.
|
|
67
|
+
- Inline code for paths, identifiers, file extensions, and short snippets.
|
|
68
|
+
- Code blocks carry a language tag, stay under 25 lines, and are explained in prose.
|
|
69
|
+
- Placeholders are descriptive `snake_case`: `your_access_token_here`, never `<TOKEN>` or `xxx`.
|
|
70
|
+
|
|
71
|
+
### Lists
|
|
72
|
+
|
|
73
|
+
- Introduce every list with a colon.
|
|
74
|
+
- Three or more list-shaped items in a paragraph become a list.
|
|
75
|
+
- No trailing period on a fragment; keep it on a full sentence.
|
|
76
|
+
- Bold term then colon for definitions: `- **Term**: description`.
|
|
77
|
+
|
|
78
|
+
### Error and toast messages
|
|
79
|
+
|
|
80
|
+
Name the subject, the action, and the consequence. No apologies. For customer-facing copy, say what happened and what to do next: "Another account already uses this email address. Sign in with it instead." Never "Error" or "Something went wrong" on its own. Keep internals out of anything a customer reads: no table names, stack traces, or raw IDs.
|
|
81
|
+
|
|
82
|
+
## Commands
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
bun run dev # Start Next.js dev server
|
|
86
|
+
bun run build # Production build
|
|
87
|
+
bun run lint # ESLint
|
|
88
|
+
bun run type-check # TypeScript check (tsgo)
|
|
89
|
+
bun run format # Prettier
|
|
90
|
+
|
|
91
|
+
bun run db:push # Apply the schema
|
|
92
|
+
bun run db:studio # Open Drizzle Studio
|
|
93
|
+
bun run db:seed # Seed a superadmin and a user
|
|
94
|
+
bun run db:reset # Drop every table (refuses in production)
|
|
95
|
+
|
|
96
|
+
bun test # Bun tests against in-memory PGlite
|
|
97
|
+
bun run test:real # Same suite against real Postgres (TEST_DATABASE=postgres)
|
|
98
|
+
bun run email:dev # React Email preview server (src/emails/)
|
|
99
|
+
bun run email:test you@example.com # Send one test email
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Do not start the dev server or any other long-running process unless asked.
|
|
103
|
+
|
|
104
|
+
After editing code, run `bunx prettier --write` on the files you touched, or `bun run format` after a broad refactor.
|
|
105
|
+
|
|
106
|
+
## Environment variables
|
|
107
|
+
|
|
108
|
+
All app env is defined and validated in [src/env.ts](src/env.ts) via `@t3-oss/env-nextjs`. Add a variable there and to `.env.example` in the same change.
|
|
109
|
+
|
|
110
|
+
## Architecture
|
|
111
|
+
|
|
112
|
+
**Stack:** Next.js 16 App Router, React 19, TypeScript, Drizzle ORM (PostgreSQL), Better Auth (admin plugin), shadcn/ui, Tailwind CSS 4, Zod, next-safe-action, TanStack Query, Workflow, React Email.
|
|
113
|
+
|
|
114
|
+
**Path alias:** `~/` maps to `src/`.
|
|
115
|
+
|
|
116
|
+
### Route groups
|
|
117
|
+
|
|
118
|
+
| Group | URL prefix | Purpose |
|
|
119
|
+
| ------------- | -------------------- | ----------------------- |
|
|
120
|
+
| `(marketing)` | `/` | Landing and legal pages |
|
|
121
|
+
| `(auth)` | `/login`, `/profile` | Sign-in and profile |
|
|
122
|
+
| `(app)` | `/app/...` | Signed-in product |
|
|
123
|
+
| `(admin)` | `/admin/...` | Platform admin panel |
|
|
124
|
+
|
|
125
|
+
### Server actions
|
|
126
|
+
|
|
127
|
+
All server actions use `next-safe-action` with layered clients defined in [src/lib/safe-action.ts](src/lib/safe-action.ts):
|
|
128
|
+
|
|
129
|
+
- `actionClient`: injects the auth session into `ctx`; no authentication required
|
|
130
|
+
- `authActionClient`: requires an authenticated user
|
|
131
|
+
- `adminActionClient`: requires the `admin` or `superadmin` system role
|
|
132
|
+
- `superAdminActionClient`: requires the `superadmin` system role
|
|
133
|
+
|
|
134
|
+
Actions live beside their routes in `actions/` directories; put actions shared across route groups in `src/app/actions/`. Keep Zod schemas for actions in a separate validation file (such as `admin/settings/users/validation.ts`) so client forms can import them.
|
|
135
|
+
|
|
136
|
+
No page or layout under `src/app` imports `~/db` directly. Fetch through an action whose client enforces the right role, then call `notFound()` when it returns no data. An ancestor layout check is not enough: the `/admin` layout checks only `isAdmin`, so any page trusting it stays open to a plain admin.
|
|
137
|
+
|
|
138
|
+
### Errors (`AppError`)
|
|
139
|
+
|
|
140
|
+
[src/lib/errors.ts](src/lib/errors.ts). Throw `AppError` for expected failures (auth, not-found, conflicts, business rules). `message` is customer-facing (what happened + next step); no internals. Use `rethrowUniqueViolation` to turn a unique-index failure into a readable conflict.
|
|
141
|
+
|
|
142
|
+
`handleServerError` in [src/lib/safe-action.ts](src/lib/safe-action.ts): `AppError` → client gets `message`; anything else → generic mask + `recordError`. Do not catch-and-rethrow as plain `Error` or return `null` on failure; let errors bubble. Try/catch only for non-fatal side effects.
|
|
143
|
+
|
|
144
|
+
### Observability
|
|
145
|
+
|
|
146
|
+
One structured span per action, with its input, duration, and outcome as attributes. Setup is in [src/instrumentation.ts](src/instrumentation.ts) and [src/lib/otel.ts](src/lib/otel.ts); add attributes with `recordWideEvent` and `recordError`. Only error spans and spans slower than 5s are exported.
|
|
147
|
+
|
|
148
|
+
Wrap new actions with `withWideEvent` from [src/lib/safe-action.ts](src/lib/safe-action.ts): `.action(withWideEvent('myAction', async ({ parsedInput, ctx }) => { … }))`. Prefer that over ad-hoc logging in handlers. No secrets in attributes.
|
|
149
|
+
|
|
150
|
+
### Client toasts
|
|
151
|
+
|
|
152
|
+
[src/lib/toast-message.ts](src/lib/toast-message.ts) only (not raw Sonner):
|
|
153
|
+
|
|
154
|
+
- `await action`: `if (toastIfActionFailed(result)) return` then `toastSuccessMessage`
|
|
155
|
+
- hook `onError`, or a Better Auth client `error`: `toastActionError(err, 'The thing was not saved. Try again.')`
|
|
156
|
+
- client-only validation: `toastErrorMessage`
|
|
157
|
+
|
|
158
|
+
Query/mutation helpers: [src/lib/safe-action-client.ts](src/lib/safe-action-client.ts). Confirm destructive actions with `ConfirmDialog` ([src/components/confirm-dialog.tsx](src/components/confirm-dialog.tsx)), never `window.confirm`.
|
|
159
|
+
|
|
160
|
+
### Client / server boundary
|
|
161
|
+
|
|
162
|
+
`'use client'` modules must never pull server runtime, secrets, or the database into the browser bundle. Transitive imports count: a Zod schema that pulls in `drizzle-orm` still ships drizzle.
|
|
163
|
+
|
|
164
|
+
**Never import from client:**
|
|
165
|
+
|
|
166
|
+
- `~/db` and anything under it (including for types)
|
|
167
|
+
- `~/env`
|
|
168
|
+
- `drizzle-orm`
|
|
169
|
+
- Any module with `import 'server-only'`, such as `~/lib/auth` and `~/lib/nodemailer`
|
|
170
|
+
|
|
171
|
+
**OK from client:**
|
|
172
|
+
|
|
173
|
+
- Server actions from `'use server'` modules
|
|
174
|
+
- Pure modules free of SQL and secrets: `constants`, `validations`, `format-*`
|
|
175
|
+
- Action validation files, as long as they only import client-safe modules
|
|
176
|
+
|
|
177
|
+
**`server-only` placement:** add it to modules that open DB connections or hold secrets or must never ship to the client. Do not add it to `~/env`, `~/db/schema`, `~/db/enums`, or `~/db/common`: drizzle-kit and scripts load those without the `react-server` condition, and the real package throws there. Scripts run with `bun --conditions=react-server` so they can import server-only modules.
|
|
178
|
+
|
|
179
|
+
[tests/lib/client-server-boundary.test.ts](tests/lib/client-server-boundary.test.ts) fails if a `'use client'` file imports a forbidden path. Keep it green; extend its list when you add a new server lib.
|
|
180
|
+
|
|
181
|
+
### Input bounds and search
|
|
182
|
+
|
|
183
|
+
Shared Zod helpers live in [src/lib/validations.ts](src/lib/validations.ts); numeric ceilings live in [src/lib/constants.ts](src/lib/constants.ts). Cap every array that reaches SQL with `MAX_FILTER_VALUES` or a named constant. Build `ILIKE` patterns with `likeContains` from [src/lib/search.ts](src/lib/search.ts) so `%` and `_` in search text stay literal.
|
|
184
|
+
|
|
185
|
+
### Roles
|
|
186
|
+
|
|
187
|
+
System roles come from the Better Auth admin plugin, defined in [src/lib/auth-permissions.ts](src/lib/auth-permissions.ts):
|
|
188
|
+
|
|
189
|
+
- `user`: regular customer with no admin-panel access
|
|
190
|
+
- `admin`: can open `/admin` and every page you add there; cannot manage users
|
|
191
|
+
- `superadmin`: full control, including user management and impersonation
|
|
192
|
+
|
|
193
|
+
### Database
|
|
194
|
+
|
|
195
|
+
Schema is all in [src/db/schema.ts](src/db/schema.ts). When you add Postgres enums, put them in `src/db/enums.ts` and re-export them from the schema. Reusable column helpers (CUID IDs, timestamps) are in [src/db/common.ts](src/db/common.ts). IDs use CUID2 with a model-name prefix (e.g., `user_xxx`, `session_xxx`).
|
|
196
|
+
|
|
197
|
+
**Changing the schema:**
|
|
198
|
+
|
|
199
|
+
- Every foreign key must be the leading column of an index and must declare an explicit `onDelete`. Without an index, deleting a parent scans the child table while holding row locks. `tests/db/fk-indexes.test.ts` enforces both.
|
|
200
|
+
- Invariants such as non-negative counts belong in CHECK constraints as well as Zod: the seed script, `psql`, and Drizzle Studio write around Zod. Before adding one, read what the code and the seed script write to that column.
|
|
201
|
+
- Better Auth owns the `users`, `sessions`, `accounts`, and `verifications` columns. Change them with `bun run gen:auth` in mind.
|
|
202
|
+
|
|
203
|
+
### Client data and forms
|
|
204
|
+
|
|
205
|
+
Query results are fresh object identities on every refetch, and TanStack refetches on window focus by default. An effect that depends on one re-runs on every alt-tab and discards typed input.
|
|
206
|
+
|
|
207
|
+
- Seed a dialog form once per open: depend on `[open, record?.id, reset]`, never on the record object, an options array, or a `useMemo` derived from them.
|
|
208
|
+
- When the data arrives after the dialog opens, guard the prefill with a ref that is cleared on close.
|
|
209
|
+
- These effects need `// eslint-disable-next-line react-hooks/exhaustive-deps` on the dependency array, with a comment saying why.
|
|
210
|
+
- Writing a ref during render is a React Compiler error here. Do it inside the effect.
|
|
211
|
+
|
|
212
|
+
### Email
|
|
213
|
+
|
|
214
|
+
Templates are React Email components in `src/emails/`. Send via helpers in [src/lib/email-service.ts](src/lib/email-service.ts), which run as Workflow steps and use Nodemailer. Call them with `start(sendXEmail, [...])` so a slow SMTP server never blocks a request.
|
|
215
|
+
|
|
216
|
+
### Tests
|
|
217
|
+
|
|
218
|
+
**Test-driven development (TDD):** For new features, bugs, or behavior changes, write or update focused tests (including edge cases).
|
|
219
|
+
|
|
220
|
+
Bun tests run against **in-memory Postgres via PGlite** by default. The preload ([tests/setup/preload.ts](tests/setup/preload.ts)) boots one PGlite instance per worker, applies the Drizzle schema, mocks `~/db`, and mocks auth (`getAuthSession` via AsyncLocalStorage). Bootstrap: [tests/setup/pglite-db.ts](tests/setup/pglite-db.ts).
|
|
221
|
+
|
|
222
|
+
Tests read env from `.env`, like the app. `bun run test:real` runs the same suite against a real server; it requires `DATABASE_URL` and a schema applied with `db:push`.
|
|
223
|
+
|
|
224
|
+
**PGlite constraint:** PGlite has a single connection, so calling the outer `db` client (or any helper that uses it) inside `db.transaction(...)` deadlocks. Run checks before opening the transaction, or pass `tx` into loaders.
|
|
225
|
+
|
|
226
|
+
**Verify a test can fail:** after writing one, break the code it covers, confirm it goes red, then restore. A test that cannot fail reads as coverage and hides bugs.
|
|
227
|
+
|
|
228
|
+
**Tests must not depend on the machine clock.** Pin the current time with `setSystemTime` from `bun:test` rather than computing offsets from the real clock.
|
|
229
|
+
|
|
230
|
+
**`db.execute` result shape differs by driver:** postgres-js returns a row array, PGlite returns `{ rows }`. Normalise before reading, as `toRows` in `tests/db/fk-indexes.test.ts` does.
|
|
231
|
+
|
|
232
|
+
**Fixtures** (full API in [tests/helpers/fixtures.ts](tests/helpers/fixtures.ts)):
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
const fx = new Fixtures()
|
|
236
|
+
afterEach(() => fx.cleanup())
|
|
237
|
+
|
|
238
|
+
const admin = await fx.platformUser('superadmin')
|
|
239
|
+
await asUser(admin, () => someAction(...))
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Use `asUser` / `asAnonymous` to set the action actor. Suites live under `tests/admin/`, `tests/db/`, and `tests/lib/`; add a folder per feature.
|
|
243
|
+
|
|
244
|
+
### Legal pages
|
|
245
|
+
|
|
246
|
+
The privacy policy and terms of service live in `src/app/(marketing)`. They are starting points: read both when you change how the product behaves. If a change makes either page inaccurate or incomplete, update it and `siteConfig.legal.lastUpdated` in the same change. Describe what an end user experiences, never internal architecture.
|
|
247
|
+
|
|
248
|
+
### Branding
|
|
249
|
+
|
|
250
|
+
Never hardcode the product name. Read `siteConfig.name` from [src/lib/siteConfig.ts](src/lib/siteConfig.ts). Replace `public/logo.svg` and `public/logo-dark.svg` with the real mark, and set the brand color in `src/app/globals.css` (`--primary`) and `BRAND` in `src/emails/components/email-layout.tsx`.
|
|
251
|
+
|
|
252
|
+
<!-- BEGIN:nextjs-agent-rules -->
|
|
253
|
+
|
|
254
|
+
# This is NOT the Next.js you know
|
|
255
|
+
|
|
256
|
+
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices.
|
|
257
|
+
|
|
258
|
+
This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.
|
|
259
|
+
|
|
260
|
+
<!-- END:nextjs-agent-rules -->
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# {{PROJECT_NAME}}
|
|
2
|
+
|
|
3
|
+
{{PROJECT_DESCRIPTION}}
|
|
4
|
+
|
|
5
|
+
## Technology
|
|
6
|
+
|
|
7
|
+
The application uses:
|
|
8
|
+
|
|
9
|
+
- **Application**: Next.js, React, and TypeScript
|
|
10
|
+
- **Interface**: Tailwind CSS, shadcn/ui, and TanStack Query
|
|
11
|
+
- **Data**: PostgreSQL and Drizzle ORM
|
|
12
|
+
- **Authentication**: Better Auth with magic links, OAuth, and system roles
|
|
13
|
+
- **Validation and actions**: Zod and next-safe-action
|
|
14
|
+
- **Runtime and tests**: Bun and PGlite
|
|
15
|
+
- **Email**: Nodemailer and React Email
|
|
16
|
+
|
|
17
|
+
## Architecture
|
|
18
|
+
|
|
19
|
+
Next.js route groups separate each product surface:
|
|
20
|
+
|
|
21
|
+
| Directory | Route | Purpose |
|
|
22
|
+
| --------------------- | -------------------- | ----------------------- |
|
|
23
|
+
| `src/app/(marketing)` | `/` | Landing and legal pages |
|
|
24
|
+
| `src/app/(auth)` | `/login`, `/profile` | Sign-in and profile |
|
|
25
|
+
| `src/app/(app)` | `/app/*` | Signed-in product |
|
|
26
|
+
| `src/app/(admin)` | `/admin/*` | Platform administration |
|
|
27
|
+
|
|
28
|
+
Shared code has clear responsibilities:
|
|
29
|
+
|
|
30
|
+
- `src/components`: shared interface components
|
|
31
|
+
- `src/lib`: access checks, validation, formatting, and integrations
|
|
32
|
+
- `src/db`: schema, relations, and database connection
|
|
33
|
+
- `src/emails`: React Email templates
|
|
34
|
+
- `tests`: focused integration and unit tests
|
|
35
|
+
|
|
36
|
+
Server actions sit beside their routes in `actions` directories. Each protected action validates input, enforces the correct role, and records one structured event. Page and layout files fetch data through actions instead of importing the database.
|
|
37
|
+
|
|
38
|
+
## Local setup
|
|
39
|
+
|
|
40
|
+
Install these prerequisites:
|
|
41
|
+
|
|
42
|
+
- [Bun](https://bun.sh/)
|
|
43
|
+
- [PostgreSQL](https://www.postgresql.org/) with an empty local database
|
|
44
|
+
- OAuth credentials for GitHub and Google
|
|
45
|
+
- SMTP credentials
|
|
46
|
+
|
|
47
|
+
Then prepare the application:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
bun install
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`create-ej-app` already wrote `.env` from `.env.example`. Fill in working credentials, then apply the database schema and add starter data:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
bun run db:push
|
|
57
|
+
bun run db:seed
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Start the development server:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
bun run dev
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Open [http://localhost:3000](http://localhost:3000). The seed creates `superadmin@example.com` and `user@example.com`; change them in `src/scripts/seed.ts` to addresses whose inbox you can read.
|
|
67
|
+
|
|
68
|
+
## Commands
|
|
69
|
+
|
|
70
|
+
Use these commands during development:
|
|
71
|
+
|
|
72
|
+
| Command | Purpose |
|
|
73
|
+
| -------------------- | ----------------------------------- |
|
|
74
|
+
| `bun run dev` | Start the development server |
|
|
75
|
+
| `bun test` | Run tests with in-memory PostgreSQL |
|
|
76
|
+
| `bun run test:real` | Run tests against `DATABASE_URL` |
|
|
77
|
+
| `bun run lint` | Run ESLint |
|
|
78
|
+
| `bun run type-check` | Check TypeScript |
|
|
79
|
+
| `bun run format` | Format source and test files |
|
|
80
|
+
| `bun run build` | Create a production build |
|
|
81
|
+
| `bun run db:push` | Apply the Drizzle schema |
|
|
82
|
+
| `bun run db:studio` | Open Drizzle Studio |
|
|
83
|
+
| `bun run db:seed` | Add starter data |
|
|
84
|
+
| `bun run db:reset` | Drop every table |
|
|
85
|
+
| `bun run email:dev` | Preview email templates |
|
|
86
|
+
| `bun run email:test` | Send a test email to an address |
|
|
87
|
+
|
|
88
|
+
Before opening a pull request, run:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
bun test
|
|
92
|
+
bun run lint
|
|
93
|
+
bun run type-check
|
|
94
|
+
bun run build
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Implement a change
|
|
98
|
+
|
|
99
|
+
Follow this path for product changes:
|
|
100
|
+
|
|
101
|
+
1. Find the matching route under `src/app` and read its nearby actions and components.
|
|
102
|
+
2. Add or update a focused test before changing behavior.
|
|
103
|
+
3. Put shared limits and repeated values in `src/lib/constants.ts`.
|
|
104
|
+
4. Validate action input with Zod and select the narrowest action client in `src/lib/safe-action.ts`.
|
|
105
|
+
5. Throw `AppError` for expected failures and give customers a clear next step.
|
|
106
|
+
6. Wrap new actions with `withWideEvent` and exclude secrets from event attributes.
|
|
107
|
+
7. Format the files you touched, then run the relevant checks.
|
|
108
|
+
|
|
109
|
+
Read [AGENTS.md](AGENTS.md) before making a substantial change. It documents authorization, testing, and writing rules.
|
|
@@ -1,21 +1,22 @@
|
|
|
1
|
-
// @ts-ignore
|
|
2
1
|
import nextVitals from 'eslint-config-next/core-web-vitals'
|
|
3
|
-
|
|
2
|
+
import nextTs from 'eslint-config-next/typescript'
|
|
4
3
|
import { defineConfig, globalIgnores } from 'eslint/config'
|
|
5
4
|
|
|
6
5
|
const eslintConfig = defineConfig([
|
|
7
6
|
...nextVitals,
|
|
8
|
-
|
|
7
|
+
...nextTs,
|
|
9
8
|
globalIgnores([
|
|
10
9
|
'node_modules/**',
|
|
11
10
|
'.next/**',
|
|
12
11
|
'out/**',
|
|
13
12
|
'build/**',
|
|
14
|
-
'next-env.d.ts'
|
|
13
|
+
'next-env.d.ts',
|
|
14
|
+
// workflow-sdk autogenerated files
|
|
15
|
+
'src/app/.well-known/**'
|
|
15
16
|
]),
|
|
16
17
|
{
|
|
17
18
|
rules: {
|
|
18
|
-
'
|
|
19
|
+
'@typescript-eslint/no-explicit-any': 'off'
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
22
|
])
|
|
@@ -4,7 +4,14 @@ import { withWorkflow } from 'workflow/next'
|
|
|
4
4
|
import { env } from '~/env'
|
|
5
5
|
|
|
6
6
|
const nextConfig: NextConfig = {
|
|
7
|
-
reactCompiler: true
|
|
7
|
+
reactCompiler: true,
|
|
8
|
+
devIndicators: false,
|
|
9
|
+
images: {
|
|
10
|
+
remotePatterns: [
|
|
11
|
+
{ hostname: 'avatars.githubusercontent.com' },
|
|
12
|
+
{ hostname: 'lh3.googleusercontent.com' }
|
|
13
|
+
]
|
|
14
|
+
}
|
|
8
15
|
}
|
|
9
16
|
|
|
10
17
|
const withBundleAnalyzer = bundleAnalyzer({
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "my-
|
|
3
|
-
"description": "A Legendary
|
|
2
|
+
"name": "my-project",
|
|
3
|
+
"description": "A Legendary Project",
|
|
4
4
|
"version": "1.0.0",
|
|
5
5
|
"private": true,
|
|
6
6
|
"scripts": {
|
|
7
7
|
"dev": "next dev",
|
|
8
8
|
"email:dev": "email dev --dir src/emails",
|
|
9
|
-
"email:test": "bun ./src/scripts/test-mail.ts",
|
|
9
|
+
"email:test": "bun --conditions=react-server ./src/scripts/test-mail.ts",
|
|
10
10
|
"workflow:inspect": "workflow inspect runs --web",
|
|
11
11
|
"build": "bun --bun next build",
|
|
12
12
|
"build:docker": "docker build -t my-app .",
|
|
@@ -14,62 +14,70 @@
|
|
|
14
14
|
"start": "bun --bun next start",
|
|
15
15
|
"lint": "eslint",
|
|
16
16
|
"type-check": "tsgo --incremental false",
|
|
17
|
-
"format": "prettier --write \"./src/**/*.{js,cjs,mjs,jsx,ts,tsx}\"",
|
|
17
|
+
"format": "prettier --log-level warn --write \"./src/**/*.{js,cjs,mjs,jsx,ts,tsx}\" --write \"./tests/**/*.{js,cjs,mjs,jsx,ts,tsx}\"",
|
|
18
|
+
"test": "bun test",
|
|
19
|
+
"test:real": "TEST_DATABASE=postgres bun test",
|
|
18
20
|
"db:push": "drizzle-kit push",
|
|
19
|
-
"db:seed": "bun ./src/scripts/seed.ts",
|
|
21
|
+
"db:seed": "bun --conditions=react-server ./src/scripts/seed.ts",
|
|
22
|
+
"db:reset": "bun --conditions=react-server ./src/scripts/reset-db.ts",
|
|
20
23
|
"db:studio": "drizzle-kit studio",
|
|
21
|
-
"gen:auth": "bunx @
|
|
24
|
+
"gen:auth": "bunx auth@latest generate"
|
|
22
25
|
},
|
|
23
26
|
"dependencies": {
|
|
24
27
|
"@ejekanshjain/simple-email-validator": "1.0.4",
|
|
25
|
-
"@hookform/resolvers": "5.
|
|
26
|
-
"@next/third-parties": "16.2.9",
|
|
28
|
+
"@hookform/resolvers": "5.9.1",
|
|
27
29
|
"@opentelemetry/api": "1.9.1",
|
|
28
|
-
"@opentelemetry/sdk-trace-base": "2.
|
|
30
|
+
"@opentelemetry/sdk-trace-base": "2.11.0",
|
|
29
31
|
"@paralleldrive/cuid2": "3.3.0",
|
|
30
|
-
"@react-email/render": "2.0
|
|
32
|
+
"@react-email/render": "2.1.0",
|
|
31
33
|
"@t3-oss/env-nextjs": "0.13.11",
|
|
32
|
-
"@tanstack/react-query": "5.
|
|
34
|
+
"@tanstack/react-query": "5.103.2",
|
|
35
|
+
"@tanstack/react-table": "9.2.4",
|
|
33
36
|
"@vercel/otel": "2.1.3",
|
|
34
|
-
"better-auth": "1.6
|
|
37
|
+
"better-auth": "1.7.6",
|
|
35
38
|
"class-variance-authority": "0.7.1",
|
|
36
39
|
"clsx": "2.1.1",
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"next
|
|
41
|
-
"next-
|
|
40
|
+
"cmdk": "1.1.1",
|
|
41
|
+
"drizzle-orm": "0.45.3",
|
|
42
|
+
"lucide-react": "1.48.0",
|
|
43
|
+
"next": "16.3.6",
|
|
44
|
+
"next-safe-action": "8.7.3",
|
|
45
|
+
"next-themes": "0.4.6",
|
|
42
46
|
"nextjs-toploader": "3.9.17",
|
|
43
|
-
"nodemailer": "
|
|
47
|
+
"nodemailer": "10.0.10",
|
|
48
|
+
"nuqs": "2.10.1",
|
|
44
49
|
"postgres": "3.4.9",
|
|
45
|
-
"radix-ui": "1.
|
|
46
|
-
"react": "19.
|
|
47
|
-
"react-dom": "19.
|
|
48
|
-
"react-
|
|
49
|
-
"
|
|
50
|
-
"
|
|
50
|
+
"radix-ui": "1.6.7",
|
|
51
|
+
"react": "19.3.0",
|
|
52
|
+
"react-dom": "19.3.0",
|
|
53
|
+
"react-email": "6.11.0",
|
|
54
|
+
"react-hook-form": "7.88.0",
|
|
55
|
+
"server-only": "0.0.1",
|
|
56
|
+
"sonner": "2.0.8",
|
|
57
|
+
"tailwind-merge": "3.7.0",
|
|
51
58
|
"tw-animate-css": "1.4.0",
|
|
52
|
-
"workflow": "4.
|
|
53
|
-
"zod": "4.
|
|
59
|
+
"workflow": "4.8.9",
|
|
60
|
+
"zod": "4.6.5"
|
|
54
61
|
},
|
|
55
62
|
"devDependencies": {
|
|
56
|
-
"@
|
|
63
|
+
"@electric-sql/pglite": "0.5.8",
|
|
64
|
+
"@next/bundle-analyzer": "16.3.6",
|
|
57
65
|
"@react-email/preview-server": "5.2.10",
|
|
58
|
-
"@
|
|
59
|
-
"@
|
|
60
|
-
"@types/
|
|
61
|
-
"@types/
|
|
62
|
-
"@types/react
|
|
63
|
-
"@
|
|
66
|
+
"@react-email/ui": "6.11.0",
|
|
67
|
+
"@tailwindcss/postcss": "4.3.3",
|
|
68
|
+
"@types/bun": "1.4.2",
|
|
69
|
+
"@types/nodemailer": "8.0.2",
|
|
70
|
+
"@types/react": "19.3.0",
|
|
71
|
+
"@types/react-dom": "19.3.0",
|
|
72
|
+
"@typescript/native-preview": "7.0.0-dev.20260707.2",
|
|
64
73
|
"babel-plugin-react-compiler": "1.0.0",
|
|
65
|
-
"drizzle-kit": "0.31.
|
|
74
|
+
"drizzle-kit": "0.31.11",
|
|
66
75
|
"eslint": "9.39.4",
|
|
67
|
-
"eslint-config-next": "16.
|
|
68
|
-
"prettier": "3.
|
|
76
|
+
"eslint-config-next": "16.3.6",
|
|
77
|
+
"prettier": "3.9.9",
|
|
69
78
|
"prettier-plugin-organize-imports": "4.3.0",
|
|
70
|
-
"prettier-plugin-tailwindcss": "0.8.
|
|
71
|
-
"
|
|
72
|
-
"tailwindcss": "4.3.1",
|
|
79
|
+
"prettier-plugin-tailwindcss": "0.8.1",
|
|
80
|
+
"tailwindcss": "4.3.3",
|
|
73
81
|
"typescript": "6.0.3"
|
|
74
82
|
},
|
|
75
83
|
"trustedDependencies": [
|