Mobile-friendly admin pages, redesigned homepage Next Event card
- Extract shared mobile components (BottomSheet, MoreMenu, Dropdown, etc.) into MobileComponents.tsx - Make admin pages mobile-friendly: bookings, emails, events, faq, payments, tickets, users - Redesign homepage Next Event card with banner image, responsive layout, and updated styling - Fix past events showing on homepage/linktree: use proper Date comparison, auto-unfeature expired events - Add "Over" tag to admin events list for past events - Fix backend FRONTEND_URL for cache revalidation Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import { faqApi, FaqItemAdmin } from '@/lib/api';
|
||||
import Card from '@/components/ui/Card';
|
||||
import Button from '@/components/ui/Button';
|
||||
import Input from '@/components/ui/Input';
|
||||
import { MoreMenu, DropdownItem, AdminMobileStyles } from '@/components/admin/MobileComponents';
|
||||
import toast from 'react-hot-toast';
|
||||
import clsx from 'clsx';
|
||||
import {
|
||||
@@ -15,19 +16,14 @@ import {
|
||||
Bars3Icon,
|
||||
XMarkIcon,
|
||||
CheckIcon,
|
||||
ArrowLeftIcon,
|
||||
ChevronUpIcon,
|
||||
ChevronDownIcon,
|
||||
} from '@heroicons/react/24/outline';
|
||||
|
||||
type FormState = { id: string | null; question: string; questionEs: string; answer: string; answerEs: string; enabled: boolean; showOnHomepage: boolean };
|
||||
|
||||
const emptyForm: FormState = {
|
||||
id: null,
|
||||
question: '',
|
||||
questionEs: '',
|
||||
answer: '',
|
||||
answerEs: '',
|
||||
enabled: true,
|
||||
showOnHomepage: false,
|
||||
id: null, question: '', questionEs: '', answer: '', answerEs: '', enabled: true, showOnHomepage: false,
|
||||
};
|
||||
|
||||
export default function AdminFaqPage() {
|
||||
@@ -40,9 +36,7 @@ export default function AdminFaqPage() {
|
||||
const [draggedId, setDraggedId] = useState<string | null>(null);
|
||||
const [dragOverId, setDragOverId] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
loadFaqs();
|
||||
}, []);
|
||||
useEffect(() => { loadFaqs(); }, []);
|
||||
|
||||
const loadFaqs = async () => {
|
||||
try {
|
||||
@@ -57,20 +51,12 @@ export default function AdminFaqPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleCreate = () => {
|
||||
setForm(emptyForm);
|
||||
setShowForm(true);
|
||||
};
|
||||
const handleCreate = () => { setForm(emptyForm); setShowForm(true); };
|
||||
|
||||
const handleEdit = (faq: FaqItemAdmin) => {
|
||||
setForm({
|
||||
id: faq.id,
|
||||
question: faq.question,
|
||||
questionEs: faq.questionEs ?? '',
|
||||
answer: faq.answer,
|
||||
answerEs: faq.answerEs ?? '',
|
||||
enabled: faq.enabled,
|
||||
showOnHomepage: faq.showOnHomepage,
|
||||
id: faq.id, question: faq.question, questionEs: faq.questionEs ?? '',
|
||||
answer: faq.answer, answerEs: faq.answerEs ?? '', enabled: faq.enabled, showOnHomepage: faq.showOnHomepage,
|
||||
});
|
||||
setShowForm(true);
|
||||
};
|
||||
@@ -84,22 +70,16 @@ export default function AdminFaqPage() {
|
||||
setSaving(true);
|
||||
if (form.id) {
|
||||
await faqApi.update(form.id, {
|
||||
question: form.question.trim(),
|
||||
questionEs: form.questionEs.trim() || null,
|
||||
answer: form.answer.trim(),
|
||||
answerEs: form.answerEs.trim() || null,
|
||||
enabled: form.enabled,
|
||||
showOnHomepage: form.showOnHomepage,
|
||||
question: form.question.trim(), questionEs: form.questionEs.trim() || null,
|
||||
answer: form.answer.trim(), answerEs: form.answerEs.trim() || null,
|
||||
enabled: form.enabled, showOnHomepage: form.showOnHomepage,
|
||||
});
|
||||
toast.success(locale === 'es' ? 'FAQ actualizado' : 'FAQ updated');
|
||||
} else {
|
||||
await faqApi.create({
|
||||
question: form.question.trim(),
|
||||
questionEs: form.questionEs.trim() || undefined,
|
||||
answer: form.answer.trim(),
|
||||
answerEs: form.answerEs.trim() || undefined,
|
||||
enabled: form.enabled,
|
||||
showOnHomepage: form.showOnHomepage,
|
||||
question: form.question.trim(), questionEs: form.questionEs.trim() || undefined,
|
||||
answer: form.answer.trim(), answerEs: form.answerEs.trim() || undefined,
|
||||
enabled: form.enabled, showOnHomepage: form.showOnHomepage,
|
||||
});
|
||||
toast.success(locale === 'es' ? 'FAQ creado' : 'FAQ created');
|
||||
}
|
||||
@@ -143,22 +123,44 @@ export default function AdminFaqPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleMoveUp = async (index: number) => {
|
||||
if (index === 0) return;
|
||||
const newOrder = [...faqs];
|
||||
[newOrder[index - 1], newOrder[index]] = [newOrder[index], newOrder[index - 1]];
|
||||
const ids = newOrder.map(f => f.id);
|
||||
try {
|
||||
const res = await faqApi.reorder(ids);
|
||||
setFaqs(res.faqs);
|
||||
} catch (err: any) {
|
||||
toast.error(err.message || (locale === 'es' ? 'Error al reordenar' : 'Failed to reorder'));
|
||||
}
|
||||
};
|
||||
|
||||
const handleMoveDown = async (index: number) => {
|
||||
if (index >= faqs.length - 1) return;
|
||||
const newOrder = [...faqs];
|
||||
[newOrder[index], newOrder[index + 1]] = [newOrder[index + 1], newOrder[index]];
|
||||
const ids = newOrder.map(f => f.id);
|
||||
try {
|
||||
const res = await faqApi.reorder(ids);
|
||||
setFaqs(res.faqs);
|
||||
} catch (err: any) {
|
||||
toast.error(err.message || (locale === 'es' ? 'Error al reordenar' : 'Failed to reorder'));
|
||||
}
|
||||
};
|
||||
|
||||
// Desktop drag handlers
|
||||
const handleDragStart = (e: React.DragEvent, id: string) => {
|
||||
setDraggedId(id);
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
e.dataTransfer.setData('text/plain', id);
|
||||
};
|
||||
|
||||
const handleDragOver = (e: React.DragEvent, id: string) => {
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = 'move';
|
||||
setDragOverId(id);
|
||||
};
|
||||
|
||||
const handleDragLeave = () => {
|
||||
setDragOverId(null);
|
||||
};
|
||||
|
||||
const handleDragLeave = () => { setDragOverId(null); };
|
||||
const handleDrop = async (e: React.DragEvent, targetId: string) => {
|
||||
e.preventDefault();
|
||||
setDragOverId(null);
|
||||
@@ -180,11 +182,7 @@ export default function AdminFaqPage() {
|
||||
toast.error(err.message || (locale === 'es' ? 'Error al reordenar' : 'Failed to reorder'));
|
||||
}
|
||||
};
|
||||
|
||||
const handleDragEnd = () => {
|
||||
setDraggedId(null);
|
||||
setDragOverId(null);
|
||||
};
|
||||
const handleDragEnd = () => { setDraggedId(null); setDragOverId(null); };
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
@@ -198,179 +196,120 @@ export default function AdminFaqPage() {
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between flex-wrap gap-4">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold font-heading">
|
||||
{locale === 'es' ? 'FAQ' : 'FAQ'}
|
||||
</h1>
|
||||
<p className="text-gray-500 text-sm mt-1">
|
||||
<h1 className="text-xl md:text-2xl font-bold font-heading">FAQ</h1>
|
||||
<p className="text-gray-500 text-xs md:text-sm mt-1 hidden md:block">
|
||||
{locale === 'es'
|
||||
? 'Crear y editar preguntas frecuentes. Arrastra para cambiar el orden.'
|
||||
: 'Create and edit FAQ questions. Drag to change order.'}
|
||||
</p>
|
||||
</div>
|
||||
<Button onClick={handleCreate}>
|
||||
<Button onClick={handleCreate} className="hidden md:flex">
|
||||
<PlusIcon className="w-4 h-4 mr-2" />
|
||||
{locale === 'es' ? 'Nueva pregunta' : 'Add question'}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Form Modal - bottom sheet on mobile */}
|
||||
{showForm && (
|
||||
<Card>
|
||||
<div className="p-6 space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<h2 className="text-lg font-semibold">
|
||||
<div className="fixed inset-0 bg-black/50 z-50 flex items-end md:items-center justify-center p-0 md:p-4">
|
||||
<Card className="w-full md:max-w-2xl max-h-[90vh] flex flex-col overflow-hidden rounded-t-2xl md:rounded-card">
|
||||
<div className="flex items-center justify-between p-4 border-b border-secondary-light-gray flex-shrink-0">
|
||||
<h2 className="text-base font-semibold">
|
||||
{form.id ? (locale === 'es' ? 'Editar pregunta' : 'Edit question') : (locale === 'es' ? 'Nueva pregunta' : 'New question')}
|
||||
</h2>
|
||||
<button
|
||||
onClick={() => { setForm(emptyForm); setShowForm(false); }}
|
||||
className="p-2 hover:bg-gray-100 rounded-full"
|
||||
>
|
||||
<button onClick={() => { setForm(emptyForm); setShowForm(false); }}
|
||||
className="p-2 hover:bg-gray-100 rounded-full min-h-[44px] min-w-[44px] flex items-center justify-center">
|
||||
<XMarkIcon className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">Question (EN) *</label>
|
||||
<Input
|
||||
value={form.question}
|
||||
onChange={e => setForm(f => ({ ...f, question: e.target.value }))}
|
||||
placeholder="Question in English"
|
||||
/>
|
||||
<div className="p-4 space-y-4 overflow-y-auto flex-1 min-h-0">
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">Question (EN) *</label>
|
||||
<Input value={form.question} onChange={e => setForm(f => ({ ...f, question: e.target.value }))} placeholder="Question in English" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">Pregunta (ES)</label>
|
||||
<Input value={form.questionEs} onChange={e => setForm(f => ({ ...f, questionEs: e.target.value }))} placeholder="Pregunta en español" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">Pregunta (ES)</label>
|
||||
<Input
|
||||
value={form.questionEs}
|
||||
onChange={e => setForm(f => ({ ...f, questionEs: e.target.value }))}
|
||||
placeholder="Pregunta en español"
|
||||
/>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">Answer (EN) *</label>
|
||||
<textarea className="w-full border border-gray-300 rounded-btn px-3 py-2 min-h-[100px]"
|
||||
value={form.answer} onChange={e => setForm(f => ({ ...f, answer: e.target.value }))} placeholder="Answer in English" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">Respuesta (ES)</label>
|
||||
<textarea className="w-full border border-gray-300 rounded-btn px-3 py-2 min-h-[100px]"
|
||||
value={form.answerEs} onChange={e => setForm(f => ({ ...f, answerEs: e.target.value }))} placeholder="Respuesta en español" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-6">
|
||||
<label className="flex items-center gap-2 cursor-pointer min-h-[44px]">
|
||||
<input type="checkbox" checked={form.enabled} onChange={e => setForm(f => ({ ...f, enabled: e.target.checked }))} className="rounded border-gray-300 w-4 h-4" />
|
||||
<span className="text-sm">{locale === 'es' ? 'Mostrar en el sitio' : 'Show on site'}</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 cursor-pointer min-h-[44px]">
|
||||
<input type="checkbox" checked={form.showOnHomepage} onChange={e => setForm(f => ({ ...f, showOnHomepage: e.target.checked }))} className="rounded border-gray-300 w-4 h-4" />
|
||||
<span className="text-sm">{locale === 'es' ? 'Mostrar en inicio' : 'Show on homepage'}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex gap-3 pt-2">
|
||||
<Button onClick={handleSave} isLoading={saving} className="flex-1 min-h-[44px]">
|
||||
<CheckIcon className="w-4 h-4 mr-1" /> {locale === 'es' ? 'Guardar' : 'Save'}
|
||||
</Button>
|
||||
<Button variant="outline" onClick={() => { setForm(emptyForm); setShowForm(false); }} disabled={saving} className="flex-1 min-h-[44px]">
|
||||
{locale === 'es' ? 'Cancelar' : 'Cancel'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">Answer (EN) *</label>
|
||||
<textarea
|
||||
className="w-full border border-gray-300 rounded-btn px-3 py-2 min-h-[100px]"
|
||||
value={form.answer}
|
||||
onChange={e => setForm(f => ({ ...f, answer: e.target.value }))}
|
||||
placeholder="Answer in English"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">Respuesta (ES)</label>
|
||||
<textarea
|
||||
className="w-full border border-gray-300 rounded-btn px-3 py-2 min-h-[100px]"
|
||||
value={form.answerEs}
|
||||
onChange={e => setForm(f => ({ ...f, answerEs: e.target.value }))}
|
||||
placeholder="Respuesta en español"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-6">
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={form.enabled}
|
||||
onChange={e => setForm(f => ({ ...f, enabled: e.target.checked }))}
|
||||
className="rounded border-gray-300"
|
||||
/>
|
||||
<span className="text-sm">{locale === 'es' ? 'Mostrar en el sitio' : 'Show on site'}</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={form.showOnHomepage}
|
||||
onChange={e => setForm(f => ({ ...f, showOnHomepage: e.target.checked }))}
|
||||
className="rounded border-gray-300"
|
||||
/>
|
||||
<span className="text-sm">{locale === 'es' ? 'Mostrar en inicio' : 'Show on homepage'}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button onClick={handleSave} isLoading={saving}>
|
||||
<CheckIcon className="w-4 h-4 mr-1" />
|
||||
{locale === 'es' ? 'Guardar' : 'Save'}
|
||||
</Button>
|
||||
<Button variant="outline" onClick={() => { setForm(emptyForm); setShowForm(false); }} disabled={saving}>
|
||||
{locale === 'es' ? 'Cancelar' : 'Cancel'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Card>
|
||||
{/* Desktop: Table */}
|
||||
<Card className="hidden md:block">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead className="bg-gray-50 border-b border-gray-200">
|
||||
<tr>
|
||||
<th className="w-10 px-4 py-3" />
|
||||
<th className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase">
|
||||
{locale === 'es' ? 'Pregunta' : 'Question'}
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase w-24">
|
||||
{locale === 'es' ? 'En sitio' : 'On site'}
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase w-28">
|
||||
{locale === 'es' ? 'En inicio' : 'Homepage'}
|
||||
</th>
|
||||
<th className="px-4 py-3 text-right text-xs font-semibold text-gray-600 uppercase w-32">
|
||||
{locale === 'es' ? 'Acciones' : 'Actions'}
|
||||
</th>
|
||||
<th className="px-4 py-2 text-left text-xs font-semibold text-gray-500 uppercase">{locale === 'es' ? 'Pregunta' : 'Question'}</th>
|
||||
<th className="px-4 py-2 text-left text-xs font-semibold text-gray-500 uppercase w-24">{locale === 'es' ? 'En sitio' : 'On site'}</th>
|
||||
<th className="px-4 py-2 text-left text-xs font-semibold text-gray-500 uppercase w-28">{locale === 'es' ? 'En inicio' : 'Homepage'}</th>
|
||||
<th className="px-4 py-2 text-right text-xs font-semibold text-gray-500 uppercase w-32">{locale === 'es' ? 'Acciones' : 'Actions'}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-200">
|
||||
{faqs.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan={5} className="px-6 py-12 text-center text-gray-500">
|
||||
{locale === 'es' ? 'No hay preguntas. Añade la primera.' : 'No questions yet. Add the first one.'}
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td colSpan={5} className="px-6 py-12 text-center text-gray-500 text-sm">{locale === 'es' ? 'No hay preguntas. Añade la primera.' : 'No questions yet. Add the first one.'}</td></tr>
|
||||
) : (
|
||||
faqs.map((faq) => (
|
||||
<tr
|
||||
key={faq.id}
|
||||
draggable
|
||||
onDragStart={e => handleDragStart(e, faq.id)}
|
||||
onDragOver={e => handleDragOver(e, faq.id)}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={e => handleDrop(e, faq.id)}
|
||||
onDragEnd={handleDragEnd}
|
||||
className={clsx(
|
||||
'hover:bg-gray-50',
|
||||
draggedId === faq.id && 'opacity-50',
|
||||
dragOverId === faq.id && 'bg-primary-yellow/10'
|
||||
)}
|
||||
>
|
||||
<tr key={faq.id} draggable onDragStart={e => handleDragStart(e, faq.id)}
|
||||
onDragOver={e => handleDragOver(e, faq.id)} onDragLeave={handleDragLeave}
|
||||
onDrop={e => handleDrop(e, faq.id)} onDragEnd={handleDragEnd}
|
||||
className={clsx('hover:bg-gray-50', draggedId === faq.id && 'opacity-50', dragOverId === faq.id && 'bg-primary-yellow/10')}>
|
||||
<td className="px-4 py-3">
|
||||
<span className="cursor-grab active:cursor-grabbing text-gray-400 hover:text-gray-600" title={locale === 'es' ? 'Arrastrar para reordenar' : 'Drag to reorder'}>
|
||||
<Bars3Icon className="w-5 h-5" />
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
<p className="font-medium text-primary-dark line-clamp-1">
|
||||
{locale === 'es' && faq.questionEs ? faq.questionEs : faq.question}
|
||||
</p>
|
||||
<p className="font-medium text-primary-dark text-sm line-clamp-1">{locale === 'es' && faq.questionEs ? faq.questionEs : faq.question}</p>
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
<button
|
||||
onClick={() => handleToggleEnabled(faq)}
|
||||
className={clsx(
|
||||
'inline-flex items-center px-2.5 py-1 rounded-full text-xs font-medium',
|
||||
faq.enabled ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-500'
|
||||
)}
|
||||
>
|
||||
{faq.enabled ? (locale === 'es' ? 'Sí' : 'Yes') : (locale === 'es' ? 'No' : 'No')}
|
||||
<button onClick={() => handleToggleEnabled(faq)}
|
||||
className={clsx('inline-flex items-center px-2.5 py-1 rounded-full text-xs font-medium min-h-[32px]',
|
||||
faq.enabled ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-500')}>
|
||||
{faq.enabled ? (locale === 'es' ? 'Sí' : 'Yes') : 'No'}
|
||||
</button>
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
<button
|
||||
onClick={() => handleToggleShowOnHomepage(faq)}
|
||||
className={clsx(
|
||||
'inline-flex items-center px-2.5 py-1 rounded-full text-xs font-medium',
|
||||
faq.showOnHomepage ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-500'
|
||||
)}
|
||||
>
|
||||
{faq.showOnHomepage ? (locale === 'es' ? 'Sí' : 'Yes') : (locale === 'es' ? 'No' : 'No')}
|
||||
<button onClick={() => handleToggleShowOnHomepage(faq)}
|
||||
className={clsx('inline-flex items-center px-2.5 py-1 rounded-full text-xs font-medium min-h-[32px]',
|
||||
faq.showOnHomepage ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-500')}>
|
||||
{faq.showOnHomepage ? (locale === 'es' ? 'Sí' : 'Yes') : 'No'}
|
||||
</button>
|
||||
</td>
|
||||
<td className="px-4 py-3 text-right">
|
||||
@@ -390,6 +329,65 @@ export default function AdminFaqPage() {
|
||||
</table>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Mobile: Card List */}
|
||||
<div className="md:hidden space-y-2">
|
||||
{faqs.length === 0 ? (
|
||||
<div className="text-center py-10 text-gray-500 text-sm">{locale === 'es' ? 'No hay preguntas. Añade la primera.' : 'No questions yet. Add the first one.'}</div>
|
||||
) : (
|
||||
faqs.map((faq, index) => (
|
||||
<Card key={faq.id} className="p-3">
|
||||
<div className="flex items-start gap-2">
|
||||
<div className="flex flex-col gap-0.5 flex-shrink-0 pt-0.5">
|
||||
<button onClick={() => handleMoveUp(index)} disabled={index === 0}
|
||||
className="p-1 text-gray-400 hover:text-gray-600 disabled:opacity-30 min-h-[28px] min-w-[28px] flex items-center justify-center">
|
||||
<ChevronUpIcon className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
<button onClick={() => handleMoveDown(index)} disabled={index >= faqs.length - 1}
|
||||
className="p-1 text-gray-400 hover:text-gray-600 disabled:opacity-30 min-h-[28px] min-w-[28px] flex items-center justify-center">
|
||||
<ChevronDownIcon className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="font-medium text-sm text-primary-dark line-clamp-2">
|
||||
{locale === 'es' && faq.questionEs ? faq.questionEs : faq.question}
|
||||
</p>
|
||||
<div className="flex items-center gap-2 mt-1.5">
|
||||
<button onClick={() => handleToggleEnabled(faq)}
|
||||
className={clsx('inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-medium min-h-[28px]',
|
||||
faq.enabled ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-500')}>
|
||||
{faq.enabled ? (locale === 'es' ? 'Sitio: Sí' : 'Site: Yes') : (locale === 'es' ? 'Sitio: No' : 'Site: No')}
|
||||
</button>
|
||||
<button onClick={() => handleToggleShowOnHomepage(faq)}
|
||||
className={clsx('inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-medium min-h-[28px]',
|
||||
faq.showOnHomepage ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-500')}>
|
||||
{faq.showOnHomepage ? (locale === 'es' ? 'Inicio: Sí' : 'Home: Yes') : (locale === 'es' ? 'Inicio: No' : 'Home: No')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<MoreMenu>
|
||||
<DropdownItem onClick={() => handleEdit(faq)}>
|
||||
<PencilSquareIcon className="w-4 h-4 mr-2" /> {locale === 'es' ? 'Editar' : 'Edit'}
|
||||
</DropdownItem>
|
||||
<DropdownItem onClick={() => handleDelete(faq.id)} className="text-red-600">
|
||||
<TrashIcon className="w-4 h-4 mr-2" /> {locale === 'es' ? 'Eliminar' : 'Delete'}
|
||||
</DropdownItem>
|
||||
</MoreMenu>
|
||||
</div>
|
||||
</Card>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Mobile FAB */}
|
||||
<div className="md:hidden fixed bottom-6 right-6 z-40">
|
||||
<button onClick={handleCreate}
|
||||
className="w-14 h-14 bg-primary-yellow text-primary-dark rounded-full shadow-lg flex items-center justify-center hover:bg-yellow-400 active:scale-95 transition-transform">
|
||||
<PlusIcon className="w-6 h-6" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<AdminMobileStyles />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user