mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
signup update
This commit is contained in:
@@ -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",
|
||||
};
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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<typeof userSchema>;
|
||||
|
||||
@@ -43,10 +76,13 @@ export default function SignupPage() {
|
||||
const { signup } = useAuth();
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
watch,
|
||||
formState: { errors },
|
||||
} = useForm<FormData>({
|
||||
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}
|
||||
/>
|
||||
|
||||
<Field data-invalid={Boolean(errors.password)}>
|
||||
<FieldLabel>Password</FieldLabel>
|
||||
<div className="relative">
|
||||
<Input
|
||||
type={showPassword ? "text" : "password"}
|
||||
placeholder="Create a strong password"
|
||||
disabled={loading}
|
||||
aria-invalid={Boolean(errors.password)}
|
||||
className="pr-10"
|
||||
{...register("password")}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
||||
tabIndex={-1}
|
||||
>
|
||||
{showPassword ? (
|
||||
<EyeOff className="size-4" />
|
||||
) : (
|
||||
<Eye className="size-4" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<FieldError errors={[errors.password]} />
|
||||
<div className="mt-2 space-y-1">
|
||||
{passwordRequirements.map((req) => {
|
||||
const met = req.test(watch("password") ?? "");
|
||||
return (
|
||||
<div
|
||||
key={req.label}
|
||||
className={cn(
|
||||
"flex items-center gap-1.5 text-xs transition-colors",
|
||||
met ? "text-emerald-600" : "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{met ? (
|
||||
<Check className="size-3" />
|
||||
) : (
|
||||
<X className="size-3" />
|
||||
)}
|
||||
{req.label}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Field>
|
||||
|
||||
<Field data-invalid={Boolean(errors.confirmPassword)}>
|
||||
<FieldLabel>Confirm Password</FieldLabel>
|
||||
<div className="relative">
|
||||
<Input
|
||||
type={showConfirmPassword ? "text" : "password"}
|
||||
placeholder="Re-enter your password"
|
||||
disabled={loading}
|
||||
aria-invalid={Boolean(errors.confirmPassword)}
|
||||
className="pr-10"
|
||||
{...register("confirmPassword")}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
||||
tabIndex={-1}
|
||||
>
|
||||
{showConfirmPassword ? (
|
||||
<EyeOff className="size-4" />
|
||||
) : (
|
||||
<Eye className="size-4" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<FieldError errors={[errors.confirmPassword]} />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
|
||||
<Button type="submit" disabled={loading} size="lg" className="w-full">
|
||||
|
||||
@@ -21,6 +21,8 @@ export interface SignupPayload {
|
||||
phoneNumber: string;
|
||||
userType: string;
|
||||
name: { en: string; am: string };
|
||||
password: string;
|
||||
confirmPassword: string;
|
||||
}
|
||||
|
||||
export interface SignupResponse {
|
||||
|
||||
Reference in New Issue
Block a user