wacrm/src/components/broadcasts/step1-choose-template.tsx
Arnas Donauskas 658d070811
Initial scaffold: WhatsApp CRM with Next.js + Supabase
Complete WhatsApp CRM application built with Next.js 14 (App Router),
Supabase (PostgreSQL + Auth + Realtime), and shadcn/ui.

Features:
- Auth: Login, signup, forgot password with Supabase Auth
- Dashboard: Analytics overview with stats, recent activity
- Inbox: Real-time WhatsApp conversations with message threading
- Contacts: CRUD with tags, custom fields, notes, CSV import
- Broadcasts: 4-step wizard with audience selection, template sending
- Pipelines: Kanban board with deal management
- Settings: WhatsApp API config, templates, tags
- WhatsApp API: Webhook (verify + receive), send, broadcast, media proxy
- Database: 16 tables with RLS policies, indexes, realtime subscriptions
- Dark theme throughout with emerald accents

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 18:17:31 +03:00

135 lines
4.7 KiB
TypeScript

'use client';
import { useEffect, useState } from 'react';
import { createClient } from '@/lib/supabase/client';
import { MessageTemplate } from '@/types';
import { Button } from '@/components/ui/button';
import { Loader2, FileText, ArrowRight } from 'lucide-react';
const categoryColors: Record<string, string> = {
Marketing: 'bg-purple-500/10 text-purple-400 border-purple-500/20',
Utility: 'bg-blue-500/10 text-blue-400 border-blue-500/20',
Authentication: 'bg-orange-500/10 text-orange-400 border-orange-500/20',
};
interface Step1Props {
selectedTemplate: MessageTemplate | null;
onSelect: (template: MessageTemplate) => void;
onNext: () => void;
onBack: () => void;
}
export function Step1ChooseTemplate({ selectedTemplate, onSelect, onNext, onBack }: Step1Props) {
const [templates, setTemplates] = useState<MessageTemplate[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
async function fetchTemplates() {
try {
const supabase = createClient();
const { data, error: fetchError } = await supabase
.from('message_templates')
.select('*')
.order('created_at', { ascending: false });
if (fetchError) throw fetchError;
setTemplates(data ?? []);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to load templates');
} finally {
setLoading(false);
}
}
fetchTemplates();
}, []);
if (loading) {
return (
<div className="flex h-64 items-center justify-center">
<Loader2 className="h-6 w-6 animate-spin text-emerald-500" />
</div>
);
}
if (error) {
return (
<div className="flex h-64 flex-col items-center justify-center gap-2">
<p className="text-sm text-red-400">{error}</p>
</div>
);
}
return (
<div className="space-y-6">
<div>
<h2 className="text-lg font-semibold text-white">Choose a Template</h2>
<p className="mt-1 text-sm text-slate-400">
Select an approved message template for your broadcast.
</p>
</div>
{templates.length === 0 ? (
<div className="flex h-48 flex-col items-center justify-center rounded-xl border border-slate-800 bg-slate-900/50">
<FileText className="mb-2 h-8 w-8 text-slate-600" />
<p className="text-sm text-slate-400">No templates available.</p>
<p className="mt-1 text-xs text-slate-500">Create a template in Settings first.</p>
</div>
) : (
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">
{templates.map((template) => {
const isSelected = selectedTemplate?.id === template.id;
const catColor = categoryColors[template.category] ?? categoryColors.Utility;
return (
<button
key={template.id}
onClick={() => onSelect(template)}
className={`flex flex-col gap-3 rounded-xl border p-4 text-left transition-all ${
isSelected
? 'border-emerald-500 bg-emerald-500/5 ring-1 ring-emerald-500/30'
: 'border-slate-800 bg-slate-900/50 hover:border-slate-700 hover:bg-slate-900'
}`}
>
<div className="flex items-start justify-between">
<h3 className="text-sm font-medium text-white">{template.name}</h3>
<span
className={`inline-flex items-center rounded-full border px-2 py-0.5 text-[10px] font-medium ${catColor}`}
>
{template.category}
</span>
</div>
<p className="line-clamp-3 text-xs text-slate-400">{template.body_text}</p>
<div className="flex items-center gap-2 text-[10px] text-slate-500">
<span>{template.language ?? 'en_US'}</span>
{template.status && (
<>
<span>-</span>
<span>{template.status}</span>
</>
)}
</div>
</button>
);
})}
</div>
)}
<div className="flex items-center justify-between border-t border-slate-800 pt-4">
<Button variant="outline" onClick={onBack} className="border-slate-700 text-slate-300">
Back
</Button>
<Button
onClick={onNext}
disabled={!selectedTemplate}
className="bg-emerald-600 text-white hover:bg-emerald-700 disabled:opacity-50"
>
Next
<ArrowRight className="h-4 w-4" />
</Button>
</div>
</div>
);
}