From 1e21c12f33b6678de31857ca8991df2d99678610 Mon Sep 17 00:00:00 2001 From: BOHA Date: Mon, 8 Jun 2026 20:51:00 +0200 Subject: [PATCH] feat(odin): personalized greeting hero on the new-conversation screen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Time-based Czech greeting + the user's first name (e.g. "Dobrý večer, Admin") above the big gradient "Odin" wordmark, with a stylish description line. Drop the redundant mark from the hero (it lives on the nav item + thinking state). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/admin/components/odin/OdinChat.tsx | 19 +++++++- src/admin/components/odin/OdinThread.tsx | 58 ++++++++++++++++-------- 2 files changed, 56 insertions(+), 21 deletions(-) diff --git a/src/admin/components/odin/OdinChat.tsx b/src/admin/components/odin/OdinChat.tsx index 3007661..82ed623 100644 --- a/src/admin/components/odin/OdinChat.tsx +++ b/src/admin/components/odin/OdinChat.tsx @@ -62,10 +62,26 @@ function summaryNote(reviews: ReviewInvoice[]): string { export default function OdinChat() { const alert = useAlert(); const qc = useQueryClient(); - const { hasPermission } = useAuth(); + const { user, hasPermission } = useAuth(); // Saving an extracted invoice hits /received-invoices (needs invoices.create); // Odin itself only needs ai.use, so the Save affordance is gated separately. const canSave = hasPermission("invoices.create"); + // Time-based Czech greeting + the user's first name (client-local time). + const greeting = (() => { + const h = new Date().getHours(); + const part = + h < 9 + ? "Dobré ráno" + : h < 12 + ? "Dobrý den" + : h < 18 + ? "Dobré odpoledne" + : "Dobrý večer"; + const name = (user?.fullName || user?.username || "") + .trim() + .split(/\s+/)[0]; + return name ? `${part}, ${name}` : part; + })(); const { data: usage } = useQuery(aiUsageOptions()); const { data: convData, isPending: convPending } = useQuery( aiConversationsOptions(), @@ -425,6 +441,7 @@ export default function OdinChat() { turns={turns} busy={busy} loading={messagesLoading} + greeting={greeting} threadRef={threadRef} /> diff --git a/src/admin/components/odin/OdinThread.tsx b/src/admin/components/odin/OdinThread.tsx index 774b9fc..3404bc6 100644 --- a/src/admin/components/odin/OdinThread.tsx +++ b/src/admin/components/odin/OdinThread.tsx @@ -7,6 +7,7 @@ interface OdinThreadProps { turns: ChatTurn[]; busy: boolean; loading: boolean; + greeting?: string; threadRef: React.RefObject; } @@ -37,6 +38,7 @@ export default function OdinThread({ turns, busy, loading, + greeting, threadRef, }: OdinThreadProps) { return ( @@ -79,34 +81,50 @@ export default function OdinThread({ flexDirection: "column", alignItems: "center", justifyContent: "center", - gap: 2.5, + gap: 1.25, textAlign: "center", px: 2, }} > - - - ({ - fontSize: "clamp(2rem, 1.2rem + 2.4vw, 2.75rem)", - fontWeight: 700, - lineHeight: 1.1, - letterSpacing: "-0.02em", - backgroundImage: `linear-gradient(120deg, ${t.vars!.palette.primary.light}, ${t.vars!.palette.primary.main})`, - WebkitBackgroundClip: "text", - backgroundClip: "text", - WebkitTextFillColor: "transparent", - })} + {greeting && ( + - Odin - + {greeting} + + )} + ({ + fontSize: "clamp(2.25rem, 1.4rem + 2.6vw, 3rem)", + fontWeight: 800, + lineHeight: 1.05, + letterSpacing: "-0.03em", + backgroundImage: `linear-gradient(120deg, ${t.vars!.palette.primary.light}, ${t.vars!.palette.primary.main})`, + WebkitBackgroundClip: "text", + backgroundClip: "text", + WebkitTextFillColor: "transparent", + })} + > + Odin - Zeptejte se, nebo přiložte fakturu (PDF) k automatickému importu. + Váš firemní AI asistent. Zeptejte se, nebo přiložte fakturu (PDF) k + importu. )}