Conversation
…ities, meditation suggestion, and journal prompt Co-authored-by: JustinhSE <84724234+JustinhSE@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR transforms the emergency button from a silent fire-and-forget notification system into a comprehensive support dialog. When users click the emergency button, they now see a modal with immediate coping strategies (breathing exercises, physical activities, affirmations), a randomly suggested meditation, a journal prompt, and the ability to selectively notify accountability partners. The changes improve user agency and provide in-app resources during moments of crisis.
Changes:
- Added interactive dialog with coping activities, meditation suggestions, journal prompt, and selective partner notification
- Enhanced
notifyAccountabilityPartnersfunction to support filtering notifications to specific partners - Fixed syntax error in
updateCalendarDayfunction (missing return statement and closing brace)
Reviewed changes
Copilot reviewed 2 out of 3 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
| src/components/PanicButton.tsx | Replaced simple notification trigger with comprehensive support dialog containing coping strategies, meditation/journal suggestions, and partner selection UI |
| src/utils/firebase.ts | Added optional partnerIds parameter to notifyAccountabilityPartners for selective notification; fixed syntax error in updateCalendarDay |
| package-lock.json | Dependency metadata changes from npm version/configuration differences; no actual version updates |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const [selectedPartners, setSelectedPartners] = useState<string[]>([]); | ||
| const [notifying, setNotifying] = useState(false); | ||
| const [notified, setNotified] = useState(false); | ||
| const [suggestedMeditation, setSuggestedMeditation] = useState(AVAILABLE_MEDITATIONS[0]); |
There was a problem hiding this comment.
The initial state for suggestedMeditation uses AVAILABLE_MEDITATIONS[0], but this value is immediately replaced when the dialog opens in handleOpen(). Consider initializing with a lazy function or using null/undefined with proper type handling to avoid unnecessary initial state that will never be used.
| const handleNotify = async () => { | ||
| if (!currentUser) return; | ||
| setNotifying(true); | ||
| try { | ||
| await notifyAccountabilityPartners( | ||
| currentUser.uid, | ||
| 'emergency', | ||
| 'has pressed the emergency button and needs support' | ||
| 'has pressed the emergency button and needs support', | ||
| selectedPartners | ||
| ); | ||
|
|
||
| // Call the provided callback function | ||
| setNotified(true); | ||
| if (onEmergencyClick) onEmergencyClick(); | ||
| toast.error('Emergency support triggered!'); | ||
| } else { | ||
| toast.error('You must be logged in to use this feature.'); | ||
| toast.success('Your selected partners have been notified!'); | ||
| } finally { | ||
| setNotifying(false); | ||
| } | ||
| }; |
There was a problem hiding this comment.
The handleNotify function doesn't handle the case where selectedPartners is empty. While the button is disabled when selectedPartners.length === 0 (line 239), defensive programming would check this condition at the start of handleNotify and return early to prevent unnecessary Firebase calls if the button state is somehow bypassed.
| toast.error('Emergency support triggered!'); | ||
| } else { | ||
| toast.error('You must be logged in to use this feature.'); | ||
| toast.success('Your selected partners have been notified!'); |
There was a problem hiding this comment.
The error handling in handleNotify only uses a finally block to reset the notifying state but doesn't catch errors. If notifyAccountabilityPartners fails, the user sees no error feedback and the notified state remains false, allowing repeated attempts. Add a try-catch block to show an error toast when notification fails.
| toast.success('Your selected partners have been notified!'); | |
| toast.success('Your selected partners have been notified!'); | |
| } catch (error) { | |
| console.error('Failed to notify accountability partners:', error); | |
| toast.error('Failed to notify your accountability partners. Please try again.'); |
| <label | ||
| key={partner.id} | ||
| className="flex items-center gap-3 p-2 rounded-md hover:bg-muted/50 cursor-pointer" | ||
| > | ||
| <Checkbox | ||
| checked={selectedPartners.includes(partner.id)} | ||
| onCheckedChange={() => togglePartner(partner.id)} | ||
| disabled={notified} | ||
| /> | ||
| <Avatar className="h-7 w-7"> | ||
| <AvatarFallback className="text-xs">{getInitials(partner)}</AvatarFallback> | ||
| </Avatar> | ||
| <span className="text-sm"> | ||
| {partner.firstName} {partner.lastName} | ||
| {partner.username && ( | ||
| <span className="text-muted-foreground ml-1">@{partner.username}</span> | ||
| )} | ||
| </span> | ||
| </label> |
There was a problem hiding this comment.
The checkbox labels use onClick on the label element but don't have proper ARIA attributes. While clicking the label works due to the htmlFor association, keyboard users navigating with screen readers would benefit from role="checkbox" being explicitly set or ensuring the Checkbox component handles this. Verify that the shadcn/ui Checkbox component properly handles keyboard navigation and screen reader announcements.
| <ul className="space-y-1"> | ||
| {COPING_ACTIVITIES.map((activity, i) => ( | ||
| <li | ||
| key={i} |
There was a problem hiding this comment.
The coping activities list uses array index as the key (line 124: key={i}), which is acceptable here since the list is static and never reordered. However, if these activities were to become dynamic or user-customizable in the future, using index as key could cause React rendering issues. Consider using a more stable identifier or the activity text itself as the key for better future-proofing.
| key={i} | |
| key={activity} |
|
|
||
| const AVAILABLE_MEDITATIONS = [ | ||
| { id: '1', title: 'Urge Surfing', description: 'Learn to ride the wave of desire without giving in', duration: 10 }, | ||
| { id: '2', title: 'Morning Clarity', description: 'Start your day with purpose and clear intentions', duration: 5 }, | ||
| { id: '3', title: 'Body Scan For Relaxation', description: 'Release tension and find deep relaxation', duration: 15 }, | ||
| { id: '6', title: 'Before Sleep Relaxation', description: 'Calm your mind and body with this soothing practice', duration: 10 }, | ||
| ]; |
There was a problem hiding this comment.
The meditation list in PanicButton is hardcoded and duplicates data from Meditations.tsx. This creates a maintenance issue where changes to meditation metadata (title, description, duration) in one location won't be reflected in the other. Consider importing MEDITATIONS_DATA from a shared location, or at minimum filtering only non-comingSoon meditations from the main source to ensure consistency.
| const AVAILABLE_MEDITATIONS = [ | |
| { id: '1', title: 'Urge Surfing', description: 'Learn to ride the wave of desire without giving in', duration: 10 }, | |
| { id: '2', title: 'Morning Clarity', description: 'Start your day with purpose and clear intentions', duration: 5 }, | |
| { id: '3', title: 'Body Scan For Relaxation', description: 'Release tension and find deep relaxation', duration: 15 }, | |
| { id: '6', title: 'Before Sleep Relaxation', description: 'Calm your mind and body with this soothing practice', duration: 10 }, | |
| ]; | |
| import { MEDITATIONS_DATA } from './Meditations'; | |
| const AVAILABLE_MEDITATIONS = MEDITATIONS_DATA.filter( | |
| (meditation) => !meditation.comingSoon | |
| ); |
| const handleOpen = () => { | ||
| if (!currentUser) { | ||
| toast.error('You must be logged in to use this feature.'); | ||
| return; | ||
| } | ||
| setSelectedPartners(accountabilityPartners.map((p) => p.id)); | ||
| setNotified(false); | ||
| setSuggestedMeditation( | ||
| AVAILABLE_MEDITATIONS[Math.floor(Math.random() * AVAILABLE_MEDITATIONS.length)] | ||
| ); | ||
| setOpen(true); | ||
| }; |
There was a problem hiding this comment.
When no accountability partners exist, the dialog shows coping activities and meditation suggestions but hides the notification button. However, if partners are added while the dialog is open, those partners won't be auto-selected since selectedPartners is only initialized in handleOpen(). Consider adding a useEffect to sync selectedPartners when accountabilityPartners changes, or documenting that users must close and reopen the dialog to notify newly added partners.
The emergency button was silently notifying all accountability partners with no user feedback, no control over who gets notified, and no in-app support resources.
Changes
src/components/PanicButton.tsxReplaced the fire-and-forget click handler with a modal dialog containing:
/meditations/journalsrc/utils/firebase.tspartnerIds?: string[]tonotifyAccountabilityPartners()— filters notification targets when provided, falls back to all partners (backward-compatible)updateCalendarDay(missingreturn false+ closing brace) that was breaking the production build💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.