FauziIsyrinApridal commited on
Commit
53bb58b
·
1 Parent(s): b53217b
Files changed (1) hide show
  1. components/SignInForm.tsx +173 -48
components/SignInForm.tsx CHANGED
@@ -8,97 +8,222 @@ import { login } from "@/utils/signIn";
8
  import { Label } from "./ui/label";
9
  import { useState } from "react";
10
  import { Eye, EyeOff, Loader2 } from "lucide-react";
 
 
 
 
 
 
11
 
12
  const schema = z.object({
13
- email: z.string().email(),
14
- password: z.string().min(8),
 
 
 
 
 
 
15
  });
16
 
17
  type FormFields = z.infer<typeof schema>;
18
 
19
  const SignInForm = () => {
20
  const [showPassword, setShowPassword] = useState(false);
 
 
21
 
22
  const {
23
  register,
24
  handleSubmit,
25
  setError,
 
 
26
  formState: { errors, isSubmitting },
27
  } = useForm<FormFields>({
28
- defaultValues: {},
 
 
 
29
  resolver: zodResolver(schema),
30
  });
31
 
32
  const onSubmit: SubmitHandler<FormFields> = async (data) => {
33
  try {
 
34
  await login(data.email, data.password);
35
  } catch (error) {
36
  setError("root", {
37
- message: "Invalid email or password",
38
  });
39
  }
40
  };
41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  const togglePasswordVisibility = () => {
43
  setShowPassword(!showPassword);
44
  };
45
 
46
  return (
47
  <>
48
- <form className="flex flex-col gap-2" onSubmit={handleSubmit(onSubmit)}>
49
- <Label htmlFor="email" className="text-left">
50
- Email
51
- </Label>
52
- <Input
53
- {...register("email")}
54
- placeholder="Email"
55
- id="email"
56
- type="email"
57
- />
58
- {errors.email && (
59
- <div className="text-left text-xs text-red-500">
60
- {errors.email.message}
61
- </div>
62
- )}
63
- <Label htmlFor="password" className="text-left">
64
- Password
65
- </Label>
66
- <div className="relative">
67
  <Input
68
- {...register("password")}
69
- placeholder="Password"
70
- type={showPassword ? "text" : "password"}
71
- id="password"
72
- className="pr-10"
 
 
 
73
  />
74
- <button
75
- type="button"
76
- onClick={togglePasswordVisibility}
77
- className="absolute inset-y-0 right-0 flex items-center pr-3 text-gray-500 hover:text-gray-700"
78
- tabIndex={-1}
79
- >
80
- {showPassword ? (
81
- <EyeOff className="h-4 w-4" />
82
- ) : (
83
- <Eye className="h-4 w-4" />
84
- )}
85
- </button>
86
  </div>
87
- {errors.password && (
88
- <div className="text-left text-xs text-red-500">
89
- {errors.password.message}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  </div>
91
  )}
 
92
  <Button
93
  disabled={isSubmitting}
94
  type="submit"
95
- className="mt-4 bg-orange-600 hover:bg-orange-800"
96
  >
97
- {isSubmitting ? "Loading..." : "Login"}
 
 
 
 
 
 
 
98
  </Button>
99
- {errors.root && (
100
- <div className="text-xs text-red-500">{errors.root.message}</div>
101
- )}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  </form>
103
  </>
104
  );
 
8
  import { Label } from "./ui/label";
9
  import { useState } from "react";
10
  import { Eye, EyeOff, Loader2 } from "lucide-react";
11
+ import { createClient } from "@supabase/supabase-js";
12
+
13
+ const supabase = createClient(
14
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
15
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
16
+ );
17
 
18
  const schema = z.object({
19
+ email: z
20
+ .string()
21
+ .min(1, "Email is required")
22
+ .email("Please enter a valid email address"),
23
+ password: z
24
+ .string()
25
+ .min(1, "Password is required")
26
+ .min(8, "Password must be at least 8 characters"),
27
  });
28
 
29
  type FormFields = z.infer<typeof schema>;
30
 
31
  const SignInForm = () => {
32
  const [showPassword, setShowPassword] = useState(false);
33
+ const [isResettingPassword, setIsResettingPassword] = useState(false);
34
+ const [resetEmailSent, setResetEmailSent] = useState(false);
35
 
36
  const {
37
  register,
38
  handleSubmit,
39
  setError,
40
+ clearErrors,
41
+ getValues,
42
  formState: { errors, isSubmitting },
43
  } = useForm<FormFields>({
44
+ defaultValues: {
45
+ email: "",
46
+ password: "",
47
+ },
48
  resolver: zodResolver(schema),
49
  });
50
 
51
  const onSubmit: SubmitHandler<FormFields> = async (data) => {
52
  try {
53
+ clearErrors("root");
54
  await login(data.email, data.password);
55
  } catch (error) {
56
  setError("root", {
57
+ message: "Email atau password salah",
58
  });
59
  }
60
  };
61
 
62
+ const handleForgotPassword = async () => {
63
+ const email = getValues("email");
64
+
65
+ if (!email) {
66
+ setError("email", {
67
+ message: "Please enter your email address first",
68
+ });
69
+ return;
70
+ }
71
+
72
+ if (!z.string().email().safeParse(email).success) {
73
+ setError("email", {
74
+ message: "Please enter a valid email address",
75
+ });
76
+ return;
77
+ }
78
+
79
+ try {
80
+ setIsResettingPassword(true);
81
+ clearErrors(["email"]);
82
+
83
+ const { error } = await supabase.auth.resetPasswordForEmail(email, {
84
+ redirectTo: `${window.location.origin}/auth/reset-password`,
85
+ });
86
+
87
+ if (error) {
88
+ throw new Error(error.message);
89
+ }
90
+
91
+ setResetEmailSent(true);
92
+
93
+ // Clear the success message after 5 seconds
94
+ setTimeout(() => setResetEmailSent(false), 5000);
95
+ } catch (error) {
96
+ let errorMessage = "Failed to send reset email. Please try again.";
97
+
98
+ if (error instanceof Error) {
99
+ if (error.message.includes("For security purposes")) {
100
+ errorMessage =
101
+ "Reset email sent (if account exists). Check your inbox.";
102
+ setResetEmailSent(true);
103
+ } else {
104
+ errorMessage = error.message;
105
+ }
106
+ }
107
+
108
+ if (!resetEmailSent) {
109
+ setError("root", {
110
+ message: errorMessage,
111
+ });
112
+ }
113
+ } finally {
114
+ setIsResettingPassword(false);
115
+ }
116
+ };
117
+
118
  const togglePasswordVisibility = () => {
119
  setShowPassword(!showPassword);
120
  };
121
 
122
  return (
123
  <>
124
+ <form className="flex flex-col gap-4" onSubmit={handleSubmit(onSubmit)}>
125
+ <div className="space-y-2">
126
+ <Label htmlFor="email" className="text-left">
127
+ Email
128
+ </Label>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
  <Input
130
+ {...register("email")}
131
+ placeholder="Enter your email"
132
+ id="email"
133
+ type="email"
134
+ autoComplete="email"
135
+ className={
136
+ errors.email ? "border-red-500 focus-visible:ring-red-500" : ""
137
+ }
138
  />
139
+ {errors.email && (
140
+ <div className="text-left text-xs text-red-500">
141
+ {errors.email.message}
142
+ </div>
143
+ )}
 
 
 
 
 
 
 
144
  </div>
145
+
146
+ <div className="space-y-2">
147
+ <Label htmlFor="password" className="text-left">
148
+ Password
149
+ </Label>
150
+ <div className="relative">
151
+ <Input
152
+ {...register("password")}
153
+ placeholder="Enter your password"
154
+ type={showPassword ? "text" : "password"}
155
+ id="password"
156
+ autoComplete="current-password"
157
+ className={
158
+ errors.password
159
+ ? "border-red-500 pr-10 focus-visible:ring-red-500"
160
+ : "pr-10"
161
+ }
162
+ />
163
+ <button
164
+ type="button"
165
+ onClick={togglePasswordVisibility}
166
+ className="absolute inset-y-0 right-0 flex items-center pr-3 text-gray-500 hover:text-gray-700"
167
+ tabIndex={-1}
168
+ >
169
+ {showPassword ? (
170
+ <EyeOff className="h-4 w-4" />
171
+ ) : (
172
+ <Eye className="h-4 w-4" />
173
+ )}
174
+ </button>
175
+ </div>
176
+ {errors.password && (
177
+ <div className="text-left text-xs text-red-500">
178
+ {errors.password.message}
179
+ </div>
180
+ )}
181
+ </div>
182
+
183
+ {errors.root && !resetEmailSent && (
184
+ <div className="rounded-md border border-red-200 bg-red-50 p-2 text-center text-xs text-red-500">
185
+ {errors.root.message}
186
+ </div>
187
+ )}
188
+
189
+ {resetEmailSent && (
190
+ <div className="rounded-md border border-green-200 bg-green-50 p-2 text-center text-xs text-green-600">
191
+ Password reset email sent! Check your inbox and spam folder.
192
  </div>
193
  )}
194
+
195
  <Button
196
  disabled={isSubmitting}
197
  type="submit"
198
+ className="mt-2 bg-orange-600 hover:bg-orange-700 disabled:opacity-50"
199
  >
200
+ {isSubmitting ? (
201
+ <>
202
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" />
203
+ Signing in...
204
+ </>
205
+ ) : (
206
+ "Sign In"
207
+ )}
208
  </Button>
209
+
210
+ <div className="text-center">
211
+ <button
212
+ type="button"
213
+ onClick={handleForgotPassword}
214
+ disabled={isResettingPassword}
215
+ className="text-xs text-orange-600 underline hover:text-orange-700 disabled:cursor-not-allowed disabled:opacity-50"
216
+ >
217
+ {isResettingPassword ? (
218
+ <>
219
+ <Loader2 className="mr-1 inline h-3 w-3 animate-spin" />
220
+ Sending reset email...
221
+ </>
222
+ ) : (
223
+ "Forgot your password?"
224
+ )}
225
+ </button>
226
+ </div>
227
  </form>
228
  </>
229
  );