@betterstart/cli 0.1.80 → 0.1.82
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/dist/cli.js +15 -2
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/templates/init/components/layout/cms-search.tsx +6 -5
- package/templates/init/components/layout/cms-sidebar.tsx +3 -7
- package/templates/init/data/navigation.ts +6 -6
- package/templates/init/lib/actions/profile.ts +4 -0
- package/templates/init/lib/auth/auth.ts +17 -0
- package/templates/init/pages/forgot-password-form.tsx +130 -0
- package/templates/init/pages/forgot-password-page.tsx +20 -0
- package/templates/init/pages/login-form.tsx +17 -1
- package/templates/init/pages/login-page.tsx +12 -2
- package/templates/init/pages/profile/profile-form.tsx +67 -137
- package/templates/init/pages/profile/profile-page.tsx +1 -1
- package/templates/init/pages/reset-password-form.tsx +159 -0
- package/templates/init/pages/reset-password-page.tsx +20 -0
- package/templates/ui/button.tsx +1 -1
- package/templates/ui/card.tsx +2 -2
- package/templates/ui/curriculum-editor.tsx +2 -2
- package/templates/ui/image-upload-field.tsx +2 -2
- package/templates/ui/input.tsx +5 -4
- package/templates/ui/media-upload-field.tsx +2 -2
- package/templates/ui/placeholder.tsx +1 -1
- package/templates/ui/sidebar.tsx +1 -1
- package/templates/ui/video-upload-field.tsx +2 -2
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { authClient } from '@cms/auth/client'
|
|
4
|
+
import { Button } from '@cms/components/ui/button'
|
|
5
|
+
import { Card, CardContent } from '@cms/components/ui/card'
|
|
6
|
+
import { Field, FieldGroup, FieldLabel } from '@cms/components/ui/field'
|
|
7
|
+
import { Input } from '@cms/components/ui/input'
|
|
8
|
+
import { cn } from '@cms/utils/cn'
|
|
9
|
+
import { LoaderCircle } from 'lucide-react'
|
|
10
|
+
import Link from 'next/link'
|
|
11
|
+
import { useRouter, useSearchParams } from 'next/navigation'
|
|
12
|
+
import * as React from 'react'
|
|
13
|
+
|
|
14
|
+
export function ResetPasswordForm({
|
|
15
|
+
className,
|
|
16
|
+
...props
|
|
17
|
+
}: React.ComponentProps<'div'>) {
|
|
18
|
+
const router = useRouter()
|
|
19
|
+
const searchParams = useSearchParams()
|
|
20
|
+
const token = searchParams.get('token')
|
|
21
|
+
|
|
22
|
+
const [newPassword, setNewPassword] = React.useState('')
|
|
23
|
+
const [confirmPassword, setConfirmPassword] = React.useState('')
|
|
24
|
+
const [error, setError] = React.useState<string | null>(null)
|
|
25
|
+
const [isPending, startTransition] = React.useTransition()
|
|
26
|
+
|
|
27
|
+
if (!token) {
|
|
28
|
+
return (
|
|
29
|
+
<div className={cn('flex flex-col gap-6', className)} {...props}>
|
|
30
|
+
<Card className="overflow-hidden p-0">
|
|
31
|
+
<CardContent className="grid p-0 md:grid-cols-[5fr_7fr]">
|
|
32
|
+
<div className="relative hidden bg-muted md:block">
|
|
33
|
+
<img
|
|
34
|
+
src="https://assets.betterstart.dev/assets/placeholder.png"
|
|
35
|
+
alt="Image"
|
|
36
|
+
className="absolute inset-0 h-full w-full object-cover dark:brightness-[0.2] dark:grayscale"
|
|
37
|
+
/>
|
|
38
|
+
</div>
|
|
39
|
+
<div className="p-6 md:p-20">
|
|
40
|
+
<FieldGroup className="pb-12 gap-10">
|
|
41
|
+
<div className="flex flex-col items-start">
|
|
42
|
+
<h1 className="text-xl font-medium">Invalid reset link</h1>
|
|
43
|
+
<p className="text-balance text-muted-foreground">
|
|
44
|
+
This password reset link is invalid or has expired. Please
|
|
45
|
+
request a new one.
|
|
46
|
+
</p>
|
|
47
|
+
</div>
|
|
48
|
+
<Link
|
|
49
|
+
href="/cms/forgot-password"
|
|
50
|
+
className="text-sm underline-offset-4 hover:underline"
|
|
51
|
+
>
|
|
52
|
+
Request a new reset link
|
|
53
|
+
</Link>
|
|
54
|
+
</FieldGroup>
|
|
55
|
+
</div>
|
|
56
|
+
</CardContent>
|
|
57
|
+
</Card>
|
|
58
|
+
</div>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
63
|
+
e.preventDefault()
|
|
64
|
+
setError(null)
|
|
65
|
+
|
|
66
|
+
if (newPassword !== confirmPassword) {
|
|
67
|
+
setError('Passwords do not match')
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (newPassword.length < 8) {
|
|
72
|
+
setError('Password must be at least 8 characters')
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
startTransition(async () => {
|
|
77
|
+
try {
|
|
78
|
+
const { error } = await authClient.resetPassword({
|
|
79
|
+
newPassword,
|
|
80
|
+
token,
|
|
81
|
+
})
|
|
82
|
+
if (error) {
|
|
83
|
+
setError(error.message || 'Failed to reset password')
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
router.push('/cms/login?reset=success')
|
|
87
|
+
} catch {
|
|
88
|
+
setError('An error occurred. Please try again.')
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<div className={cn('flex flex-col gap-6', className)} {...props}>
|
|
95
|
+
<Card className="overflow-hidden p-0">
|
|
96
|
+
<CardContent className="grid p-0 md:grid-cols-[5fr_7fr]">
|
|
97
|
+
<div className="relative hidden bg-muted md:block">
|
|
98
|
+
<img
|
|
99
|
+
src="https://assets.betterstart.dev/assets/placeholder.png"
|
|
100
|
+
alt="Image"
|
|
101
|
+
className="absolute inset-0 h-full w-full object-cover dark:brightness-[0.2] dark:grayscale"
|
|
102
|
+
/>
|
|
103
|
+
</div>
|
|
104
|
+
<form onSubmit={handleSubmit} className="p-6 md:p-20">
|
|
105
|
+
<FieldGroup className="pb-12 gap-10">
|
|
106
|
+
<div className="flex flex-col items-start">
|
|
107
|
+
<h1 className="text-xl font-medium">Reset password</h1>
|
|
108
|
+
<p className="text-balance text-muted-foreground">
|
|
109
|
+
Enter your new password below
|
|
110
|
+
</p>
|
|
111
|
+
</div>
|
|
112
|
+
{error ? (
|
|
113
|
+
<div className="bg-destructive/10 text-destructive text-sm p-3 rounded-md">
|
|
114
|
+
{error}
|
|
115
|
+
</div>
|
|
116
|
+
) : null}
|
|
117
|
+
<div className="flex flex-col gap-4">
|
|
118
|
+
<Field>
|
|
119
|
+
<FieldLabel htmlFor="newPassword">New Password</FieldLabel>
|
|
120
|
+
<Input
|
|
121
|
+
id="newPassword"
|
|
122
|
+
type="password"
|
|
123
|
+
autoComplete="new-password"
|
|
124
|
+
placeholder="Enter new password"
|
|
125
|
+
value={newPassword}
|
|
126
|
+
onChange={(e) => setNewPassword(e.target.value)}
|
|
127
|
+
required
|
|
128
|
+
disabled={isPending}
|
|
129
|
+
/>
|
|
130
|
+
</Field>
|
|
131
|
+
<Field>
|
|
132
|
+
<FieldLabel htmlFor="confirmPassword">
|
|
133
|
+
Confirm Password
|
|
134
|
+
</FieldLabel>
|
|
135
|
+
<Input
|
|
136
|
+
id="confirmPassword"
|
|
137
|
+
type="password"
|
|
138
|
+
autoComplete="new-password"
|
|
139
|
+
placeholder="Confirm new password"
|
|
140
|
+
value={confirmPassword}
|
|
141
|
+
onChange={(e) => setConfirmPassword(e.target.value)}
|
|
142
|
+
required
|
|
143
|
+
disabled={isPending}
|
|
144
|
+
/>
|
|
145
|
+
</Field>
|
|
146
|
+
<Field className="pt-4">
|
|
147
|
+
<Button type="submit" disabled={isPending}>
|
|
148
|
+
{isPending && <LoaderCircle className="animate-spin" />}
|
|
149
|
+
{isPending ? 'Resetting...' : 'Reset Password'}
|
|
150
|
+
</Button>
|
|
151
|
+
</Field>
|
|
152
|
+
</div>
|
|
153
|
+
</FieldGroup>
|
|
154
|
+
</form>
|
|
155
|
+
</CardContent>
|
|
156
|
+
</Card>
|
|
157
|
+
</div>
|
|
158
|
+
)
|
|
159
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Metadata } from 'next'
|
|
2
|
+
import { Suspense } from 'react'
|
|
3
|
+
import { ResetPasswordForm } from './reset-password-form'
|
|
4
|
+
|
|
5
|
+
export const metadata: Metadata = {
|
|
6
|
+
title: 'Reset Password',
|
|
7
|
+
robots: { index: false, follow: false },
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default function ResetPasswordPage() {
|
|
11
|
+
return (
|
|
12
|
+
<div className="flex min-h-svh flex-col items-center justify-center bg-background p-6 md:p-10">
|
|
13
|
+
<div className="w-full max-w-sm md:max-w-4xl">
|
|
14
|
+
<Suspense>
|
|
15
|
+
<ResetPasswordForm />
|
|
16
|
+
</Suspense>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
)
|
|
20
|
+
}
|
package/templates/ui/button.tsx
CHANGED
|
@@ -22,7 +22,7 @@ const buttonVariants = cva(
|
|
|
22
22
|
},
|
|
23
23
|
size: {
|
|
24
24
|
default:
|
|
25
|
-
"h-
|
|
25
|
+
"h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
|
26
26
|
xs: "h-7 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
|
27
27
|
sm: "h-9 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
|
|
28
28
|
lg: "h-12 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3",
|
package/templates/ui/card.tsx
CHANGED
|
@@ -12,7 +12,7 @@ function Card({
|
|
|
12
12
|
data-slot="card"
|
|
13
13
|
data-size={size}
|
|
14
14
|
className={cn(
|
|
15
|
-
"group/card flex flex-col gap-6 overflow-hidden rounded
|
|
15
|
+
"group/card flex flex-col gap-6 overflow-hidden rounded bg-card py-6 text-sm text-card-foreground has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t *:[img:last-child]:rounded-b material-sm",
|
|
16
16
|
className
|
|
17
17
|
)}
|
|
18
18
|
{...props}
|
|
@@ -84,7 +84,7 @@ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
84
84
|
<div
|
|
85
85
|
data-slot="card-footer"
|
|
86
86
|
className={cn(
|
|
87
|
-
"flex items-center border-t bg-
|
|
87
|
+
"flex items-center border-t bg-background px-6 pt-4 pb-4.5 group-data-[size=sm]/card:p-3",
|
|
88
88
|
className
|
|
89
89
|
)}
|
|
90
90
|
{...props}
|
|
@@ -296,7 +296,7 @@ const SequentialEditor = ({
|
|
|
296
296
|
>
|
|
297
297
|
{items.map((item, index) => (
|
|
298
298
|
<AccordionItem key={index} value={`item-${index + 1}`} className="p-0 border-none">
|
|
299
|
-
<div className="space-y-5 rounded-lg border p-4 bg-
|
|
299
|
+
<div className="space-y-5 rounded-lg border p-4 bg-background corner-squircle [&_h3]:m-0 w-full">
|
|
300
300
|
<AccordionTrigger className="flex items-center p-0 justify-between w-full">
|
|
301
301
|
<div className="flex w-full items-center justify-between">
|
|
302
302
|
<h4 className="text-sm font-medium w-full">
|
|
@@ -438,7 +438,7 @@ const WeeklyEditor = ({
|
|
|
438
438
|
value={`week-${weekIndex + 1}`}
|
|
439
439
|
className="p-0 border-none"
|
|
440
440
|
>
|
|
441
|
-
<div className="space-y-5 rounded-lg border p-4 bg-
|
|
441
|
+
<div className="space-y-5 rounded-lg border p-4 bg-background corner-squircle [&_h3]:m-0 w-full">
|
|
442
442
|
<AccordionTrigger className="flex items-center p-0 justify-between w-full">
|
|
443
443
|
<div className="flex w-full items-center justify-between">
|
|
444
444
|
<div className="flex flex-col items-start">
|
|
@@ -277,7 +277,7 @@ export function ImageUploadField({
|
|
|
277
277
|
|
|
278
278
|
{/* Preview or Upload Area */}
|
|
279
279
|
{displayPreview ? (
|
|
280
|
-
<div className="relative w-full rounded-lg corner-squircle border border-dashed border-border bg-
|
|
280
|
+
<div className="relative w-full rounded-lg corner-squircle border border-dashed border-border bg-background h-50 flex items-center justify-center p-10 group">
|
|
281
281
|
<img
|
|
282
282
|
src={displayPreview}
|
|
283
283
|
alt="Preview"
|
|
@@ -320,7 +320,7 @@ export function ImageUploadField({
|
|
|
320
320
|
onClick={handleClick}
|
|
321
321
|
disabled={disabled || isUploading}
|
|
322
322
|
className={cn(
|
|
323
|
-
'w-full rounded-lg corner-squircle border border-dashed border-border bg-
|
|
323
|
+
'w-full rounded-lg corner-squircle border border-dashed border-border bg-background h-50 flex items-center justify-center p-10 group',
|
|
324
324
|
'hover:border-primary/50 transition-colors',
|
|
325
325
|
'flex flex-col items-center justify-center gap-2 p-8',
|
|
326
326
|
'disabled:opacity-50 disabled:cursor-not-allowed'
|
package/templates/ui/input.tsx
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type * as React from 'react'
|
|
1
|
+
import * as React from "react"
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
import { cn } from "@cms/utils/cn"
|
|
4
|
+
|
|
5
|
+
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
|
5
6
|
return (
|
|
6
7
|
<input
|
|
7
8
|
type={type}
|
|
8
9
|
data-slot="input"
|
|
9
10
|
className={cn(
|
|
10
|
-
|
|
11
|
+
"h-9 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
|
|
11
12
|
className
|
|
12
13
|
)}
|
|
13
14
|
{...props}
|
|
@@ -209,7 +209,7 @@ export function MediaUploadField({
|
|
|
209
209
|
|
|
210
210
|
{/* Preview or Upload Area */}
|
|
211
211
|
{previewUrl ? (
|
|
212
|
-
<div className="relative w-full rounded-lg corner-squircle border border-dashed border-border bg-
|
|
212
|
+
<div className="relative w-full rounded-lg corner-squircle border border-dashed border-border bg-background group">
|
|
213
213
|
{mediaType === 'video' ? (
|
|
214
214
|
<video
|
|
215
215
|
src={previewUrl}
|
|
@@ -265,7 +265,7 @@ export function MediaUploadField({
|
|
|
265
265
|
onClick={handleClick}
|
|
266
266
|
disabled={disabled || isUploading}
|
|
267
267
|
className={cn(
|
|
268
|
-
'w-full rounded-lg corner-squircle border border-dashed border-border bg-
|
|
268
|
+
'w-full rounded-lg corner-squircle border border-dashed border-border bg-background',
|
|
269
269
|
'hover:border-primary/50 transition-colors',
|
|
270
270
|
'flex flex-col items-center justify-center gap-2 p-8',
|
|
271
271
|
'disabled:opacity-50 disabled:cursor-not-allowed'
|
|
@@ -12,7 +12,7 @@ export function Placeholder({ label, description, action, className, ...props }:
|
|
|
12
12
|
return (
|
|
13
13
|
<div
|
|
14
14
|
className={cn(
|
|
15
|
-
'border flex items-center flex-col gap-2 border-dashed border-border rounded-lg corner-squircle p-8 bg-
|
|
15
|
+
'border flex items-center flex-col gap-2 border-dashed border-border rounded-lg corner-squircle p-8 bg-background',
|
|
16
16
|
className
|
|
17
17
|
)}
|
|
18
18
|
{...props}
|
package/templates/ui/sidebar.tsx
CHANGED
|
@@ -454,7 +454,7 @@ const sidebarMenuButtonVariants = cva(
|
|
|
454
454
|
'bg-background hover:bg-sidebar-accent hover:text-sidebar-primary shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]'
|
|
455
455
|
},
|
|
456
456
|
size: {
|
|
457
|
-
default: 'h-
|
|
457
|
+
default: 'h-8 text-sm',
|
|
458
458
|
sm: 'h-7 text-xs',
|
|
459
459
|
lg: 'h-12 text-sm group-data-[collapsible=icon]:p-0!'
|
|
460
460
|
}
|
|
@@ -283,7 +283,7 @@ export function VideoUploadField({
|
|
|
283
283
|
|
|
284
284
|
{/* Preview or Upload Area */}
|
|
285
285
|
{displayPreview ? (
|
|
286
|
-
<div className="relative w-full rounded-lg corner-squircle border border-dashed border-border bg-
|
|
286
|
+
<div className="relative w-full rounded-lg corner-squircle border border-dashed border-border bg-background h-50 flex items-center justify-center p-10 group overflow-hidden">
|
|
287
287
|
<video
|
|
288
288
|
src={displayPreview}
|
|
289
289
|
controls
|
|
@@ -328,7 +328,7 @@ export function VideoUploadField({
|
|
|
328
328
|
onClick={handleClick}
|
|
329
329
|
disabled={disabled || isUploading}
|
|
330
330
|
className={cn(
|
|
331
|
-
'w-full rounded-lg corner-squircle border border-dashed border-border bg-
|
|
331
|
+
'w-full rounded-lg corner-squircle border border-dashed border-border bg-background',
|
|
332
332
|
'hover:border-primary/50 transition-colors',
|
|
333
333
|
'flex flex-col items-center justify-center gap-2 p-8',
|
|
334
334
|
'disabled:opacity-50 disabled:cursor-not-allowed'
|