create-ej-app 25.0.0 → 26.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.
Files changed (160) hide show
  1. package/cli.js +4 -0
  2. package/cli.ts +5 -0
  3. package/package.json +2 -2
  4. package/template/api/package.json +2 -2
  5. package/template/nextjs/gitignore +1 -0
  6. package/template/nextjs/package.json +18 -18
  7. package/template/saas-starter/.dockerignore +7 -0
  8. package/template/saas-starter/.env.example +25 -0
  9. package/template/saas-starter/.vscode/extensions.json +11 -0
  10. package/template/saas-starter/.vscode/settings.json +3 -0
  11. package/template/saas-starter/Dockerfile +15 -0
  12. package/template/saas-starter/README.md +3 -0
  13. package/template/saas-starter/components.json +25 -0
  14. package/template/saas-starter/drizzle.config.ts +10 -0
  15. package/template/saas-starter/eslint.config.mjs +23 -0
  16. package/template/saas-starter/gitignore +9 -0
  17. package/template/saas-starter/next-env.d.ts +6 -0
  18. package/template/saas-starter/next.config.ts +14 -0
  19. package/template/saas-starter/package.json +92 -0
  20. package/template/saas-starter/postcss.config.mjs +7 -0
  21. package/template/saas-starter/prettier.config.mjs +11 -0
  22. package/template/saas-starter/public/placeholder.svg +1 -0
  23. package/template/saas-starter/src/actions/uploads.ts +28 -0
  24. package/template/saas-starter/src/actions/uploads.validation.ts +14 -0
  25. package/template/saas-starter/src/app/(admin)/actions/users.ts +97 -0
  26. package/template/saas-starter/src/app/(admin)/admin/_components/admin-header.tsx +21 -0
  27. package/template/saas-starter/src/app/(admin)/admin/_components/admin-search.tsx +16 -0
  28. package/template/saas-starter/src/app/(admin)/admin/_components/admin-sidebar.tsx +40 -0
  29. package/template/saas-starter/src/app/(admin)/admin/layout.tsx +57 -0
  30. package/template/saas-starter/src/app/(admin)/admin/page.tsx +3 -0
  31. package/template/saas-starter/src/app/(admin)/admin/settings/users/_components/create-user-dialog.tsx +183 -0
  32. package/template/saas-starter/src/app/(admin)/admin/settings/users/_components/edit-name-dialog.tsx +129 -0
  33. package/template/saas-starter/src/app/(admin)/admin/settings/users/_components/users-table.tsx +415 -0
  34. package/template/saas-starter/src/app/(admin)/admin/settings/users/page.tsx +15 -0
  35. package/template/saas-starter/src/app/(app)/actions/billing.ts +152 -0
  36. package/template/saas-starter/src/app/(app)/actions/members.ts +163 -0
  37. package/template/saas-starter/src/app/(app)/actions/organization.ts +92 -0
  38. package/template/saas-starter/src/app/(app)/app/[orgId]/dashboard/page.tsx +20 -0
  39. package/template/saas-starter/src/app/(app)/app/[orgId]/layout.tsx +34 -0
  40. package/template/saas-starter/src/app/(app)/app/[orgId]/settings/_components/billing-email-form.tsx +100 -0
  41. package/template/saas-starter/src/app/(app)/app/[orgId]/settings/_components/danger-zone.tsx +364 -0
  42. package/template/saas-starter/src/app/(app)/app/[orgId]/settings/_components/general-settings-form.tsx +155 -0
  43. package/template/saas-starter/src/app/(app)/app/[orgId]/settings/_components/plans-section.tsx +232 -0
  44. package/template/saas-starter/src/app/(app)/app/[orgId]/settings/billing/page.tsx +116 -0
  45. package/template/saas-starter/src/app/(app)/app/[orgId]/settings/members/_components/invite-member-dialog.tsx +169 -0
  46. package/template/saas-starter/src/app/(app)/app/[orgId]/settings/members/_components/members-tabs.tsx +571 -0
  47. package/template/saas-starter/src/app/(app)/app/[orgId]/settings/members/page.tsx +40 -0
  48. package/template/saas-starter/src/app/(app)/app/[orgId]/settings/page.tsx +69 -0
  49. package/template/saas-starter/src/app/(app)/app/_components/app-header.tsx +29 -0
  50. package/template/saas-starter/src/app/(app)/app/_components/app-search.tsx +17 -0
  51. package/template/saas-starter/src/app/(app)/app/_components/app-sidebar.tsx +27 -0
  52. package/template/saas-starter/src/app/(app)/app/_components/organization-context.tsx +41 -0
  53. package/template/saas-starter/src/app/(app)/app/_components/organization-switcher.tsx +140 -0
  54. package/template/saas-starter/src/app/(app)/app/layout.tsx +52 -0
  55. package/template/saas-starter/src/app/(app)/app/onboarding/_components/create-organization-form.tsx +129 -0
  56. package/template/saas-starter/src/app/(app)/app/onboarding/page.tsx +42 -0
  57. package/template/saas-starter/src/app/(app)/app/page.tsx +81 -0
  58. package/template/saas-starter/src/app/(auth)/login/email-login-form.tsx +109 -0
  59. package/template/saas-starter/src/app/(auth)/login/page.tsx +87 -0
  60. package/template/saas-starter/src/app/(auth)/login/social-login-buttons.tsx +75 -0
  61. package/template/saas-starter/src/app/(invitation)/accept-invitation/[id]/accept-invitation-form.tsx +88 -0
  62. package/template/saas-starter/src/app/(invitation)/accept-invitation/[id]/page.tsx +167 -0
  63. package/template/saas-starter/src/app/(marketing)/layout.tsx +14 -0
  64. package/template/saas-starter/src/app/(marketing)/page.tsx +3 -0
  65. package/template/saas-starter/src/app/(marketing)/privacy/page.tsx +3 -0
  66. package/template/saas-starter/src/app/(marketing)/terms/page.tsx +3 -0
  67. package/template/saas-starter/src/app/api/auth/[...all]/route.ts +4 -0
  68. package/template/saas-starter/src/app/api/webhooks/stripe/route.ts +94 -0
  69. package/template/saas-starter/src/app/favicon.ico +0 -0
  70. package/template/saas-starter/src/app/globals.css +240 -0
  71. package/template/saas-starter/src/app/layout.tsx +53 -0
  72. package/template/saas-starter/src/app/not-found.tsx +27 -0
  73. package/template/saas-starter/src/app/proxy.ts +28 -0
  74. package/template/saas-starter/src/app/robots.ts +32 -0
  75. package/template/saas-starter/src/app/sitemap.ts +20 -0
  76. package/template/saas-starter/src/components/dashboard-header.tsx +64 -0
  77. package/template/saas-starter/src/components/data-table/enum.ts +5 -0
  78. package/template/saas-starter/src/components/data-table/index.tsx +960 -0
  79. package/template/saas-starter/src/components/data-table/types.ts +75 -0
  80. package/template/saas-starter/src/components/error.tsx +26 -0
  81. package/template/saas-starter/src/components/file-upload.tsx +215 -0
  82. package/template/saas-starter/src/components/impersonation-banner.tsx +44 -0
  83. package/template/saas-starter/src/components/loader.tsx +25 -0
  84. package/template/saas-starter/src/components/navigation-search.tsx +140 -0
  85. package/template/saas-starter/src/components/navigation-sidebar.tsx +241 -0
  86. package/template/saas-starter/src/components/page-heading.tsx +32 -0
  87. package/template/saas-starter/src/components/react-query-provider.tsx +12 -0
  88. package/template/saas-starter/src/components/screen-size.tsx +40 -0
  89. package/template/saas-starter/src/components/site-footer.tsx +23 -0
  90. package/template/saas-starter/src/components/theme-provider.tsx +67 -0
  91. package/template/saas-starter/src/components/theme-toggle.tsx +22 -0
  92. package/template/saas-starter/src/components/ui/avatar.tsx +112 -0
  93. package/template/saas-starter/src/components/ui/badge.tsx +49 -0
  94. package/template/saas-starter/src/components/ui/button.tsx +67 -0
  95. package/template/saas-starter/src/components/ui/card.tsx +103 -0
  96. package/template/saas-starter/src/components/ui/checkbox.tsx +32 -0
  97. package/template/saas-starter/src/components/ui/collapsible.tsx +33 -0
  98. package/template/saas-starter/src/components/ui/command.tsx +192 -0
  99. package/template/saas-starter/src/components/ui/dialog.tsx +164 -0
  100. package/template/saas-starter/src/components/ui/dropdown-menu.tsx +273 -0
  101. package/template/saas-starter/src/components/ui/field.tsx +238 -0
  102. package/template/saas-starter/src/components/ui/input-group.tsx +156 -0
  103. package/template/saas-starter/src/components/ui/input.tsx +19 -0
  104. package/template/saas-starter/src/components/ui/label.tsx +24 -0
  105. package/template/saas-starter/src/components/ui/popover.tsx +89 -0
  106. package/template/saas-starter/src/components/ui/select.tsx +195 -0
  107. package/template/saas-starter/src/components/ui/separator.tsx +28 -0
  108. package/template/saas-starter/src/components/ui/sheet.tsx +143 -0
  109. package/template/saas-starter/src/components/ui/sidebar.tsx +702 -0
  110. package/template/saas-starter/src/components/ui/skeleton.tsx +13 -0
  111. package/template/saas-starter/src/components/ui/sonner.tsx +45 -0
  112. package/template/saas-starter/src/components/ui/switch.tsx +33 -0
  113. package/template/saas-starter/src/components/ui/table.tsx +116 -0
  114. package/template/saas-starter/src/components/ui/tabs.tsx +90 -0
  115. package/template/saas-starter/src/components/ui/textarea.tsx +18 -0
  116. package/template/saas-starter/src/components/ui/tooltip.tsx +57 -0
  117. package/template/saas-starter/src/components/user-menu.tsx +90 -0
  118. package/template/saas-starter/src/db/common.ts +31 -0
  119. package/template/saas-starter/src/db/enums.ts +26 -0
  120. package/template/saas-starter/src/db/index.ts +51 -0
  121. package/template/saas-starter/src/db/relations.ts +75 -0
  122. package/template/saas-starter/src/db/schema.ts +191 -0
  123. package/template/saas-starter/src/emails/components/email-layout.tsx +63 -0
  124. package/template/saas-starter/src/emails/invitation.tsx +56 -0
  125. package/template/saas-starter/src/emails/magic-link.tsx +49 -0
  126. package/template/saas-starter/src/emails/test.tsx +32 -0
  127. package/template/saas-starter/src/emails/welcome.tsx +34 -0
  128. package/template/saas-starter/src/env.ts +66 -0
  129. package/template/saas-starter/src/hooks/use-mobile.ts +19 -0
  130. package/template/saas-starter/src/instrumentation.ts +26 -0
  131. package/template/saas-starter/src/lib/actions.ts +21 -0
  132. package/template/saas-starter/src/lib/admin-navigation.ts +31 -0
  133. package/template/saas-starter/src/lib/app-navigation.ts +59 -0
  134. package/template/saas-starter/src/lib/auth-client.ts +29 -0
  135. package/template/saas-starter/src/lib/auth-permissions.ts +22 -0
  136. package/template/saas-starter/src/lib/auth.ts +211 -0
  137. package/template/saas-starter/src/lib/billing.ts +117 -0
  138. package/template/saas-starter/src/lib/cn.ts +4 -0
  139. package/template/saas-starter/src/lib/constants.ts +22 -0
  140. package/template/saas-starter/src/lib/delay.ts +2 -0
  141. package/template/saas-starter/src/lib/email-service.ts +89 -0
  142. package/template/saas-starter/src/lib/format-currency.ts +27 -0
  143. package/template/saas-starter/src/lib/format-date.ts +12 -0
  144. package/template/saas-starter/src/lib/nodemailer.ts +43 -0
  145. package/template/saas-starter/src/lib/organization-access.ts +93 -0
  146. package/template/saas-starter/src/lib/organization-plan-limits.ts +38 -0
  147. package/template/saas-starter/src/lib/otel.ts +27 -0
  148. package/template/saas-starter/src/lib/plans.ts +63 -0
  149. package/template/saas-starter/src/lib/safe-action-client.ts +75 -0
  150. package/template/saas-starter/src/lib/safe-action.ts +67 -0
  151. package/template/saas-starter/src/lib/siteConfig.ts +4 -0
  152. package/template/saas-starter/src/lib/storage.ts +181 -0
  153. package/template/saas-starter/src/lib/stripe.ts +6 -0
  154. package/template/saas-starter/src/lib/toast-message.ts +15 -0
  155. package/template/saas-starter/src/lib/validations.ts +12 -0
  156. package/template/saas-starter/src/scripts/reset-db.ts +22 -0
  157. package/template/saas-starter/src/scripts/seed.ts +11 -0
  158. package/template/saas-starter/src/scripts/setup-stripe-plans.ts +200 -0
  159. package/template/saas-starter/src/scripts/test-mail.ts +35 -0
  160. package/template/saas-starter/tsconfig.json +51 -0
@@ -0,0 +1,240 @@
1
+ @import 'tailwindcss';
2
+ @import 'tw-animate-css';
3
+
4
+ @custom-variant dark (&:is(.dark *));
5
+
6
+ @theme inline {
7
+ --color-background: var(--background);
8
+ --color-foreground: var(--foreground);
9
+ --color-card: var(--card);
10
+ --color-card-foreground: var(--card-foreground);
11
+ --color-popover: var(--popover);
12
+ --color-popover-foreground: var(--popover-foreground);
13
+ --color-primary: var(--primary);
14
+ --color-primary-foreground: var(--primary-foreground);
15
+ --color-secondary: var(--secondary);
16
+ --color-secondary-foreground: var(--secondary-foreground);
17
+ --color-muted: var(--muted);
18
+ --color-muted-foreground: var(--muted-foreground);
19
+ --color-accent: var(--accent);
20
+ --color-accent-foreground: var(--accent-foreground);
21
+ --color-destructive: var(--destructive);
22
+ --color-destructive-foreground: var(--destructive-foreground);
23
+ --color-border: var(--border);
24
+ --color-input: var(--input);
25
+ --color-ring: var(--ring);
26
+ --color-chart-1: var(--chart-1);
27
+ --color-chart-2: var(--chart-2);
28
+ --color-chart-3: var(--chart-3);
29
+ --color-chart-4: var(--chart-4);
30
+ --color-chart-5: var(--chart-5);
31
+ --color-sidebar: var(--sidebar);
32
+ --color-sidebar-foreground: var(--sidebar-foreground);
33
+ --color-sidebar-primary: var(--sidebar-primary);
34
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
35
+ --color-sidebar-accent: var(--sidebar-accent);
36
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
37
+ --color-sidebar-border: var(--sidebar-border);
38
+ --color-sidebar-ring: var(--sidebar-ring);
39
+
40
+ --font-heading: var(--font-geist-sans);
41
+ --font-sans: var(--font-geist-sans);
42
+ --font-mono: var(--font-geist-mono);
43
+
44
+ --radius-sm: calc(var(--radius) * 0.6);
45
+ --radius-md: calc(var(--radius) * 0.8);
46
+ --radius-lg: var(--radius);
47
+ --radius-xl: calc(var(--radius) * 1.4);
48
+ --radius-2xl: calc(var(--radius) * 1.8);
49
+ --radius-3xl: calc(var(--radius) * 2.2);
50
+ --radius-4xl: calc(var(--radius) * 2.6);
51
+ }
52
+
53
+ :root {
54
+ --radius: 0.625rem;
55
+ --background: oklch(1 0 0);
56
+ --foreground: oklch(0.145 0 0);
57
+ --card: oklch(1 0 0);
58
+ --card-foreground: oklch(0.145 0 0);
59
+ --popover: oklch(1 0 0);
60
+ --popover-foreground: oklch(0.145 0 0);
61
+ --primary: oklch(0.205 0 0);
62
+ --primary-foreground: oklch(0.985 0 0);
63
+ --secondary: oklch(0.97 0 0);
64
+ --secondary-foreground: oklch(0.205 0 0);
65
+ --muted: oklch(0.97 0 0);
66
+ --muted-foreground: oklch(0.556 0 0);
67
+ --accent: oklch(0.97 0 0);
68
+ --accent-foreground: oklch(0.205 0 0);
69
+ --destructive: oklch(0.577 0.245 27.325);
70
+ --destructive-foreground: oklch(1 0 0);
71
+ --border: oklch(0.922 0 0);
72
+ --input: oklch(0.922 0 0);
73
+ --ring: oklch(0.708 0 0);
74
+ --chart-1: oklch(0.87 0 0);
75
+ --chart-2: oklch(0.556 0 0);
76
+ --chart-3: oklch(0.439 0 0);
77
+ --chart-4: oklch(0.371 0 0);
78
+ --chart-5: oklch(0.269 0 0);
79
+ --sidebar: oklch(0.985 0 0);
80
+ --sidebar-foreground: oklch(0.145 0 0);
81
+ --sidebar-primary: oklch(0.205 0 0);
82
+ --sidebar-primary-foreground: oklch(0.985 0 0);
83
+ --sidebar-accent: oklch(0.97 0 0);
84
+ --sidebar-accent-foreground: oklch(0.205 0 0);
85
+ --sidebar-border: oklch(0.922 0 0);
86
+ --sidebar-ring: oklch(0.708 0 0);
87
+ }
88
+
89
+ .dark {
90
+ --background: oklch(0.145 0 0);
91
+ --foreground: oklch(0.985 0 0);
92
+ --card: oklch(0.205 0 0);
93
+ --card-foreground: oklch(0.985 0 0);
94
+ --popover: oklch(0.205 0 0);
95
+ --popover-foreground: oklch(0.985 0 0);
96
+ --primary: oklch(0.922 0 0);
97
+ --primary-foreground: oklch(0.205 0 0);
98
+ --secondary: oklch(0.269 0 0);
99
+ --secondary-foreground: oklch(0.985 0 0);
100
+ --muted: oklch(0.269 0 0);
101
+ --muted-foreground: oklch(0.708 0 0);
102
+ --accent: oklch(0.269 0 0);
103
+ --accent-foreground: oklch(0.985 0 0);
104
+ --destructive: oklch(0.704 0.191 22.216);
105
+ --destructive-foreground: oklch(0.985 0 0);
106
+ --border: oklch(1 0 0 / 10%);
107
+ --input: oklch(1 0 0 / 15%);
108
+ --ring: oklch(0.556 0 0);
109
+ --chart-1: oklch(0.87 0 0);
110
+ --chart-2: oklch(0.556 0 0);
111
+ --chart-3: oklch(0.439 0 0);
112
+ --chart-4: oklch(0.371 0 0);
113
+ --chart-5: oklch(0.269 0 0);
114
+ --sidebar: oklch(0.205 0 0);
115
+ --sidebar-foreground: oklch(0.985 0 0);
116
+ --sidebar-primary: oklch(0.488 0.243 264.376);
117
+ --sidebar-primary-foreground: oklch(0.985 0 0);
118
+ --sidebar-accent: oklch(0.269 0 0);
119
+ --sidebar-accent-foreground: oklch(0.985 0 0);
120
+ --sidebar-border: oklch(1 0 0 / 10%);
121
+ --sidebar-ring: oklch(0.556 0 0);
122
+ }
123
+
124
+ @layer base {
125
+ * {
126
+ @apply border-border outline-ring/50;
127
+ }
128
+
129
+ html {
130
+ @apply font-sans antialiased;
131
+ }
132
+
133
+ body {
134
+ @apply text-foreground bg-background min-h-screen;
135
+ }
136
+
137
+ button:not([disabled]),
138
+ [role='button']:not([disabled]) {
139
+ cursor: pointer;
140
+ }
141
+ }
142
+
143
+ @theme inline {
144
+ @keyframes accordion-down {
145
+ from {
146
+ height: 0;
147
+ }
148
+ to {
149
+ height: var(
150
+ --radix-accordion-content-height,
151
+ var(--accordion-panel-height, auto)
152
+ );
153
+ }
154
+ }
155
+
156
+ @keyframes accordion-up {
157
+ from {
158
+ height: var(
159
+ --radix-accordion-content-height,
160
+ var(--accordion-panel-height, auto)
161
+ );
162
+ }
163
+ to {
164
+ height: 0;
165
+ }
166
+ }
167
+ }
168
+
169
+ @custom-variant data-open {
170
+ &:where([data-state='open']),
171
+ &:where([data-open]:not([data-open='false'])) {
172
+ @slot;
173
+ }
174
+ }
175
+
176
+ @custom-variant data-closed {
177
+ &:where([data-state='closed']),
178
+ &:where([data-closed]:not([data-closed='false'])) {
179
+ @slot;
180
+ }
181
+ }
182
+
183
+ @custom-variant data-checked {
184
+ &:where([data-state='checked']),
185
+ &:where([data-checked]:not([data-checked='false'])) {
186
+ @slot;
187
+ }
188
+ }
189
+
190
+ @custom-variant data-unchecked {
191
+ &:where([data-state='unchecked']),
192
+ &:where([data-unchecked]:not([data-unchecked='false'])) {
193
+ @slot;
194
+ }
195
+ }
196
+
197
+ @custom-variant data-selected {
198
+ &:where([data-selected='true']) {
199
+ @slot;
200
+ }
201
+ }
202
+
203
+ @custom-variant data-disabled {
204
+ &:where([data-disabled='true']),
205
+ &:where([data-disabled]:not([data-disabled='false'])) {
206
+ @slot;
207
+ }
208
+ }
209
+
210
+ @custom-variant data-active {
211
+ &:where([data-state='active']),
212
+ &:where([data-active]:not([data-active='false'])) {
213
+ @slot;
214
+ }
215
+ }
216
+
217
+ @custom-variant data-horizontal {
218
+ &:where([data-orientation='horizontal']) {
219
+ @slot;
220
+ }
221
+ }
222
+
223
+ @custom-variant data-vertical {
224
+ &:where([data-orientation='vertical']) {
225
+ @slot;
226
+ }
227
+ }
228
+
229
+ @utility no-scrollbar {
230
+ -ms-overflow-style: none;
231
+ scrollbar-width: none;
232
+
233
+ &::-webkit-scrollbar {
234
+ display: none;
235
+ }
236
+ }
237
+
238
+ @utility container {
239
+ @apply mx-auto p-4;
240
+ }
@@ -0,0 +1,53 @@
1
+ import type { Metadata } from 'next'
2
+ import { Geist, Geist_Mono } from 'next/font/google'
3
+ import { Loader } from '~/components/loader'
4
+ import { ReactQueryProvider } from '~/components/react-query-provider'
5
+ import { ScreenSize } from '~/components/screen-size'
6
+ import { ThemeProvider } from '~/components/theme-provider'
7
+ import { Toaster } from '~/components/ui/sonner'
8
+ import { env } from '~/env'
9
+ import { siteConfig } from '~/lib/siteConfig'
10
+ import './globals.css'
11
+
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
+ export const metadata: Metadata = {
23
+ title: siteConfig.name,
24
+ description: siteConfig.description
25
+ }
26
+
27
+ export default function RootLayout({
28
+ children
29
+ }: Readonly<{
30
+ children: React.ReactNode
31
+ }>) {
32
+ return (
33
+ <html
34
+ lang="en"
35
+ suppressHydrationWarning
36
+ className={`${geistSans.variable} ${geistMono.variable}`}
37
+ >
38
+ <body>
39
+ <ReactQueryProvider>
40
+ <ThemeProvider>
41
+ <Loader />
42
+ {children}
43
+ {env.APP_ENV === 'development' ? <ScreenSize /> : null}
44
+ <Toaster />
45
+ </ThemeProvider>
46
+ </ReactQueryProvider>
47
+ </body>
48
+ </html>
49
+ )
50
+ }
51
+
52
+ /** Forces dynamic rendering - page will be re-rendered on each request */
53
+ export const dynamic = 'force-dynamic'
@@ -0,0 +1,27 @@
1
+ 'use client'
2
+
3
+ import Link from 'next/link'
4
+ import { usePathname } from 'next/navigation'
5
+ import { Button } from '~/components/ui/button'
6
+
7
+ export default function NotFound() {
8
+ const pathname = usePathname()
9
+ const homeRoute = pathname?.startsWith('/admin') ? '/admin' : '/'
10
+
11
+ return (
12
+ <main className="bg-background text-foreground flex min-h-screen flex-col">
13
+ <section className="flex min-h-[60vh] flex-col items-center justify-center px-4">
14
+ <div className="text-center">
15
+ <h1 className="text-foreground mb-2 text-6xl font-bold">404</h1>
16
+ <p className="text-muted-foreground mb-8 text-xl">Page not found</p>
17
+ <p className="text-muted-foreground mb-8 max-w-md text-base">
18
+ The page you're looking for doesn't exist.
19
+ </p>
20
+ <Button asChild>
21
+ <Link href={homeRoute}>Return to Home</Link>
22
+ </Button>
23
+ </div>
24
+ </section>
25
+ </main>
26
+ )
27
+ }
@@ -0,0 +1,28 @@
1
+ import { NextResponse } from 'next/server'
2
+
3
+ export function proxy() {
4
+ return NextResponse.next()
5
+ }
6
+
7
+ export const config = {
8
+ matcher: [
9
+ {
10
+ /**
11
+ * Route matching pattern
12
+ *
13
+ * This regex pattern matches all routes EXCEPT:
14
+ * - /_next/static - Next.js static assets
15
+ * - /_next/image - Next.js image optimization
16
+ * - /favicon.ico - Browser favicon
17
+ * - /.well-known/workflow/ - Internal workflow paths
18
+ *
19
+ * Excluding these paths ensures that:
20
+ * 1. Static assets load efficiently without middleware overhead
21
+ * 2. Image optimization is not interrupted
22
+ * 3. Workflow execution and resumption function correctly
23
+ */
24
+ source:
25
+ '/((?!_next/static|_next/image|favicon.ico|.well-known/workflow/).*)'
26
+ }
27
+ ]
28
+ }
@@ -0,0 +1,32 @@
1
+ import type { MetadataRoute } from 'next'
2
+ import { env } from '~/env'
3
+
4
+ export const dynamic = 'force-dynamic'
5
+
6
+ export default function robots(): MetadataRoute.Robots {
7
+ // Block all crawlers in staging to prevent accidental indexing
8
+ if (env.APP_ENV === 'staging') {
9
+ return {
10
+ rules: [
11
+ {
12
+ userAgent: '*',
13
+ disallow: '/'
14
+ }
15
+ ]
16
+ }
17
+ }
18
+
19
+ const baseUrl = env.BETTER_AUTH_URL
20
+
21
+ return {
22
+ rules: [
23
+ {
24
+ userAgent: '*',
25
+ allow: '/',
26
+ disallow: ['/api/', '/admin/', '/app/', '/accept-invitation/']
27
+ }
28
+ ],
29
+ sitemap: `${baseUrl}/sitemap.xml`,
30
+ host: baseUrl
31
+ }
32
+ }
@@ -0,0 +1,20 @@
1
+ import type { MetadataRoute } from 'next'
2
+ import { env } from '~/env'
3
+
4
+ export const dynamic = 'force-dynamic'
5
+
6
+ export default function sitemap(): MetadataRoute.Sitemap {
7
+ if (env.APP_ENV === 'staging') return []
8
+
9
+ const baseUrl = env.BETTER_AUTH_URL
10
+ const lastModified = new Date()
11
+
12
+ const routes = ['/', '/privacy', '/terms', '/login']
13
+
14
+ return routes.map(path => ({
15
+ url: `${baseUrl}${path === '/' ? '' : path}`,
16
+ lastModified,
17
+ changeFrequency: path === '/' ? 'weekly' : 'monthly',
18
+ priority: path === '/' ? 1 : 0.7
19
+ }))
20
+ }
@@ -0,0 +1,64 @@
1
+ 'use client'
2
+
3
+ import { ReactNode } from 'react'
4
+ import { Separator } from '~/components/ui/separator'
5
+ import { SidebarTrigger } from '~/components/ui/sidebar'
6
+ import {
7
+ Tooltip,
8
+ TooltipContent,
9
+ TooltipTrigger
10
+ } from '~/components/ui/tooltip'
11
+ import { UserMenu } from '~/components/user-menu'
12
+
13
+ type DashboardHeaderProps = {
14
+ user: {
15
+ name?: string | null
16
+ email?: string | null
17
+ image?: string | null
18
+ }
19
+ search?: ReactNode
20
+ actions?: ReactNode
21
+ fallbackName?: string
22
+ fallbackInitials?: string
23
+ }
24
+
25
+ export function DashboardHeader({
26
+ user,
27
+ search,
28
+ actions,
29
+ fallbackName,
30
+ fallbackInitials
31
+ }: DashboardHeaderProps) {
32
+ return (
33
+ <header className="bg-background sticky top-0 z-50 flex h-14 shrink-0 items-center gap-2 border-b px-4">
34
+ <Tooltip>
35
+ <TooltipTrigger asChild>
36
+ <SidebarTrigger className="-ml-1 hidden sm:inline-flex" />
37
+ </TooltipTrigger>
38
+ <TooltipContent side="right">
39
+ Toggle Sidebar{' '}
40
+ <kbd className="bg-muted text-muted-foreground pointer-events-none inline-flex h-5 items-center gap-1 rounded border px-1.5 font-mono text-[10px] font-medium opacity-100 select-none">
41
+ <span className="text-xs">⌘</span>B
42
+ </kbd>
43
+ </TooltipContent>
44
+ </Tooltip>
45
+ <SidebarTrigger className="-ml-1 sm:hidden" />
46
+
47
+ {search ? (
48
+ <>
49
+ <Separator orientation="vertical" className="mr-2" />
50
+ {search}
51
+ </>
52
+ ) : null}
53
+
54
+ <div className="flex flex-1 items-center justify-end gap-2">
55
+ {actions}
56
+ <UserMenu
57
+ user={user}
58
+ fallbackName={fallbackName}
59
+ fallbackInitials={fallbackInitials}
60
+ />
61
+ </div>
62
+ </header>
63
+ )
64
+ }
@@ -0,0 +1,5 @@
1
+ /** Sort direction values used by the DataTable for column ordering. */
2
+ export enum SortOrderEnum {
3
+ ASC = 'asc',
4
+ DESC = 'desc'
5
+ }