The scaffold had the brand right and nothing behind it: the login was a 600ms setTimeout with a TODO, and three pieces of the frontgen deploy contract were missing. - Sign in against POST /usr/login (org_id as a STRING — a number is rejected by the gateway) with the session in a provider; surface the gateway's own message rather than a generic "invalid credentials", because a wrong password and a user without access to this app look identical from here and are not. - Runtime config: the API URL is read from the config.js the server writes at placement, never compiled in, and requireConfigValue throws so a build with no config.js fails loudly instead of calling whichever backend built it. - base: './' plus a router basename taken from <base href>, so one build serves any mount path. - Move the fonts and logo from public/ into src/assets/ — Vite rewrites bundled asset URLs to be relative, while a public/ file referenced as "/fonts/..." stays absolute and 404s under the /zurich-kotak/ mount. - API client for recordview / detailview / form-screens / start / activity, and the pipeline shell whose sidebar is the state machine in the order a lead moves. Queue and lead-file data wait on the record and detail views. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
210 lines
6.3 KiB
JavaScript
210 lines
6.3 KiB
JavaScript
import { useState } from 'react'
|
|
import { useNavigate } from 'react-router-dom'
|
|
import { useZino } from '../api/provider.jsx'
|
|
import { ORG_ID } from '../api/config.js'
|
|
import logo from '../assets/brand/zurich_logo.webp'
|
|
import './Login.css'
|
|
|
|
const FEATURES = [
|
|
{
|
|
title: 'Instant policy issuance',
|
|
body: 'No inspection; hassle free policy',
|
|
icon: (
|
|
<>
|
|
<path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8l-5-5Z" />
|
|
<path d="M14 3v5h5" />
|
|
<path d="m9.2 14.2 1.9 1.9 3.7-3.7" />
|
|
</>
|
|
),
|
|
},
|
|
{
|
|
title: '24/7 claim assistance',
|
|
body: 'Making claims simple, transparent, and customer-first',
|
|
icon: (
|
|
<>
|
|
<path d="M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18Z" />
|
|
<path d="M12 7.2V12l3.2 1.9" />
|
|
</>
|
|
),
|
|
},
|
|
{
|
|
title: 'A trusted brand',
|
|
body: 'You can count on Zurich Kotak General Insurance at all times',
|
|
icon: (
|
|
<>
|
|
<path d="M12 3 4.8 5.9v5.4c0 4.3 3 8.3 7.2 9.4 4.2-1.1 7.2-5.1 7.2-9.4V5.9L12 3Z" />
|
|
<path d="m9.2 12.1 2 2 3.6-3.8" />
|
|
</>
|
|
),
|
|
},
|
|
]
|
|
|
|
export default function Login() {
|
|
const [email, setEmail] = useState('')
|
|
const [password, setPassword] = useState('')
|
|
const [error, setError] = useState('')
|
|
const [submitting, setSubmitting] = useState(false)
|
|
const { signIn } = useZino()
|
|
const navigate = useNavigate()
|
|
|
|
async function handleSubmit(event) {
|
|
event.preventDefault()
|
|
setError('')
|
|
|
|
if (!email.trim() || !password) {
|
|
setError('Enter your email and password to continue.')
|
|
return
|
|
}
|
|
|
|
setSubmitting(true)
|
|
try {
|
|
// org_id must be a STRING — the gateway rejects a number with
|
|
// "cannot unmarshal number into Go struct field LoginRequest.org_id".
|
|
await signIn(email.trim(), password, ORG_ID)
|
|
navigate('/', { replace: true })
|
|
} catch (err) {
|
|
// Surface what the gateway actually said. "Invalid credentials" would
|
|
// hide the two failures that look identical from here and are not:
|
|
// a wrong password, and a user with no access to this app.
|
|
setError(err?.message || 'We could not sign you in. Please try again.')
|
|
} finally {
|
|
setSubmitting(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<main className="login">
|
|
<section className="login__brand">
|
|
<div className="login__grid" aria-hidden="true" />
|
|
<div className="login__glow" aria-hidden="true" />
|
|
|
|
<img
|
|
className="login__logo"
|
|
src={logo}
|
|
alt="Zurich Kotak General Insurance"
|
|
/>
|
|
|
|
<div className="login__brand-body">
|
|
<span className="login__badge">
|
|
<svg
|
|
viewBox="0 0 24 24"
|
|
aria-hidden="true"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
strokeWidth="1.6"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
>
|
|
<path d="M3.2 12.4a8.8 8.8 0 0 1 17.6 0H3.2Z" />
|
|
<path d="M12 12.4v5.9a2.4 2.4 0 0 0 4.8 0" />
|
|
</svg>
|
|
General insurance that has it all
|
|
</span>
|
|
|
|
<h1 className="login__headline">
|
|
Protecting what
|
|
<span className="login__headline-accent">
|
|
matters the most to you
|
|
</span>
|
|
</h1>
|
|
|
|
<p className="login__lede">
|
|
Get tailored insurance for all your needs, cause we cover it all
|
|
</p>
|
|
|
|
<ul className="login__features">
|
|
{FEATURES.map((feature) => (
|
|
<li key={feature.title}>
|
|
<span className="login__feature-icon" aria-hidden="true">
|
|
<svg
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
strokeWidth="1.7"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
>
|
|
{feature.icon}
|
|
</svg>
|
|
</span>
|
|
<span>
|
|
<strong>{feature.title}</strong>
|
|
<em>{feature.body}</em>
|
|
</span>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
</section>
|
|
|
|
<section className="login__panel">
|
|
<div className="login__form-wrap">
|
|
<img
|
|
className="login__logo login__logo--mobile"
|
|
src={logo}
|
|
alt="Zurich Kotak General Insurance"
|
|
/>
|
|
|
|
<h2 className="login__title">Sign in</h2>
|
|
<p className="login__subtitle">
|
|
Enter your credentials to access your dashboard.
|
|
</p>
|
|
|
|
<form className="login__form" onSubmit={handleSubmit} noValidate>
|
|
<label className="login__field">
|
|
<span>Email</span>
|
|
<input
|
|
type="email"
|
|
name="email"
|
|
autoComplete="email"
|
|
placeholder="you@company.com"
|
|
value={email}
|
|
onChange={(event) => setEmail(event.target.value)}
|
|
/>
|
|
</label>
|
|
|
|
<label className="login__field">
|
|
<span>Password</span>
|
|
<input
|
|
type="password"
|
|
name="password"
|
|
autoComplete="current-password"
|
|
placeholder="••••••••"
|
|
value={password}
|
|
onChange={(event) => setPassword(event.target.value)}
|
|
/>
|
|
</label>
|
|
|
|
{error ? (
|
|
<p className="login__error" role="alert">
|
|
{error}
|
|
</p>
|
|
) : null}
|
|
|
|
<button className="login__submit" type="submit" disabled={submitting}>
|
|
{submitting ? 'Signing in…' : 'Sign in'}
|
|
{submitting ? null : (
|
|
<svg
|
|
viewBox="0 0 24 24"
|
|
aria-hidden="true"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
strokeWidth="1.9"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
>
|
|
<path d="M4 12h15m0 0-5.5-5.5M19 12l-5.5 5.5" />
|
|
</svg>
|
|
)}
|
|
</button>
|
|
</form>
|
|
|
|
<p className="login__powered">
|
|
Powered by <span>zino</span>
|
|
</p>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
)
|
|
}
|