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; }
|
public bool? IsMember { get; set; }
|
||||||
|
|
||||||
[Required]
|
[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;
|
public string FirstName { get; set; } = string.Empty;
|
||||||
|
|
||||||
[Required]
|
[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;
|
public string SurName { get; set; } = string.Empty;
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
[RegularExpression(@"^([1-9]|Gymnasium [1-3])$",
|
[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 Grade { get; set; } = string.Empty;
|
||||||
|
|
||||||
public string? PhoneNumber { get; set; }
|
public string? PhoneNumber { get; set; }
|
||||||
|
|
@ -23,6 +29,9 @@ public class ParticipantRegistrationRequest
|
||||||
public string? Email { get; set; }
|
public string? Email { get; set; }
|
||||||
|
|
||||||
[Required]
|
[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;
|
public string GuardianName { get; set; } = string.Empty;
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
|
|
@ -36,6 +45,8 @@ public class ParticipantRegistrationRequest
|
||||||
public bool? IsVisitor { get; set; }
|
public bool? IsVisitor { get; set; }
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
|
[Range(typeof(bool), "true", "true",
|
||||||
|
ErrorMessage = "GDPR-godkännande krävs.")]
|
||||||
public bool? HasApprovedGdpr { get; set; }
|
public bool? HasApprovedGdpr { get; set; }
|
||||||
|
|
||||||
public string? Friends { get; set; }
|
public string? Friends { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,15 @@ namespace Registration.API.RequestModels;
|
||||||
public class VolunteerRegistrationRequest
|
public class VolunteerRegistrationRequest
|
||||||
{
|
{
|
||||||
[Required]
|
[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;
|
public string FirstName { get; set; } = string.Empty;
|
||||||
|
|
||||||
[Required]
|
[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;
|
public string SurName { get; set; } = string.Empty;
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
|
|
@ -18,6 +24,8 @@ public class VolunteerRegistrationRequest
|
||||||
public string Email { get; set; } = string.Empty;
|
public string Email { get; set; } = string.Empty;
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
|
[Range(typeof(bool), "true", "true",
|
||||||
|
ErrorMessage = "GDPR-godkännande krävs.")]
|
||||||
public bool? HasApprovedGdpr { get; set; }
|
public bool? HasApprovedGdpr { get; set; }
|
||||||
|
|
||||||
[Required]
|
[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 Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
import {
|
||||||
|
normalizeSsn,
|
||||||
|
isValidSsnFormat,
|
||||||
|
getAgeFromSsn,
|
||||||
|
isValidName,
|
||||||
|
isValidEmail,
|
||||||
|
normalizeMobileNumber,
|
||||||
|
isValidMobileNumber,
|
||||||
|
} from "@/app/lib/validation";
|
||||||
|
|
||||||
interface EventContent {
|
interface EventContent {
|
||||||
registrationEnabled: boolean;
|
registrationEnabled: boolean;
|
||||||
|
|
@ -12,33 +21,6 @@ interface EventContent {
|
||||||
volunteerAreas: string;
|
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() {
|
export default function RegisterPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [content, setContent] = useState<EventContent | null>(null);
|
const [content, setContent] = useState<EventContent | null>(null);
|
||||||
|
|
@ -154,15 +136,37 @@ export default function RegisterPage() {
|
||||||
|
|
||||||
if (!trimmedFirstName)
|
if (!trimmedFirstName)
|
||||||
nextFieldErrors.firstName = "Förnamn är obligatoriskt.";
|
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)
|
if (!trimmedSurName)
|
||||||
nextFieldErrors.surName = "Efternamn är obligatoriskt.";
|
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 (!trimmedGrade) nextFieldErrors.grade = "Årskurs är obligatorisk.";
|
||||||
if (!trimmedGuardianFirstName)
|
if (!trimmedGuardianFirstName)
|
||||||
nextFieldErrors.guardianFirstName =
|
nextFieldErrors.guardianFirstName =
|
||||||
"Vårdnadshavares förnamn är obligatoriskt.";
|
"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)
|
if (!trimmedGuardianLastName)
|
||||||
nextFieldErrors.guardianLastName =
|
nextFieldErrors.guardianLastName =
|
||||||
"Vårdnadshavares efternamn är obligatoriskt.";
|
"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)
|
if (!trimmedGuardianPhone)
|
||||||
nextFieldErrors.guardianPhoneNumber =
|
nextFieldErrors.guardianPhoneNumber =
|
||||||
"Vårdnadshavares Mobilnummer är obligatoriskt.";
|
"Vårdnadshavares Mobilnummer är obligatoriskt.";
|
||||||
|
|
@ -388,6 +392,7 @@ export default function RegisterPage() {
|
||||||
name="firstName"
|
name="firstName"
|
||||||
id="firstName"
|
id="firstName"
|
||||||
required
|
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"}`}
|
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}
|
value={formData.firstName}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
|
|
@ -411,6 +416,7 @@ export default function RegisterPage() {
|
||||||
name="surName"
|
name="surName"
|
||||||
id="surName"
|
id="surName"
|
||||||
required
|
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"}`}
|
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}
|
value={formData.surName}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
|
|
@ -520,6 +526,7 @@ export default function RegisterPage() {
|
||||||
name="guardianFirstName"
|
name="guardianFirstName"
|
||||||
id="guardianFirstName"
|
id="guardianFirstName"
|
||||||
required
|
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"}`}
|
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}
|
value={formData.guardianFirstName}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
|
|
@ -543,6 +550,7 @@ export default function RegisterPage() {
|
||||||
name="guardianLastName"
|
name="guardianLastName"
|
||||||
id="guardianLastName"
|
id="guardianLastName"
|
||||||
required
|
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"}`}
|
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}
|
value={formData.guardianLastName}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
|
|
|
||||||
|
|
@ -3,24 +3,18 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
import {
|
||||||
|
isValidName,
|
||||||
|
isValidEmail,
|
||||||
|
normalizeMobileNumber,
|
||||||
|
isValidMobileNumber,
|
||||||
|
} from "@/app/lib/validation";
|
||||||
|
|
||||||
interface EventContent {
|
interface EventContent {
|
||||||
volunteerAreas: string;
|
volunteerAreas: string;
|
||||||
volunteerRegistrationEnabled?: boolean;
|
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() {
|
export default function VolunteerPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [content, setContent] = useState<EventContent | null>(null);
|
const [content, setContent] = useState<EventContent | null>(null);
|
||||||
|
|
@ -104,8 +98,16 @@ export default function VolunteerPage() {
|
||||||
|
|
||||||
if (!trimmedFirstName)
|
if (!trimmedFirstName)
|
||||||
nextFieldErrors.firstName = "Förnamn är obligatoriskt.";
|
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)
|
if (!trimmedSurName)
|
||||||
nextFieldErrors.surName = "Efternamn är obligatoriskt.";
|
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)
|
if (!trimmedPhoneNumber)
|
||||||
nextFieldErrors.phoneNumber = "Mobilnummer är obligatoriskt.";
|
nextFieldErrors.phoneNumber = "Mobilnummer är obligatoriskt.";
|
||||||
if (!trimmedEmail) nextFieldErrors.email = "E-post är obligatorisk.";
|
if (!trimmedEmail) nextFieldErrors.email = "E-post är obligatorisk.";
|
||||||
|
|
@ -239,6 +241,7 @@ export default function VolunteerPage() {
|
||||||
name="firstName"
|
name="firstName"
|
||||||
id="firstName"
|
id="firstName"
|
||||||
required
|
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"}`}
|
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}
|
value={formData.firstName}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
|
|
@ -262,6 +265,7 @@ export default function VolunteerPage() {
|
||||||
name="surName"
|
name="surName"
|
||||||
id="surName"
|
id="surName"
|
||||||
required
|
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"}`}
|
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}
|
value={formData.surName}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue