zurich_kotak/src/pages/Login.jsx
Yashas 9210681488 Wire the console to the platform and make it deployable
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>
2026-08-24 15:20:13 +05:30

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>
)
}