Add LynkedUp roofing portal: full login + registration auth UI
- Split-screen dark/orange auth shell with animated scan showcase - Login state machine: social/email, passkey, password, OTP (email/SMS/WhatsApp), TOTP, push, try-another-way, forgot-password, terms gate, success - 4-step registration wizard: Account, Property, Verify, Address - US-default phone + address with /api/geo lookup (PIN auto-fill / search) - Roofing-themed content, Inter font, logo from public/image Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
"use client";
|
||||
|
||||
import { GoogleMark, MicrosoftMark, AppleMark, Icon } from "./icons";
|
||||
|
||||
/** Left brand showcase panel for the split-screen auth layout. */
|
||||
export function PortalAside() {
|
||||
return (
|
||||
<aside className="portal-aside">
|
||||
<span className="scanline" />
|
||||
<div className="aside-top">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img className="logo-img" src="/image/logo.png" alt="Lynkedup Pro Portal" />
|
||||
<span className="eyebrow"><i className="pdot" /> Drone-powered</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2>
|
||||
Roofing,
|
||||
<br />
|
||||
Revolutionized.
|
||||
</h2>
|
||||
<p className="lede">
|
||||
AI-powered drone & LiDAR roof inspections, instant estimates and
|
||||
insurance-ready reports — for homeowners and contractors.
|
||||
</p>
|
||||
<div className="stat-strip">
|
||||
<div className="stat-chip"><b>5,847</b><span>Roofs scanned</span></div>
|
||||
<div className="stat-chip"><b>15 min</b><span>Avg estimate</span></div>
|
||||
<div className="stat-chip"><b>96%</b><span>Lead accuracy</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="preview-card">
|
||||
<div className="pc-row">
|
||||
<span className="pc-av">◎</span>
|
||||
<span className="pc-id">
|
||||
<b>Roof scan #8842-A</b>
|
||||
<span>LiDAR mapping · Verified</span>
|
||||
</span>
|
||||
<span className="pc-check"><Icon name="check" size={15} /></span>
|
||||
</div>
|
||||
<div className="pc-bar"><span style={{ width: "76%" }} /></div>
|
||||
<div className="pc-meta"><span>Structure integrity</span><span>76%</span></div>
|
||||
</div>
|
||||
|
||||
<div className="aside-trust">
|
||||
<span><Icon name="lock" size={13} /> 256-bit SSL</span>
|
||||
<span><Icon name="shield" size={13} /> Licensed & Insured</span>
|
||||
<span><Icon name="check" size={13} /> Drone-Powered</span>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
/** Compact logo shown above the form on small screens (aside is hidden). */
|
||||
export function PanelBrand() {
|
||||
return (
|
||||
<div className="panel-brand">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img src="/image/logo.png" alt="Lynkedup Pro Portal" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/** The three OAuth provider buttons used on login + register. */
|
||||
export function OAuthButtons({ onPick }: { onPick: (p: string) => void }) {
|
||||
return (
|
||||
<div className="col gap-3">
|
||||
<button className="btn btn-oauth" onClick={() => onPick("Google")}>
|
||||
<GoogleMark /> Continue with Google
|
||||
</button>
|
||||
<button className="btn btn-oauth" onClick={() => onPick("Microsoft")}>
|
||||
<MicrosoftMark /> Continue with Microsoft
|
||||
</button>
|
||||
<button className="btn btn-oauth" onClick={() => onPick("Apple")}>
|
||||
<AppleMark /> Continue with Apple
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function Divider({ label }: { label: string }) {
|
||||
return <div className="divider">{label}</div>;
|
||||
}
|
||||
|
||||
/** SSL / government / official-portal trust strip shown under the card. */
|
||||
export function TrustBadges() {
|
||||
return (
|
||||
<div className="trust">
|
||||
<span><Icon name="lock" size={14} /> 256-bit SSL</span>
|
||||
<span><Icon name="shield" size={14} /> Licensed & Insured</span>
|
||||
<span><Icon name="check" size={14} /> Drone-Powered</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/** A small "← email@…" chip shown on inner steps. */
|
||||
export function BackChip({ label, onBack }: { label: string; onBack: () => void }) {
|
||||
return (
|
||||
<button className="btn btn-sm" style={{ width: "auto", borderRadius: 999 }} onClick={onBack}>
|
||||
<Icon name="chevL" size={15} /> {label}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user