2025-07-12 21:14:12 +00:00
|
|
|
import { z } from "zod";
|
|
|
|
|
|
|
|
|
|
// Base schemas for validation
|
|
|
|
|
export const registrationSchema = z.object({
|
|
|
|
|
firstName: z
|
|
|
|
|
.string()
|
|
|
|
|
.min(1, { message: "Full name is required" })
|
|
|
|
|
.min(2, { message: "Full name must be at least 2 characters" })
|
|
|
|
|
.max(100, { message: "Full name must be less than 100 characters" })
|
2025-12-18 03:00:24 +00:00
|
|
|
.regex(/^[a-zA-Z\s.,]+$/, { message: "Full name can only contain letters and spaces" }),
|
2025-07-12 21:14:12 +00:00
|
|
|
username: z
|
|
|
|
|
.string()
|
|
|
|
|
.min(1, { message: "Username is required" })
|
|
|
|
|
.min(3, { message: "Username must be at least 3 characters" })
|
|
|
|
|
.max(50, { message: "Username must be less than 50 characters" })
|
|
|
|
|
.regex(/^[a-zA-Z0-9._-]+$/, { message: "Username can only contain letters, numbers, dots, underscores, and hyphens" }),
|
|
|
|
|
phoneNumber: z
|
|
|
|
|
.string()
|
|
|
|
|
.min(1, { message: "Phone number is required" })
|
|
|
|
|
.regex(/^[0-9+\-\s()]+$/, { message: "Please enter a valid phone number" }),
|
|
|
|
|
email: z
|
|
|
|
|
.string()
|
|
|
|
|
.min(1, { message: "Email is required" })
|
|
|
|
|
.email({ message: "Please enter a valid email address" }),
|
|
|
|
|
address: z
|
|
|
|
|
.string()
|
|
|
|
|
.min(1, { message: "Address is required" })
|
|
|
|
|
.min(10, { message: "Address must be at least 10 characters" })
|
|
|
|
|
.max(500, { message: "Address must be less than 500 characters" }),
|
|
|
|
|
provinsi: z
|
|
|
|
|
.string()
|
|
|
|
|
.min(1, { message: "Province is required" }),
|
|
|
|
|
kota: z
|
|
|
|
|
.string()
|
|
|
|
|
.min(1, { message: "City is required" }),
|
|
|
|
|
kecamatan: z
|
|
|
|
|
.string()
|
|
|
|
|
.min(1, { message: "Subdistrict is required" }),
|
|
|
|
|
password: z
|
|
|
|
|
.string()
|
|
|
|
|
.min(1, { message: "Password is required" })
|
|
|
|
|
.min(8, { message: "Password must be at least 8 characters" })
|
|
|
|
|
.max(100, { message: "Password must be less than 100 characters" })
|
2025-12-09 08:46:23 +00:00
|
|
|
.regex(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[#@$!%*?&])[A-Za-z\d@$!%*#?&]/, {
|
2025-07-12 21:14:12 +00:00
|
|
|
message: "Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character"
|
|
|
|
|
}),
|
|
|
|
|
passwordConf: z
|
|
|
|
|
.string()
|
|
|
|
|
.min(1, { message: "Password confirmation is required" }),
|
|
|
|
|
}).refine((data) => data.password === data.passwordConf, {
|
|
|
|
|
message: "Passwords don't match",
|
|
|
|
|
path: ["passwordConf"],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const journalistRegistrationSchema = z.object({
|
|
|
|
|
journalistCertificate: z
|
|
|
|
|
.string()
|
|
|
|
|
.min(1, { message: "Journalist certificate number is required" })
|
|
|
|
|
.min(5, { message: "Journalist certificate number must be at least 5 characters" }),
|
|
|
|
|
association: z
|
|
|
|
|
.string()
|
|
|
|
|
.min(1, { message: "Association is required" }),
|
|
|
|
|
email: z
|
|
|
|
|
.string()
|
|
|
|
|
.min(1, { message: "Email is required" })
|
|
|
|
|
.email({ message: "Please enter a valid email address" }),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const personnelRegistrationSchema = z.object({
|
|
|
|
|
policeNumber: z
|
|
|
|
|
.string()
|
|
|
|
|
.min(1, { message: "Police number is required" })
|
|
|
|
|
.min(5, { message: "Police number must be at least 5 characters" }),
|
|
|
|
|
email: z
|
|
|
|
|
.string()
|
|
|
|
|
.min(1, { message: "Email is required" })
|
|
|
|
|
.email({ message: "Please enter a valid email address" }),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const generalRegistrationSchema = z.object({
|
|
|
|
|
email: z
|
|
|
|
|
.string()
|
|
|
|
|
.min(1, { message: "Email is required" })
|
|
|
|
|
.email({ message: "Please enter a valid email address" }),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const instituteSchema = z.object({
|
2025-07-14 10:14:20 +00:00
|
|
|
id: z.string(),
|
2025-07-12 21:14:12 +00:00
|
|
|
name: z
|
|
|
|
|
.string()
|
|
|
|
|
.min(1, { message: "Institute name is required" })
|
|
|
|
|
.min(2, { message: "Institute name must be at least 2 characters" }),
|
|
|
|
|
address: z
|
|
|
|
|
.string()
|
|
|
|
|
.min(1, { message: "Institute address is required" })
|
|
|
|
|
.min(10, { message: "Institute address must be at least 10 characters" }),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Inferred types from schemas
|
|
|
|
|
export type RegistrationFormData = z.infer<typeof registrationSchema>;
|
|
|
|
|
export type JournalistRegistrationData = z.infer<typeof journalistRegistrationSchema>;
|
|
|
|
|
export type PersonnelRegistrationData = z.infer<typeof personnelRegistrationSchema>;
|
|
|
|
|
export type GeneralRegistrationData = z.infer<typeof generalRegistrationSchema>;
|
|
|
|
|
export type InstituteData = z.infer<typeof instituteSchema>;
|
|
|
|
|
|
|
|
|
|
// API response types
|
|
|
|
|
export interface RegistrationResponse {
|
|
|
|
|
data?: {
|
|
|
|
|
id: string;
|
|
|
|
|
message: string;
|
|
|
|
|
};
|
|
|
|
|
error?: boolean;
|
|
|
|
|
message?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface OTPRequestResponse {
|
|
|
|
|
data?: {
|
|
|
|
|
message: string;
|
|
|
|
|
};
|
|
|
|
|
error?: boolean;
|
|
|
|
|
message?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface OTPVerificationResponse {
|
|
|
|
|
data?: {
|
|
|
|
|
message: string;
|
|
|
|
|
userData?: any;
|
|
|
|
|
};
|
|
|
|
|
error?: boolean;
|
|
|
|
|
message?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface InstituteResponse {
|
|
|
|
|
data?: {
|
|
|
|
|
data: InstituteData[];
|
|
|
|
|
};
|
|
|
|
|
error?: boolean;
|
|
|
|
|
message?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface LocationData {
|
|
|
|
|
id: number;
|
|
|
|
|
name: string;
|
|
|
|
|
provName?: string;
|
|
|
|
|
cityName?: string;
|
|
|
|
|
disName?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface LocationResponse {
|
|
|
|
|
data?: {
|
|
|
|
|
data: LocationData[];
|
|
|
|
|
};
|
|
|
|
|
error?: boolean;
|
|
|
|
|
message?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Registration step types
|
|
|
|
|
export type RegistrationStep = "identity" | "otp" | "profile";
|
|
|
|
|
|
|
|
|
|
// User category types
|
|
|
|
|
export type UserCategory = "6" | "7" | "general"; // 6=Journalist, 7=Personnel, general=Public
|
|
|
|
|
|
|
|
|
|
// Component props types
|
|
|
|
|
export interface RegistrationLayoutProps {
|
|
|
|
|
children: React.ReactNode;
|
|
|
|
|
currentStep: RegistrationStep;
|
|
|
|
|
totalSteps: number;
|
|
|
|
|
className?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface IdentityFormProps {
|
|
|
|
|
category: UserCategory;
|
|
|
|
|
onSuccess: (data: JournalistRegistrationData | PersonnelRegistrationData | GeneralRegistrationData) => void;
|
|
|
|
|
onError: (error: string) => void;
|
|
|
|
|
className?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface RegistrationOTPFormProps {
|
|
|
|
|
email: string;
|
|
|
|
|
category: UserCategory;
|
|
|
|
|
memberIdentity?: string;
|
|
|
|
|
onSuccess: (userData: any) => void;
|
|
|
|
|
onError: (error: string) => void;
|
|
|
|
|
onResend: () => void;
|
|
|
|
|
className?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface ProfileFormProps {
|
|
|
|
|
userData: any;
|
|
|
|
|
category: UserCategory;
|
|
|
|
|
onSuccess: (data: RegistrationFormData) => void;
|
|
|
|
|
onError: (error: string) => void;
|
|
|
|
|
className?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface InstituteFormProps {
|
|
|
|
|
onInstituteChange: (institute: InstituteData | null) => void;
|
|
|
|
|
className?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface LocationSelectorProps {
|
|
|
|
|
onProvinceChange: (provinceId: string) => void;
|
|
|
|
|
onCityChange: (cityId: string) => void;
|
|
|
|
|
onDistrictChange: (districtId: string) => void;
|
|
|
|
|
selectedProvince?: string;
|
|
|
|
|
selectedCity?: string;
|
|
|
|
|
selectedDistrict?: string;
|
|
|
|
|
className?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Registration state types
|
|
|
|
|
export interface RegistrationState {
|
|
|
|
|
currentStep: RegistrationStep;
|
|
|
|
|
category: UserCategory;
|
|
|
|
|
identityData: JournalistRegistrationData | PersonnelRegistrationData | GeneralRegistrationData | null;
|
|
|
|
|
userData: any;
|
|
|
|
|
loading: boolean;
|
|
|
|
|
error: string | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface RegistrationContextType extends RegistrationState {
|
|
|
|
|
setStep: (step: RegistrationStep) => void;
|
|
|
|
|
setIdentityData: (data: JournalistRegistrationData | PersonnelRegistrationData | GeneralRegistrationData) => void;
|
|
|
|
|
setUserData: (data: any) => void;
|
|
|
|
|
reset: () => void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Association types
|
|
|
|
|
export interface Association {
|
|
|
|
|
id: string;
|
|
|
|
|
name: string;
|
|
|
|
|
value: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Timer types
|
|
|
|
|
export interface TimerState {
|
|
|
|
|
countdown: number;
|
|
|
|
|
isActive: boolean;
|
|
|
|
|
isExpired: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Password validation types
|
|
|
|
|
export interface PasswordValidation {
|
|
|
|
|
isValid: boolean;
|
|
|
|
|
errors: string[];
|
|
|
|
|
strength: 'weak' | 'medium' | 'strong';
|
|
|
|
|
}
|