wacrm/src/components/inbox/message-composer.tsx
Arnas Donauskas 77dfa4f165
chore: switch accent palette to Hostinger violet, drop emerald green
The legal brief on third-party trademark use says: avoid the WhatsApp
teal/green palette and any visual treatment that could read as a
WhatsApp/Meta product. Emerald-500 was used as the primary brand colour
across hero, CTAs, sidebar, charts, badges, and the favicon — close
enough in hue to WhatsApp's signature green to muddy the line.

Swapping to Tailwind violet (which matches the Hostinger brand purple
#7c3aed) makes the visual identity unambiguously ours.

Scope:
- Mechanical Tailwind class swap: emerald-* → violet-* across src/.
- src/app/globals.css: --primary, --ring, --sidebar-primary, --sidebar-ring,
  --chart-1 oklch tokens swapped from emerald (oklch 0.696 0.17 162) to
  Hostinger-aligned violet (oklch 0.526 0.247 293).
- src/app/icon.tsx: favicon background #10b981 → #7c3aed.
- src/app/opengraph-image.tsx: badge bg, accent text, gradient.
- src/components/landing/hero.tsx + cta-banner.tsx: radial-gradient hex.
- src/components/landing/mock/analytics-mock.tsx: marketing chart line.
- src/components/dashboard/conversations-chart.tsx + response-time-chart.tsx:
  "Outgoing" series colour. Distinguishable from "Incoming" blue at a
  glance; we can revisit the chart pair later for stronger colourblind
  contrast (separate concern).
- README.md: license badge colour parameter.

Out of scope:
- src/components/settings/tag-manager.tsx still offers "Emerald" as one
  of the user-pickable contact-tag colours alongside Blue / Yellow / Red
  / etc. — that's a user data choice, not a brand mark.

Contrast check: white text on #7c3aed measures ~4.78:1 (WCAG AA passing
for normal body text). Violet-400 (#a78bfa) on slate-950 measures ~10:1
for accent text, well clear of AA.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 11:57:12 +03:00

134 lines
3.9 KiB
TypeScript

"use client";
import { useState, useRef, useCallback, KeyboardEvent } from "react";
import { Send, LayoutTemplate } from "lucide-react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
interface MessageComposerProps {
conversationId: string;
sessionExpired: boolean;
onSend: (text: string) => void;
onOpenTemplates: () => void;
}
export function MessageComposer({
conversationId,
sessionExpired,
onSend,
onOpenTemplates,
}: MessageComposerProps) {
const [text, setText] = useState("");
const [sending, setSending] = useState(false);
const textareaRef = useRef<HTMLTextAreaElement>(null);
const adjustHeight = useCallback(() => {
const el = textareaRef.current;
if (!el) return;
el.style.height = "auto";
// Max 4 lines (~96px)
el.style.height = `${Math.min(el.scrollHeight, 96)}px`;
}, []);
const handleSend = useCallback(async () => {
const trimmed = text.trim();
if (!trimmed || sending || sessionExpired) return;
setSending(true);
try {
onSend(trimmed);
setText("");
if (textareaRef.current) {
textareaRef.current.style.height = "auto";
}
} finally {
setSending(false);
}
}, [text, sending, sessionExpired, onSend]);
const handleKeyDown = useCallback(
(e: KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
handleSend();
}
},
[handleSend]
);
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLTextAreaElement>) => {
setText(e.target.value);
adjustHeight();
},
[adjustHeight]
);
return (
<div className="border-t border-slate-800 bg-slate-900 p-3">
{sessionExpired && (
<div className="mb-2 flex items-center justify-between rounded-lg bg-amber-500/10 px-3 py-2">
<p className="text-xs text-amber-400">
24-hour session expired. Use a template to re-engage.
</p>
<Button
variant="ghost"
size="sm"
className="h-7 text-xs text-amber-400 hover:text-amber-300"
onClick={onOpenTemplates}
>
<LayoutTemplate className="mr-1 h-3 w-3" />
Templates
</Button>
</div>
)}
<div className="flex items-end gap-2">
<Button
variant="ghost"
size="sm"
className="h-9 w-9 shrink-0 p-0 text-slate-400 hover:text-white"
onClick={onOpenTemplates}
title="Send template"
>
<LayoutTemplate className="h-4 w-4" />
</Button>
<textarea
ref={textareaRef}
value={text}
onChange={handleChange}
onKeyDown={handleKeyDown}
placeholder={
sessionExpired
? "Session expired - use a template"
: "Type a message... (Shift+Enter for new line)"
}
disabled={sessionExpired}
rows={1}
className={cn(
"flex-1 resize-none rounded-xl border border-slate-700 bg-slate-800 px-4 py-2.5 text-sm text-white placeholder-slate-500 outline-none transition-colors focus:border-violet-500/50",
sessionExpired && "cursor-not-allowed opacity-50"
)}
/>
<Button
size="sm"
className="h-9 w-9 shrink-0 bg-violet-600 p-0 hover:bg-violet-500 disabled:opacity-40"
disabled={!text.trim() || sessionExpired || sending}
onClick={handleSend}
>
<Send className="h-4 w-4" />
</Button>
</div>
{/* Hint sits outside the flex row so its height doesn't push
`items-end` buttons below the textarea. Indented to line up
under the textarea left edge (w-9 button + gap-2 = 44px). */}
<p className="mt-1 pl-11 text-[10px] text-slate-600">
Type &apos;/&apos; for quick replies
</p>
</div>
);
}