@alfredmouelle/create-stack 0.1.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 (206) hide show
  1. package/README.md +56 -0
  2. package/_stack/apps/next-base/.dockerignore +10 -0
  3. package/_stack/apps/next-base/Dockerfile +34 -0
  4. package/_stack/apps/next-base/README.md +32 -0
  5. package/_stack/apps/next-base/components.json +25 -0
  6. package/_stack/apps/next-base/drizzle.config.ts +16 -0
  7. package/_stack/apps/next-base/next.config.ts +8 -0
  8. package/_stack/apps/next-base/package.json +70 -0
  9. package/_stack/apps/next-base/postcss.config.mjs +7 -0
  10. package/_stack/apps/next-base/src/app/api/auth/[...all]/route.ts +4 -0
  11. package/_stack/apps/next-base/src/app/api/trpc/[trpc]/route.ts +23 -0
  12. package/_stack/apps/next-base/src/app/auth/_components/forgot-password-form.tsx +92 -0
  13. package/_stack/apps/next-base/src/app/auth/_components/reset-password-form.tsx +105 -0
  14. package/_stack/apps/next-base/src/app/auth/_components/sign-in-form.tsx +126 -0
  15. package/_stack/apps/next-base/src/app/auth/_components/sign-up-form.tsx +139 -0
  16. package/_stack/apps/next-base/src/app/auth/_components/verify-email-actions.tsx +45 -0
  17. package/_stack/apps/next-base/src/app/auth/forgot-password/page.tsx +19 -0
  18. package/_stack/apps/next-base/src/app/auth/layout.tsx +9 -0
  19. package/_stack/apps/next-base/src/app/auth/reset-password/page.tsx +26 -0
  20. package/_stack/apps/next-base/src/app/auth/sign-in/page.tsx +27 -0
  21. package/_stack/apps/next-base/src/app/auth/sign-up/page.tsx +27 -0
  22. package/_stack/apps/next-base/src/app/auth/verify-email/page.tsx +30 -0
  23. package/_stack/apps/next-base/src/app/dashboard/page.tsx +12 -0
  24. package/_stack/apps/next-base/src/app/globals.css +171 -0
  25. package/_stack/apps/next-base/src/app/layout.tsx +23 -0
  26. package/_stack/apps/next-base/src/app/page.tsx +15 -0
  27. package/_stack/apps/next-base/src/components/data-table.tsx +77 -0
  28. package/_stack/apps/next-base/src/components/infinite-data-table.tsx +102 -0
  29. package/_stack/apps/next-base/src/components/sortable-header.tsx +37 -0
  30. package/_stack/apps/next-base/src/components/theme-provider.tsx +8 -0
  31. package/_stack/apps/next-base/src/components/theme-toggle.tsx +37 -0
  32. package/_stack/apps/next-base/src/components/ui/button.tsx +64 -0
  33. package/_stack/apps/next-base/src/components/ui/calendar.tsx +185 -0
  34. package/_stack/apps/next-base/src/components/ui/card.tsx +84 -0
  35. package/_stack/apps/next-base/src/components/ui/date-picker.tsx +85 -0
  36. package/_stack/apps/next-base/src/components/ui/date-range-picker.tsx +138 -0
  37. package/_stack/apps/next-base/src/components/ui/dropdown-menu.tsx +246 -0
  38. package/_stack/apps/next-base/src/components/ui/form.tsx +149 -0
  39. package/_stack/apps/next-base/src/components/ui/input-group.tsx +97 -0
  40. package/_stack/apps/next-base/src/components/ui/input.tsx +18 -0
  41. package/_stack/apps/next-base/src/components/ui/label.tsx +18 -0
  42. package/_stack/apps/next-base/src/components/ui/popover.tsx +76 -0
  43. package/_stack/apps/next-base/src/components/ui/skeleton.tsx +13 -0
  44. package/_stack/apps/next-base/src/components/ui/spinner.tsx +8 -0
  45. package/_stack/apps/next-base/src/components/ui/table.tsx +87 -0
  46. package/_stack/apps/next-base/src/emails/components/components.tsx +199 -0
  47. package/_stack/apps/next-base/src/emails/components/context.tsx +18 -0
  48. package/_stack/apps/next-base/src/emails/components/index.ts +23 -0
  49. package/_stack/apps/next-base/src/emails/components/theme.ts +65 -0
  50. package/_stack/apps/next-base/src/emails/reset-password.tsx +16 -0
  51. package/_stack/apps/next-base/src/emails/verify-email.tsx +15 -0
  52. package/_stack/apps/next-base/src/env.ts +41 -0
  53. package/_stack/apps/next-base/src/features/auth/auth-card.tsx +30 -0
  54. package/_stack/apps/next-base/src/features/auth/form-alert.tsx +27 -0
  55. package/_stack/apps/next-base/src/features/auth/google-button.tsx +66 -0
  56. package/_stack/apps/next-base/src/features/auth/schemas.ts +35 -0
  57. package/_stack/apps/next-base/src/lib/date.ts +4 -0
  58. package/_stack/apps/next-base/src/lib/utils.ts +6 -0
  59. package/_stack/apps/next-base/src/server/api/root.ts +10 -0
  60. package/_stack/apps/next-base/src/server/api/routers/health.router.ts +8 -0
  61. package/_stack/apps/next-base/src/server/api/trpc.ts +56 -0
  62. package/_stack/apps/next-base/src/server/auth/guards.ts +10 -0
  63. package/_stack/apps/next-base/src/server/better-auth/client.ts +9 -0
  64. package/_stack/apps/next-base/src/server/better-auth/config.ts +60 -0
  65. package/_stack/apps/next-base/src/server/better-auth/emails.tsx +25 -0
  66. package/_stack/apps/next-base/src/server/better-auth/index.ts +1 -0
  67. package/_stack/apps/next-base/src/server/better-auth/server.ts +14 -0
  68. package/_stack/apps/next-base/src/server/db/index.ts +6 -0
  69. package/_stack/apps/next-base/src/server/db/keyset.ts +63 -0
  70. package/_stack/apps/next-base/src/server/db/schemas/auth.schema.ts +71 -0
  71. package/_stack/apps/next-base/src/server/db/schemas/index.ts +2 -0
  72. package/_stack/apps/next-base/src/server/db/seed.ts +27 -0
  73. package/_stack/apps/next-base/src/server/email/adapters/resend/config.ts +7 -0
  74. package/_stack/apps/next-base/src/server/email/adapters/resend/index.ts +75 -0
  75. package/_stack/apps/next-base/src/server/email/core/address.ts +21 -0
  76. package/_stack/apps/next-base/src/server/email/core/port.ts +89 -0
  77. package/_stack/apps/next-base/src/server/email/core/render.ts +16 -0
  78. package/_stack/apps/next-base/src/server/email/factory.ts +47 -0
  79. package/_stack/apps/next-base/src/server/email/index.ts +36 -0
  80. package/_stack/apps/next-base/src/trpc/query-client.ts +19 -0
  81. package/_stack/apps/next-base/src/trpc/react.tsx +62 -0
  82. package/_stack/apps/next-base/src/trpc/server.ts +23 -0
  83. package/_stack/apps/next-base/tsconfig.json +37 -0
  84. package/_stack/apps/tanstack-base/.dockerignore +13 -0
  85. package/_stack/apps/tanstack-base/Dockerfile +28 -0
  86. package/_stack/apps/tanstack-base/README.md +31 -0
  87. package/_stack/apps/tanstack-base/components.json +25 -0
  88. package/_stack/apps/tanstack-base/drizzle.config.ts +16 -0
  89. package/_stack/apps/tanstack-base/package.json +85 -0
  90. package/_stack/apps/tanstack-base/public/favicon.ico +0 -0
  91. package/_stack/apps/tanstack-base/public/logo192.png +0 -0
  92. package/_stack/apps/tanstack-base/public/logo512.png +0 -0
  93. package/_stack/apps/tanstack-base/public/manifest.json +25 -0
  94. package/_stack/apps/tanstack-base/public/robots.txt +3 -0
  95. package/_stack/apps/tanstack-base/src/components/data-table.tsx +77 -0
  96. package/_stack/apps/tanstack-base/src/components/form/field-error.tsx +18 -0
  97. package/_stack/apps/tanstack-base/src/components/form/text-field.tsx +47 -0
  98. package/_stack/apps/tanstack-base/src/components/infinite-data-table.tsx +102 -0
  99. package/_stack/apps/tanstack-base/src/components/sortable-header.tsx +37 -0
  100. package/_stack/apps/tanstack-base/src/components/theme-provider.tsx +69 -0
  101. package/_stack/apps/tanstack-base/src/components/theme-toggle.tsx +35 -0
  102. package/_stack/apps/tanstack-base/src/components/ui/button.tsx +64 -0
  103. package/_stack/apps/tanstack-base/src/components/ui/calendar.tsx +185 -0
  104. package/_stack/apps/tanstack-base/src/components/ui/card.tsx +84 -0
  105. package/_stack/apps/tanstack-base/src/components/ui/date-picker.tsx +83 -0
  106. package/_stack/apps/tanstack-base/src/components/ui/date-range-picker.tsx +136 -0
  107. package/_stack/apps/tanstack-base/src/components/ui/dropdown-menu.tsx +246 -0
  108. package/_stack/apps/tanstack-base/src/components/ui/input-group.tsx +97 -0
  109. package/_stack/apps/tanstack-base/src/components/ui/input.tsx +18 -0
  110. package/_stack/apps/tanstack-base/src/components/ui/label.tsx +18 -0
  111. package/_stack/apps/tanstack-base/src/components/ui/popover.tsx +74 -0
  112. package/_stack/apps/tanstack-base/src/components/ui/skeleton.tsx +13 -0
  113. package/_stack/apps/tanstack-base/src/components/ui/spinner.tsx +8 -0
  114. package/_stack/apps/tanstack-base/src/components/ui/table.tsx +87 -0
  115. package/_stack/apps/tanstack-base/src/emails/components/components.tsx +199 -0
  116. package/_stack/apps/tanstack-base/src/emails/components/context.tsx +18 -0
  117. package/_stack/apps/tanstack-base/src/emails/components/index.ts +23 -0
  118. package/_stack/apps/tanstack-base/src/emails/components/theme.ts +65 -0
  119. package/_stack/apps/tanstack-base/src/emails/reset-password.tsx +16 -0
  120. package/_stack/apps/tanstack-base/src/emails/verify-email.tsx +15 -0
  121. package/_stack/apps/tanstack-base/src/env.ts +41 -0
  122. package/_stack/apps/tanstack-base/src/features/auth/auth-card.tsx +30 -0
  123. package/_stack/apps/tanstack-base/src/features/auth/form-alert.tsx +27 -0
  124. package/_stack/apps/tanstack-base/src/features/auth/google-button.tsx +64 -0
  125. package/_stack/apps/tanstack-base/src/features/auth/schemas.ts +35 -0
  126. package/_stack/apps/tanstack-base/src/lib/date.ts +4 -0
  127. package/_stack/apps/tanstack-base/src/lib/utils.ts +6 -0
  128. package/_stack/apps/tanstack-base/src/router.tsx +40 -0
  129. package/_stack/apps/tanstack-base/src/routes/__root.tsx +73 -0
  130. package/_stack/apps/tanstack-base/src/routes/_authed/dashboard.tsx +12 -0
  131. package/_stack/apps/tanstack-base/src/routes/_authed.tsx +21 -0
  132. package/_stack/apps/tanstack-base/src/routes/api/auth/$.ts +14 -0
  133. package/_stack/apps/tanstack-base/src/routes/api.trpc.$.tsx +31 -0
  134. package/_stack/apps/tanstack-base/src/routes/auth/forgot-password.tsx +89 -0
  135. package/_stack/apps/tanstack-base/src/routes/auth/reset-password.tsx +111 -0
  136. package/_stack/apps/tanstack-base/src/routes/auth/sign-in.tsx +117 -0
  137. package/_stack/apps/tanstack-base/src/routes/auth/sign-up.tsx +119 -0
  138. package/_stack/apps/tanstack-base/src/routes/auth/verify-email.tsx +72 -0
  139. package/_stack/apps/tanstack-base/src/routes/auth.tsx +22 -0
  140. package/_stack/apps/tanstack-base/src/routes/index.tsx +18 -0
  141. package/_stack/apps/tanstack-base/src/server/api/root.ts +10 -0
  142. package/_stack/apps/tanstack-base/src/server/api/routers/health.router.ts +8 -0
  143. package/_stack/apps/tanstack-base/src/server/api/trpc.ts +61 -0
  144. package/_stack/apps/tanstack-base/src/server/better-auth/client.ts +9 -0
  145. package/_stack/apps/tanstack-base/src/server/better-auth/config.ts +68 -0
  146. package/_stack/apps/tanstack-base/src/server/better-auth/emails.tsx +25 -0
  147. package/_stack/apps/tanstack-base/src/server/better-auth/index.ts +1 -0
  148. package/_stack/apps/tanstack-base/src/server/better-auth/session.ts +9 -0
  149. package/_stack/apps/tanstack-base/src/server/db/index.ts +6 -0
  150. package/_stack/apps/tanstack-base/src/server/db/keyset.ts +63 -0
  151. package/_stack/apps/tanstack-base/src/server/db/schemas/auth.schema.ts +71 -0
  152. package/_stack/apps/tanstack-base/src/server/db/schemas/index.ts +2 -0
  153. package/_stack/apps/tanstack-base/src/server/db/seed.ts +27 -0
  154. package/_stack/apps/tanstack-base/src/server/email/adapters/resend/config.ts +7 -0
  155. package/_stack/apps/tanstack-base/src/server/email/adapters/resend/index.ts +75 -0
  156. package/_stack/apps/tanstack-base/src/server/email/core/address.ts +21 -0
  157. package/_stack/apps/tanstack-base/src/server/email/core/port.ts +89 -0
  158. package/_stack/apps/tanstack-base/src/server/email/core/render.ts +16 -0
  159. package/_stack/apps/tanstack-base/src/server/email/factory.ts +47 -0
  160. package/_stack/apps/tanstack-base/src/server/email/index.ts +36 -0
  161. package/_stack/apps/tanstack-base/src/styles.css +171 -0
  162. package/_stack/apps/tanstack-base/src/trpc/devtools.tsx +6 -0
  163. package/_stack/apps/tanstack-base/src/trpc/query-client.ts +19 -0
  164. package/_stack/apps/tanstack-base/src/trpc/react.tsx +49 -0
  165. package/_stack/apps/tanstack-base/src/trpc/server.ts +11 -0
  166. package/_stack/apps/tanstack-base/tsconfig.json +27 -0
  167. package/_stack/apps/tanstack-base/tsr.config.json +3 -0
  168. package/_stack/apps/tanstack-base/vite.config.ts +15 -0
  169. package/_stack/packages/analytics/capability.json +26 -0
  170. package/_stack/packages/cache/capability.json +21 -0
  171. package/_stack/packages/error-tracking/capability.json +21 -0
  172. package/_stack/packages/jobs/capability.json +26 -0
  173. package/_stack/packages/logger/capability.json +21 -0
  174. package/_stack/packages/mailer/capability.json +28 -0
  175. package/_stack/packages/mailer/package.json +37 -0
  176. package/_stack/packages/mailer/src/adapters/brevo/config.ts +7 -0
  177. package/_stack/packages/mailer/src/adapters/brevo/index.ts +90 -0
  178. package/_stack/packages/mailer/src/adapters/resend/config.ts +7 -0
  179. package/_stack/packages/mailer/src/adapters/resend/index.ts +75 -0
  180. package/_stack/packages/mailer/src/adapters/ses/config.ts +13 -0
  181. package/_stack/packages/mailer/src/adapters/ses/index.ts +103 -0
  182. package/_stack/packages/storage/capability.json +32 -0
  183. package/_stack/patterns/README.md +58 -0
  184. package/_stack/patterns/_baseline/README-author.md +10 -0
  185. package/_stack/patterns/_baseline/biome.jsonc +119 -0
  186. package/_stack/patterns/_baseline/env.ts +31 -0
  187. package/_stack/patterns/_baseline/tsconfig.json +27 -0
  188. package/_stack/patterns/better-auth/pattern.json +73 -0
  189. package/_stack/patterns/better-auth-next/pattern.json +76 -0
  190. package/_stack/patterns/data-table/pattern.json +43 -0
  191. package/_stack/patterns/drizzle/pattern.json +61 -0
  192. package/_stack/patterns/trpc/pattern.json +61 -0
  193. package/_stack/patterns/trpc-next/pattern.json +64 -0
  194. package/index.mjs +216 -0
  195. package/lib/build.mjs +64 -0
  196. package/lib/env.mjs +56 -0
  197. package/lib/identity.mjs +33 -0
  198. package/lib/mailer.mjs +95 -0
  199. package/lib/manifests.mjs +61 -0
  200. package/lib/scaffold.mjs +49 -0
  201. package/lib/strip.mjs +132 -0
  202. package/lib/util.mjs +82 -0
  203. package/package.json +51 -0
  204. package/templates/next/layout.no-trpc.tsx +22 -0
  205. package/templates/tanstack/__root.no-trpc.tsx +63 -0
  206. package/templates/tanstack/router.no-trpc.tsx +24 -0
@@ -0,0 +1,139 @@
1
+ 'use client'
2
+
3
+ import { valibotResolver } from '@hookform/resolvers/valibot'
4
+ import { Lock, Mail, User } from 'lucide-react'
5
+ import { useRouter } from 'next/navigation'
6
+ import { useState } from 'react'
7
+ import { useForm } from 'react-hook-form'
8
+ import { Button } from '~/components/ui/button'
9
+ import {
10
+ Form,
11
+ FormControl,
12
+ FormField,
13
+ FormItem,
14
+ FormLabel,
15
+ FormMessage,
16
+ } from '~/components/ui/form'
17
+ import { InputGroup, InputGroupAddon, InputGroupInput } from '~/components/ui/input-group'
18
+ import { Spinner } from '~/components/ui/spinner'
19
+ import { FormAlert } from '~/features/auth/form-alert'
20
+ import { AuthDivider, GoogleButton } from '~/features/auth/google-button'
21
+ import { type SignUpInput, SignUpSchema } from '~/features/auth/schemas'
22
+ import { authClient } from '~/server/better-auth/client'
23
+
24
+ export function SignUpForm() {
25
+ const router = useRouter()
26
+ const [formError, setFormError] = useState<string | null>(null)
27
+ const form = useForm<SignUpInput>({
28
+ resolver: valibotResolver(SignUpSchema),
29
+ defaultValues: { name: '', email: '', password: '' },
30
+ })
31
+
32
+ const onSubmit = form.handleSubmit(async (values) => {
33
+ setFormError(null)
34
+ const { error } = await authClient.signUp.email({
35
+ name: values.name,
36
+ email: values.email,
37
+ password: values.password,
38
+ callbackURL: '/',
39
+ })
40
+
41
+ if (!error) {
42
+ router.push(`/auth/verify-email?email=${encodeURIComponent(values.email)}`)
43
+ return
44
+ }
45
+
46
+ setFormError(
47
+ error.code === 'USER_ALREADY_EXISTS'
48
+ ? 'An account already exists with this email.'
49
+ : (error.message ?? 'Could not sign up. Try again.'),
50
+ )
51
+ })
52
+
53
+ return (
54
+ <Form {...form}>
55
+ <form className="grid gap-4" onSubmit={onSubmit}>
56
+ <GoogleButton label="Sign up with Google" />
57
+ <AuthDivider />
58
+
59
+ {formError ? <FormAlert>{formError}</FormAlert> : null}
60
+
61
+ <FormField
62
+ control={form.control}
63
+ name="name"
64
+ render={({ field }) => (
65
+ <FormItem>
66
+ <FormLabel>Name</FormLabel>
67
+ <InputGroup className="h-10">
68
+ <InputGroupAddon align="inline-start">
69
+ <User />
70
+ </InputGroupAddon>
71
+ <FormControl>
72
+ <InputGroupInput autoComplete="name" placeholder="Your name" {...field} />
73
+ </FormControl>
74
+ </InputGroup>
75
+ <FormMessage />
76
+ </FormItem>
77
+ )}
78
+ />
79
+
80
+ <FormField
81
+ control={form.control}
82
+ name="email"
83
+ render={({ field }) => (
84
+ <FormItem>
85
+ <FormLabel>Email</FormLabel>
86
+ <InputGroup className="h-10">
87
+ <InputGroupAddon align="inline-start">
88
+ <Mail />
89
+ </InputGroupAddon>
90
+ <FormControl>
91
+ <InputGroupInput
92
+ autoComplete="email"
93
+ placeholder="you@example.com"
94
+ type="email"
95
+ {...field}
96
+ />
97
+ </FormControl>
98
+ </InputGroup>
99
+ <FormMessage />
100
+ </FormItem>
101
+ )}
102
+ />
103
+
104
+ <FormField
105
+ control={form.control}
106
+ name="password"
107
+ render={({ field }) => (
108
+ <FormItem>
109
+ <FormLabel>Password</FormLabel>
110
+ <InputGroup className="h-10">
111
+ <InputGroupAddon align="inline-start">
112
+ <Lock />
113
+ </InputGroupAddon>
114
+ <FormControl>
115
+ <InputGroupInput
116
+ autoComplete="new-password"
117
+ placeholder="At least 8 characters"
118
+ type="password"
119
+ {...field}
120
+ />
121
+ </FormControl>
122
+ </InputGroup>
123
+ <FormMessage />
124
+ </FormItem>
125
+ )}
126
+ />
127
+
128
+ <Button
129
+ className="w-full cursor-pointer"
130
+ disabled={form.formState.isSubmitting}
131
+ type="submit"
132
+ >
133
+ {form.formState.isSubmitting ? <Spinner /> : null}
134
+ {form.formState.isSubmitting ? 'Creating…' : 'Create account'}
135
+ </Button>
136
+ </form>
137
+ </Form>
138
+ )
139
+ }
@@ -0,0 +1,45 @@
1
+ 'use client'
2
+
3
+ import { useState } from 'react'
4
+ import { Button } from '~/components/ui/button'
5
+ import { Spinner } from '~/components/ui/spinner'
6
+ import { FormAlert } from '~/features/auth/form-alert'
7
+ import { authClient } from '~/server/better-auth/client'
8
+
9
+ export function VerifyEmailActions({ email }: { email?: string }) {
10
+ const [status, setStatus] = useState<'idle' | 'sending' | 'sent' | 'error'>('idle')
11
+
12
+ const resend = async () => {
13
+ if (!email) return
14
+ setStatus('sending')
15
+ const { error } = await authClient.sendVerificationEmail({ email, callbackURL: '/' })
16
+ setStatus(error ? 'error' : 'sent')
17
+ }
18
+
19
+ return (
20
+ <div className="grid gap-4">
21
+ {status === 'sent' ? (
22
+ <FormAlert variant="success">
23
+ Request sent. If your address isn't confirmed yet, you'll get an email — check your spam
24
+ folder.
25
+ </FormAlert>
26
+ ) : null}
27
+ {status === 'error' ? <FormAlert>Could not send. Try again shortly.</FormAlert> : null}
28
+
29
+ <p className="text-muted-foreground text-sm">
30
+ Didn't get anything? Check your spam folder or resend the link.
31
+ </p>
32
+
33
+ <Button
34
+ className="w-full cursor-pointer"
35
+ disabled={!email || status === 'sending'}
36
+ onClick={resend}
37
+ type="button"
38
+ variant="outline"
39
+ >
40
+ {status === 'sending' ? <Spinner /> : null}
41
+ {status === 'sending' ? 'Sending…' : 'Resend link'}
42
+ </Button>
43
+ </div>
44
+ )
45
+ }
@@ -0,0 +1,19 @@
1
+ import Link from 'next/link'
2
+ import { AuthCard } from '~/features/auth/auth-card'
3
+ import { ForgotPasswordForm } from '../_components/forgot-password-form'
4
+
5
+ export default function ForgotPasswordPage() {
6
+ return (
7
+ <AuthCard
8
+ description="Enter your email to receive a reset link."
9
+ footer={
10
+ <Link className="text-foreground hover:underline" href="/auth/sign-in">
11
+ Back to sign in
12
+ </Link>
13
+ }
14
+ title="Forgot password"
15
+ >
16
+ <ForgotPasswordForm />
17
+ </AuthCard>
18
+ )
19
+ }
@@ -0,0 +1,9 @@
1
+ import type { ReactNode } from 'react'
2
+
3
+ export default function AuthLayout({ children }: { children: ReactNode }) {
4
+ return (
5
+ <div className="flex min-h-svh flex-col items-center justify-center gap-8 bg-gradient-to-b from-background to-muted/50 px-4 py-10">
6
+ <div className="w-full max-w-sm">{children}</div>
7
+ </div>
8
+ )
9
+ }
@@ -0,0 +1,26 @@
1
+ import Link from 'next/link'
2
+ import { AuthCard } from '~/features/auth/auth-card'
3
+ import { ResetPasswordForm } from '../_components/reset-password-form'
4
+
5
+ export default async function ResetPasswordPage({
6
+ searchParams,
7
+ }: {
8
+ searchParams: Promise<{ token?: string; error?: string }>
9
+ }) {
10
+ const { token, error } = await searchParams
11
+ const validToken = token && error !== 'INVALID_TOKEN' ? token : undefined
12
+
13
+ return (
14
+ <AuthCard
15
+ description="Choose a new password for your account."
16
+ footer={
17
+ <Link className="text-foreground hover:underline" href="/auth/forgot-password">
18
+ Request a new link
19
+ </Link>
20
+ }
21
+ title="New password"
22
+ >
23
+ <ResetPasswordForm token={validToken} />
24
+ </AuthCard>
25
+ )
26
+ }
@@ -0,0 +1,27 @@
1
+ import Link from 'next/link'
2
+ import { redirect } from 'next/navigation'
3
+ import { AuthCard } from '~/features/auth/auth-card'
4
+ import { getSession } from '~/server/better-auth/server'
5
+ import { SignInForm } from '../_components/sign-in-form'
6
+
7
+ export default async function SignInPage() {
8
+ const session = await getSession()
9
+ if (session) redirect('/')
10
+
11
+ return (
12
+ <AuthCard
13
+ description="Sign in to your account."
14
+ footer={
15
+ <>
16
+ No account yet?{' '}
17
+ <Link className="ml-1 text-foreground hover:underline" href="/auth/sign-up">
18
+ Create an account
19
+ </Link>
20
+ </>
21
+ }
22
+ title="Sign in"
23
+ >
24
+ <SignInForm />
25
+ </AuthCard>
26
+ )
27
+ }
@@ -0,0 +1,27 @@
1
+ import Link from 'next/link'
2
+ import { redirect } from 'next/navigation'
3
+ import { AuthCard } from '~/features/auth/auth-card'
4
+ import { getSession } from '~/server/better-auth/server'
5
+ import { SignUpForm } from '../_components/sign-up-form'
6
+
7
+ export default async function SignUpPage() {
8
+ const session = await getSession()
9
+ if (session) redirect('/')
10
+
11
+ return (
12
+ <AuthCard
13
+ description="Create your account."
14
+ footer={
15
+ <>
16
+ Already have an account?{' '}
17
+ <Link className="ml-1 text-foreground hover:underline" href="/auth/sign-in">
18
+ Sign in
19
+ </Link>
20
+ </>
21
+ }
22
+ title="Create an account"
23
+ >
24
+ <SignUpForm />
25
+ </AuthCard>
26
+ )
27
+ }
@@ -0,0 +1,30 @@
1
+ import Link from 'next/link'
2
+ import { AuthCard } from '~/features/auth/auth-card'
3
+ import { VerifyEmailActions } from '../_components/verify-email-actions'
4
+
5
+ export default async function VerifyEmailPage({
6
+ searchParams,
7
+ }: {
8
+ searchParams: Promise<{ email?: string }>
9
+ }) {
10
+ const { email } = await searchParams
11
+
12
+ return (
13
+ <AuthCard
14
+ description={
15
+ <>
16
+ A confirmation link was sent{email ? ` to ${email}` : ''}. Click it to activate your
17
+ account.
18
+ </>
19
+ }
20
+ footer={
21
+ <Link className="text-foreground hover:underline" href="/auth/sign-in">
22
+ Back to sign in
23
+ </Link>
24
+ }
25
+ title="Confirm your email"
26
+ >
27
+ <VerifyEmailActions email={email} />
28
+ </AuthCard>
29
+ )
30
+ }
@@ -0,0 +1,12 @@
1
+ import { requireAuth } from '~/server/auth/guards'
2
+
3
+ export default async function DashboardPage() {
4
+ await requireAuth()
5
+
6
+ return (
7
+ <main className="p-8">
8
+ <h1 className="font-heading font-medium text-2xl">Dashboard</h1>
9
+ <p className="mt-2 text-muted-foreground">Protected route.</p>
10
+ </main>
11
+ )
12
+ }
@@ -0,0 +1,171 @@
1
+ @import "tailwindcss";
2
+ @import "tw-animate-css";
3
+ @import "@fontsource-variable/geist";
4
+
5
+ @custom-variant dark (&:is(.dark *));
6
+
7
+ :root {
8
+ --background: oklch(1 0 0);
9
+ --foreground: oklch(0.145 0 0);
10
+ --card: oklch(1 0 0);
11
+ --card-foreground: oklch(0.145 0 0);
12
+ --popover: oklch(1 0 0);
13
+ --popover-foreground: oklch(0.145 0 0);
14
+ --primary: oklch(0.511 0.096 186.391);
15
+ --primary-foreground: oklch(0.984 0.014 180.72);
16
+ --secondary: oklch(0.967 0.001 286.375);
17
+ --secondary-foreground: oklch(0.21 0.006 285.885);
18
+ --muted: oklch(0.97 0 0);
19
+ --muted-foreground: oklch(0.556 0 0);
20
+ --accent: oklch(0.97 0 0);
21
+ --accent-foreground: oklch(0.205 0 0);
22
+ --destructive: oklch(0.577 0.245 27.325);
23
+ --destructive-foreground: oklch(0.577 0.245 27.325);
24
+ --border: oklch(0.922 0 0);
25
+ --input: oklch(0.922 0 0);
26
+ --ring: oklch(0.708 0 0);
27
+ --chart-1: oklch(0.646 0.222 41.116);
28
+ --chart-2: oklch(0.6 0.118 184.704);
29
+ --chart-3: oklch(0.398 0.07 227.392);
30
+ --chart-4: oklch(0.828 0.189 84.429);
31
+ --chart-5: oklch(0.769 0.188 70.08);
32
+ --radius: 0.45rem;
33
+ --sidebar: oklch(0.985 0 0);
34
+ --sidebar-foreground: oklch(0.145 0 0);
35
+ --sidebar-primary: oklch(0.6 0.118 184.704);
36
+ --sidebar-primary-foreground: oklch(0.984 0.014 180.72);
37
+ --sidebar-accent: oklch(0.97 0 0);
38
+ --sidebar-accent-foreground: oklch(0.205 0 0);
39
+ --sidebar-border: oklch(0.922 0 0);
40
+ --sidebar-ring: oklch(0.708 0 0);
41
+ }
42
+
43
+ .dark {
44
+ --background: oklch(0.145 0 0);
45
+ --foreground: oklch(0.985 0 0);
46
+ --card: oklch(0.205 0 0);
47
+ --card-foreground: oklch(0.985 0 0);
48
+ --popover: oklch(0.205 0 0);
49
+ --popover-foreground: oklch(0.985 0 0);
50
+ --primary: oklch(0.437 0.078 188.216);
51
+ --primary-foreground: oklch(0.984 0.014 180.72);
52
+ --secondary: oklch(0.274 0.006 286.033);
53
+ --secondary-foreground: oklch(0.985 0 0);
54
+ --muted: oklch(0.269 0 0);
55
+ --muted-foreground: oklch(0.708 0 0);
56
+ --accent: oklch(0.269 0 0);
57
+ --accent-foreground: oklch(0.985 0 0);
58
+ --destructive: oklch(0.704 0.191 22.216);
59
+ --destructive-foreground: oklch(0.637 0.237 25.331);
60
+ --border: oklch(1 0 0 / 10%);
61
+ --input: oklch(1 0 0 / 15%);
62
+ --ring: oklch(0.556 0 0);
63
+ --chart-1: oklch(0.488 0.243 264.376);
64
+ --chart-2: oklch(0.696 0.17 162.48);
65
+ --chart-3: oklch(0.769 0.188 70.08);
66
+ --chart-4: oklch(0.627 0.265 303.9);
67
+ --chart-5: oklch(0.645 0.246 16.439);
68
+ --sidebar: oklch(0.205 0 0);
69
+ --sidebar-foreground: oklch(0.985 0 0);
70
+ --sidebar-primary: oklch(0.704 0.14 182.503);
71
+ --sidebar-primary-foreground: oklch(0.277 0.046 192.524);
72
+ --sidebar-accent: oklch(0.269 0 0);
73
+ --sidebar-accent-foreground: oklch(0.985 0 0);
74
+ --sidebar-border: oklch(1 0 0 / 10%);
75
+ --sidebar-ring: oklch(0.556 0 0);
76
+ }
77
+
78
+ @theme inline {
79
+ --font-sans: "Geist Variable", sans-serif;
80
+ --font-heading: var(--font-sans);
81
+ --color-background: var(--background);
82
+ --color-foreground: var(--foreground);
83
+ --color-card: var(--card);
84
+ --color-card-foreground: var(--card-foreground);
85
+ --color-popover: var(--popover);
86
+ --color-popover-foreground: var(--popover-foreground);
87
+ --color-primary: var(--primary);
88
+ --color-primary-foreground: var(--primary-foreground);
89
+ --color-secondary: var(--secondary);
90
+ --color-secondary-foreground: var(--secondary-foreground);
91
+ --color-muted: var(--muted);
92
+ --color-muted-foreground: var(--muted-foreground);
93
+ --color-accent: var(--accent);
94
+ --color-accent-foreground: var(--accent-foreground);
95
+ --color-destructive: var(--destructive);
96
+ --color-destructive-foreground: var(--destructive-foreground);
97
+ --color-border: var(--border);
98
+ --color-input: var(--input);
99
+ --color-ring: var(--ring);
100
+ --color-chart-1: var(--chart-1);
101
+ --color-chart-2: var(--chart-2);
102
+ --color-chart-3: var(--chart-3);
103
+ --color-chart-4: var(--chart-4);
104
+ --color-chart-5: var(--chart-5);
105
+ --color-sidebar: var(--sidebar);
106
+ --color-sidebar-foreground: var(--sidebar-foreground);
107
+ --color-sidebar-primary: var(--sidebar-primary);
108
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
109
+ --color-sidebar-accent: var(--sidebar-accent);
110
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
111
+ --color-sidebar-border: var(--sidebar-border);
112
+ --color-sidebar-ring: var(--sidebar-ring);
113
+ --radius-sm: calc(var(--radius) - 4px);
114
+ --radius-md: calc(var(--radius) - 2px);
115
+ --radius-lg: var(--radius);
116
+ --radius-xl: calc(var(--radius) + 4px);
117
+ --radius-2xl: calc(var(--radius) * 1.8);
118
+ --radius-3xl: calc(var(--radius) * 2.2);
119
+ --radius-4xl: calc(var(--radius) * 2.6);
120
+ }
121
+
122
+ @layer base {
123
+ * {
124
+ @apply border-border outline-ring/50;
125
+ }
126
+ body {
127
+ @apply bg-background text-foreground;
128
+ }
129
+ html {
130
+ @apply font-sans;
131
+ }
132
+ }
133
+
134
+ @layer utilities {
135
+ .scrollbar {
136
+ scrollbar-width: thin;
137
+ scrollbar-color: var(--border) transparent;
138
+ }
139
+ .scrollbar::-webkit-scrollbar {
140
+ width: 8px;
141
+ height: 8px;
142
+ }
143
+ .scrollbar::-webkit-scrollbar-track {
144
+ background: transparent;
145
+ }
146
+ .scrollbar::-webkit-scrollbar-thumb {
147
+ background-color: var(--border);
148
+ border-radius: var(--radius-3xl);
149
+ }
150
+ .scrollbar:hover::-webkit-scrollbar-thumb {
151
+ background-color: var(--muted-foreground);
152
+ }
153
+
154
+ .scrollbar-padded-x::-webkit-scrollbar {
155
+ width: 16px;
156
+ }
157
+ .scrollbar-padded-x::-webkit-scrollbar-thumb {
158
+ border-left: 4px solid transparent;
159
+ border-right: 4px solid transparent;
160
+ background-clip: padding-box;
161
+ }
162
+
163
+ .scrollbar-padded-y::-webkit-scrollbar {
164
+ height: 16px;
165
+ }
166
+ .scrollbar-padded-y::-webkit-scrollbar-thumb {
167
+ border-top: 4px solid transparent;
168
+ border-bottom: 4px solid transparent;
169
+ background-clip: padding-box;
170
+ }
171
+ }
@@ -0,0 +1,23 @@
1
+ import type { Metadata } from 'next'
2
+ import type { ReactNode } from 'react'
3
+ import { ThemeProvider } from '~/components/theme-provider'
4
+ import { TRPCReactProvider } from '~/trpc/react'
5
+ import './globals.css'
6
+
7
+ export const metadata: Metadata = {
8
+ title: 'App',
9
+ description: 'Next.js base.',
10
+ authors: [{ name: 'Alfred MOUELLE', url: 'https://alfredmouelle.com' }],
11
+ }
12
+
13
+ export default function RootLayout({ children }: Readonly<{ children: ReactNode }>) {
14
+ return (
15
+ <html lang="en" suppressHydrationWarning>
16
+ <body>
17
+ <ThemeProvider attribute="class" defaultTheme="system" enableSystem>
18
+ <TRPCReactProvider>{children}</TRPCReactProvider>
19
+ </ThemeProvider>
20
+ </body>
21
+ </html>
22
+ )
23
+ }
@@ -0,0 +1,15 @@
1
+ import { ThemeToggle } from '~/components/theme-toggle'
2
+
3
+ export default function Home() {
4
+ return (
5
+ <main className="p-8">
6
+ <div className="flex items-center justify-between">
7
+ <h1 className="font-bold text-4xl">Base</h1>
8
+ <ThemeToggle />
9
+ </div>
10
+ <p className="mt-4 text-lg">
11
+ Next.js base. Add tools with <code>add-capability</code>.
12
+ </p>
13
+ </main>
14
+ )
15
+ }
@@ -0,0 +1,77 @@
1
+ import { flexRender, type Table as ReactTable } from '@tanstack/react-table'
2
+ import { Skeleton } from '~/components/ui/skeleton'
3
+ import {
4
+ Table,
5
+ TableBody,
6
+ TableCell,
7
+ TableHead,
8
+ TableHeader,
9
+ TableRow,
10
+ } from '~/components/ui/table'
11
+
12
+ interface DataTableProps<TData> {
13
+ table: ReactTable<TData>
14
+ columnCount: number
15
+ isLoading: boolean
16
+ emptyLabel: string
17
+ skeletonRows?: number
18
+ }
19
+
20
+ export function DataTable<TData>({
21
+ table,
22
+ columnCount,
23
+ isLoading,
24
+ emptyLabel,
25
+ skeletonRows = 3,
26
+ }: DataTableProps<TData>) {
27
+ const rows = table.getRowModel().rows
28
+
29
+ return (
30
+ <div className="overflow-hidden rounded-lg border">
31
+ <Table>
32
+ <TableHeader>
33
+ {table.getHeaderGroups().map((headerGroup) => (
34
+ <TableRow key={headerGroup.id}>
35
+ {headerGroup.headers.map((header) => (
36
+ <TableHead key={header.id}>
37
+ {header.isPlaceholder
38
+ ? null
39
+ : flexRender(header.column.columnDef.header, header.getContext())}
40
+ </TableHead>
41
+ ))}
42
+ </TableRow>
43
+ ))}
44
+ </TableHeader>
45
+ <TableBody>
46
+ {isLoading ? (
47
+ Array.from({ length: skeletonRows }, (_, rowIndex) => (
48
+ <TableRow key={rowIndex}>
49
+ {Array.from({ length: columnCount }, (_, cellIndex) => (
50
+ <TableCell key={cellIndex}>
51
+ <Skeleton className="h-6 w-full" />
52
+ </TableCell>
53
+ ))}
54
+ </TableRow>
55
+ ))
56
+ ) : rows.length === 0 ? (
57
+ <TableRow>
58
+ <TableCell className="h-24 text-center text-muted-foreground" colSpan={columnCount}>
59
+ {emptyLabel}
60
+ </TableCell>
61
+ </TableRow>
62
+ ) : (
63
+ rows.map((row) => (
64
+ <TableRow className="group" key={row.id}>
65
+ {row.getVisibleCells().map((cell) => (
66
+ <TableCell key={cell.id}>
67
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
68
+ </TableCell>
69
+ ))}
70
+ </TableRow>
71
+ ))
72
+ )}
73
+ </TableBody>
74
+ </Table>
75
+ </div>
76
+ )
77
+ }