@marvs13/marvinel-nextjs-supabase-starting-kit 1.0.2
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/cli.js +95 -0
- package/package.json +27 -0
- package/template/.env.local_example +5 -0
- package/template/src/app/(auth)/change-password/page.tsx +11 -0
- package/template/src/app/(auth)/login/page.tsx +11 -0
- package/template/src/app/(auth)/request-reset-password/page.tsx +11 -0
- package/template/src/app/(auth)/reset-password/page.tsx +11 -0
- package/template/src/app/(auth)/signup/page.tsx +11 -0
- package/template/src/app/(auth)/signup-success/page.tsx +34 -0
- package/template/src/app/favicon.ico +0 -0
- package/template/src/app/globals.css +124 -0
- package/template/src/app/home/page.tsx +35 -0
- package/template/src/app/layout.tsx +35 -0
- package/template/src/app/not-found.tsx +9 -0
- package/template/src/app/page.tsx +11 -0
- package/template/src/components/forms/change-password.tsx +197 -0
- package/template/src/components/forms/login-form.tsx +188 -0
- package/template/src/components/forms/request-reset-password-form.tsx +137 -0
- package/template/src/components/forms/reset-password-form.tsx +199 -0
- package/template/src/components/forms/signup-form.tsx +231 -0
- package/template/src/components/hero.tsx +38 -0
- package/template/src/components/logout-button.tsx +52 -0
- package/template/src/components/page-not-found.tsx +32 -0
- package/template/src/components/ui/button.tsx +65 -0
- package/template/src/components/ui/card.tsx +92 -0
- package/template/src/components/ui/field.tsx +249 -0
- package/template/src/components/ui/input-group.tsx +171 -0
- package/template/src/components/ui/input.tsx +21 -0
- package/template/src/components/ui/label.tsx +25 -0
- package/template/src/components/ui/separator.tsx +29 -0
- package/template/src/components/ui/spinner.tsx +16 -0
- package/template/src/components/ui/textarea.tsx +18 -0
- package/template/src/hooks/use-auth-form.ts +47 -0
- package/template/src/lib/supabase/client.ts +8 -0
- package/template/src/lib/supabase/proxy.ts +55 -0
- package/template/src/lib/supabase/server.ts +34 -0
- package/template/src/lib/utils.ts +6 -0
- package/template/src/proxy.ts +21 -0
- package/template/src/schema/form-schema.ts +55 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import Link from "next/link";
|
|
4
|
+
import { useRouter } from "next/navigation";
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
|
|
7
|
+
import { EyeIcon, EyeOffIcon } from "lucide-react";
|
|
8
|
+
import { Controller } from "react-hook-form";
|
|
9
|
+
import z from "zod";
|
|
10
|
+
|
|
11
|
+
import { useAuthForm } from "@/hooks/use-auth-form";
|
|
12
|
+
import { createClient } from "@/lib/supabase/client";
|
|
13
|
+
import { LogInFormSchema } from "@/schema/form-schema";
|
|
14
|
+
|
|
15
|
+
import { Button } from "../ui/button";
|
|
16
|
+
import {
|
|
17
|
+
Card,
|
|
18
|
+
CardContent,
|
|
19
|
+
CardDescription,
|
|
20
|
+
CardHeader,
|
|
21
|
+
CardTitle,
|
|
22
|
+
} from "../ui/card";
|
|
23
|
+
import {
|
|
24
|
+
Field,
|
|
25
|
+
FieldDescription,
|
|
26
|
+
FieldError,
|
|
27
|
+
FieldGroup,
|
|
28
|
+
FieldLabel,
|
|
29
|
+
} from "../ui/field";
|
|
30
|
+
import { Input } from "../ui/input";
|
|
31
|
+
import {
|
|
32
|
+
InputGroup,
|
|
33
|
+
InputGroupAddon,
|
|
34
|
+
InputGroupInput,
|
|
35
|
+
} from "../ui/input-group";
|
|
36
|
+
import { Spinner } from "../ui/spinner";
|
|
37
|
+
|
|
38
|
+
export const LogInForm = () => {
|
|
39
|
+
const { loginform: form } = useAuthForm();
|
|
40
|
+
|
|
41
|
+
const router = useRouter();
|
|
42
|
+
|
|
43
|
+
const [loading, setLoading] = useState<boolean>(false);
|
|
44
|
+
const [open, setOpen] = useState<boolean>(false);
|
|
45
|
+
const [error, setError] = useState<string | null>(null);
|
|
46
|
+
|
|
47
|
+
// Show password toggle function
|
|
48
|
+
const isOpen = () => {
|
|
49
|
+
setOpen((prev) => !prev);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
async function onSubmit(values: z.infer<typeof LogInFormSchema>) {
|
|
53
|
+
const supabase = createClient();
|
|
54
|
+
setLoading(true);
|
|
55
|
+
setError(null);
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const { error } = await supabase.auth.signInWithPassword({
|
|
59
|
+
email: values.email,
|
|
60
|
+
password: values.password,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (error) throw error;
|
|
64
|
+
|
|
65
|
+
router.push("/home"); // TODO : customize this url depends on where do you want to redirect after a successful sign up
|
|
66
|
+
} catch (error) {
|
|
67
|
+
setError(error instanceof Error ? error.message : "An error occurred!");
|
|
68
|
+
} finally {
|
|
69
|
+
setLoading(false);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
// customize this depends of your needs (eg. background color, size, etc.)
|
|
75
|
+
// you can add props if you want too
|
|
76
|
+
<Card className="w-full max-w-sm">
|
|
77
|
+
<CardHeader>
|
|
78
|
+
<CardTitle className="text-xl font-semibold">
|
|
79
|
+
Login to your account
|
|
80
|
+
</CardTitle>
|
|
81
|
+
<CardDescription>
|
|
82
|
+
Enter your credentials below to login to your account
|
|
83
|
+
</CardDescription>
|
|
84
|
+
</CardHeader>
|
|
85
|
+
<CardContent>
|
|
86
|
+
<form id="login-form" onSubmit={form.handleSubmit(onSubmit)}>
|
|
87
|
+
<FieldGroup>
|
|
88
|
+
{/* Email input */}
|
|
89
|
+
<Controller
|
|
90
|
+
control={form.control}
|
|
91
|
+
name="email"
|
|
92
|
+
render={({ field, fieldState }) => (
|
|
93
|
+
<Field data-invalid={fieldState.invalid}>
|
|
94
|
+
<FieldLabel htmlFor="email-input">Email</FieldLabel>
|
|
95
|
+
<Input
|
|
96
|
+
{...field}
|
|
97
|
+
id="email-input"
|
|
98
|
+
aria-invalid={fieldState.invalid}
|
|
99
|
+
placeholder="example@mail.com"
|
|
100
|
+
autoComplete="off"
|
|
101
|
+
/>
|
|
102
|
+
|
|
103
|
+
{fieldState.invalid && (
|
|
104
|
+
<FieldError errors={[fieldState.error]} />
|
|
105
|
+
)}
|
|
106
|
+
</Field>
|
|
107
|
+
)}
|
|
108
|
+
/>
|
|
109
|
+
|
|
110
|
+
{/* Password input */}
|
|
111
|
+
<Controller
|
|
112
|
+
control={form.control}
|
|
113
|
+
name="password"
|
|
114
|
+
render={({ field, fieldState }) => (
|
|
115
|
+
<Field data-invalid={fieldState.invalid}>
|
|
116
|
+
<FieldLabel htmlFor="password-input">Password</FieldLabel>
|
|
117
|
+
<InputGroup>
|
|
118
|
+
<InputGroupInput
|
|
119
|
+
{...field}
|
|
120
|
+
id="password-input"
|
|
121
|
+
aria-invalid={fieldState.invalid}
|
|
122
|
+
type={open ? "text" : "password"}
|
|
123
|
+
autoComplete="off"
|
|
124
|
+
/>
|
|
125
|
+
|
|
126
|
+
<InputGroupAddon align="inline-end">
|
|
127
|
+
<Button
|
|
128
|
+
variant="ghost"
|
|
129
|
+
size="icon"
|
|
130
|
+
className="hover:text-muted-foreground hover:bg-transparent"
|
|
131
|
+
type="button"
|
|
132
|
+
onClick={isOpen}
|
|
133
|
+
>
|
|
134
|
+
{open ? <EyeIcon /> : <EyeOffIcon />}
|
|
135
|
+
</Button>
|
|
136
|
+
</InputGroupAddon>
|
|
137
|
+
</InputGroup>
|
|
138
|
+
<FieldLabel className="flex justify-end">
|
|
139
|
+
<Link
|
|
140
|
+
href="request-reset-password"
|
|
141
|
+
className="font-normal underline-offset-4 hover:underline"
|
|
142
|
+
>
|
|
143
|
+
Forgot your password?
|
|
144
|
+
</Link>
|
|
145
|
+
</FieldLabel>
|
|
146
|
+
|
|
147
|
+
{fieldState.invalid && (
|
|
148
|
+
<FieldError errors={[fieldState.error]} />
|
|
149
|
+
)}
|
|
150
|
+
</Field>
|
|
151
|
+
)}
|
|
152
|
+
/>
|
|
153
|
+
|
|
154
|
+
{/* Displaying the error that comes from the server */}
|
|
155
|
+
{error && <Field className="text-red-500">{error}</Field>}
|
|
156
|
+
|
|
157
|
+
<Field>
|
|
158
|
+
<Button
|
|
159
|
+
size="lg"
|
|
160
|
+
type="submit"
|
|
161
|
+
form="login-form"
|
|
162
|
+
disabled={loading}
|
|
163
|
+
>
|
|
164
|
+
{loading ? (
|
|
165
|
+
<>
|
|
166
|
+
<Spinner /> Logging in...
|
|
167
|
+
</>
|
|
168
|
+
) : (
|
|
169
|
+
"Login"
|
|
170
|
+
)}
|
|
171
|
+
</Button>
|
|
172
|
+
|
|
173
|
+
<FieldDescription className="text-center">
|
|
174
|
+
Don't have an account?{" "}
|
|
175
|
+
<Link
|
|
176
|
+
href={"/signup"}
|
|
177
|
+
className="text-sm text-neutral-800 hover:text-black"
|
|
178
|
+
>
|
|
179
|
+
Sign up
|
|
180
|
+
</Link>
|
|
181
|
+
</FieldDescription>
|
|
182
|
+
</Field>
|
|
183
|
+
</FieldGroup>
|
|
184
|
+
</form>
|
|
185
|
+
</CardContent>
|
|
186
|
+
</Card>
|
|
187
|
+
);
|
|
188
|
+
};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useRouter } from "next/navigation";
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
|
|
6
|
+
import { Controller } from "react-hook-form";
|
|
7
|
+
import z from "zod";
|
|
8
|
+
|
|
9
|
+
import { useAuthForm } from "@/hooks/use-auth-form";
|
|
10
|
+
import { createClient } from "@/lib/supabase/client";
|
|
11
|
+
import { ReqResetPasswordSchema } from "@/schema/form-schema";
|
|
12
|
+
|
|
13
|
+
import { Button } from "../ui/button";
|
|
14
|
+
import {
|
|
15
|
+
Card,
|
|
16
|
+
CardContent,
|
|
17
|
+
CardDescription,
|
|
18
|
+
CardHeader,
|
|
19
|
+
CardTitle,
|
|
20
|
+
} from "../ui/card";
|
|
21
|
+
import { Field, FieldError, FieldGroup, FieldLabel } from "../ui/field";
|
|
22
|
+
import { Input } from "../ui/input";
|
|
23
|
+
import { Spinner } from "../ui/spinner";
|
|
24
|
+
|
|
25
|
+
export const RequestResetPassword = () => {
|
|
26
|
+
const { reqresetpasswordform: form } = useAuthForm();
|
|
27
|
+
const router = useRouter();
|
|
28
|
+
|
|
29
|
+
const [loading, setLoading] = useState<boolean>(false);
|
|
30
|
+
const [error, setError] = useState<null | string>(null);
|
|
31
|
+
const [success, setSuccess] = useState<boolean>(false);
|
|
32
|
+
|
|
33
|
+
async function onSubmit(values: z.infer<typeof ReqResetPasswordSchema>) {
|
|
34
|
+
const supabase = createClient();
|
|
35
|
+
|
|
36
|
+
setLoading(true);
|
|
37
|
+
setError(null);
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const { error } = await supabase.auth.resetPasswordForEmail(
|
|
41
|
+
values.email,
|
|
42
|
+
{
|
|
43
|
+
redirectTo: `${window.location.origin}/reset-password`,
|
|
44
|
+
}
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
if (error) throw error;
|
|
48
|
+
|
|
49
|
+
setSuccess(true);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
setError(error instanceof Error ? error.message : "An error occurred!");
|
|
52
|
+
} finally {
|
|
53
|
+
setLoading(false);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (success) {
|
|
58
|
+
return (
|
|
59
|
+
// customize this depends of your needs (eg. background color, size, etc.)
|
|
60
|
+
// you can add props if you want too
|
|
61
|
+
<Card className="w-full max-w-md">
|
|
62
|
+
<CardHeader>
|
|
63
|
+
<CardTitle className="text-lg">Check Your Email</CardTitle>
|
|
64
|
+
<CardDescription className="text-base">
|
|
65
|
+
Reset password request link already sent to your email.
|
|
66
|
+
</CardDescription>
|
|
67
|
+
</CardHeader>
|
|
68
|
+
<CardContent>
|
|
69
|
+
<p className="text-muted-foreground text-sm">
|
|
70
|
+
If you registered using your email and password, you will receive a
|
|
71
|
+
password reset email.
|
|
72
|
+
</p>
|
|
73
|
+
</CardContent>
|
|
74
|
+
</Card>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
// customize this depends of your needs (eg. background color, size, etc.)
|
|
80
|
+
// you can add props if you want too
|
|
81
|
+
<Card className="w-full max-w-sm">
|
|
82
|
+
<CardHeader>
|
|
83
|
+
<CardTitle className="text-lg">Request Reset Password</CardTitle>
|
|
84
|
+
<CardDescription>
|
|
85
|
+
Enter your email and we'll send you a link to reset your
|
|
86
|
+
password.
|
|
87
|
+
</CardDescription>
|
|
88
|
+
</CardHeader>
|
|
89
|
+
<CardContent>
|
|
90
|
+
<form id="email-form" onSubmit={form.handleSubmit(onSubmit)}>
|
|
91
|
+
<FieldGroup>
|
|
92
|
+
<Controller
|
|
93
|
+
control={form.control}
|
|
94
|
+
name="email"
|
|
95
|
+
render={({ field, fieldState }) => (
|
|
96
|
+
<Field data-invalid={fieldState.invalid}>
|
|
97
|
+
<FieldLabel htmlFor="email-input">Email</FieldLabel>
|
|
98
|
+
|
|
99
|
+
<Input
|
|
100
|
+
{...field}
|
|
101
|
+
id="email-input"
|
|
102
|
+
aria-invalid={fieldState.invalid}
|
|
103
|
+
autoComplete="off"
|
|
104
|
+
placeholder="example@mail.com"
|
|
105
|
+
/>
|
|
106
|
+
|
|
107
|
+
{fieldState.invalid && (
|
|
108
|
+
<FieldError errors={[fieldState.error]} />
|
|
109
|
+
)}
|
|
110
|
+
</Field>
|
|
111
|
+
)}
|
|
112
|
+
/>
|
|
113
|
+
{error && <Field className="text-red-500">{error}</Field>}
|
|
114
|
+
<Field>
|
|
115
|
+
<Button type="submit" form="email-form" disabled={loading}>
|
|
116
|
+
{loading ? (
|
|
117
|
+
<>
|
|
118
|
+
<Spinner /> Sending...
|
|
119
|
+
</>
|
|
120
|
+
) : (
|
|
121
|
+
"Send reset email"
|
|
122
|
+
)}
|
|
123
|
+
</Button>
|
|
124
|
+
<Button
|
|
125
|
+
variant={"outline"}
|
|
126
|
+
onClick={() => router.push("/login")}
|
|
127
|
+
type="button"
|
|
128
|
+
>
|
|
129
|
+
Back to login
|
|
130
|
+
</Button>
|
|
131
|
+
</Field>
|
|
132
|
+
</FieldGroup>
|
|
133
|
+
</form>
|
|
134
|
+
</CardContent>
|
|
135
|
+
</Card>
|
|
136
|
+
);
|
|
137
|
+
};
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import Link from "next/link";
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
|
|
6
|
+
import { EyeIcon, EyeOffIcon } from "lucide-react";
|
|
7
|
+
import { Controller } from "react-hook-form";
|
|
8
|
+
import z from "zod";
|
|
9
|
+
|
|
10
|
+
import { useAuthForm } from "@/hooks/use-auth-form";
|
|
11
|
+
import { createClient } from "@/lib/supabase/client";
|
|
12
|
+
import { UpdatePasswordSchema } from "@/schema/form-schema";
|
|
13
|
+
|
|
14
|
+
import { Button } from "../ui/button";
|
|
15
|
+
import {
|
|
16
|
+
Card,
|
|
17
|
+
CardContent,
|
|
18
|
+
CardDescription,
|
|
19
|
+
CardHeader,
|
|
20
|
+
CardTitle,
|
|
21
|
+
} from "../ui/card";
|
|
22
|
+
import {
|
|
23
|
+
Field,
|
|
24
|
+
FieldContent,
|
|
25
|
+
FieldError,
|
|
26
|
+
FieldGroup,
|
|
27
|
+
FieldLabel,
|
|
28
|
+
} from "../ui/field";
|
|
29
|
+
import { Input } from "../ui/input";
|
|
30
|
+
import {
|
|
31
|
+
InputGroup,
|
|
32
|
+
InputGroupAddon,
|
|
33
|
+
InputGroupInput,
|
|
34
|
+
} from "../ui/input-group";
|
|
35
|
+
import { Spinner } from "../ui/spinner";
|
|
36
|
+
|
|
37
|
+
export const ResetPasswordForm = () => {
|
|
38
|
+
const { updatepasswordform: form } = useAuthForm();
|
|
39
|
+
|
|
40
|
+
const [open, setOpen] = useState<boolean>(false);
|
|
41
|
+
const [loading, setLoading] = useState<boolean>(false);
|
|
42
|
+
const [success, setSuccess] = useState<boolean>(false);
|
|
43
|
+
const [error, setError] = useState<string | null>(null);
|
|
44
|
+
|
|
45
|
+
// Show password toggle function
|
|
46
|
+
const isOpen = () => {
|
|
47
|
+
setOpen((prev) => !prev);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
async function onSubmit(values: z.infer<typeof UpdatePasswordSchema>) {
|
|
51
|
+
const supabase = createClient();
|
|
52
|
+
|
|
53
|
+
setLoading(true);
|
|
54
|
+
setError(null);
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const { error } = await supabase.auth.updateUser({
|
|
58
|
+
password: values.password,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (error) {
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Force the user to login after the successful reset password
|
|
66
|
+
//NOTE : In this case the scope is local so that the user will only sign out in the device their
|
|
67
|
+
//are currently using. Use a global scope if you want the user to signout all the devices that they currently login
|
|
68
|
+
await supabase.auth.signOut({
|
|
69
|
+
scope: "local",
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
setSuccess(true);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
setError(error instanceof Error ? error.message : "An error occurred!");
|
|
75
|
+
} finally {
|
|
76
|
+
setLoading(false);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (success) {
|
|
81
|
+
return (
|
|
82
|
+
// customize this depends of your needs (eg. background color, size, etc.)
|
|
83
|
+
// you can add props if you want too
|
|
84
|
+
<Card className="w-full max-w-sm">
|
|
85
|
+
<CardHeader>
|
|
86
|
+
<CardTitle className="text-lg">Password Successfully Reset</CardTitle>
|
|
87
|
+
<CardDescription>
|
|
88
|
+
<p>You can now login to your account using your new password.</p>
|
|
89
|
+
<p className="mt-4">
|
|
90
|
+
Go back to
|
|
91
|
+
<Link
|
|
92
|
+
href={"/login"}
|
|
93
|
+
className="text-neutral-800 underline underline-offset-4 hover:text-neutral-950"
|
|
94
|
+
>
|
|
95
|
+
{" "}
|
|
96
|
+
Login
|
|
97
|
+
</Link>
|
|
98
|
+
</p>
|
|
99
|
+
</CardDescription>
|
|
100
|
+
</CardHeader>
|
|
101
|
+
</Card>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
// customize this depends of your needs (eg. background color, size, etc.)
|
|
107
|
+
// you can add props if you want too
|
|
108
|
+
<Card className="w-full max-w-sm">
|
|
109
|
+
<CardHeader>
|
|
110
|
+
<CardTitle className="text-lg">Reset Your Password</CardTitle>
|
|
111
|
+
<CardDescription className="text-base">
|
|
112
|
+
Enter your new password below.
|
|
113
|
+
</CardDescription>
|
|
114
|
+
</CardHeader>
|
|
115
|
+
<CardContent>
|
|
116
|
+
<form id="reset-password-form" onSubmit={form.handleSubmit(onSubmit)}>
|
|
117
|
+
<FieldGroup>
|
|
118
|
+
{/* Password input */}
|
|
119
|
+
<Controller
|
|
120
|
+
control={form.control}
|
|
121
|
+
name="password"
|
|
122
|
+
render={({ field, fieldState }) => (
|
|
123
|
+
<Field data-invalid={fieldState.invalid}>
|
|
124
|
+
<FieldLabel htmlFor="new-password-input">
|
|
125
|
+
New Password
|
|
126
|
+
</FieldLabel>
|
|
127
|
+
<InputGroup>
|
|
128
|
+
<InputGroupInput
|
|
129
|
+
{...field}
|
|
130
|
+
id="new-password-input"
|
|
131
|
+
aria-invalid={fieldState.invalid}
|
|
132
|
+
placeholder="Enter new password"
|
|
133
|
+
autoComplete="off"
|
|
134
|
+
type={open ? "text" : "password"}
|
|
135
|
+
/>
|
|
136
|
+
<InputGroupAddon align="inline-end">
|
|
137
|
+
<Button
|
|
138
|
+
type="button"
|
|
139
|
+
className="hover:text-muted-foreground hover:bg-transparent"
|
|
140
|
+
size="icon"
|
|
141
|
+
variant="ghost"
|
|
142
|
+
onClick={isOpen}
|
|
143
|
+
>
|
|
144
|
+
{open ? <EyeIcon /> : <EyeOffIcon />}
|
|
145
|
+
</Button>
|
|
146
|
+
</InputGroupAddon>
|
|
147
|
+
</InputGroup>
|
|
148
|
+
{fieldState.invalid && (
|
|
149
|
+
<FieldError errors={[fieldState.error]} />
|
|
150
|
+
)}
|
|
151
|
+
</Field>
|
|
152
|
+
)}
|
|
153
|
+
/>
|
|
154
|
+
|
|
155
|
+
{/* Confirm Password input */}
|
|
156
|
+
<Controller
|
|
157
|
+
control={form.control}
|
|
158
|
+
name="confirmPassword"
|
|
159
|
+
render={({ field, fieldState }) => (
|
|
160
|
+
<Field data-invalid={fieldState.invalid}>
|
|
161
|
+
<FieldLabel htmlFor="confirm-password-input">
|
|
162
|
+
Confirm Password
|
|
163
|
+
</FieldLabel>
|
|
164
|
+
<Input
|
|
165
|
+
{...field}
|
|
166
|
+
id="confirm-password-input"
|
|
167
|
+
aria-invalid={fieldState.invalid}
|
|
168
|
+
placeholder="Confirm password"
|
|
169
|
+
autoComplete="off"
|
|
170
|
+
type="password"
|
|
171
|
+
/>
|
|
172
|
+
{fieldState.invalid && (
|
|
173
|
+
<FieldError errors={[fieldState.error]} />
|
|
174
|
+
)}
|
|
175
|
+
</Field>
|
|
176
|
+
)}
|
|
177
|
+
/>
|
|
178
|
+
{error && <p className="text-red-500">{error}</p>}
|
|
179
|
+
<FieldContent>
|
|
180
|
+
<Button
|
|
181
|
+
type="submit"
|
|
182
|
+
form="reset-password-form"
|
|
183
|
+
disabled={loading}
|
|
184
|
+
>
|
|
185
|
+
{loading ? (
|
|
186
|
+
<>
|
|
187
|
+
<Spinner /> Saving...
|
|
188
|
+
</>
|
|
189
|
+
) : (
|
|
190
|
+
"Save new password"
|
|
191
|
+
)}
|
|
192
|
+
</Button>
|
|
193
|
+
</FieldContent>
|
|
194
|
+
</FieldGroup>
|
|
195
|
+
</form>
|
|
196
|
+
</CardContent>
|
|
197
|
+
</Card>
|
|
198
|
+
);
|
|
199
|
+
};
|