Remove passkey requirement from user onboarding flow Update UI badges to show passkeys as optional rather than pending Update documentation to reflect the new behavior
110 lines
2.5 KiB
TypeScript
110 lines
2.5 KiB
TypeScript
export const DEFAULT_USER_PASSWORD = '123456'
|
|
export const MIN_PASSWORD_LENGTH = 8
|
|
export const MIN_FULL_NAME_LENGTH = 2
|
|
export const USERNAME_PATTERN = /^[a-z0-9._-]{3,32}$/
|
|
export const PHONE_NUMBER_PATTERN = /^\+?\d{8,15}$/
|
|
export const DEFAULT_PHONE_COUNTRY_CODE = '+60'
|
|
|
|
export type UserRole = 'super_admin' | 'staff'
|
|
|
|
export function normalizeUsername(value: string) {
|
|
return value.trim().toLowerCase()
|
|
}
|
|
|
|
export function normalizeFullName(value: string) {
|
|
return value.trim()
|
|
}
|
|
|
|
export function normalizePhoneNumber(value: string) {
|
|
const trimmed = value.trim()
|
|
|
|
if (!trimmed) {
|
|
return ''
|
|
}
|
|
|
|
const hasPlusPrefix = trimmed.startsWith('+')
|
|
const digitsOnly = trimmed.replace(/\D/g, '')
|
|
|
|
if (!digitsOnly) {
|
|
return ''
|
|
}
|
|
|
|
if (hasPlusPrefix) {
|
|
return `+${digitsOnly}`
|
|
}
|
|
|
|
const defaultCountryDigits = DEFAULT_PHONE_COUNTRY_CODE.replace(/\D/g, '')
|
|
|
|
if (digitsOnly.startsWith(defaultCountryDigits)) {
|
|
return `+${digitsOnly}`
|
|
}
|
|
|
|
return `+${defaultCountryDigits}${digitsOnly.replace(/^0+/, '')}`
|
|
}
|
|
|
|
export function isValidPhoneNumber(value: string) {
|
|
return PHONE_NUMBER_PATTERN.test(normalizePhoneNumber(value))
|
|
}
|
|
|
|
export function isValidUsername(value: string) {
|
|
return USERNAME_PATTERN.test(normalizeUsername(value))
|
|
}
|
|
|
|
export function hasValidFullName(value: string) {
|
|
return normalizeFullName(value).length >= MIN_FULL_NAME_LENGTH
|
|
}
|
|
|
|
export function isUserRole(value: string | null | undefined): value is UserRole {
|
|
return value === 'super_admin' || value === 'staff'
|
|
}
|
|
|
|
export interface AuthUser {
|
|
id: string
|
|
username: string
|
|
fullName: string
|
|
phoneNumber: string | null
|
|
role: UserRole
|
|
isActive: boolean
|
|
mustChangePassword: boolean
|
|
needsPasskeySetup: boolean
|
|
passkeyCount: number
|
|
createdAt: string
|
|
lastLoginAt: string | null
|
|
}
|
|
|
|
export interface ManagedUser extends AuthUser {
|
|
createdBy: string | null
|
|
}
|
|
|
|
export interface PublicContact {
|
|
id: string
|
|
fullName: string
|
|
phoneNumber: string
|
|
role: UserRole
|
|
}
|
|
|
|
export interface PasskeySummary {
|
|
id: string
|
|
label: string
|
|
createdAt: string
|
|
lastUsedAt: string | null
|
|
deviceType: 'singleDevice' | 'multiDevice'
|
|
backedUp: boolean
|
|
}
|
|
|
|
export function needsUserOnboarding(
|
|
user: Pick<AuthUser, 'mustChangePassword'> | null | undefined
|
|
) {
|
|
return Boolean(user?.mustChangePassword)
|
|
}
|
|
|
|
export function getDefaultAuthenticatedPath(
|
|
user: Pick<AuthUser, 'role' | 'mustChangePassword'>
|
|
) {
|
|
if (needsUserOnboarding(user)) {
|
|
return '/security'
|
|
}
|
|
|
|
return user.role === 'super_admin' ? '/management/users' : '/bookings'
|
|
}
|