diff --git a/components/auth/onboarding-form.tsx b/components/auth/onboarding-form.tsx index d5218eb..f08e84d 100644 --- a/components/auth/onboarding-form.tsx +++ b/components/auth/onboarding-form.tsx @@ -1,7 +1,7 @@ "use client"; import { useUser } from "@clerk/nextjs"; -import { AlertCircle } from "lucide-react"; +import { AlertCircle, Check, X } from "lucide-react"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { finishOnboarding } from "@/app/actions/_userActions"; @@ -20,30 +20,51 @@ import { cryptoService } from "@/lib/crypto"; import { isErrorResponse, getErrorInfo } from "@/lib/query-utils"; import { cn } from "@/lib/utils"; +const passwordSchema = z + .object({ + password: z + .string() + .min(12, "At least 12 characters") + .regex(/[A-Z]/, "At least 1 uppercase letter (A–Z)") + .regex(/[a-z]/, "At least 1 lowercase letter (a–z)") + .regex(/\d/, "At least 1 number (0–9)") + .regex( + /[!@#$%^&*()_+\-]/, + "At least 1 special character (!@#$%^&*()_+-)" + ), + repeatPassword: z.string(), + }) + .refine(data => data.password === data.repeatPassword, { + message: "Passwords do not match", + path: ["repeatPassword"], + }); + +type PasswordFormValues = z.infer; + export function SignUpForm({ className, ...props }: React.ComponentPropsWithoutRef<"div">) { - const [password, setPassword] = useState(""); - const [repeatPassword, setRepeatPassword] = useState(""); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(""); const router = useRouter(); const { user } = useUser(); - const handleSignUp = async (e: React.FormEvent) => { - e.preventDefault(); + const { + register, + handleSubmit, + formState: { errors, isValid }, + } = useForm({ + resolver: zodResolver(passwordSchema), + mode: "onChange", + }); + + const onSubmit = async (data: PasswordFormValues) => { setError(""); setIsLoading(true); - if (password !== repeatPassword) { - setError("Passwords do not match"); - setIsLoading(false); - return; - } - try { const { publicKey, wrappedPrivateKey, salt, wrappedDefaultVaultKey } = - await cryptoService.onboarding(password); + await cryptoService.onboarding(data.password); const response = await finishOnboarding({ salt, @@ -52,22 +73,18 @@ export function SignUpForm({ wrappedDefaultVaultKey, }); - // Handle error responses if (isErrorResponse(response)) { const { error } = response; console.error(`[${error.code}] Onboarding failed: ${error.message}`); - const errorMessage = error.code === "ONBOARDING_FAILED" ? "Failed to complete onboarding. Please try again." : error.code === "UNAUTHORIZED" ? "Authentication failed. Please sign in again." : error.message; - setError(errorMessage); return; } - await user?.reload(); router.push("/"); } catch (error) { @@ -105,7 +122,7 @@ export function SignUpForm({ -
+ {error && (
setPassword(e.target.value)} + {...register("password")} /> + {errors.password && ( +
+ {errors.password.message as string} +
+ )}
@@ -142,11 +163,21 @@ export function SignUpForm({ id="repeat-password" type="password" required - value={repeatPassword} - onChange={e => setRepeatPassword(e.target.value)} + {...register("repeatPassword")} /> + {errors.repeatPassword && ( +
+ {errors.repeatPassword.message as string} +
+ )}
-