216 lines
7.3 KiB
TypeScript
216 lines
7.3 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Label } from "@/components/ui/label";
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { MessageSquarePlus, Trash2, Pencil, Check, X } from "lucide-react";
|
|
import {
|
|
loadCannedMessages,
|
|
saveCannedMessage,
|
|
deleteCannedMessage,
|
|
type CannedMessage,
|
|
} from "@/lib/canned-messages-store";
|
|
import { MAX_CANNED_MESSAGES, MAX_CANNED_MESSAGE_LENGTH } from "@/lib/limits";
|
|
import { useAuth } from "@/lib/auth/context";
|
|
|
|
interface CannedMessagesPanelProps {
|
|
compact?: boolean;
|
|
onSelect?: (content: string) => void;
|
|
}
|
|
|
|
export function CannedMessagesPanel({ compact, onSelect }: CannedMessagesPanelProps) {
|
|
const { user } = useAuth();
|
|
const [messages, setMessages] = useState<CannedMessage[]>([]);
|
|
const [editing, setEditing] = useState<string | null>(null);
|
|
const [editTitle, setEditTitle] = useState("");
|
|
const [editContent, setEditContent] = useState("");
|
|
const [showNew, setShowNew] = useState(false);
|
|
const [newTitle, setNewTitle] = useState("");
|
|
const [newContent, setNewContent] = useState("");
|
|
const [error, setError] = useState("");
|
|
|
|
const refresh = async () => {
|
|
setMessages(await loadCannedMessages(user?.id));
|
|
};
|
|
|
|
useEffect(() => {
|
|
refresh();
|
|
}, [user?.id]);
|
|
|
|
const startEdit = (msg: CannedMessage) => {
|
|
setEditing(msg.id);
|
|
setEditTitle(msg.title);
|
|
setEditContent(msg.content);
|
|
};
|
|
|
|
const saveEdit = async () => {
|
|
const { error: err } = await saveCannedMessage(
|
|
{ id: editing!, title: editTitle, content: editContent },
|
|
user?.id
|
|
);
|
|
if (err) setError(err);
|
|
else {
|
|
setEditing(null);
|
|
setError("");
|
|
await refresh();
|
|
}
|
|
};
|
|
|
|
const handleCreate = async () => {
|
|
const { error: err } = await saveCannedMessage(
|
|
{ title: newTitle, content: newContent },
|
|
user?.id
|
|
);
|
|
if (err) setError(err);
|
|
else {
|
|
setShowNew(false);
|
|
setNewTitle("");
|
|
setNewContent("");
|
|
setError("");
|
|
await refresh();
|
|
}
|
|
};
|
|
|
|
const handleDelete = async (id: string) => {
|
|
await deleteCannedMessage(id, user?.id);
|
|
await refresh();
|
|
};
|
|
|
|
if (compact) {
|
|
return (
|
|
<div className="flex flex-wrap gap-1.5 max-h-24 overflow-y-auto">
|
|
{messages.map((msg) => (
|
|
<button
|
|
key={msg.id}
|
|
type="button"
|
|
onClick={() => onSelect?.(msg.content)}
|
|
className="text-xs px-2.5 py-1.5 rounded-full border border-border hover:border-horny-pink/50 hover:bg-horny-pink/10 transition-colors truncate max-w-[140px]"
|
|
title={msg.content}
|
|
>
|
|
{msg.title}
|
|
</button>
|
|
))}
|
|
{messages.length === 0 && (
|
|
<p className="text-xs text-muted-foreground">No quick replies — add them in Profile</p>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Card>
|
|
<CardHeader className="pb-3">
|
|
<div className="flex items-center justify-between">
|
|
<CardTitle className="text-lg flex items-center gap-2">
|
|
<MessageSquarePlus className="h-5 w-5 text-horny-pink" />
|
|
Quick Replies
|
|
</CardTitle>
|
|
<Badge variant="secondary" className="text-[10px]">
|
|
{messages.length}/{MAX_CANNED_MESSAGES}
|
|
</Badge>
|
|
</div>
|
|
<p className="text-xs text-muted-foreground">
|
|
Save addresses, directions, hosting notes — tap to insert while messaging.
|
|
</p>
|
|
</CardHeader>
|
|
<CardContent className="space-y-3">
|
|
{messages.map((msg) => (
|
|
<div
|
|
key={msg.id}
|
|
className="rounded-lg border border-border p-3 space-y-2"
|
|
>
|
|
{editing === msg.id ? (
|
|
<>
|
|
<Input
|
|
value={editTitle}
|
|
onChange={(e) => setEditTitle(e.target.value)}
|
|
placeholder="Label (e.g. Address)"
|
|
/>
|
|
<textarea
|
|
value={editContent}
|
|
onChange={(e) => setEditContent(e.target.value)}
|
|
maxLength={MAX_CANNED_MESSAGE_LENGTH}
|
|
rows={3}
|
|
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm resize-none"
|
|
placeholder="Message text..."
|
|
/>
|
|
<div className="flex gap-2">
|
|
<Button variant="horny" size="sm" onClick={saveEdit}>
|
|
<Check className="h-3 w-3 mr-1" /> Save
|
|
</Button>
|
|
<Button variant="ghost" size="sm" onClick={() => setEditing(null)}>
|
|
<X className="h-3 w-3" />
|
|
</Button>
|
|
</div>
|
|
</>
|
|
) : (
|
|
<div className="flex items-start justify-between gap-2">
|
|
<div className="min-w-0 flex-1">
|
|
<p className="text-sm font-medium text-horny-pink">{msg.title}</p>
|
|
<p className="text-xs text-muted-foreground truncate">{msg.content}</p>
|
|
</div>
|
|
<div className="flex gap-1 shrink-0">
|
|
<Button variant="ghost" size="icon" className="h-7 w-7" onClick={() => startEdit(msg)}>
|
|
<Pencil className="h-3 w-3" />
|
|
</Button>
|
|
<Button variant="ghost" size="icon" className="h-7 w-7" onClick={() => handleDelete(msg.id)}>
|
|
<Trash2 className="h-3 w-3" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
))}
|
|
|
|
{showNew ? (
|
|
<div className="space-y-2 border border-dashed border-border rounded-lg p-3">
|
|
<div>
|
|
<Label className="text-xs">Label</Label>
|
|
<Input
|
|
value={newTitle}
|
|
onChange={(e) => setNewTitle(e.target.value)}
|
|
placeholder="e.g. Door code"
|
|
className="mt-1"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<Label className="text-xs">Message</Label>
|
|
<textarea
|
|
value={newContent}
|
|
onChange={(e) => setNewContent(e.target.value)}
|
|
maxLength={MAX_CANNED_MESSAGE_LENGTH}
|
|
rows={3}
|
|
className="mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm resize-none"
|
|
placeholder="Full message to insert..."
|
|
/>
|
|
</div>
|
|
<div className="flex gap-2">
|
|
<Button variant="horny" size="sm" onClick={handleCreate}>
|
|
Add Quick Reply
|
|
</Button>
|
|
<Button variant="ghost" size="sm" onClick={() => setShowNew(false)}>
|
|
Cancel
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
messages.length < MAX_CANNED_MESSAGES && (
|
|
<Button
|
|
variant="outline"
|
|
className="w-full"
|
|
onClick={() => setShowNew(true)}
|
|
>
|
|
<MessageSquarePlus className="h-4 w-4 mr-2" />
|
|
Add Quick Reply
|
|
</Button>
|
|
)
|
|
)}
|
|
|
|
{error && <p className="text-xs text-destructive text-center">{error}</p>}
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
} |