Files
exposedgays/src/components/CannedMessagesPanel.tsx

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>
);
}