diff --git a/README.md b/README.md index 5cde6c5..e5d73a0 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ -# EV Sales Desk Console +# Mahindra EV Sales Desk Console Two-screen operator console for the **live call-transfer demo**: place an -outbound AI sales call, and watch it transfer to a human colleague mid-call. +outbound Mahindra EV sales call, and watch it transfer to a human colleague mid-call. -Backs onto Zino app **583** (org 900000104, `dev`). Built on the frontgen React +Backs onto Zino app **583** (org 900000104, `dev`). Line-up: **BE 6**, **XEV 9e**, **XEV 9S**. Built on the frontgen React scaffold — Vite + Tailwind + React Router + TanStack Query + `src/zino-sdk`. ## The two screens -**Place a call** — lead name, model, and the two numbers: the **customer** Aria +**Place a call** — lead name, model, and the two numbers: the **customer** Meera dials, and the **colleague** the transfer bridges to. Submitting starts a `Lead Outreach` workflow instance; its trigger places the call server-side. @@ -23,7 +23,7 @@ echo 'VITE_ZINO_API_URL=https://dev-apps.getzino.in' >> .env npm run dev ``` -Sign in with an app user of app 583 (e.g. `salesops@verta-motors.demo`). +Sign in with an app user of app 583 (e.g. `salesops@mahindra-ev.demo` / `Mahindra@2026` (prefilled on the login screen)). `VITE_ZINO_API_URL` is only read from `.env` in **dev**. A production build requires the `config.js` the server writes at placement time, and throws if it diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 1b29060..fd31769 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -2,6 +2,7 @@ import type { ReactNode } from 'react' import { NavLink } from 'react-router-dom' import { LogOut, PhoneOutgoing, Radio, Zap } from 'lucide-react' import { useZino } from '../zino-sdk' +import { BRAND } from '../config' const navItem = 'inline-flex items-center gap-2 rounded-lg px-3 py-2 text-sm font-medium transition-colors' @@ -31,12 +32,12 @@ export default function Layout({ children }: { children: ReactNode }) {
- +
-
Verta Motors
-
EV Sales Desk
+
{BRAND.name}
+
{BRAND.product}
@@ -46,7 +47,7 @@ export default function Layout({ children }: { children: ReactNode }) { className={({ isActive }) => `${navItem} ${ isActive - ? 'bg-emerald-50 text-emerald-700' + ? 'bg-red-50 text-red-700' : 'text-slate-600 hover:bg-slate-100' }` } @@ -59,7 +60,7 @@ export default function Layout({ children }: { children: ReactNode }) { className={({ isActive }) => `${navItem} ${ isActive - ? 'bg-emerald-50 text-emerald-700' + ? 'bg-red-50 text-red-700' : 'text-slate-600 hover:bg-slate-100' }` } diff --git a/src/config.ts b/src/config.ts index 98276cf..9797566 100644 --- a/src/config.ts +++ b/src/config.ts @@ -37,5 +37,22 @@ export const FIELDS = { */ export const END_REASON_HANDOVER = 'handover' -/** Models the agent is briefed on. */ -export const MODELS = ['Verta One', 'Verta SUV', 'Verta Basic'] as const +/** + * Mahindra's born-electric SUVs, matching the line-up in Meera's brief. Keep + * this list and the agent's `goal` in step — a model offered here that the agent + * has never heard of is a call where she has to improvise. + */ +export const MODELS = ['BE 6', 'XEV 9e', 'XEV 9S'] as const + +export const BRAND = { + name: 'Mahindra', + product: 'EV Sales Desk', + /** The voice agent's persona, as the customer hears it. */ + agentName: 'Meera', +} as const + +/** Prefilled on the login screen — this is a demo tenant, not a real account. */ +export const DEMO_LOGIN = { + email: 'salesops@mahindra-ev.demo', + password: 'Mahindra@2026', +} as const diff --git a/src/pages/CallMonitor.tsx b/src/pages/CallMonitor.tsx index d52e4a7..28942d3 100644 --- a/src/pages/CallMonitor.tsx +++ b/src/pages/CallMonitor.tsx @@ -153,7 +153,7 @@ function OutcomePill({ row }: { row: LeadRow }) { } if (status === 'dialled') { return ( - + On call ) @@ -197,7 +197,7 @@ function DetailDrawer({ row, onClose }: { row: LeadRow; onClose: () => void }) {

Transferred

- Aria bridged{' '} + Meera bridged{' '} {str(row[FIELDS.HANDOVER_NUMBER]) || 'the colleague'}{' '} into the call and stayed on as a silent listener, briefing them as the conversation continued. diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index 036c3c1..e076e3f 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -1,11 +1,14 @@ import { useState } from 'react' import { Zap } from 'lucide-react' import { useZino } from '../zino-sdk' +import { BRAND, DEMO_LOGIN } from '../config' export default function Login() { const { auth, setUser } = useZino() - const [email, setEmail] = useState('') - const [password, setPassword] = useState('') + // Prefilled: this is a demo tenant on dev, and typing credentials in front of + // an audience is friction with no upside. Both fields stay editable. + const [email, setEmail] = useState(DEMO_LOGIN.email) + const [password, setPassword] = useState(DEMO_LOGIN.password) const [error, setError] = useState('') const [busy, setBusy] = useState(false) @@ -29,15 +32,16 @@ export default function Login() { onSubmit={submit} className="w-full max-w-sm rounded-2xl border border-slate-200 bg-white p-8 shadow-sm" > -

- +
+
-
Verta Motors
-
EV Sales Desk
+
{BRAND.name}
+
{BRAND.product}
+

Sign in to place and monitor calls.

setEmail(e.target.value)} required - autoFocus - className="mb-4 w-full rounded-lg border border-slate-300 px-3 py-2 text-sm outline-none focus:border-emerald-500 focus:ring-2 focus:ring-emerald-100" + className="mb-4 w-full rounded-lg border border-slate-300 px-3 py-2 text-sm outline-none focus:border-red-500 focus:ring-2 focus:ring-red-100" /> @@ -55,7 +58,7 @@ export default function Login() { value={password} onChange={(e) => setPassword(e.target.value)} required - className="mb-5 w-full rounded-lg border border-slate-300 px-3 py-2 text-sm outline-none focus:border-emerald-500 focus:ring-2 focus:ring-emerald-100" + className="mb-5 w-full rounded-lg border border-slate-300 px-3 py-2 text-sm outline-none focus:border-red-500 focus:ring-2 focus:ring-red-100" /> {error && ( @@ -65,7 +68,8 @@ export default function Login() { diff --git a/src/pages/PlaceCall.tsx b/src/pages/PlaceCall.tsx index 17004ce..0e1f092 100644 --- a/src/pages/PlaceCall.tsx +++ b/src/pages/PlaceCall.tsx @@ -2,7 +2,7 @@ import { useState } from 'react' import { useNavigate } from 'react-router-dom' import { ArrowRight, PhoneForwarded, PhoneOutgoing, User } from 'lucide-react' import { useZino } from '../zino-sdk' -import { MODELS } from '../config' +import { BRAND, MODELS } from '../config' import { isPlausiblePhone, placeCall, toE164 } from '../api' export default function PlaceCall() { @@ -51,9 +51,9 @@ export default function PlaceCall() {

Place a call

- Aria calls the customer and qualifies the lead. When the caller asks for a person — - or pushes on price — she announces a transfer and bridges the colleague below into - the same call, then keeps listening to brief them. + {BRAND.agentName} calls the customer and qualifies the {BRAND.name} EV lead. When the + caller asks for a person — or pushes on price — she announces a transfer and bridges + the colleague below into the same call, then keeps listening to brief them.

setLeadName(e.target.value)} placeholder="Yashas" - className="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm outline-none focus:border-emerald-500 focus:ring-2 focus:ring-emerald-100" + className="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm outline-none focus:border-red-500 focus:ring-2 focus:ring-red-100" />
@@ -80,7 +80,7 @@ export default function PlaceCall() {