@helloleo/plugins 0.2.0-beta.5 → 0.2.0-beta.7

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.
@@ -0,0 +1,241 @@
1
+ import type { OAuthProvider } from "./constants.ts";
2
+ import { PROVIDER_LABELS } from "./constants.ts";
3
+
4
+ function socialButtonsJsx(oauth: OAuthProvider[]): string {
5
+ return oauth
6
+ .map(
7
+ (p) => ` <Button
8
+ type="button"
9
+ variant="outline"
10
+ className="w-full"
11
+ onClick={() => authClient.signIn.social({ provider: '${p}', callbackURL: '/' })}
12
+ >
13
+ Continue with ${PROVIDER_LABELS[p]}
14
+ </Button>`,
15
+ )
16
+ .join("\n");
17
+ }
18
+
19
+ export function authedLayoutTsx(): string {
20
+ return `import { createFileRoute, redirect } from '@tanstack/react-router'
21
+ import { getSession } from '#/lib/auth-server'
22
+
23
+ // Pathless guard -- no URL segment of its own. Put protected pages under
24
+ // src/routes/_authed/ (e.g. _authed/dashboard.tsx -> /dashboard) and they
25
+ // inherit this beforeLoad. See src/routes/README.md, "Authenticated routes".
26
+ export const Route = createFileRoute('/_authed')({
27
+ beforeLoad: async () => {
28
+ const session = await getSession()
29
+ if (!session) throw redirect({ to: '/login' })
30
+ return { user: session.user }
31
+ },
32
+ })
33
+ `;
34
+ }
35
+
36
+ export function loginPageTsx(
37
+ hasPassword: boolean,
38
+ oauth: OAuthProvider[],
39
+ ): string {
40
+ const social = oauth.length ? `\n${socialButtonsJsx(oauth)}\n` : "";
41
+
42
+ const divider =
43
+ oauth.length && hasPassword
44
+ ? `
45
+ <div className="relative">
46
+ <div className="absolute inset-0 flex items-center">
47
+ <span className="w-full border-t" />
48
+ </div>
49
+ <div className="relative flex justify-center text-xs uppercase">
50
+ <span className="bg-card px-2 text-muted-foreground">Or continue with email</span>
51
+ </div>
52
+ </div>
53
+ `
54
+ : "";
55
+
56
+ const passwordForm = hasPassword
57
+ ? `
58
+ <form onSubmit={handleSubmit} className="space-y-4">
59
+ <div className="space-y-2">
60
+ <Label htmlFor="email">Email</Label>
61
+ <Input id="email" type="email" value={email} onChange={e => setEmail(e.target.value)} required autoComplete="email" />
62
+ </div>
63
+ <div className="space-y-2">
64
+ <Label htmlFor="password">Password</Label>
65
+ <Input id="password" type="password" value={password} onChange={e => setPassword(e.target.value)} required autoComplete="current-password" />
66
+ </div>
67
+ {error && <p className="text-sm text-destructive">{error}</p>}
68
+ <Button type="submit" className="w-full" disabled={loading}>
69
+ {loading ? 'Logging in…' : 'Log in'}
70
+ </Button>
71
+ <p className="text-sm text-center text-muted-foreground">
72
+ No account? <Link to="/signup" className="underline">Sign up</Link>
73
+ </p>
74
+ </form>
75
+ `
76
+ : "";
77
+
78
+ const stateAndHandler = hasPassword
79
+ ? `
80
+ const router = useRouter()
81
+ const [email, setEmail] = useState('')
82
+ const [password, setPassword] = useState('')
83
+ const [error, setError] = useState('')
84
+ const [loading, setLoading] = useState(false)
85
+
86
+ async function handleSubmit(e: React.FormEvent) {
87
+ e.preventDefault()
88
+ setError('')
89
+ setLoading(true)
90
+ const { error } = await authClient.signIn.email({ email, password })
91
+ setLoading(false)
92
+ if (error) {
93
+ setError(error.message ?? 'Something went wrong.')
94
+ return
95
+ }
96
+ router.navigate({ to: '/' })
97
+ }
98
+ `
99
+ : "";
100
+
101
+ const imports = [
102
+ hasPassword
103
+ ? "import { createFileRoute, Link, useRouter } from '@tanstack/react-router'"
104
+ : "import { createFileRoute } from '@tanstack/react-router'",
105
+ hasPassword ? "import { useState } from 'react'" : "",
106
+ "import { authClient } from '#/lib/auth-client'",
107
+ "import { Button } from '#/components/ui/button'",
108
+ hasPassword ? "import { Input } from '#/components/ui/input'" : "",
109
+ hasPassword ? "import { Label } from '#/components/ui/label'" : "",
110
+ "import { Card, CardContent, CardHeader, CardTitle } from '#/components/ui/card'",
111
+ ]
112
+ .filter(Boolean)
113
+ .join("\n");
114
+
115
+ return `${imports}
116
+
117
+ export const Route = createFileRoute('/login')({
118
+ component: LoginPage,
119
+ })
120
+
121
+ function LoginPage() {${stateAndHandler}
122
+ return (
123
+ <div className="min-h-screen flex items-center justify-center bg-background p-4">
124
+ <Card className="w-full max-w-sm">
125
+ <CardHeader>
126
+ <CardTitle>Log in</CardTitle>
127
+ </CardHeader>
128
+ <CardContent className="space-y-4">${social}${divider}${passwordForm}
129
+ </CardContent>
130
+ </Card>
131
+ </div>
132
+ )
133
+ }
134
+ `;
135
+ }
136
+
137
+ export function signupPageTsx(): string {
138
+ return `import { createFileRoute, Link, useRouter } from '@tanstack/react-router'
139
+ import { useState } from 'react'
140
+ import { authClient } from '#/lib/auth-client'
141
+ import { Button } from '#/components/ui/button'
142
+ import { Input } from '#/components/ui/input'
143
+ import { Label } from '#/components/ui/label'
144
+ import { Card, CardContent, CardHeader, CardTitle } from '#/components/ui/card'
145
+
146
+ export const Route = createFileRoute('/signup')({
147
+ component: SignupPage,
148
+ })
149
+
150
+ function SignupPage() {
151
+ const router = useRouter()
152
+ const [name, setName] = useState('')
153
+ const [email, setEmail] = useState('')
154
+ const [password, setPassword] = useState('')
155
+ const [error, setError] = useState('')
156
+ const [loading, setLoading] = useState(false)
157
+
158
+ async function handleSubmit(e: React.FormEvent) {
159
+ e.preventDefault()
160
+ setError('')
161
+ setLoading(true)
162
+ const { error } = await authClient.signUp.email({ name, email, password })
163
+ setLoading(false)
164
+ if (error) {
165
+ setError(error.message ?? 'Something went wrong.')
166
+ return
167
+ }
168
+ router.navigate({ to: '/' })
169
+ }
170
+
171
+ return (
172
+ <div className="min-h-screen flex items-center justify-center bg-background p-4">
173
+ <Card className="w-full max-w-sm">
174
+ <CardHeader>
175
+ <CardTitle>Sign up</CardTitle>
176
+ </CardHeader>
177
+ <CardContent>
178
+ <form onSubmit={handleSubmit} className="space-y-4">
179
+ <div className="space-y-2">
180
+ <Label htmlFor="name">Name</Label>
181
+ <Input id="name" type="text" value={name} onChange={e => setName(e.target.value)} required autoComplete="name" />
182
+ </div>
183
+ <div className="space-y-2">
184
+ <Label htmlFor="email">Email</Label>
185
+ <Input id="email" type="email" value={email} onChange={e => setEmail(e.target.value)} required autoComplete="email" />
186
+ </div>
187
+ <div className="space-y-2">
188
+ <Label htmlFor="password">Password</Label>
189
+ <Input id="password" type="password" value={password} onChange={e => setPassword(e.target.value)} required autoComplete="new-password" minLength={8} />
190
+ </div>
191
+ {error && <p className="text-sm text-destructive">{error}</p>}
192
+ <Button type="submit" className="w-full" disabled={loading}>
193
+ {loading ? 'Signing up…' : 'Sign up'}
194
+ </Button>
195
+ <p className="text-sm text-center text-muted-foreground">
196
+ Already have an account? <Link to="/login" className="underline">Log in</Link>
197
+ </p>
198
+ </form>
199
+ </CardContent>
200
+ </Card>
201
+ </div>
202
+ )
203
+ }
204
+ `;
205
+ }
206
+
207
+ export function dashboardPageTsx(): string {
208
+ return `import { createFileRoute, useRouter } from '@tanstack/react-router'
209
+ import { authClient } from '#/lib/auth-client'
210
+ import { Button } from '#/components/ui/button'
211
+
212
+ // Example protected page -- _authed.tsx needs at least one child route or
213
+ // it collides with index.tsx on path "/" and \`tsr generate\` fails. Replace
214
+ // this, or add more pages alongside it under src/routes/_authed/.
215
+ export const Route = createFileRoute('/_authed/dashboard')({
216
+ component: DashboardPage,
217
+ })
218
+
219
+ function DashboardPage() {
220
+ const router = useRouter()
221
+ const { data: session } = authClient.useSession()
222
+
223
+ return (
224
+ <div className="min-h-screen flex items-center justify-center bg-background p-4">
225
+ <div className="text-center space-y-4">
226
+ <p className="text-lg">Signed in as {session?.user.email}</p>
227
+ <Button
228
+ onClick={() =>
229
+ authClient.signOut({
230
+ fetchOptions: { onSuccess: () => router.navigate({ to: '/login' }) },
231
+ })
232
+ }
233
+ >
234
+ Sign out
235
+ </Button>
236
+ </div>
237
+ </div>
238
+ )
239
+ }
240
+ `;
241
+ }