@amirulabu/create-recurring-rabbit-app 0.0.0-alpha

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 (64) hide show
  1. package/bin/index.js +2 -0
  2. package/dist/index.js +592 -0
  3. package/package.json +43 -0
  4. package/templates/default/.editorconfig +21 -0
  5. package/templates/default/.env.example +15 -0
  6. package/templates/default/.eslintrc.json +35 -0
  7. package/templates/default/.prettierrc.json +7 -0
  8. package/templates/default/README.md +346 -0
  9. package/templates/default/app.config.ts +20 -0
  10. package/templates/default/docs/adding-features.md +439 -0
  11. package/templates/default/docs/adr/001-use-sqlite-for-development-database.md +22 -0
  12. package/templates/default/docs/adr/002-use-tanstack-start-over-nextjs.md +22 -0
  13. package/templates/default/docs/adr/003-use-better-auth-over-nextauth.md +22 -0
  14. package/templates/default/docs/adr/004-use-drizzle-over-prisma.md +22 -0
  15. package/templates/default/docs/adr/005-use-trpc-for-api-layer.md +22 -0
  16. package/templates/default/docs/adr/006-use-tailwind-css-v4-with-shadcn-ui.md +22 -0
  17. package/templates/default/docs/architecture.md +241 -0
  18. package/templates/default/docs/database.md +376 -0
  19. package/templates/default/docs/deployment.md +435 -0
  20. package/templates/default/docs/troubleshooting.md +668 -0
  21. package/templates/default/drizzle/migrations/0001_initial_schema.sql +39 -0
  22. package/templates/default/drizzle/migrations/meta/0001_snapshot.json +225 -0
  23. package/templates/default/drizzle/migrations/meta/_journal.json +12 -0
  24. package/templates/default/drizzle.config.ts +10 -0
  25. package/templates/default/lighthouserc.json +78 -0
  26. package/templates/default/src/app/__root.tsx +32 -0
  27. package/templates/default/src/app/api/auth/$.ts +15 -0
  28. package/templates/default/src/app/api/trpc.server.ts +12 -0
  29. package/templates/default/src/app/auth/forgot-password.tsx +107 -0
  30. package/templates/default/src/app/auth/login.tsx +34 -0
  31. package/templates/default/src/app/auth/register.tsx +34 -0
  32. package/templates/default/src/app/auth/reset-password.tsx +171 -0
  33. package/templates/default/src/app/auth/verify-email.tsx +111 -0
  34. package/templates/default/src/app/dashboard/index.tsx +122 -0
  35. package/templates/default/src/app/dashboard/settings.tsx +161 -0
  36. package/templates/default/src/app/globals.css +55 -0
  37. package/templates/default/src/app/index.tsx +83 -0
  38. package/templates/default/src/components/features/auth/login-form.tsx +172 -0
  39. package/templates/default/src/components/features/auth/register-form.tsx +202 -0
  40. package/templates/default/src/components/layout/dashboard-layout.tsx +27 -0
  41. package/templates/default/src/components/layout/header.tsx +29 -0
  42. package/templates/default/src/components/layout/sidebar.tsx +38 -0
  43. package/templates/default/src/components/ui/button.tsx +57 -0
  44. package/templates/default/src/components/ui/card.tsx +79 -0
  45. package/templates/default/src/components/ui/input.tsx +24 -0
  46. package/templates/default/src/lib/api.ts +42 -0
  47. package/templates/default/src/lib/auth.ts +292 -0
  48. package/templates/default/src/lib/email.ts +221 -0
  49. package/templates/default/src/lib/env.ts +119 -0
  50. package/templates/default/src/lib/hydration-timing.ts +289 -0
  51. package/templates/default/src/lib/monitoring.ts +336 -0
  52. package/templates/default/src/lib/utils.ts +6 -0
  53. package/templates/default/src/server/api/root.ts +10 -0
  54. package/templates/default/src/server/api/routers/dashboard.ts +37 -0
  55. package/templates/default/src/server/api/routers/user.ts +31 -0
  56. package/templates/default/src/server/api/trpc.ts +132 -0
  57. package/templates/default/src/server/auth/config.ts +241 -0
  58. package/templates/default/src/server/db/index.ts +153 -0
  59. package/templates/default/src/server/db/migrate.ts +125 -0
  60. package/templates/default/src/server/db/schema.ts +170 -0
  61. package/templates/default/src/server/db/seed.ts +130 -0
  62. package/templates/default/src/types/global.d.ts +25 -0
  63. package/templates/default/tailwind.config.js +46 -0
  64. package/templates/default/tsconfig.json +36 -0
@@ -0,0 +1,225 @@
1
+ {
2
+ "version": "7.28.0",
3
+ "dialect": "sqlite",
4
+ "id": "0001_initial_schema",
5
+ "prevId": "0000",
6
+ "tables": {
7
+ "accounts": {
8
+ "name": "accounts",
9
+ "columns": {
10
+ "id": {
11
+ "name": "id",
12
+ "type": "text",
13
+ "primaryKey": true,
14
+ "isNotNull": true
15
+ },
16
+ "user_id": {
17
+ "name": "user_id",
18
+ "type": "text",
19
+ "primaryKey": false,
20
+ "isNotNull": true
21
+ },
22
+ "account_id": {
23
+ "name": "account_id",
24
+ "type": "text",
25
+ "primaryKey": false,
26
+ "isNotNull": true
27
+ },
28
+ "provider_id": {
29
+ "name": "provider_id",
30
+ "type": "text",
31
+ "primaryKey": false,
32
+ "isNotNull": true
33
+ },
34
+ "access_token": {
35
+ "name": "access_token",
36
+ "type": "text",
37
+ "primaryKey": false,
38
+ "isNotNull": false
39
+ },
40
+ "refresh_token": {
41
+ "name": "refresh_token",
42
+ "type": "text",
43
+ "primaryKey": false,
44
+ "isNotNull": false
45
+ },
46
+ "expires_at": {
47
+ "name": "expires_at",
48
+ "type": "integer",
49
+ "primaryKey": false,
50
+ "isNotNull": false
51
+ },
52
+ "created_at": {
53
+ "name": "created_at",
54
+ "type": "integer",
55
+ "primaryKey": false,
56
+ "isNotNull": true,
57
+ "default": "strftime('%s', 'now')"
58
+ }
59
+ },
60
+ "indexes": {},
61
+ "foreignKeys": {
62
+ "accounts_user_id_users_id_fk": {
63
+ "name": "accounts_user_id_users_id_fk",
64
+ "tableFrom": "accounts",
65
+ "tableTo": "users",
66
+ "columnsFrom": ["user_id"],
67
+ "columnsTo": ["id"],
68
+ "onDelete": "cascade",
69
+ "onUpdate": "no action"
70
+ }
71
+ },
72
+ "compositePrimaryKeys": {},
73
+ "uniqueConstraints": {}
74
+ },
75
+ "sessions": {
76
+ "name": "sessions",
77
+ "columns": {
78
+ "id": {
79
+ "name": "id",
80
+ "type": "text",
81
+ "primaryKey": true,
82
+ "isNotNull": true
83
+ },
84
+ "user_id": {
85
+ "name": "user_id",
86
+ "type": "text",
87
+ "primaryKey": false,
88
+ "isNotNull": true
89
+ },
90
+ "expires_at": {
91
+ "name": "expires_at",
92
+ "type": "integer",
93
+ "primaryKey": false,
94
+ "isNotNull": true
95
+ },
96
+ "created_at": {
97
+ "name": "created_at",
98
+ "type": "integer",
99
+ "primaryKey": false,
100
+ "isNotNull": true,
101
+ "default": "strftime('%s', 'now')"
102
+ }
103
+ },
104
+ "indexes": {},
105
+ "foreignKeys": {
106
+ "sessions_user_id_users_id_fk": {
107
+ "name": "sessions_user_id_users_id_fk",
108
+ "tableFrom": "sessions",
109
+ "tableTo": "users",
110
+ "columnsFrom": ["user_id"],
111
+ "columnsTo": ["id"],
112
+ "onDelete": "cascade",
113
+ "onUpdate": "no action"
114
+ }
115
+ },
116
+ "compositePrimaryKeys": {},
117
+ "uniqueConstraints": {}
118
+ },
119
+ "users": {
120
+ "name": "users",
121
+ "columns": {
122
+ "id": {
123
+ "name": "id",
124
+ "type": "text",
125
+ "primaryKey": true,
126
+ "isNotNull": true
127
+ },
128
+ "email": {
129
+ "name": "email",
130
+ "type": "text",
131
+ "primaryKey": false,
132
+ "isNotNull": true
133
+ },
134
+ "name": {
135
+ "name": "name",
136
+ "type": "text",
137
+ "primaryKey": false,
138
+ "isNotNull": true
139
+ },
140
+ "email_verified": {
141
+ "name": "email_verified",
142
+ "type": "integer",
143
+ "primaryKey": false,
144
+ "isNotNull": false,
145
+ "default": false
146
+ },
147
+ "image": {
148
+ "name": "image",
149
+ "type": "text",
150
+ "primaryKey": false,
151
+ "isNotNull": false
152
+ },
153
+ "created_at": {
154
+ "name": "created_at",
155
+ "type": "integer",
156
+ "primaryKey": false,
157
+ "isNotNull": true,
158
+ "default": "strftime('%s', 'now')"
159
+ },
160
+ "updated_at": {
161
+ "name": "updated_at",
162
+ "type": "integer",
163
+ "primaryKey": false,
164
+ "isNotNull": false
165
+ }
166
+ },
167
+ "indexes": {
168
+ "users_email_unique": {
169
+ "name": "users_email_unique",
170
+ "columns": ["email"],
171
+ "isUnique": true
172
+ }
173
+ },
174
+ "foreignKeys": {},
175
+ "compositePrimaryKeys": {},
176
+ "uniqueConstraints": {}
177
+ },
178
+ "verifications": {
179
+ "name": "verifications",
180
+ "columns": {
181
+ "id": {
182
+ "name": "id",
183
+ "type": "text",
184
+ "primaryKey": true,
185
+ "isNotNull": true
186
+ },
187
+ "identifier": {
188
+ "name": "identifier",
189
+ "type": "text",
190
+ "primaryKey": false,
191
+ "isNotNull": true
192
+ },
193
+ "value": {
194
+ "name": "value",
195
+ "type": "text",
196
+ "primaryKey": false,
197
+ "isNotNull": true
198
+ },
199
+ "expires_at": {
200
+ "name": "expires_at",
201
+ "type": "integer",
202
+ "primaryKey": false,
203
+ "isNotNull": true
204
+ },
205
+ "created_at": {
206
+ "name": "created_at",
207
+ "type": "integer",
208
+ "primaryKey": false,
209
+ "isNotNull": true,
210
+ "default": "strftime('%s', 'now')"
211
+ }
212
+ },
213
+ "indexes": {},
214
+ "foreignKeys": {},
215
+ "compositePrimaryKeys": {},
216
+ "uniqueConstraints": {}
217
+ }
218
+ },
219
+ "enums": {},
220
+ "_meta": {
221
+ "columns": {},
222
+ "schemas": {},
223
+ "tables": {}
224
+ }
225
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "dialect": "sqlite",
3
+ "entries": [
4
+ {
5
+ "idx": 0,
6
+ "version": "7.28.0",
7
+ "when": 1737620842625,
8
+ "tag": "0001_initial_schema",
9
+ "breakpoints": true
10
+ }
11
+ ]
12
+ }
@@ -0,0 +1,10 @@
1
+ import type { Config } from 'drizzle-kit'
2
+
3
+ export default {
4
+ schema: './src/server/db/schema.ts',
5
+ out: './drizzle/migrations',
6
+ dialect: 'sqlite',
7
+ dbCredentials: {
8
+ url: process.env.DATABASE_URL || 'data/app.db',
9
+ },
10
+ } satisfies Config
@@ -0,0 +1,78 @@
1
+ {
2
+ "ci": {
3
+ "assert": {
4
+ "assertions": {
5
+ "categories:performance": ["error", { "minScore": 0.9 }],
6
+ "categories:accessibility": ["warn", { "minScore": 0.8 }],
7
+ "categories:best-practices": ["warn", { "minScore": 0.9 }],
8
+ "categories:seo": ["warn", { "minScore": 0.9 }]
9
+ }
10
+ },
11
+ "upload": {
12
+ "target": "temporary-public-storage"
13
+ }
14
+ },
15
+ "budgets": [
16
+ {
17
+ "path": "/*",
18
+ "timings": [
19
+ {
20
+ "metric": "first-contentful-paint",
21
+ "budget": 2000
22
+ },
23
+ {
24
+ "metric": "largest-contentful-paint",
25
+ "budget": 2500
26
+ },
27
+ {
28
+ "metric": "first-meaningful-paint",
29
+ "budget": 2000
30
+ },
31
+ {
32
+ "metric": "interactive",
33
+ "budget": 3500
34
+ },
35
+ {
36
+ "metric": "speed-index",
37
+ "budget": 3000
38
+ },
39
+ {
40
+ "metric": "total-blocking-time",
41
+ "budget": 600
42
+ }
43
+ ],
44
+ "resourceSizes": [
45
+ {
46
+ "resourceType": "script",
47
+ "budget": 150
48
+ },
49
+ {
50
+ "resourceType": "total",
51
+ "budget": 500
52
+ },
53
+ {
54
+ "resourceType": "stylesheet",
55
+ "budget": 50
56
+ },
57
+ {
58
+ "resourceType": "image",
59
+ "budget": 200
60
+ },
61
+ {
62
+ "resourceType": "font",
63
+ "budget": 100
64
+ }
65
+ ],
66
+ "resourceCounts": [
67
+ {
68
+ "resourceType": "script",
69
+ "budget": 10
70
+ },
71
+ {
72
+ "resourceType": "total",
73
+ "budget": 50
74
+ }
75
+ ]
76
+ }
77
+ ]
78
+ }
@@ -0,0 +1,32 @@
1
+ import { createRootRoute, Outlet } from '@tanstack/react-router'
2
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
3
+ import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
4
+ import { useState } from 'react'
5
+ import { TRPCProvider } from '@/lib/api'
6
+ import { createTRPCClient, httpBatchLink } from '@trpc/client'
7
+ import type { AppRouter } from '@/server/api/root'
8
+ import '@tanstack/start/router'
9
+
10
+ export const Route = createRootRoute({
11
+ component: RootComponent,
12
+ })
13
+
14
+ function RootComponent() {
15
+ const [queryClient] = useState(() => new QueryClient())
16
+ const trpc = createTRPCClient<AppRouter>({
17
+ links: [
18
+ httpBatchLink({
19
+ url: 'http://localhost:3000/api/trpc',
20
+ }),
21
+ ],
22
+ })
23
+
24
+ return (
25
+ <QueryClientProvider client={queryClient}>
26
+ <TRPCProvider client={queryClient} trpcClient={trpc}>
27
+ <Outlet />
28
+ </TRPCProvider>
29
+ <ReactQueryDevtools initialIsOpen={false} />
30
+ </QueryClientProvider>
31
+ )
32
+ }
@@ -0,0 +1,15 @@
1
+ import { createFileRoute } from '@tanstack/react-router'
2
+ import { auth } from '@/server/auth/config'
3
+
4
+ export const Route = createFileRoute('/api/auth/_')({
5
+ server: {
6
+ handlers: {
7
+ GET: async ({ request }: { request: Request }) => {
8
+ return await auth.handler(request)
9
+ },
10
+ POST: async ({ request }: { request: Request }) => {
11
+ return await auth.handler(request)
12
+ },
13
+ },
14
+ },
15
+ })
@@ -0,0 +1,12 @@
1
+ import { fetchRequestHandler } from '@trpc/server/adapters/fetch'
2
+ import { appRouter } from '@/server/api/root'
3
+ import { createTRPCContext } from '@/server/api/trpc'
4
+
5
+ export default async function handler(req: Request) {
6
+ return fetchRequestHandler({
7
+ endpoint: '/api/trpc',
8
+ req,
9
+ router: appRouter,
10
+ createContext: (opts) => createTRPCContext(opts),
11
+ })
12
+ }
@@ -0,0 +1,107 @@
1
+ import { createFileRoute, useRouter } from '@tanstack/react-router'
2
+ import { useState } from 'react'
3
+ import { authClient } from '@/lib/auth'
4
+ import { Button } from '@/components/ui/button'
5
+ import { Input } from '@/components/ui/input'
6
+ import {
7
+ Card,
8
+ CardContent,
9
+ CardDescription,
10
+ CardHeader,
11
+ CardTitle,
12
+ } from '@/components/ui/card'
13
+
14
+ export const Route = createFileRoute('/auth/forgot-password')({
15
+ component: ForgotPassword,
16
+ })
17
+
18
+ function ForgotPassword() {
19
+ const router = useRouter()
20
+ const [email, setEmail] = useState('')
21
+ const [loading, setLoading] = useState(false)
22
+ const [message, setMessage] = useState('')
23
+ const [error, setError] = useState('')
24
+
25
+ const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
26
+ e.preventDefault()
27
+ setLoading(true)
28
+ setMessage('')
29
+ setError('')
30
+
31
+ try {
32
+ await authClient.forgetPassword({
33
+ email,
34
+ })
35
+
36
+ setMessage(
37
+ 'If an account with this email exists, you will receive a password reset link.'
38
+ )
39
+ setEmail('')
40
+ } catch (err) {
41
+ setError('Failed to send password reset email. Please try again.')
42
+ console.error(err)
43
+ } finally {
44
+ setLoading(false)
45
+ }
46
+ }
47
+
48
+ return (
49
+ <div className="min-h-screen flex items-center justify-center bg-gray-50 p-4">
50
+ <Card className="w-full max-w-md">
51
+ <CardHeader>
52
+ <CardTitle>Forgot Password</CardTitle>
53
+ <CardDescription>
54
+ Enter your email to receive a password reset link
55
+ </CardDescription>
56
+ </CardHeader>
57
+ <CardContent>
58
+ <form onSubmit={handleSubmit} className="space-y-4">
59
+ <div className="space-y-2">
60
+ <label
61
+ htmlFor="email"
62
+ className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
63
+ >
64
+ Email
65
+ </label>
66
+ <Input
67
+ id="email"
68
+ type="email"
69
+ value={email}
70
+ onChange={(e) => setEmail(e.target.value)}
71
+ placeholder="your@email.com"
72
+ required
73
+ disabled={loading}
74
+ />
75
+ </div>
76
+
77
+ {message && (
78
+ <div className="p-3 text-sm text-green-700 bg-green-50 border border-green-200 rounded-md">
79
+ {message}
80
+ </div>
81
+ )}
82
+
83
+ {error && (
84
+ <div className="p-3 text-sm text-red-600 bg-red-50 border border-red-200 rounded-md">
85
+ {error}
86
+ </div>
87
+ )}
88
+
89
+ <Button type="submit" disabled={loading} className="w-full">
90
+ {loading ? 'Sending...' : 'Send Reset Link'}
91
+ </Button>
92
+
93
+ <div className="text-center space-y-2">
94
+ <button
95
+ type="button"
96
+ onClick={() => router.navigate({ to: '/auth/login' })}
97
+ className="text-sm text-gray-600 hover:text-gray-900"
98
+ >
99
+ Back to Sign In
100
+ </button>
101
+ </div>
102
+ </form>
103
+ </CardContent>
104
+ </Card>
105
+ </div>
106
+ )
107
+ }
@@ -0,0 +1,34 @@
1
+ import { createFileRoute, Link } from '@tanstack/react-router'
2
+ import { LoginForm } from '@/components/features/auth/login-form'
3
+
4
+ export const Route = createFileRoute()({
5
+ component: Login,
6
+ })
7
+
8
+ function Login() {
9
+ return (
10
+ <div className="min-h-screen flex items-center justify-center bg-gradient-to-b from-blue-50 to-white py-12 px-4">
11
+ <div className="w-full max-w-md">
12
+ <div className="text-center mb-8">
13
+ <Link
14
+ to="/"
15
+ className="text-2xl font-bold text-blue-600 hover:text-blue-700"
16
+ >
17
+ Recurring Rabbit
18
+ </Link>
19
+ <p className="mt-2 text-gray-600">Sign in to your account</p>
20
+ </div>
21
+ <LoginForm />
22
+ <p className="mt-6 text-center text-sm text-gray-600">
23
+ Don't have an account?{' '}
24
+ <Link
25
+ to="/auth/register"
26
+ className="text-blue-600 hover:text-blue-700 font-medium"
27
+ >
28
+ Sign up
29
+ </Link>
30
+ </p>
31
+ </div>
32
+ </div>
33
+ )
34
+ }
@@ -0,0 +1,34 @@
1
+ import { createFileRoute, Link } from '@tanstack/react-router'
2
+ import { RegisterForm } from '@/components/features/auth/register-form'
3
+
4
+ export const Route = createFileRoute()({
5
+ component: Register,
6
+ })
7
+
8
+ function Register() {
9
+ return (
10
+ <div className="min-h-screen flex items-center justify-center bg-gradient-to-b from-blue-50 to-white py-12 px-4">
11
+ <div className="w-full max-w-md">
12
+ <div className="text-center mb-8">
13
+ <Link
14
+ to="/"
15
+ className="text-2xl font-bold text-blue-600 hover:text-blue-700"
16
+ >
17
+ Recurring Rabbit
18
+ </Link>
19
+ <p className="mt-2 text-gray-600">Create your account</p>
20
+ </div>
21
+ <RegisterForm />
22
+ <p className="mt-6 text-center text-sm text-gray-600">
23
+ Already have an account?{' '}
24
+ <Link
25
+ to="/auth/login"
26
+ className="text-blue-600 hover:text-blue-700 font-medium"
27
+ >
28
+ Sign in
29
+ </Link>
30
+ </p>
31
+ </div>
32
+ </div>
33
+ )
34
+ }