feat(odin): personalized greeting hero on the new-conversation screen

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) <noreply@anthropic.com>
This commit is contained in:
BOHA
2026-06-08 20:51:00 +02:00
parent ab12f9d791
commit 1e21c12f33
2 changed files with 56 additions and 21 deletions

View File

@@ -62,10 +62,26 @@ function summaryNote(reviews: ReviewInvoice[]): string {
export default function OdinChat() { export default function OdinChat() {
const alert = useAlert(); const alert = useAlert();
const qc = useQueryClient(); const qc = useQueryClient();
const { hasPermission } = useAuth(); const { user, hasPermission } = useAuth();
// Saving an extracted invoice hits /received-invoices (needs invoices.create); // Saving an extracted invoice hits /received-invoices (needs invoices.create);
// Odin itself only needs ai.use, so the Save affordance is gated separately. // Odin itself only needs ai.use, so the Save affordance is gated separately.
const canSave = hasPermission("invoices.create"); 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: usage } = useQuery(aiUsageOptions());
const { data: convData, isPending: convPending } = useQuery( const { data: convData, isPending: convPending } = useQuery(
aiConversationsOptions(), aiConversationsOptions(),
@@ -425,6 +441,7 @@ export default function OdinChat() {
turns={turns} turns={turns}
busy={busy} busy={busy}
loading={messagesLoading} loading={messagesLoading}
greeting={greeting}
threadRef={threadRef} threadRef={threadRef}
/> />

View File

@@ -7,6 +7,7 @@ interface OdinThreadProps {
turns: ChatTurn[]; turns: ChatTurn[];
busy: boolean; busy: boolean;
loading: boolean; loading: boolean;
greeting?: string;
threadRef: React.RefObject<HTMLDivElement | null>; threadRef: React.RefObject<HTMLDivElement | null>;
} }
@@ -37,6 +38,7 @@ export default function OdinThread({
turns, turns,
busy, busy,
loading, loading,
greeting,
threadRef, threadRef,
}: OdinThreadProps) { }: OdinThreadProps) {
return ( return (
@@ -79,34 +81,50 @@ export default function OdinThread({
flexDirection: "column", flexDirection: "column",
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
gap: 2.5, gap: 1.25,
textAlign: "center", textAlign: "center",
px: 2, px: 2,
}} }}
> >
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}> {greeting && (
<OdinMark size={52} /> <Typography
<Box component="div"
component="span" sx={{
sx={(t) => ({ color: "text.secondary",
fontSize: "clamp(2rem, 1.2rem + 2.4vw, 2.75rem)", fontWeight: 500,
fontWeight: 700, fontSize: "clamp(1rem, 0.85rem + 0.6vw, 1.25rem)",
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",
})}
> >
Odin {greeting}
</Box> </Typography>
)}
<Box
component="span"
sx={(t) => ({
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
</Box> </Box>
<Typography <Typography
variant="body2" component="div"
sx={{ color: "text.secondary", maxWidth: 440 }} sx={{
color: "text.secondary",
maxWidth: 460,
mt: 0.5,
fontSize: "0.95rem",
lineHeight: 1.5,
}}
> >
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.
</Typography> </Typography>
</Box> </Box>
)} )}