mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-24 07:53:09 +00:00
refactor(admin): share the dev notification tester between desktop and phone
Both panels loaded the same trips and users, built the same twelve test payloads and sent them the same way. That now lives in useDevNotifications; each panel keeps its own markup and the phone keeps its own wording of the failure toast.
This commit is contained in:
@@ -1,24 +1,14 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import React from 'react'
|
||||
import {
|
||||
Bell, Zap, CheckCircle, Navigation, Calendar, Clock, Image,
|
||||
MessageSquare, Tag, UserPlus, Download, MapPin, Loader2,
|
||||
} from 'lucide-react'
|
||||
import type { LucideIcon } from 'lucide-react'
|
||||
import { adminApi, tripsApi } from '../../../api/client'
|
||||
import { useAuthStore } from '../../../store/authStore'
|
||||
import { useToast } from '../../../components/shared/Toast'
|
||||
import { useDevNotifications } from '../../../components/Admin/useDevNotifications'
|
||||
import { MAdminCard, MAdminCardHead } from './MAdminUi'
|
||||
|
||||
interface Trip {
|
||||
id: number
|
||||
title: string
|
||||
}
|
||||
|
||||
interface AppUser {
|
||||
id: number
|
||||
username: string
|
||||
email: string
|
||||
}
|
||||
/** The phone panel toasts the thrown error's own message rather than the server's error field. */
|
||||
const errorMessage = (err: unknown) => (err instanceof Error ? err.message : 'Failed')
|
||||
|
||||
const SELECT_CLASS =
|
||||
'mb-2 h-[42px] w-full rounded-xl border border-[color:var(--m-rowbr)] bg-[color:var(--m-ic)] px-3 text-[0.84375rem] text-m-ink outline-none focus:border-[color:var(--m-faint)]'
|
||||
@@ -57,46 +47,13 @@ function Btn({
|
||||
}
|
||||
|
||||
// Dev-only notification testing panel, re-skinned to the mobile admin system.
|
||||
// All state, fetches and fire() payloads are ported verbatim from the desktop
|
||||
// DevNotificationsPanel — only the presentation layer changes.
|
||||
// All state, fetches and payloads come from useDevNotifications, shared
|
||||
// with the desktop DevNotificationsPanel; only the presentation layer differs.
|
||||
export default function MAdminDevNotificationsPanel(): React.ReactElement {
|
||||
const toast = useToast()
|
||||
const user = useAuthStore(s => s.user)
|
||||
const [sending, setSending] = useState<string | null>(null)
|
||||
const [trips, setTrips] = useState<Trip[]>([])
|
||||
const [selectedTripId, setSelectedTripId] = useState<number | null>(null)
|
||||
const [users, setUsers] = useState<AppUser[]>([])
|
||||
const [selectedUserId, setSelectedUserId] = useState<number | null>(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<string, unknown>) => {
|
||||
setSending(label)
|
||||
try {
|
||||
await adminApi.sendTestNotification(payload)
|
||||
toast.success(`Sent: ${label}`)
|
||||
} catch (err: unknown) {
|
||||
toast.error(err instanceof Error ? err.message : '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'
|
||||
const {
|
||||
sending, trips, selectedTripId, setSelectedTripId, users, selectedUserId, setSelectedUserId,
|
||||
tripInviteId, vacayInviteId, send,
|
||||
} = useDevNotifications({ errorMessage })
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
@@ -118,41 +75,16 @@ export default function MAdminDevNotificationsPanel(): React.ReactElement {
|
||||
/>
|
||||
<div className="mt-2 grid grid-cols-1 gap-2">
|
||||
<Btn sending={sending} id="simple-me" label="Simple → Me" sub="test_simple · user" icon={Bell} color="#6366f1"
|
||||
onClick={() => fire('simple-me', {
|
||||
event: 'test_simple',
|
||||
scope: 'user',
|
||||
targetId: user?.id,
|
||||
params: {},
|
||||
})}
|
||||
onClick={send.simpleMe}
|
||||
/>
|
||||
<Btn sending={sending} id="boolean-me" label="Boolean → Me" sub="test_boolean · user" icon={CheckCircle} color="#10b981"
|
||||
onClick={() => 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: {} },
|
||||
},
|
||||
})}
|
||||
onClick={send.booleanMe}
|
||||
/>
|
||||
<Btn sending={sending} id="navigate-me" label="Navigate → Me" sub="test_navigate · user" icon={Navigation} color="#f59e0b"
|
||||
onClick={() => fire('navigate-me', {
|
||||
event: 'test_navigate',
|
||||
scope: 'user',
|
||||
targetId: user?.id,
|
||||
params: {},
|
||||
})}
|
||||
onClick={send.navigateMe}
|
||||
/>
|
||||
<Btn sending={sending} id="simple-admins" label="Simple → All Admins" sub="test_simple · admin" icon={Zap} color="#ef4444"
|
||||
onClick={() => fire('simple-admins', {
|
||||
event: 'test_simple',
|
||||
scope: 'admin',
|
||||
targetId: 0,
|
||||
params: {},
|
||||
})}
|
||||
onClick={send.simpleAdmins}
|
||||
/>
|
||||
</div>
|
||||
</MAdminCard>
|
||||
@@ -174,44 +106,19 @@ export default function MAdminDevNotificationsPanel(): React.ReactElement {
|
||||
</select>
|
||||
<div className="grid grid-cols-1 gap-2">
|
||||
<Btn sending={sending} id="booking_change" label="booking_change" sub="navigate · trip" icon={Calendar} color="#6366f1"
|
||||
onClick={() => selectedTripId && fire('booking_change', {
|
||||
event: 'booking_change',
|
||||
scope: 'trip',
|
||||
targetId: selectedTripId,
|
||||
params: { actor: username, trip: tripTitle, booking: 'Test Hotel', type: 'hotel', tripId: String(selectedTripId) },
|
||||
})}
|
||||
onClick={send.bookingChange}
|
||||
/>
|
||||
<Btn sending={sending} id="trip_reminder" label="trip_reminder" sub="navigate · trip" icon={Clock} color="#10b981"
|
||||
onClick={() => selectedTripId && fire('trip_reminder', {
|
||||
event: 'trip_reminder',
|
||||
scope: 'trip',
|
||||
targetId: selectedTripId,
|
||||
params: { trip: tripTitle, tripId: String(selectedTripId) },
|
||||
})}
|
||||
onClick={send.tripReminder}
|
||||
/>
|
||||
<Btn sending={sending} id="photos_shared" label="photos_shared" sub="navigate · trip" icon={Image} color="#f59e0b"
|
||||
onClick={() => selectedTripId && fire('photos_shared', {
|
||||
event: 'photos_shared',
|
||||
scope: 'trip',
|
||||
targetId: selectedTripId,
|
||||
params: { actor: username, trip: tripTitle, count: '5', tripId: String(selectedTripId) },
|
||||
})}
|
||||
onClick={send.photosShared}
|
||||
/>
|
||||
<Btn sending={sending} id="collab_message" label="collab_message" sub="navigate · trip" icon={MessageSquare} color="#8b5cf6"
|
||||
onClick={() => 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) },
|
||||
})}
|
||||
onClick={send.collabMessage}
|
||||
/>
|
||||
<Btn sending={sending} id="packing_tagged" label="packing_tagged" sub="navigate · trip" icon={Tag} color="#ec4899"
|
||||
onClick={() => selectedTripId && fire('packing_tagged', {
|
||||
event: 'packing_tagged',
|
||||
scope: 'trip',
|
||||
targetId: selectedTripId,
|
||||
params: { actor: username, trip: tripTitle, category: 'Clothing', tripId: String(selectedTripId) },
|
||||
})}
|
||||
onClick={send.packingTagged}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -236,31 +143,21 @@ export default function MAdminDevNotificationsPanel(): React.ReactElement {
|
||||
<div className="grid grid-cols-1 gap-2">
|
||||
<Btn
|
||||
sending={sending}
|
||||
id={`trip_invite-${selectedUserId}`}
|
||||
id={tripInviteId}
|
||||
label="trip_invite"
|
||||
sub="navigate · user"
|
||||
icon={UserPlus}
|
||||
color="#06b6d4"
|
||||
onClick={() => selectedUserId && fire(`trip_invite-${selectedUserId}`, {
|
||||
event: 'trip_invite',
|
||||
scope: 'user',
|
||||
targetId: selectedUserId,
|
||||
params: { actor: username, trip: tripTitle, invitee: selectedUser?.email || '', tripId: String(selectedTripId ?? 0) },
|
||||
})}
|
||||
onClick={send.tripInvite}
|
||||
/>
|
||||
<Btn
|
||||
sending={sending}
|
||||
id={`vacay_invite-${selectedUserId}`}
|
||||
id={vacayInviteId}
|
||||
label="vacay_invite"
|
||||
sub="navigate · user"
|
||||
icon={MapPin}
|
||||
color="#f97316"
|
||||
onClick={() => selectedUserId && fire(`vacay_invite-${selectedUserId}`, {
|
||||
event: 'vacay_invite',
|
||||
scope: 'user',
|
||||
targetId: selectedUserId,
|
||||
params: { actor: username, planId: '1' },
|
||||
})}
|
||||
onClick={send.vacayInvite}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -275,12 +172,7 @@ export default function MAdminDevNotificationsPanel(): React.ReactElement {
|
||||
/>
|
||||
<div className="mt-2 grid grid-cols-1 gap-2">
|
||||
<Btn sending={sending} id="version_available" label="version_available" sub="navigate · admin" icon={Download} color="#64748b"
|
||||
onClick={() => fire('version_available', {
|
||||
event: 'version_available',
|
||||
scope: 'admin',
|
||||
targetId: 0,
|
||||
params: { version: '9.9.9-test' },
|
||||
})}
|
||||
onClick={send.versionAvailable}
|
||||
/>
|
||||
</div>
|
||||
</MAdminCard>
|
||||
|
||||
Reference in New Issue
Block a user