Files
dticket.tootaio.com/shared/auth.ts
xiaomai c214d643dd feat: send ticket receipts via WhatsApp and normalize phone numbers
Add WhatsApp API integration for automated receipt delivery
Enforce country codes for all phone number inputs (defaults to +60)
2026-04-27 13:12:25 +08:00

110 lines
2.6 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' | 'needsPasskeySetup'> | null | undefined
) {
return Boolean(user && (user.mustChangePassword || user.needsPasskeySetup))
}
export function getDefaultAuthenticatedPath(
user: Pick<AuthUser, 'role' | 'mustChangePassword' | 'needsPasskeySetup'>
) {
if (needsUserOnboarding(user)) {
return '/security'
}
return user.role === 'super_admin' ? '/management/users' : '/bookings'
}