import React, { useState, useEffect, useId } from 'react' import { adminApi, tripsApi } from '../../api/client' import { getApiErrorMessage } from '../../utils/apiError' import { useAuthStore } from '../../store/authStore' import { useToast } from '../shared/Toast' import { fs } from '../shared/DialogShell' import { EditorField, INPUT } from '../shared/dialogParts' import { SettingsCard, StatusPill } from '../Settings/settingsKit' import { Bell, Zap, CheckCircle, Navigation, User, Calendar, Clock, Image, MessageSquare, Tag, UserPlus, Download, MapPin, ChevronDown, FlaskConical, Loader2, Map as MapIcon, ShieldCheck, } from 'lucide-react' interface Trip { id: number title: string } interface AppUser { id: number username: string email: string } /** A native select in the editors' box look, with the chevron the CustomSelect draws. */ const SELECT = `${INPUT} appearance-none pe-9` function SelectBox({ id, children, ...rest }: React.SelectHTMLAttributes): React.ReactElement { return (
) } /** The buttons of one group, two to a row from a small window up. */ function ButtonGrid({ children }: { children: React.ReactNode }): React.ReactElement { return
{children}
} export default function DevNotificationsPanel(): React.ReactElement { const toast = useToast() const ids = useId() const user = useAuthStore(s => s.user) const [sending, setSending] = useState(null) const [trips, setTrips] = useState([]) const [selectedTripId, setSelectedTripId] = useState(null) const [users, setUsers] = useState([]) const [selectedUserId, setSelectedUserId] = useState(null) useEffect(() => { tripsApi.list().then(data => { const list = (data.trips || data || []) as Trip[] setTrips(list) if (list.length > 0) setSelectedTripId(list[0].id) }).catch(() => {}) adminApi.users().then(data => { const list = (data.users || data || []) as AppUser[] setUsers(list) if (list.length > 0) setSelectedUserId(list[0].id) }).catch(() => {}) }, []) const fire = async (label: string, payload: Record) => { setSending(label) try { await adminApi.sendTestNotification(payload) toast.success(`Sent: ${label}`) } catch (err: unknown) { toast.error(getApiErrorMessage(err, 'Failed')) } finally { setSending(null) } } const selectedTrip = trips.find(t => t.id === selectedTripId) const selectedUser = users.find(u => u.id === selectedUserId) const username = user?.username || 'Admin' const tripTitle = selectedTrip?.title || 'Test Trip' // ── Helpers ────────────────────────────────────────────────────────────── const Btn = ({ id, label, sub, icon: Icon, onClick, }: { id: string; label: string; sub: string; icon: React.ElementType; onClick: () => void }) => ( ) return (
{/* ── Type Testing ─────────────────────────────────────────────────── */} {/* The whole tab is a dev build's tool; the first card, always there, says so in its band. */} DEV ONLY} > fire('simple-me', { event: 'test_simple', scope: 'user', targetId: user?.id, params: {}, })} /> fire('boolean-me', { event: 'test_boolean', scope: 'user', targetId: user?.id, params: {}, inApp: { type: 'boolean', positiveCallback: { action: 'test_approve', payload: {} }, negativeCallback: { action: 'test_deny', payload: {} }, }, })} /> fire('navigate-me', { event: 'test_navigate', scope: 'user', targetId: user?.id, params: {}, })} /> fire('simple-admins', { event: 'test_simple', scope: 'admin', targetId: 0, params: {}, })} /> {/* ── Trip-Scoped Events ───────────────────────────────────────────── */} {trips.length > 0 && ( setSelectedTripId(Number(e.target.value))} > {trips.map(trip => )} selectedTripId && fire('booking_change', { event: 'booking_change', scope: 'trip', targetId: selectedTripId, params: { actor: username, trip: tripTitle, booking: 'Test Hotel', type: 'hotel', tripId: String(selectedTripId) }, })} /> selectedTripId && fire('trip_reminder', { event: 'trip_reminder', scope: 'trip', targetId: selectedTripId, params: { trip: tripTitle, tripId: String(selectedTripId) }, })} /> selectedTripId && fire('photos_shared', { event: 'photos_shared', scope: 'trip', targetId: selectedTripId, // A string on purpose: notification params are Record end to end, the server // sends count: String(added), and resolveTemplate reads a numeric string as the plural count. params: { actor: username, trip: tripTitle, count: '5', tripId: String(selectedTripId) }, })} /> selectedTripId && fire('collab_message', { event: 'collab_message', scope: 'trip', targetId: selectedTripId, params: { actor: username, trip: tripTitle, preview: 'This is a test message preview.', tripId: String(selectedTripId) }, })} /> selectedTripId && fire('packing_tagged', { event: 'packing_tagged', scope: 'trip', targetId: selectedTripId, params: { actor: username, trip: tripTitle, category: 'Clothing', tripId: String(selectedTripId) }, })} /> )} {/* ── User-Scoped Events ───────────────────────────────────────────── */} {users.length > 0 && ( setSelectedUserId(Number(e.target.value))} > {users.map(u => )} selectedUserId && fire(`trip_invite-${selectedUserId}`, { event: 'trip_invite', scope: 'user', targetId: selectedUserId, params: { actor: username, trip: tripTitle, invitee: selectedUser?.email || '', tripId: String(selectedTripId ?? 0) }, })} /> selectedUserId && fire(`vacay_invite-${selectedUserId}`, { event: 'vacay_invite', scope: 'user', targetId: selectedUserId, params: { actor: username, planId: '1' }, })} /> )} {/* ── Admin-Scoped Events ──────────────────────────────────────────── */} fire('version_available', { event: 'version_available', scope: 'admin', targetId: 0, params: { version: '9.9.9-test' }, })} />
) }