Implement #86 for auditing Late Cancellation Reason - #100
Conversation
juneha1120
left a comment
There was a problem hiding this comment.
Thanks, this looks good overall.
I left one comment about the late-cancel state potentially becoming stale if the user keeps the page open across the cutoff. I think that’s the one thing worth fixing before merge.
Non-blocking:
package-lock.jsonchanged even thoughpackage.jsondid not. If this was just local npm churn, it would be cleaner to revert the lockfile noise.- Using
lateCancelWarningas both display text and business state is a bit stringly-typed. Passing structured cancellation state would be easier to maintain.
zzawook
left a comment
There was a problem hiding this comment.
Thanks, this is close. I found a couple of things I think should be addressed before merge:
-
app/(app)/walk/[walkId]/walk-detail-client.tsx: the late-cancel state can become stale if the page is loaded before the cutoff and the user cancels after the cutoff passes. The server correctly rejects the cancellation without a reason, but the dialog has no reason field, so the user cannot recover without refreshing. It would be safer to pass structured timing data, such as the slot start and late-cancel window, and compute the late-cancel state when opening/submitting the dialog. -
app/(app)/admin/users/[userId]/history/page.tsx:formatEventTime()uses theen-SGlocale but does not settimeZone, so audit timestamps can render in the server timezone instead of Singapore time. Please settimeZone: 'Asia/Singapore'or reuse the shared app timezone constant.
Non-blocking:
package-lock.jsonchanged without a correspondingpackage.jsondependency change. If this is local npm churn, it would be cleaner to revert it.- Please confirm the required database changes are already applied/deployable for the shared Supabase backend, since this depends on
user_audit_logs, the new enum, and the updated RPC signature.
Small caveat: I may be missing context on any of the above. If a comment is not fully correct, please let me know and feel free to ignore that part.
|
Thank you everyone for your code reviews/smoketests. I will get back into the flow of looking into the concerns you've raised once my internship settles for abit. In the meantime, refer to #86 (comment) for the requirements clarification by Dr. Andie for the issues tackled by this PR. |
There was a problem hiding this comment.
I think the direction is good and the creation of the audit log table may give way for potential features in the future which require logging specific user activities.
Following up with the requirements clarification, since it has now been stated that withdrawal before Wednesday notification, a late window (E.g. 24 hours before the walk) might not be as applicable since regardless of which day the walk takes place, the time when a withdrawal or cancellation is considered late seems to remain the same throughout for walks in upcoming Saturday to Friday (which is Wednesday).
Changing from a cancellation window of 24 hours to either a specific time (or late cancellation is only triggered based on a certain state after the reminder email has been sent) might be more applicable, but that may also be handled as a follow-up issue with PR if needed since this PR has successfully implemented the feature of late cancelling (along with user history/log auditing)
…ogic; Implement Reminder Cron Job and Update Participant Notification
Reminder Email sent via Resend:
To set it up for local use, create a Resend API Key and verify your domain (Yes, you need an actual domain that you own before you can use Resend). Next, update Ensure |
seanlim
left a comment
There was a problem hiding this comment.
Found two possible issues and a few other clarifications. Everything else looks good to go after these are resolved! Nice work!
|
I've helped to update the PR along with smoke testing. Here are the list of changes:
|



Closes #86
This PR started as audit support for late cancellation reasons, but the branch now goes further and converts late-cancellation behavior from an hour-based cutoff to a reminder-driven workflow.
Summary
This PR now includes:
/admin/settings.What Changed
1. Admin user audit history
/admin/users/[userId]/history.reminder_sent_at.2. Late cancellation is now reminder-driven
Previously, late cancellation depended on
app_settings.late_cancel_hoursand walk start time.This PR removes that model from application behavior. A walk is now considered "late" once reminders for that slot have been sent, tracked by
walk_slots.reminder_sent_at.Practical effect:
reminder_sent_atinstead of an hours-before-start calculation.3. Cancellation flow and audit enforcement
LATE_WALK_CANCELLATIONaudit rows with slot metadata.reminder_sent_atinstead oflate_cancel_hours.4. Configurable weekly reminder settings
The admin settings page now controls the reminder batch logic with:
reminder_send_weekdayreminder_send_timereminder_window_start_offset_daysreminder_window_length_daysThis allows admins to configure both:
Example supported behavior:
5. Reminder emails and participant update emails
The reminder endpoint now:
walk_slots.reminder_sent_atonce the slot has been processedAdditional post-reminder notifications were added:
Current email subject lines:
Reminder: Upcoming Survey WalkNotice: Walk Participant UpdateNote: these emails are sent individually; no email-threading behavior is implemented in this PR.
6. Admin walk edit restrictions after reminders
Once
reminder_sent_atis set for a walk slot:walk_dateorstart_timeThis protects the reminder roster and late-cancellation semantics after participant notifications have already gone out.
7. Reminder endpoint / triggering behavior
The reminder route remains:
GET /api/cron/remindersImportant operational note:
The endpoint is protected by
CRON_SECRET, and middleware was updated so this specific route is not redirected to/loginbefore token validation.Database Persistent Changes
This section covers the database changes made as a result of the PR.
Added Table:
public.user_audit_logsPurpose: expandable audit trail for user-related events. Issue 86 only writes late walk cancellations, but the shape supports future events such as invitations, slot registration/cancellation, admin role changes, account approvals/rejections, and similar history entries.
Columns:
iduuidgen_random_uuid().user_iduuidpublic.profiles(id).actor_iduuidpublic.profiles(id).event_typepublic.user_audit_event_typeLATE_WALK_CANCELLATION.reasontextslot_iduuidpublic.walk_slots(id).round_iduuidpublic.survey_rounds(id).metadatajsonb{}.occurred_attimestamptznow().created_attimestamptznow().Indexes:
user_audit_logs_user_occurred_idxon(user_id, occurred_at desc)for the per-user history page.user_audit_logs_event_type_idxonevent_type.user_audit_logs_actor_idxonactor_id.RLS:
Added Enum:
public.user_audit_event_typeInitial values:
LATE_WALK_CANCELLATIONAdd future audit event values here before writing new event types into
public.user_audit_logs.Schema changes
public.app_settingsreminder_send_weekday integer not null default 3reminder_send_time time not null default '13:00:00'reminder_window_start_offset_days integer not null default 2reminder_window_length_days integer not null default 7late_cancel_hourspublic.walk_slotsreminder_sent_at timestamptzFunction change
Old signature:
New signature:
Original Function
public.cancel_slot_with_draft_cleanup(p_slot_id uuid)Live definition captured from:
public.cancel_slot_with_draft_cleanup(uuid, text)now:walk_slots.reminder_sent_atreminder_sent_atin audit metadataNotes / follow-up