feat: Zino brand logo in sidebar + login, strip header search/bell

- inline ZinoLogo SVG (from sm2 renderer asset); replace aria badge in
  sidebar (centered) and login brand block
- remove search + notification buttons from topbar
- carry pending field prefill (field_defaults.value) + qualify RBAC trim

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bhanu Prakash Sai Potteri 2026-06-26 17:36:18 +05:30
parent 52a691fd75
commit a4d0f80f71
7 changed files with 53 additions and 36 deletions

View File

@ -176,7 +176,7 @@ export const SUPER_ROLES = ['Super Admin', 'Admin'];
// is an Aria-only screen — managers oversee agent meetings, agents see theirs. // is an Aria-only screen — managers oversee agent meetings, agents see theirs.
export const SCREEN_ACCESS: Record<string, string[]> = { export const SCREEN_ACCESS: Record<string, string[]> = {
'/pipeline': ['Sales Agent', 'Underwriter', 'Branch Manager'], '/pipeline': ['Sales Agent', 'Underwriter', 'Branch Manager'],
'/qualify': ['Sales Agent', 'Branch Manager'], '/qualify': ['Branch Manager'],
'/engage': ['Sales Agent'], '/engage': ['Sales Agent'],
'/documents': ['Underwriter'], '/documents': ['Underwriter'],
'/underwriting': ['Underwriter', 'Branch Manager'], '/underwriting': ['Underwriter', 'Branch Manager'],

View File

@ -40,6 +40,9 @@ export interface FieldDef {
/** Designer `field_defaults[id].disabled` render read-only; the value is /** Designer `field_defaults[id].disabled` render read-only; the value is
* computed (e.g. premium_amount written by the custom_js). */ * computed (e.g. premium_amount written by the custom_js). */
disabled?: boolean; disabled?: boolean;
/** Designer `field_defaults[id].value` a server-supplied default to seed
* with (e.g. a hidden preferred_language defaulting to "english"). */
prefill?: unknown;
options?: FieldOption[]; options?: FieldOption[];
prefix?: string; prefix?: string;
placeholder?: string; placeholder?: string;

View File

@ -72,6 +72,7 @@ export function schemaToFields(resp: FormScreenResponse): FieldDef[] {
...d, ...d,
hidden: !!resp.field_defaults?.[d.id]?.hidden, hidden: !!resp.field_defaults?.[d.id]?.hidden,
disabled: !!resp.field_defaults?.[d.id]?.disabled, disabled: !!resp.field_defaults?.[d.id]?.disabled,
prefill: resp.field_defaults?.[d.id]?.value,
})); }));
} }
@ -89,5 +90,6 @@ export function fieldFromSchema(
...def, ...def,
hidden: !!resp?.field_defaults?.[id]?.hidden, hidden: !!resp?.field_defaults?.[id]?.hidden,
disabled: !!resp?.field_defaults?.[id]?.disabled, disabled: !!resp?.field_defaults?.[id]?.disabled,
prefill: resp?.field_defaults?.[id]?.value,
}; };
} }

View File

@ -0,0 +1,22 @@
// Zino wordmark (copied from sm2 renderer assets/zino-logo.svg). Inlined as a
// component so it works under both the dev root and the /lead-to-policy/ build
// base without an asset-path dance. Brand orange (#ed563b) by default; pass a
// className with a `text-*` colour + `fill-current` to recolour.
export function ZinoLogo({ className }: { className?: string }) {
return (
<svg
viewBox="0 0 486.596 229"
role="img"
aria-label="Zino"
className={className}
fill="#ed563b"
>
<g transform="translate(-61 -34)" fillRule="evenodd">
<path d="M464.867,87.973a54.254,54.254,0,0,0-15.651-38.165,53.085,53.085,0,0,0-75.566,0A54.248,54.248,0,0,0,358,87.973V241.024s.193,21.3.763,21.589,7.674.391,12.6.386h.386c4.738,0,11.084-.01,11.827-.385.76-.384,1.142-173.286,1.145-174.632v-.008a27.124,27.124,0,0,1,7.825-19.082,26.542,26.542,0,0,1,37.783,0,27.129,27.129,0,0,1,7.826,19.082h0s-.534,53.337-.38,87.513c.152,33.726.76,85.2,1.14,86.356.362,1.1,6.936,1.13,11.81,1.153l.734,0c5.214.027,12.261-.452,13.3-1.157s0-173.869,0-173.869Z" transform="translate(-70.29)" />
<path d="M661.263,64.763a46.933,46.933,0,0,1,4.067,19.071V209.842h0a53.434,53.434,0,0,1-106.844,1.424h-.022V83.834a46.934,46.934,0,0,1,4.068-19.071A49.748,49.748,0,0,1,574.114,48.6a53.837,53.837,0,0,1,17.335-10.8,57.009,57.009,0,0,1,40.9,0A53.84,53.84,0,0,1,649.68,48.6,49.76,49.76,0,0,1,661.263,64.763ZM585.18,209.744a26.717,26.717,0,0,0,53.433,0V83.834a23.467,23.467,0,0,0-2.034-9.535,24.88,24.88,0,0,0-5.791-8.084,26.926,26.926,0,0,0-8.668-5.4,28.505,28.505,0,0,0-20.448,0,26.927,26.927,0,0,0-8.668,5.4,24.88,24.88,0,0,0-5.791,8.084,23.466,23.466,0,0,0-2.034,9.535Z" transform="translate(-117.733)" />
<path d="M69.512,35.045c-1.221.732-1.527,3.345-1.527,12.022s.305,11.29,1.527,12.022c.916.627,15.674,1.045,34.91,1.045,24.325,0,33.281.314,33.281,1.15,0,1.15-1.934,5.541-12.519,29.167-1.934,4.286-8.651,19.34-14.961,33.452S97.3,152.757,95.465,156.834c-8.142,17.876-17,37.53-21.882,48.611-2.952,6.586-6.717,15.054-8.346,18.817-2.239,4.914-3.257,9.3-3.867,15.89-1.221,12.754.509,18.712,6.31,21.849,2.748,1.463,97.5,1.254,99.233-.209,1.628-1.464,1.832-18.818.305-21.953-1.018-1.882-2.544-1.986-39.184-1.986-27.277,0-38.167-.314-38.167-1.15,0-.627,3.054-7.84,6.718-15.994,3.664-8.05,7.836-17.354,9.262-20.49s4.58-10.036,6.921-15.158c2.34-5.227,6.92-15.263,10.178-22.476s8.956-19.863,12.722-28.226,9.771-21.535,13.231-29.272,8.956-19.758,12.112-26.657A89.4,89.4,0,0,0,164.432,66.6c.8-4.207,2.278-11.688,2.278-11.688,0-13.172-1.424-16.622-7.633-19.235C155.514,34.209,149.408,34,112.971,34,88.341,34,70.428,34.418,69.512,35.045Z" />
<path d="M263.343,35.149c-1.73.94-1.832,7.522-1.832,113.768,0,79.4.305,112.933,1.12,113.351.611.418,6.412.731,12.824.731,8.752,0,12.009-.314,12.62-1.358.509-.836.916-51.609.916-112.933,0-99.874-.2-111.575-1.628-113.037C286.141,34.313,283.495,34,275.455,34,269.755,34,264.259,34.522,263.343,35.149Z" transform="translate(-47.454)" />
</g>
</svg>
);
}

View File

@ -41,6 +41,9 @@ function seedFor(field: FieldDef, record?: LeadRecord | null): unknown {
const key = field.seedKey ?? field.id.replace(/_input$/, ''); const key = field.seedKey ?? field.id.replace(/_input$/, '');
const fromRecord = record ? (record as Record<string, unknown>)[key] : undefined; const fromRecord = record ? (record as Record<string, unknown>)[key] : undefined;
if (fromRecord !== undefined && fromRecord !== null) return fromRecord; if (fromRecord !== undefined && fromRecord !== null) return fromRecord;
// Designer-supplied default (field_defaults.value) — lets a hidden field like
// preferred_language submit a fixed value (e.g. "english") with no UI.
if (field.prefill !== undefined && field.prefill !== null && field.prefill !== '') return field.prefill;
switch (field.type) { switch (field.type) {
case 'multiselect': case 'multiselect':
return []; return [];

View File

@ -1,9 +1,10 @@
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import { Outlet, useLocation, useNavigate } from 'react-router-dom'; import { Outlet, useLocation, useNavigate } from 'react-router-dom';
import type { LucideIcon } from 'lucide-react'; import type { LucideIcon } from 'lucide-react';
import { ArrowLeft, Bell, CalendarClock, FileText, LogOut, Menu, Search, ShieldCheck, Sparkles, UserCheck, Users, X } from 'lucide-react'; import { ArrowLeft, CalendarClock, FileText, LogOut, Menu, ShieldCheck, Sparkles, UserCheck, Users, X } from 'lucide-react';
import { cn } from '../lib/cn'; import { cn } from '../lib/cn';
import { Avatar } from '../components/core'; import { Avatar } from '../components/core';
import { ZinoLogo } from '../components/ZinoLogo';
import { canSeeScreen } from '../api/config'; import { canSeeScreen } from '../api/config';
import { useZino } from '../api/provider'; import { useZino } from '../api/provider';
import { useLeads } from '../api/leads'; import { useLeads } from '../api/leads';
@ -35,13 +36,10 @@ function pipelineSubtitle(leads: Lead[]): string {
return `${total.toLocaleString('en-IN')} leads · ${hot}% hot · ${ai}% AI-handled`; return `${total.toLocaleString('en-IN')} leads · ${hot}% hot · ${ai}% AI-handled`;
} }
function AriaLogo() { function BrandLogo() {
return ( return (
<div className="flex items-center gap-2.5"> <div className="flex items-center justify-center px-1">
<span className="w-8 h-8 rounded-[9px] bg-sunrise shadow-sunrise flex items-center justify-center text-white font-extrabold text-lg font-numeric"> <ZinoLogo className="h-7 w-auto" />
A
</span>
<span className="text-lg font-extrabold tracking-[-0.02em] text-on-navy">aria</span>
</div> </div>
); );
} }
@ -51,7 +49,7 @@ function SidebarInner({ items, activeTo, onNav }: { items: NavItem[]; activeTo:
return ( return (
<> <>
<div className="px-2 py-1"> <div className="px-2 py-1">
<AriaLogo /> <BrandLogo />
</div> </div>
<nav className="flex flex-col gap-1"> <nav className="flex flex-col gap-1">
@ -78,7 +76,7 @@ function SidebarInner({ items, activeTo, onNav }: { items: NavItem[]; activeTo:
); );
} }
function Topbar({ title, subtitle, onMenu }: { title: string; subtitle: string; onMenu: () => void }) { function Topbar({ title, subtitle, onMenu, showBack }: { title: string; subtitle: string; onMenu: () => void; showBack: boolean }) {
const { user, logout } = useZino(); const { user, logout } = useZino();
const navigate = useNavigate(); const navigate = useNavigate();
return ( return (
@ -90,6 +88,7 @@ function Topbar({ title, subtitle, onMenu }: { title: string; subtitle: string;
> >
<Menu size={20} /> <Menu size={20} />
</button> </button>
{showBack && (
<button <button
onClick={() => (window.history.length > 1 ? navigate(-1) : navigate('/pipeline'))} onClick={() => (window.history.length > 1 ? navigate(-1) : navigate('/pipeline'))}
aria-label="Go back" aria-label="Go back"
@ -98,20 +97,12 @@ function Topbar({ title, subtitle, onMenu }: { title: string; subtitle: string;
> >
<ArrowLeft size={18} /> <ArrowLeft size={18} />
</button> </button>
)}
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<h1 className="m-0 text-lg font-bold text-strong truncate">{title}</h1> <h1 className="m-0 text-lg font-bold text-strong truncate">{title}</h1>
{subtitle && <div className="text-xs text-faint mt-px truncate">{subtitle}</div>} {subtitle && <div className="text-xs text-faint mt-px truncate">{subtitle}</div>}
</div> </div>
<div className="flex items-center gap-3.5"> <div className="flex items-center gap-3.5">
<button className="w-[38px] h-[38px] rounded-md border border-border-subtle bg-card cursor-pointer text-muted flex items-center justify-center">
<Search size={18} />
</button>
<div className="relative">
<button className="w-[38px] h-[38px] rounded-md border border-border-subtle bg-card cursor-pointer text-muted flex items-center justify-center">
<Bell size={18} />
</button>
<span className="absolute -top-0.5 -right-0.5 w-[9px] h-[9px] rounded-full bg-sunrise-500 border-2 border-card" />
</div>
<div className="flex items-center gap-2 pl-1.5"> <div className="flex items-center gap-2 pl-1.5">
<Avatar name={user?.name || 'User'} size={36} /> <Avatar name={user?.name || 'User'} size={36} />
<div className="hidden md:block leading-tight whitespace-nowrap"> <div className="hidden md:block leading-tight whitespace-nowrap">
@ -184,7 +175,7 @@ export function Shell() {
)} )}
<div className="flex-1 flex flex-col min-w-0"> <div className="flex-1 flex flex-col min-w-0">
<Topbar title={active.title} subtitle={subtitle} onMenu={() => setNavOpen(true)} /> <Topbar title={active.title} subtitle={subtitle} onMenu={() => setNavOpen(true)} showBack={!navMatch} />
<main className="flex-1 p-4 lg:p-7"> <main className="flex-1 p-4 lg:p-7">
<Outlet /> <Outlet />
</main> </main>

View File

@ -3,6 +3,7 @@ import type { FormEvent } from 'react';
import { useLocation, useNavigate } from 'react-router-dom'; import { useLocation, useNavigate } from 'react-router-dom';
import { ArrowRight, CalendarClock, Eye, EyeOff, Lock, Mail, ShieldCheck, Sparkles } from 'lucide-react'; import { ArrowRight, CalendarClock, Eye, EyeOff, Lock, Mail, ShieldCheck, Sparkles } from 'lucide-react';
import { Button } from '../components'; import { Button } from '../components';
import { ZinoLogo } from '../components/ZinoLogo';
import { useZino } from '../api/provider'; import { useZino } from '../api/provider';
const HIGHLIGHTS = [ const HIGHLIGHTS = [
@ -82,14 +83,9 @@ export function Login() {
<div className="relative w-full max-w-[920px] rounded-2xl bg-white/[0.07] backdrop-blur-2xl border border-white/15 shadow-2xl overflow-hidden grid lg:grid-cols-2"> <div className="relative w-full max-w-[920px] rounded-2xl bg-white/[0.07] backdrop-blur-2xl border border-white/15 shadow-2xl overflow-hidden grid lg:grid-cols-2">
{/* brand story */} {/* brand story */}
<div className="hidden lg:flex flex-col gap-9 p-10 border-r border-white/10"> <div className="hidden lg:flex flex-col gap-9 p-10 border-r border-white/10">
<div className="flex items-center gap-2.5"> <div className="flex items-center gap-3">
<span className="w-11 h-11 rounded-2xl bg-sunrise shadow-sunrise flex items-center justify-center text-white font-extrabold text-2xl font-numeric"> <ZinoLogo className="h-9 w-auto" />
A <div className="text-xs text-on-navy-muted border-l border-white/15 pl-3">Lead-to-policy console</div>
</span>
<div>
<div className="text-xl font-extrabold tracking-[-0.02em] text-on-navy">aria</div>
<div className="text-xs text-on-navy-muted">Lead-to-policy console</div>
</div>
</div> </div>
<h1 className="m-0 text-[34px] leading-[1.12] font-extrabold tracking-[-0.025em] text-on-navy"> <h1 className="m-0 text-[34px] leading-[1.12] font-extrabold tracking-[-0.025em] text-on-navy">