diff --git a/frontend/src/components/AuthenticationTabs.tsx b/frontend/src/components/AuthenticationTabs.tsx index 85180d0..4fcebe0 100644 --- a/frontend/src/components/AuthenticationTabs.tsx +++ b/frontend/src/components/AuthenticationTabs.tsx @@ -1,11 +1,6 @@ -import { - Tabs, - TabsContent, - TabsList, - TabsTrigger, -} from "@/components/ui/tabs" -import { RegisterForm } from "./RegisterForm" -import { LoginForm } from "./LoginForm" +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { RegisterForm } from "./RegisterForm"; +import { LoginForm } from "./LoginForm"; import { useState } from "react"; export type ActiveTab = "login" | "register"; @@ -16,8 +11,12 @@ const AuthenticationTabs = () => { return ( - setActiveTab("login")}>Login - setActiveTab("register")}>Register + setActiveTab("login")}> + Login + + setActiveTab("register")}> + Register + @@ -26,6 +25,6 @@ const AuthenticationTabs = () => { - ) -} -export { AuthenticationTabs } \ No newline at end of file + ); +}; +export { AuthenticationTabs }; diff --git a/frontend/src/components/LoginForm.tsx b/frontend/src/components/LoginForm.tsx index ab22f0a..1de6ebd 100644 --- a/frontend/src/components/LoginForm.tsx +++ b/frontend/src/components/LoginForm.tsx @@ -21,19 +21,17 @@ import { passwordSchema } from "@/lib/utils/schemas"; import { Card, CardContent, CardFooter, CardHeader } from "./ui/card"; import { AxiosError } from "axios"; import type { ActiveTab } from "./AuthenticationTabs"; +import type React from "react"; -const FormSchema = z - .object({ - password: passwordSchema, - email: z - .string() - .email({ message: "Email must be a valid email address." }), - }); +const FormSchema = z.object({ + password: passwordSchema, + email: z.string().email({ message: "Email must be a valid email address." }), +}); type FormValues = z.infer; type LoginFormProps = { - setActiveTab: React.Dispatch> + setActiveTab: React.Dispatch>; }; const LoginForm = ({ setActiveTab }: LoginFormProps) => { @@ -63,14 +61,12 @@ const LoginForm = ({ setActiveTab }: LoginFormProps) => { } return ( -
+

Login

-

- Login with your account. -

+

Login with your account.

{ /> - -

- or if you have not -

- + +

+ {" "} + - or if you have not -{" "} +

+
- + ); }; diff --git a/frontend/src/components/MaskedPassword.tsx b/frontend/src/components/MaskedPassword.tsx index 77f9443..cdff3f2 100644 --- a/frontend/src/components/MaskedPassword.tsx +++ b/frontend/src/components/MaskedPassword.tsx @@ -27,8 +27,7 @@ const MaskedPassword = ({ setInputMask((mask) => (mask === "text" ? "password" : "text")); return ( -
+
{ - event.preventDefault() - togglePassword() - }}> + event.preventDefault(); + togglePassword(); + }} + > {inputMask === "text" ? visible : hidden}
diff --git a/frontend/src/components/RegisterForm.tsx b/frontend/src/components/RegisterForm.tsx index 50b13e2..6a413a5 100644 --- a/frontend/src/components/RegisterForm.tsx +++ b/frontend/src/components/RegisterForm.tsx @@ -1,4 +1,3 @@ - import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; import { z } from "zod"; @@ -20,6 +19,7 @@ import { passwordSchema, nameSchema } from "@/lib/utils/schemas"; import { Card, CardContent, CardFooter, CardHeader } from "./ui/card"; import { AxiosError } from "axios"; import type { ActiveTab } from "./AuthenticationTabs"; +import type React from "react"; const FormSchema = z .object({ @@ -63,9 +63,7 @@ const RegisterForm = ({ setActiveTab }: RegisterFormProps) => { toast({ variant: "destructive", title: "Error during registration", - description: ( -

{error.response.data?.error || "Unknown error"}

- ), + description:

{error.response.data?.error || "Unknown error"}

, }); } } @@ -78,7 +76,8 @@ const RegisterForm = ({ setActiveTab }: RegisterFormProps) => {

Registration

- Make changes to your account here. Click 'Register' when you're done. + Make changes to your account here. Click 'Register' when you're + done.

@@ -149,13 +148,24 @@ const RegisterForm = ({ setActiveTab }: RegisterFormProps) => { /> - -

- or if you already have and account -

- + +

+ {" "} + - or if you already have and account -{" "} +

+
- - + + ); }; diff --git a/frontend/src/components/ui/card.tsx b/frontend/src/components/ui/card.tsx index 77e9fb7..75cc369 100644 --- a/frontend/src/components/ui/card.tsx +++ b/frontend/src/components/ui/card.tsx @@ -1,6 +1,6 @@ -import * as React from "react" +import * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; const Card = React.forwardRef< HTMLDivElement, @@ -10,12 +10,12 @@ const Card = React.forwardRef< ref={ref} className={cn( "rounded-xl border bg-card text-card-foreground shadow", - className + className, )} {...props} /> -)) -Card.displayName = "Card" +)); +Card.displayName = "Card"; const CardHeader = React.forwardRef< HTMLDivElement, @@ -26,8 +26,8 @@ const CardHeader = React.forwardRef< className={cn("flex flex-col space-y-1.5 p-6", className)} {...props} /> -)) -CardHeader.displayName = "CardHeader" +)); +CardHeader.displayName = "CardHeader"; const CardTitle = React.forwardRef< HTMLParagraphElement, @@ -38,8 +38,8 @@ const CardTitle = React.forwardRef< className={cn("font-semibold leading-none tracking-tight", className)} {...props} /> -)) -CardTitle.displayName = "CardTitle" +)); +CardTitle.displayName = "CardTitle"; const CardDescription = React.forwardRef< HTMLParagraphElement, @@ -50,16 +50,16 @@ const CardDescription = React.forwardRef< className={cn("text-sm text-muted-foreground", className)} {...props} /> -)) -CardDescription.displayName = "CardDescription" +)); +CardDescription.displayName = "CardDescription"; const CardContent = React.forwardRef< HTMLDivElement, React.HTMLAttributes >(({ className, ...props }, ref) => (
-)) -CardContent.displayName = "CardContent" +)); +CardContent.displayName = "CardContent"; const CardFooter = React.forwardRef< HTMLDivElement, @@ -70,7 +70,14 @@ const CardFooter = React.forwardRef< className={cn("flex items-center p-6 pt-0", className)} {...props} /> -)) -CardFooter.displayName = "CardFooter" +)); +CardFooter.displayName = "CardFooter"; -export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardDescription, + CardContent, +}; diff --git a/frontend/src/lib/utils/schemas.ts b/frontend/src/lib/utils/schemas.ts index fc3599c..0b3daa9 100644 --- a/frontend/src/lib/utils/schemas.ts +++ b/frontend/src/lib/utils/schemas.ts @@ -1,28 +1,40 @@ -import { z } from "zod"; +import { z, ZodIssueCode } from "zod"; const nameSchema = z .string() .min(2, { message: "Name must be at least 2 characters long." }) .max(40, { message: "Name must be at most 40 characters long." }); -const passwordSchema = z - .string() - .min(4, { message: "Password must be at least 4 characters long." }) - .max(40, { message: "Password must be at most 40 characters long." }) - .refine((v) => !/\s/.test(v), { - message: "Password cannot contain whitespace.", - }) - .refine((v) => /[A-Z]/.test(v), { - message: "Password must contain at least one uppercase letter.", - }) - .refine((v) => /\d/.test(v), { - message: "Password must contain at least one digit.", - }) - .refine((v) => /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]+/.test(v), { - message: "Password must contain at least one special character.", - }) - .refine((v) => /[a-z]/.test(v), { - message: "Password must contain at least one lowercase letter.", - }); +const passwordSchema = z.string().superRefine((val, ctx): val is string => { + const issues: string[] = []; + if (val.length < 4) { + issues.push("at least 4 characters"); + } + if (val.length > 40) { + issues.push("less then 40 characters"); + } + if (/\s/.test(val)) { + issues.push("no whitespace"); + } + if (!/[A-Z]/.test(val)) { + issues.push("at least one uppercase letter"); + } + if (!/\d/.test(val)) { + issues.push("at least one digit"); + } + if (!/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]+/.test(val)) { + issues.push("at least one special character"); + } + if (!/[a-z]/.test(val)) { + issues.push("at least one lowercase letter"); + } + if (issues.length) { + ctx.addIssue({ + code: ZodIssueCode.custom, + message: `Password must contain ${issues.join(", ")}.`, + }); + } + return false; +}); export { passwordSchema, nameSchema }; diff --git a/frontend/src/pages/Main.tsx b/frontend/src/pages/Main.tsx index 1ca83b3..e0ccafc 100644 --- a/frontend/src/pages/Main.tsx +++ b/frontend/src/pages/Main.tsx @@ -2,7 +2,6 @@ import { Link } from "react-router-dom"; import { usePetClinicState } from "../state"; import { AuthenticationTabs } from "@/components/AuthenticationTabs"; - export function Main() { const { auth } = usePetClinicState(); const isAuthenticated = auth.token !== null;