fix(lunch): reminder filter, recap failure surfacing, manual send-reminders
- Extract selectUnpickedAttendees helper with OR filter (is null OR pickedAt null) to fix cron missing attendees with no MemberLunchPick row at all - Update cron route to use the helper - sendRecap now throws TRPCError on email failure instead of silently stamping success - Add lunch.sendReminders adminProcedure for manual on-demand reminder sends - Add "Send reminders now" AlertDialog button to LunchRecapActions - Tests: lunch-reminder-filter.test.ts (2 new), all 5 lunch test files pass (40 tests) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -11,15 +11,28 @@ import {
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
} from '@/components/ui/dialog'
|
||||
import { Send, Eye } from 'lucide-react'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
} from '@/components/ui/alert-dialog'
|
||||
import { Send, Eye, Bell } from 'lucide-react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
export function LunchRecapActions({
|
||||
programId,
|
||||
lunchEventId,
|
||||
recapSentAt,
|
||||
extraRecipientCount,
|
||||
}: {
|
||||
programId: string
|
||||
lunchEventId: string
|
||||
recapSentAt: Date | null
|
||||
extraRecipientCount: number
|
||||
}) {
|
||||
@@ -46,6 +59,15 @@ export function LunchRecapActions({
|
||||
},
|
||||
})
|
||||
|
||||
const sendReminders = trpc.lunch.sendReminders.useMutation({
|
||||
onSuccess: (data) => {
|
||||
toast.success(`Reminders sent to ${data.sent} attendee${data.sent === 1 ? '' : 's'}`)
|
||||
},
|
||||
onError: (e) => {
|
||||
toast.error(`Failed to send reminders: ${e.message}`)
|
||||
},
|
||||
})
|
||||
|
||||
const { data: preview, isLoading: loadingPreview } =
|
||||
trpc.lunch.getRecapPreview.useQuery(
|
||||
{ programId },
|
||||
@@ -68,6 +90,31 @@ export function LunchRecapActions({
|
||||
>
|
||||
<Send className="mr-2 h-4 w-4" /> Send recap now
|
||||
</Button>
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="outline" disabled={sendReminders.isPending}>
|
||||
<Bell className="mr-2 h-4 w-4" /> Send reminders now
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Send lunch pick reminders?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This will send a reminder email to all confirmed attendees who
|
||||
haven't picked a lunch dish yet. You can do this multiple
|
||||
times — it won't affect the automatic reminder window.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={() => sendReminders.mutate({ lunchEventId })}
|
||||
>
|
||||
Send reminders
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
<p className="text-muted-foreground text-xs">
|
||||
{recapSentAt
|
||||
|
||||
Reference in New Issue
Block a user