// ============================================================ // Seed / mock data + helpers for the Lynkedup Pro auth demo. // All lookups are client-side mocks so the journey is clickable. // ============================================================ export type Account = { firstName: string; name: string; initials: string; hasPasskey: boolean; hasTotp: boolean; hasPush: boolean; maskedEmail: string; maskedPhone: string; maskedWa: string; }; /* ---- mask helpers ---- */ export function maskEmail(email: string): string { const [user = "", domain = ""] = email.split("@"); if (!domain) return email; if (user.length <= 2) return `${user[0] ?? ""}••@${domain}`; return `${user[0]}${"•".repeat(Math.max(4, user.length - 2))}${user[user.length - 1]}@${domain}`; } export const MASKED_PHONE = "(•••) •••-••12"; export const MASKED_WA = "+91 ••••• ••012"; /* ---- demo account "on file" ---- */ export const DEMO_USER = { name: "James Carter", firstName: "James", lastName: "Carter", initials: "JC", allotmentNo: "YEA-654321", sector: "Sector 18", pocket: "Pocket B", plot: "181", category: "Residential", size: "300 sq.m", maskedMobile: MASKED_PHONE, email: "james.carter@example.com", relationship: "Self", isAllottee: true, }; /** * Look up an "on file" account by email. Returns null for emails that contain * "new", "unknown" or "notfound" (so the NOT-FOUND screen is reachable). */ export function lookupAccount(email: string): Account | null { const e = email.trim().toLowerCase(); if (!e || /(\bnew\b|unknown|notfound|new@)/.test(e) || e.startsWith("new")) return null; return { firstName: DEMO_USER.firstName, name: DEMO_USER.name, initials: DEMO_USER.initials, hasPasskey: true, hasTotp: true, hasPush: true, maskedEmail: maskEmail(email), maskedPhone: MASKED_PHONE, maskedWa: MASKED_WA, }; } /* ---- country codes ---- */ export type CountryCode = { code: string; flag: string; name: string; digits: number; example: string }; export const countryCodes: CountryCode[] = [ { code: "+1", flag: "🇺🇸", name: "United States", digits: 10, example: "201 555 0123" }, { code: "+91", flag: "🇮🇳", name: "India", digits: 10, example: "98765 43210" }, { code: "+44", flag: "🇬🇧", name: "United Kingdom", digits: 10, example: "7400 123456" }, { code: "+971", flag: "🇦🇪", name: "UAE", digits: 9, example: "50 123 4567" }, { code: "+65", flag: "🇸🇬", name: "Singapore", digits: 8, example: "8123 4567" }, { code: "+61", flag: "🇦🇺", name: "Australia", digits: 9, example: "412 345 678" }, ]; export const relationshipOptions = [ "Homeowner", "Contractor", "Property Manager", "Tenant", "Authorized Representative", ]; export const sectors = [ "Sector 17A", "Sector 18", "Sector 20", "Sector 22D", "Sector 22E", "Sector 24A", ]; export const pockets = ["Pocket A", "Pocket B", "Pocket C", "Pocket D"]; /* ---- address countries ---- */ export type AddrCountry = { code: string; name: string; lookup: "pincode" | "search" | "none"; postalLabel: string; postalLen: number; states: string[]; }; export const addressCountries: AddrCountry[] = [ { code: "US", name: "United States", lookup: "search", postalLabel: "ZIP code", postalLen: 5, states: ["California", "New York", "Texas", "Washington", "New Jersey", "Florida"] }, { code: "IN", name: "India", lookup: "pincode", postalLabel: "PIN code", postalLen: 6, states: ["Uttar Pradesh", "Delhi", "Haryana", "Maharashtra", "Karnataka", "Rajasthan"], }, { code: "GB", name: "United Kingdom", lookup: "search", postalLabel: "Postcode", postalLen: 7, states: [] }, { code: "AE", name: "United Arab Emirates", lookup: "none", postalLabel: "PO Box", postalLen: 6, states: ["Dubai", "Abu Dhabi", "Sharjah"] }, ]; /* ---- legal documents (own copy) ---- */ export type LegalDoc = { title: string; updated: string; sections: { h: string; p: string }[] }; export const TERMS: LegalDoc = { title: "Terms of Use", updated: "Last updated: June 2026", sections: [ { h: "1. Authorised use", p: "This portal is provided for property owners and their authorised representatives to manage roof inspections, estimates and records. You agree to access only accounts and records you are entitled to." }, { h: "2. Accuracy of records", p: "You agree to provide accurate, current and complete information during registration and to keep it updated. Records are matched against authority data." }, { h: "3. Data & privacy", p: "Your data is processed in line with our Privacy Policy. We collect only what is needed to verify identity and provide portal services." }, { h: "4. Security", p: "You are responsible for keeping your credentials, one-time codes and passkeys secure. Notify us immediately of any unauthorised access." }, { h: "5. Acceptable conduct", p: "You must not misuse the portal, attempt to bypass verification, or access it through automated means without permission." }, ], }; export const PRIVACY: LegalDoc = { title: "Privacy Policy", updated: "Last updated: June 2026", sections: [ { h: "1. What we collect", p: "Identity details, contact information, property identifiers and device/security signals required to protect your account." }, { h: "2. How we use it", p: "To verify your identity, prevent fraud, provide portal features and meet legal obligations." }, { h: "3. Sharing", p: "We share data only with the authority and processors acting on our behalf, under appropriate safeguards." }, { h: "4. Your rights", p: "You may request access, correction or deletion of your data, subject to record-retention requirements." }, { h: "5. Cookies", p: "We use essential cookies for security and sign-in, and optional cookies (with consent) for analytics." }, ], }; /* ---- password strength ---- */ export type Strength = { score: number; label: "Weak" | "Fair" | "Good" | "Strong"; checks: { ok: boolean; label: string }[] }; export function passwordStrength(pw: string): Strength { const checks = [ { ok: pw.length >= 8, label: "At least 8 characters" }, { ok: /[a-z]/.test(pw) && /[A-Z]/.test(pw), label: "Upper & lower case" }, { ok: /\d/.test(pw), label: "A number" }, { ok: /[^A-Za-z0-9]/.test(pw), label: "A special character" }, ]; const passed = checks.filter((c) => c.ok).length; const weakList = ["1234", "abcd", "password", "qwerty", "0000"]; const isWeak = weakList.some((w) => pw.toLowerCase().includes(w)); const score = isWeak ? Math.min(passed, 1) : passed; const label = score <= 1 ? "Weak" : score === 2 ? "Fair" : score === 3 ? "Good" : "Strong"; return { score, label, checks }; } /* ---- oauth ---- */ type Provider = "google" | "microsoft" | "apple"; export function buildOAuthUrl(provider: Provider): string | null { if (typeof window === "undefined") return null; const map = { google: { id: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID, auth: "https://accounts.google.com/o/oauth2/v2/auth", scope: "openid email profile" }, microsoft: { id: process.env.NEXT_PUBLIC_MS_CLIENT_ID, auth: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize", scope: "openid email profile" }, apple: { id: process.env.NEXT_PUBLIC_APPLE_CLIENT_ID, auth: "https://appleid.apple.com/auth/authorize", scope: "name email" }, } as const; const cfg = map[provider]; if (!cfg.id) return null; const rand = () => (crypto.randomUUID ? crypto.randomUUID() : String(Math.random()).slice(2)); const params = new URLSearchParams({ client_id: cfg.id, redirect_uri: `${window.location.origin}/portal/login`, response_type: "code", scope: cfg.scope, state: rand(), nonce: rand(), }); return `${cfg.auth}?${params.toString()}`; }