diff --git a/apps/edr-freight-api/src/config/database.config.ts b/apps/edr-freight-api/src/config/database.config.ts index a511c1654..0e7375b19 100644 --- a/apps/edr-freight-api/src/config/database.config.ts +++ b/apps/edr-freight-api/src/config/database.config.ts @@ -17,7 +17,6 @@ import { PositionType, Position, Project, - UnitSetting, GlobalUnitConfiguration, Unit, EmployeeSignature, @@ -64,7 +63,6 @@ const iamEntities = [ PositionType, Position, Project, - UnitSetting, GlobalUnitConfiguration, Unit, EmployeeSignature, @@ -98,10 +96,8 @@ const iamMigrationsGlob = join( ); const freightMigrationsGlob = join(__dirname, "../migrations/*.js"); -export default registerAs( - "database", - (): TypeOrmModuleOptions => { - return { +export default registerAs("database", (): TypeOrmModuleOptions => { + return { type: "postgres", host: process.env.DB_HOST ?? "localhost", port: parseInt(process.env.DB_PORT ?? "5433", 10), @@ -124,5 +120,4 @@ export default registerAs( synchronize: false, logging: process.env.NODE_ENV === "development", }; - }, -); +}); diff --git a/apps/edr-freight-web/portal/src/pages/accounts/SignupPage.tsx b/apps/edr-freight-web/portal/src/pages/accounts/SignupPage.tsx index b5289a5dc..8c305e0ef 100644 --- a/apps/edr-freight-web/portal/src/pages/accounts/SignupPage.tsx +++ b/apps/edr-freight-web/portal/src/pages/accounts/SignupPage.tsx @@ -3,12 +3,21 @@ import { useNavigate } from "react-router-dom"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; -import { ArrowRight, UserPlus, Loader2 } from "lucide-react"; +import { + ArrowRight, + Eye, + EyeOff, + UserPlus, + Loader2, + Check, + X, +} from "lucide-react"; import { userType } from "@/enums/userType"; import useAuth from "@/hooks/useAuth"; import type { SignupPayload } from "@/types/auth"; import AuthLayout from "@/components/auth/AuthLayout"; import PhoneInput from "@/components/auth/PhoneInput"; +import { cn } from "@/lib/utils"; import { Button, Input, @@ -18,23 +27,47 @@ import { FieldGroup, } from "@edr/ui-common"; -const userSchema = z.object({ - email: z.string().email("Invalid email address"), - countryCode: z.string().min(1, "Country code is required"), - phone: z - .string() - .min(9, "Phone number is too short") - .max(9, "Phone number is too long"), - userType: z.string(), - firstName: z.object({ - en: z.string().min(2, "Name is required"), - am: z.string().nullable(), - }), - lastName: z.object({ - en: z.string().min(2, "Name is required"), - am: z.string().nullable(), - }), -}); +const passwordRequirements = [ + { label: "At least 8 characters", test: (v: string) => v.length >= 8 }, + { label: "One uppercase letter", test: (v: string) => /[A-Z]/.test(v) }, + { label: "One lowercase letter", test: (v: string) => /[a-z]/.test(v) }, + { label: "One number", test: (v: string) => /\d/.test(v) }, + { + label: "One special character", + test: (v: string) => /[^A-Za-z0-9]/.test(v), + }, +] as const; + +const userSchema = z + .object({ + email: z.string().email("Invalid email address"), + countryCode: z.string().min(1, "Country code is required"), + phone: z + .string() + .min(9, "Phone number is too short") + .max(9, "Phone number is too long"), + userType: z.string(), + firstName: z.object({ + en: z.string().min(2, "Name is required"), + am: z.string().nullable(), + }), + lastName: z.object({ + en: z.string().min(2, "Name is required"), + am: z.string().nullable(), + }), + password: z + .string() + .min(8, "Password must be at least 8 characters") + .regex(/[A-Z]/, "Password must include an uppercase letter") + .regex(/[a-z]/, "Password must include a lowercase letter") + .regex(/\d/, "Password must include a number") + .regex(/[^A-Za-z0-9]/, "Password must include a special character"), + confirmPassword: z.string().min(1, "Please confirm your password"), + }) + .refine((data) => data.password === data.confirmPassword, { + message: "Passwords do not match", + path: ["confirmPassword"], + }); type FormData = z.infer; @@ -43,10 +76,13 @@ export default function SignupPage() { const { signup } = useAuth(); const [error, setError] = useState(null); const [loading, setLoading] = useState(false); + const [showPassword, setShowPassword] = useState(false); + const [showConfirmPassword, setShowConfirmPassword] = useState(false); const { register, handleSubmit, + watch, formState: { errors }, } = useForm({ resolver: zodResolver(userSchema), @@ -57,6 +93,8 @@ export default function SignupPage() { userType: userType.individual, firstName: { en: "", am: "" }, lastName: { en: "", am: "" }, + password: "", + confirmPassword: "", }, }); @@ -73,6 +111,8 @@ export default function SignupPage() { phoneNumber: `${data.countryCode}${normalizedPhone}`, userType: data.userType, name: { en: `${data.firstName.en} ${data.lastName.en}`, am: "" }, + password: data.password, + confirmPassword: data.confirmPassword, }; const result = await signup(payload); if (result.success) { @@ -171,6 +211,81 @@ export default function SignupPage() { countryCodeError={errors.countryCode} phoneError={errors.phone} /> + + + Password +
+ + +
+ +
+ {passwordRequirements.map((req) => { + const met = req.test(watch("password") ?? ""); + return ( +
+ {met ? ( + + ) : ( + + )} + {req.label} +
+ ); + })} +
+
+ + + Confirm Password +
+ + +
+ +