Skip to content

Commit

Permalink
fix: password validation error message
Browse files Browse the repository at this point in the history
  • Loading branch information
markkovari committed Oct 11, 2024
1 parent 58df65e commit b99fbce
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 81 deletions.
25 changes: 12 additions & 13 deletions frontend/src/components/AuthenticationTabs.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -16,8 +11,12 @@ const AuthenticationTabs = () => {
return (
<Tabs defaultValue="login" value={activeTab} className="w-[400px]">
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="login" onClick={(_) => setActiveTab("login")}>Login</TabsTrigger>
<TabsTrigger value="register" onClick={(_) => setActiveTab("register")}>Register</TabsTrigger>
<TabsTrigger value="login" onClick={(_) => setActiveTab("login")}>
Login
</TabsTrigger>
<TabsTrigger value="register" onClick={(_) => setActiveTab("register")}>
Register
</TabsTrigger>
</TabsList>
<TabsContent value="login">
<LoginForm setActiveTab={setActiveTab} />
Expand All @@ -26,6 +25,6 @@ const AuthenticationTabs = () => {
<RegisterForm setActiveTab={setActiveTab} />
</TabsContent>
</Tabs>
)
}
export { AuthenticationTabs }
);
};
export { AuthenticationTabs };
39 changes: 23 additions & 16 deletions frontend/src/components/LoginForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof FormSchema>;

type LoginFormProps = {
setActiveTab: React.Dispatch<React.SetStateAction<ActiveTab>>
setActiveTab: React.Dispatch<React.SetStateAction<ActiveTab>>;
};

const LoginForm = ({ setActiveTab }: LoginFormProps) => {
Expand Down Expand Up @@ -63,14 +61,12 @@ const LoginForm = ({ setActiveTab }: LoginFormProps) => {
}

return (
<Form {...form} >
<Form {...form}>
<Card>
<form onSubmit={form.handleSubmit(onSubmit)}>
<CardHeader className="flex flex-col items-center">
<h2 className="text-xl font-semibold">Login</h2>
<p className="text-sm text-slate-600">
Login with your account.
</p>
<p className="text-sm text-slate-600">Login with your account.</p>
</CardHeader>
<CardContent>
<FormField
Expand Down Expand Up @@ -101,13 +97,24 @@ const LoginForm = ({ setActiveTab }: LoginFormProps) => {
/>
</CardContent>
<CardFooter className="flex flex-col">
<Button type="submit" className="my-2 w-1/2">Login</Button>
<p className="text-sm text-slate-600 my-4"> - or if you have not - </p>
<Button className="my-2" variant="link" onClick={() => setActiveTab("register")}>Register</Button>
<Button type="submit" className="my-2 w-1/2">
Login
</Button>
<p className="text-sm text-slate-600 my-4">
{" "}
- or if you have not -{" "}
</p>
<Button
className="my-2"
variant="link"
onClick={() => setActiveTab("register")}
>
Register
</Button>
</CardFooter>
</form>
</Card>
</Form >
</Form>
);
};

Expand Down
10 changes: 5 additions & 5 deletions frontend/src/components/MaskedPassword.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ const MaskedPassword = ({
setInputMask((mask) => (mask === "text" ? "password" : "text"));

return (
<div
className="flex w-full space-x-2">
<div className="flex w-full space-x-2">
<Input
type={inputMask}
placeholder={placeholder}
Expand All @@ -43,9 +42,10 @@ const MaskedPassword = ({
className="flex items-center justify-center"
variant="secondary"
onClick={(event) => {
event.preventDefault()
togglePassword()
}}>
event.preventDefault();
togglePassword();
}}
>
{inputMask === "text" ? visible : hidden}
</Button>
</div>
Expand Down
30 changes: 20 additions & 10 deletions frontend/src/components/RegisterForm.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
Expand All @@ -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({
Expand Down Expand Up @@ -63,9 +63,7 @@ const RegisterForm = ({ setActiveTab }: RegisterFormProps) => {
toast({
variant: "destructive",
title: "Error during registration",
description: (
<p>{error.response.data?.error || "Unknown error"}</p>
),
description: <p>{error.response.data?.error || "Unknown error"}</p>,
});
}
}
Expand All @@ -78,7 +76,8 @@ const RegisterForm = ({ setActiveTab }: RegisterFormProps) => {
<CardHeader className="flex flex-col items-center">
<h2 className="text-xl font-semibold">Registration</h2>
<p className="text-sm text-slate-600">
Make changes to your account here. Click 'Register' when you're done.
Make changes to your account here. Click 'Register' when you're
done.
</p>
</CardHeader>
<CardContent>
Expand Down Expand Up @@ -149,13 +148,24 @@ const RegisterForm = ({ setActiveTab }: RegisterFormProps) => {
/>
</CardContent>
<CardFooter className="flex flex-col">
<Button type="submit" className="my-2 w-1/2">Register</Button>
<p className="text-sm text-slate-600 my-4"> - or if you already have and account - </p>
<Button className="my-2" variant="link" onClick={() => setActiveTab("login")}>Login</Button>
<Button type="submit" className="my-2 w-1/2">
Register
</Button>
<p className="text-sm text-slate-600 my-4">
{" "}
- or if you already have and account -{" "}
</p>
<Button
className="my-2"
variant="link"
onClick={() => setActiveTab("login")}
>
Login
</Button>
</CardFooter>
</form>
</Card >
</Form >
</Card>
</Form>
);
};

Expand Down
39 changes: 23 additions & 16 deletions frontend/src/components/ui/card.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"
));
CardContent.displayName = "CardContent";

const CardFooter = React.forwardRef<
HTMLDivElement,
Expand All @@ -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,
};
52 changes: 32 additions & 20 deletions frontend/src/lib/utils/schemas.ts
Original file line number Diff line number Diff line change
@@ -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 };
1 change: 0 additions & 1 deletion frontend/src/pages/Main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit b99fbce

Please sign in to comment.