| @@ -1,9 +1,66 @@ | |||||
| 'use client'; | 'use client'; | ||||
| import { useState, useEffect } from 'react'; | |||||
| import { Card, Portal, MediaItem } from '@/types'; | |||||
| import { useState, useEffect, useRef } from 'react'; | |||||
| import { Card, Portal, MediaItem, CardType } from '@/types'; | |||||
| import { EXTERNAL_LINK_ENABLED } from '@/lib/config'; | import { EXTERNAL_LINK_ENABLED } from '@/lib/config'; | ||||
| function CardTypeSelect({ | |||||
| value, | |||||
| onChange, | |||||
| options, | |||||
| }: { | |||||
| value: CardType; | |||||
| onChange: (v: CardType) => void; | |||||
| options: { value: CardType; label: string }[]; | |||||
| }) { | |||||
| const [open, setOpen] = useState(false); | |||||
| const ref = useRef<HTMLDivElement>(null); | |||||
| useEffect(() => { | |||||
| if (!open) return; | |||||
| const onClick = (e: MouseEvent) => { | |||||
| if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false); | |||||
| }; | |||||
| const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') setOpen(false); }; | |||||
| document.addEventListener('mousedown', onClick); | |||||
| document.addEventListener('keydown', onKey); | |||||
| return () => { | |||||
| document.removeEventListener('mousedown', onClick); | |||||
| document.removeEventListener('keydown', onKey); | |||||
| }; | |||||
| }, [open]); | |||||
| const current = options.find(o => o.value === value); | |||||
| const inputBase = "w-full border border-gray-300 p-2.5 rounded-lg outline-none focus:ring-2 focus:ring-blue-500 bg-white text-gray-900"; | |||||
| return ( | |||||
| <div ref={ref} className="relative"> | |||||
| <button | |||||
| type="button" | |||||
| onClick={() => setOpen(o => !o)} | |||||
| className={`${inputBase} text-left flex items-center justify-between cursor-pointer`} | |||||
| > | |||||
| <span>{current?.label || ''}</span> | |||||
| <span className={`text-gray-500 transition-transform ${open ? 'rotate-180' : ''}`}>▾</span> | |||||
| </button> | |||||
| {open && ( | |||||
| <div className="absolute left-0 right-0 top-full mt-1 bg-white border border-gray-200 rounded-lg shadow-lg z-30 overflow-hidden"> | |||||
| {options.map(o => ( | |||||
| <button | |||||
| key={o.value} | |||||
| type="button" | |||||
| onClick={() => { onChange(o.value); setOpen(false); }} | |||||
| className={`w-full text-left px-3 py-2.5 hover:bg-blue-50 transition-colors ${o.value === value ? 'bg-blue-100 font-semibold text-blue-700' : 'text-gray-800'}`} | |||||
| > | |||||
| {o.label} | |||||
| </button> | |||||
| ))} | |||||
| </div> | |||||
| )} | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| const isVideoUrl = (url: string) => /\.(mp4|webm|mov|m4v|ogv)(\?|$)/i.test(url); | const isVideoUrl = (url: string) => /\.(mp4|webm|mov|m4v|ogv)(\?|$)/i.test(url); | ||||
| const isPdfFile = (file: File) => | const isPdfFile = (file: File) => | ||||
| file.type === 'application/pdf' || /\.pdf$/i.test(file.name); | file.type === 'application/pdf' || /\.pdf$/i.test(file.name); | ||||
| @@ -496,11 +553,15 @@ export default function AdminDashboard() { | |||||
| </div> | </div> | ||||
| <div> | <div> | ||||
| <label className="block text-sm font-semibold text-gray-800 mb-1">Card Type</label> | <label className="block text-sm font-semibold text-gray-800 mb-1">Card Type</label> | ||||
| <select value={isEditing.cardType || 'INFO_PAGE'} onChange={e => setIsEditing({...isEditing, cardType: e.target.value as any})} className={inputClasses}> | |||||
| <option value="INFO_PAGE">Info Page</option> | |||||
| <option value="IMAGE_GALLERY">Image Gallery</option> | |||||
| {EXTERNAL_LINK_ENABLED && <option value="EXTERNAL_LINK">External Link</option>} | |||||
| </select> | |||||
| <CardTypeSelect | |||||
| value={(isEditing.cardType || 'INFO_PAGE') as CardType} | |||||
| onChange={(v) => setIsEditing({ ...isEditing, cardType: v })} | |||||
| options={[ | |||||
| { value: 'INFO_PAGE', label: 'Info Page' }, | |||||
| { value: 'IMAGE_GALLERY', label: 'Image Gallery' }, | |||||
| ...(EXTERNAL_LINK_ENABLED ? [{ value: 'EXTERNAL_LINK' as CardType, label: 'External Link' }] : []), | |||||
| ]} | |||||
| /> | |||||
| </div> | </div> | ||||
| {isEditing.cardType === 'EXTERNAL_LINK' ? ( | {isEditing.cardType === 'EXTERNAL_LINK' ? ( | ||||
| <> | <> | ||||