Add regex to names and move frontendvaliadtion to separate file
This commit is contained in:
parent
5535ef672c
commit
95bacea624
5 changed files with 107 additions and 40 deletions
|
|
@ -8,14 +8,20 @@ public class ParticipantRegistrationRequest
|
|||
public bool? IsMember { get; set; }
|
||||
|
||||
[Required]
|
||||
[MaxLength(30)]
|
||||
[RegularExpression(@"^[\p{L}]+([-\s][\p{L}]+)?$",
|
||||
ErrorMessage = "Förnamn får endast innehålla bokstäver, ett bindestreck eller ett mellanslag.")]
|
||||
public string FirstName { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[MaxLength(30)]
|
||||
[RegularExpression(@"^[\p{L}]+([-\s][\p{L}]+)?$",
|
||||
ErrorMessage = "Efternamn får endast innehålla bokstäver, ett bindestreck eller ett mellanslag.")]
|
||||
public string SurName { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[RegularExpression(@"^([1-9]|Gymnasium [1-3])$",
|
||||
ErrorMessage = "Grade must be 1–9 or Gymnasium 1–3.")]
|
||||
ErrorMessage = "Årkurs måste vara 1–9 eller Gymnasium 1–3.")]
|
||||
public string Grade { get; set; } = string.Empty;
|
||||
|
||||
public string? PhoneNumber { get; set; }
|
||||
|
|
@ -23,6 +29,9 @@ public class ParticipantRegistrationRequest
|
|||
public string? Email { get; set; }
|
||||
|
||||
[Required]
|
||||
[MaxLength(61)]
|
||||
[RegularExpression(@"^[\p{L}]+([-\s][\p{L}]+)*$",
|
||||
ErrorMessage = "Vårdnadshavares namn får endast innehålla bokstäver, bindestreck och mellanslag.")]
|
||||
public string GuardianName { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
|
|
@ -36,6 +45,8 @@ public class ParticipantRegistrationRequest
|
|||
public bool? IsVisitor { get; set; }
|
||||
|
||||
[Required]
|
||||
[Range(typeof(bool), "true", "true",
|
||||
ErrorMessage = "GDPR-godkännande krävs.")]
|
||||
public bool? HasApprovedGdpr { get; set; }
|
||||
|
||||
public string? Friends { get; set; }
|
||||
|
|
|
|||
|
|
@ -5,9 +5,15 @@ namespace Registration.API.RequestModels;
|
|||
public class VolunteerRegistrationRequest
|
||||
{
|
||||
[Required]
|
||||
[MaxLength(30)]
|
||||
[RegularExpression(@"^[\p{L}]+([-\s][\p{L}]+)?$",
|
||||
ErrorMessage = "Förnamn får endast innehålla bokstäver, ett bindestreck eller ett mellanslag.")]
|
||||
public string FirstName { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[MaxLength(30)]
|
||||
[RegularExpression(@"^[\p{L}]+([-\s][\p{L}]+)?$",
|
||||
ErrorMessage = "Efternamn får endast innehålla bokstäver, ett bindestreck eller ett mellanslag.")]
|
||||
public string SurName { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
|
|
@ -18,6 +24,8 @@ public class VolunteerRegistrationRequest
|
|||
public string Email { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[Range(typeof(bool), "true", "true",
|
||||
ErrorMessage = "GDPR-godkännande krävs.")]
|
||||
public bool? HasApprovedGdpr { get; set; }
|
||||
|
||||
[Required]
|
||||
|
|
|
|||
36
src/Web/lan-frontend/app/lib/validation.ts
Normal file
36
src/Web/lan-frontend/app/lib/validation.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
export const normalizeSsn = (value: string) =>
|
||||
value.replace(/[\s\-+]/g, "").replace(/\D/g, "");
|
||||
|
||||
export const isValidSsnFormat = (value: string) => {
|
||||
if (!/^\d{12}$/.test(value)) return false;
|
||||
const month = parseInt(value.substring(4, 6));
|
||||
const day = parseInt(value.substring(6, 8));
|
||||
return month >= 1 && month <= 12 && day >= 1 && day <= 31;
|
||||
};
|
||||
|
||||
export const getAgeFromSsn = (value: string) => {
|
||||
const birthYear = parseInt(value.substring(0, 4));
|
||||
const currentYear = new Date().getFullYear();
|
||||
return currentYear - birthYear;
|
||||
};
|
||||
|
||||
export const isValidName = (value: string) => {
|
||||
if (!/^[\p{L}\-\s]+$/u.test(value)) return false;
|
||||
if ((value.match(/-/g) ?? []).length > 1) return false;
|
||||
if ((value.match(/\s/g) ?? []).length > 1) return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
export const isValidEmail = (value: string) =>
|
||||
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
|
||||
|
||||
export const normalizeMobileNumber = (value: string) => {
|
||||
const digitsOnly = value.replace(/\D/g, "");
|
||||
if (digitsOnly.startsWith("0046")) return `0${digitsOnly.slice(4)}`;
|
||||
if (digitsOnly.startsWith("46")) return `0${digitsOnly.slice(2)}`;
|
||||
if (digitsOnly.startsWith("7")) return `0${digitsOnly}`;
|
||||
return digitsOnly;
|
||||
};
|
||||
|
||||
export const isValidMobileNumber = (value: string) =>
|
||||
/^07\d{8}$/.test(normalizeMobileNumber(value));
|
||||
|
|
@ -5,6 +5,15 @@ import { useEffect, useRef, useState } from "react";
|
|||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import {
|
||||
normalizeSsn,
|
||||
isValidSsnFormat,
|
||||
getAgeFromSsn,
|
||||
isValidName,
|
||||
isValidEmail,
|
||||
normalizeMobileNumber,
|
||||
isValidMobileNumber,
|
||||
} from "@/app/lib/validation";
|
||||
|
||||
interface EventContent {
|
||||
registrationEnabled: boolean;
|
||||
|
|
@ -12,33 +21,6 @@ interface EventContent {
|
|||
volunteerAreas: string;
|
||||
}
|
||||
|
||||
const normalizeSsn = (value: string) =>
|
||||
value.replace(/[\s\-+]/g, "").replace(/\D/g, "");
|
||||
|
||||
const isValidSsnFormat = (value: string) => {
|
||||
if (!/^\d{12}$/.test(value)) return false;
|
||||
const month = parseInt(value.substring(4, 6));
|
||||
const day = parseInt(value.substring(6, 8));
|
||||
return month >= 1 && month <= 12 && day >= 1 && day <= 31;
|
||||
};
|
||||
|
||||
const getAgeFromSsn = (value: string) => {
|
||||
const birthYear = parseInt(value.substring(0, 4));
|
||||
const currentYear = new Date().getFullYear();
|
||||
return currentYear - birthYear;
|
||||
};
|
||||
const isValidEmail = (value: string) =>
|
||||
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
|
||||
const normalizeMobileNumber = (value: string) => {
|
||||
const digitsOnly = value.replace(/\D/g, "");
|
||||
if (digitsOnly.startsWith("0046")) return `0${digitsOnly.slice(4)}`;
|
||||
if (digitsOnly.startsWith("46")) return `0${digitsOnly.slice(2)}`;
|
||||
if (digitsOnly.startsWith("7")) return `0${digitsOnly}`;
|
||||
return digitsOnly;
|
||||
};
|
||||
const isValidMobileNumber = (value: string) =>
|
||||
/^07\d{8}$/.test(normalizeMobileNumber(value));
|
||||
|
||||
export default function RegisterPage() {
|
||||
const router = useRouter();
|
||||
const [content, setContent] = useState<EventContent | null>(null);
|
||||
|
|
@ -154,15 +136,37 @@ export default function RegisterPage() {
|
|||
|
||||
if (!trimmedFirstName)
|
||||
nextFieldErrors.firstName = "Förnamn är obligatoriskt.";
|
||||
else if (trimmedFirstName.length > 30)
|
||||
nextFieldErrors.firstName = "Förnamn får vara max 30 tecken.";
|
||||
else if (!isValidName(trimmedFirstName))
|
||||
nextFieldErrors.firstName =
|
||||
"Förnamn får endast innehålla bokstäver och bindestreck.";
|
||||
if (!trimmedSurName)
|
||||
nextFieldErrors.surName = "Efternamn är obligatoriskt.";
|
||||
else if (trimmedSurName.length > 30)
|
||||
nextFieldErrors.surName = "Efternamn får vara max 30 tecken.";
|
||||
else if (!isValidName(trimmedSurName))
|
||||
nextFieldErrors.surName =
|
||||
"Efternamn får endast innehålla bokstäver och bindestreck.";
|
||||
if (!trimmedGrade) nextFieldErrors.grade = "Årskurs är obligatorisk.";
|
||||
if (!trimmedGuardianFirstName)
|
||||
nextFieldErrors.guardianFirstName =
|
||||
"Vårdnadshavares förnamn är obligatoriskt.";
|
||||
else if (trimmedGuardianFirstName.length > 30)
|
||||
nextFieldErrors.guardianFirstName =
|
||||
"Vårdnadshavares förnamn får vara max 30 tecken.";
|
||||
else if (!isValidName(trimmedGuardianFirstName))
|
||||
nextFieldErrors.guardianFirstName =
|
||||
"Vårdnadshavares förnamn får endast innehålla bokstäver och bindestreck.";
|
||||
if (!trimmedGuardianLastName)
|
||||
nextFieldErrors.guardianLastName =
|
||||
"Vårdnadshavares efternamn är obligatoriskt.";
|
||||
else if (trimmedGuardianLastName.length > 30)
|
||||
nextFieldErrors.guardianLastName =
|
||||
"Vårdnadshavares efternamn får vara max 30 tecken.";
|
||||
else if (!isValidName(trimmedGuardianLastName))
|
||||
nextFieldErrors.guardianLastName =
|
||||
"Vårdnadshavares efternamn får endast innehålla bokstäver och bindestreck.";
|
||||
if (!trimmedGuardianPhone)
|
||||
nextFieldErrors.guardianPhoneNumber =
|
||||
"Vårdnadshavares Mobilnummer är obligatoriskt.";
|
||||
|
|
@ -388,6 +392,7 @@ export default function RegisterPage() {
|
|||
name="firstName"
|
||||
id="firstName"
|
||||
required
|
||||
maxLength={30}
|
||||
className={`mt-1 block w-full rounded-md shadow-sm sm:text-sm p-2 border text-gray-900 ${fieldErrors.firstName ? "border-red-500 focus:border-red-500 focus:ring-red-500" : "border-gray-300 focus:border-blue-500 focus:ring-blue-500"}`}
|
||||
value={formData.firstName}
|
||||
onChange={handleInputChange}
|
||||
|
|
@ -411,6 +416,7 @@ export default function RegisterPage() {
|
|||
name="surName"
|
||||
id="surName"
|
||||
required
|
||||
maxLength={30}
|
||||
className={`mt-1 block w-full rounded-md shadow-sm sm:text-sm p-2 border text-gray-900 ${fieldErrors.surName ? "border-red-500 focus:border-red-500 focus:ring-red-500" : "border-gray-300 focus:border-blue-500 focus:ring-blue-500"}`}
|
||||
value={formData.surName}
|
||||
onChange={handleInputChange}
|
||||
|
|
@ -520,6 +526,7 @@ export default function RegisterPage() {
|
|||
name="guardianFirstName"
|
||||
id="guardianFirstName"
|
||||
required
|
||||
maxLength={30}
|
||||
className={`mt-1 block w-full rounded-md shadow-sm sm:text-sm p-2 border text-gray-900 ${fieldErrors.guardianFirstName ? "border-red-500 focus:border-red-500 focus:ring-red-500" : "border-gray-300 focus:border-blue-500 focus:ring-blue-500"}`}
|
||||
value={formData.guardianFirstName}
|
||||
onChange={handleInputChange}
|
||||
|
|
@ -543,6 +550,7 @@ export default function RegisterPage() {
|
|||
name="guardianLastName"
|
||||
id="guardianLastName"
|
||||
required
|
||||
maxLength={30}
|
||||
className={`mt-1 block w-full rounded-md shadow-sm sm:text-sm p-2 border text-gray-900 ${fieldErrors.guardianLastName ? "border-red-500 focus:border-red-500 focus:ring-red-500" : "border-gray-300 focus:border-blue-500 focus:ring-blue-500"}`}
|
||||
value={formData.guardianLastName}
|
||||
onChange={handleInputChange}
|
||||
|
|
|
|||
|
|
@ -3,24 +3,18 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/navigation";
|
||||
import {
|
||||
isValidName,
|
||||
isValidEmail,
|
||||
normalizeMobileNumber,
|
||||
isValidMobileNumber,
|
||||
} from "@/app/lib/validation";
|
||||
|
||||
interface EventContent {
|
||||
volunteerAreas: string;
|
||||
volunteerRegistrationEnabled?: boolean;
|
||||
}
|
||||
|
||||
const isValidEmail = (value: string) =>
|
||||
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
|
||||
const normalizeMobileNumber = (value: string) => {
|
||||
const digitsOnly = value.replace(/\D/g, "");
|
||||
if (digitsOnly.startsWith("0046")) return `0${digitsOnly.slice(4)}`;
|
||||
if (digitsOnly.startsWith("46")) return `0${digitsOnly.slice(2)}`;
|
||||
if (digitsOnly.startsWith("7")) return `0${digitsOnly}`;
|
||||
return digitsOnly;
|
||||
};
|
||||
const isValidMobileNumber = (value: string) =>
|
||||
/^07\d{8}$/.test(normalizeMobileNumber(value));
|
||||
|
||||
export default function VolunteerPage() {
|
||||
const router = useRouter();
|
||||
const [content, setContent] = useState<EventContent | null>(null);
|
||||
|
|
@ -104,8 +98,16 @@ export default function VolunteerPage() {
|
|||
|
||||
if (!trimmedFirstName)
|
||||
nextFieldErrors.firstName = "Förnamn är obligatoriskt.";
|
||||
else if (trimmedFirstName.length > 30)
|
||||
nextFieldErrors.firstName = "Förnamn får vara max 30 tecken.";
|
||||
else if (!isValidName(trimmedFirstName))
|
||||
nextFieldErrors.firstName = "Förnamn får endast innehålla bokstäver och bindestreck.";
|
||||
if (!trimmedSurName)
|
||||
nextFieldErrors.surName = "Efternamn är obligatoriskt.";
|
||||
else if (trimmedSurName.length > 30)
|
||||
nextFieldErrors.surName = "Efternamn får vara max 30 tecken.";
|
||||
else if (!isValidName(trimmedSurName))
|
||||
nextFieldErrors.surName = "Efternamn får endast innehålla bokstäver och bindestreck.";
|
||||
if (!trimmedPhoneNumber)
|
||||
nextFieldErrors.phoneNumber = "Mobilnummer är obligatoriskt.";
|
||||
if (!trimmedEmail) nextFieldErrors.email = "E-post är obligatorisk.";
|
||||
|
|
@ -239,6 +241,7 @@ export default function VolunteerPage() {
|
|||
name="firstName"
|
||||
id="firstName"
|
||||
required
|
||||
maxLength={30}
|
||||
className={`mt-1 block w-full rounded-md shadow-sm sm:text-sm p-2 border text-gray-900 ${fieldErrors.firstName ? "border-red-500 focus:border-red-500 focus:ring-red-500" : "border-gray-300 focus:border-blue-500 focus:ring-blue-500"}`}
|
||||
value={formData.firstName}
|
||||
onChange={handleInputChange}
|
||||
|
|
@ -262,6 +265,7 @@ export default function VolunteerPage() {
|
|||
name="surName"
|
||||
id="surName"
|
||||
required
|
||||
maxLength={30}
|
||||
className={`mt-1 block w-full rounded-md shadow-sm sm:text-sm p-2 border text-gray-900 ${fieldErrors.surName ? "border-red-500 focus:border-red-500 focus:ring-red-500" : "border-gray-300 focus:border-blue-500 focus:ring-blue-500"}`}
|
||||
value={formData.surName}
|
||||
onChange={handleInputChange}
|
||||
|
|
|
|||
Loading…
Reference in a new issue