Skip to content

E11: WhatsApp-first operations — templates, copy buttons, and wa.me deep links - #105

Merged
Taleef7 merged 3 commits into
mainfrom
copilot/implement-epic-e11-whatsapp-first-operations
Feb 25, 2026
Merged

E11: WhatsApp-first operations — templates, copy buttons, and wa.me deep links#105
Taleef7 merged 3 commits into
mainfrom
copilot/implement-epic-e11-whatsapp-first-operations

Conversation

Copilot AI commented Feb 25, 2026

Copy link
Copy Markdown
Contributor

Implements Epic E11 in full: all admin pages now have one-click WhatsApp message copy and direct chat open, eliminating manual template typing.

Summary

  • lib/whatsapp/templates.ts — 14 typed template functions covering the full OPS.md section 6 lifecycle (greeting → intake → packages → paybank → paid → tutorAvailCheck → matched → rem1h → reschedAck → reschedConfirmed → lateJoin → studentNoShow → tutorNoShow → renewalReminder). Each accepts only the variables it needs.
  • lib/whatsapp/buildLink.tsbuildWaLink(number, message?) strips all non-digit characters (via /\D/g), returns '' when no digits remain so callers suppress invalid links, builds https://wa.me/{digits}?text={encoded}.
  • components/CopyMessageButton.tsx — client component; "📋 Copy message" with 2s ✅ Copied! toast + optional "💬 Open WhatsApp" wa.me link when whatsappNumber is supplied. Clipboard write is wrapped in try/catch with a hidden-textarea execCommand('copy') fallback for older browsers; errors are logged without showing a false success state.
  • components/WhatsAppLink.tsx — standalone "💬 Open WhatsApp" link; graceful No WhatsApp number fallback.

Changes

Admin page integrations

Page Additions
/admin/matches/[id] Copy: matched confirmation, 1-hr reminder ×2 (student + tutor), tutor avail check (slots pre-formatted as [e.g. Mon 5:00 PM PKT]); Open chat links on student/tutor numbers
/admin/payments Copy: payment confirmed, payment instructions — buttons only rendered when student name and subject are both resolved from DB; Open chat per row
/admin/sessions Copy per session: 1-hr reminder ×2 (student + tutor), late join, student no-show, tutor no-show. Time in WhatsApp messages is formatted in scheduleTz (student/schedule timezone) so the time label matches the timezone shown. Reschedule confirmed button removed — new day/time are not available at list render time
/admin/users Open chat link next to each WhatsApp number
/admin/requests/[id] Open chat link next to student number
/admin/tutors/[id] Open chat link next to tutor number

Testing

  • Verified locally
  • Checked key flows manually

Notes

  • Templates are pre-filled from DB data passed through server components; CopyMessageButton receives a fully rendered string as a prop.
  • paybank template pulls bank details from PAYMENT_INSTRUCTIONS in lib/config/pricing.ts (fill in before launch).
  • WhatsApp message buttons on the payments page are guarded and only appear when student name and subject name are both resolved (not '—').
  • Session WhatsApp messages use the schedule/student timezone for time formatting, keeping the displayed time consistent with the timezone label.
  • TypeScript compiles clean (tsc --noEmit); ESLint passes on all modified files.

✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

… S11.2)

Co-authored-by: Taleef7 <89072337+Taleef7@users.noreply.github.com>
Copilot AI changed the title [WIP] Implement epic E11: WhatsApp-first operations E11: WhatsApp-first operations — templates, copy buttons, and wa.me deep links Feb 25, 2026
@Taleef7
Taleef7 marked this pull request as ready for review February 25, 2026 21:01
Copilot AI review requested due to automatic review settings February 25, 2026 21:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements Epic E11 by centralizing WhatsApp message templates and adding admin UI affordances to copy prefilled messages and open wa.me deep links across multiple admin pages.

Changes:

  • Added a WhatsApp template library (lib/whatsapp/templates.ts) and a wa.me link builder (lib/whatsapp/buildLink.ts).
  • Introduced reusable UI components for copying messages and opening WhatsApp chats (CopyMessageButton, WhatsAppLink).
  • Integrated WhatsApp actions/links into key admin surfaces (matches, payments, sessions, users, requests, tutors) and updated README status.

Reviewed changes

Copilot reviewed 11 out of 12 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
package-lock.json Lockfile churn (removes several "peer": true entries).
lib/whatsapp/templates.ts Adds 14 lifecycle WhatsApp message templates.
lib/whatsapp/buildLink.ts Adds buildWaLink() helper for generating wa.me URLs.
components/WhatsAppLink.tsx New standalone “Open WhatsApp” link component with fallback UI.
components/CopyMessageButton.tsx New client component to copy a message + optionally open a prefilled WhatsApp chat.
app/admin/users/page.tsx Adds “Open chat” link next to users’ WhatsApp numbers.
app/admin/tutors/[id]/page.tsx Adds “Open chat” link on tutor detail page.
app/admin/requests/[id]/page.tsx Adds “Open chat” link on request detail page.
app/admin/payments/page.tsx Adds WhatsApp copy/link actions for payment-confirmed + paybank templates.
app/admin/sessions/page.tsx Adds WhatsApp copy actions per session (reminder, late-join, no-show, reschedule).
app/admin/matches/[id]/page.tsx Adds WhatsApp copy/link actions for matched/reminder/availability-check messages.
README.md Marks E11 WhatsApp features as completed and documents new modules/components.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/whatsapp/buildLink.ts Outdated
* If message is provided, appends it as a URL-encoded ?text= parameter.
*/
export function buildWaLink(whatsappNumber: string, message?: string): string {
const digits = whatsappNumber.replace(/[^\d+]/g, '').replace(/^\+/, '')

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

buildWaLink is documented to strip all non-digits, but the current regex preserves '+' characters (except a leading one). This can generate an invalid wa.me/{number} path if the stored number contains '+' anywhere. Consider stripping everything except digits (e.g., keep only 0-9) and treating an empty result as “no number” (return null/empty or throw) so callers don’t render https://wa.me/ links.

Suggested change
const digits = whatsappNumber.replace(/[^\d+]/g, '').replace(/^\+/, '')
const digits = whatsappNumber.replace(/\D/g, '')
if (!digits) return ''

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b138922. Changed to whatsappNumber.replace(/\D/g, '') to strip all non-digits (including any embedded +), and added an early return of '' when no digits remain so callers ({waHref && ...}, WhatsAppLink's !number guard) naturally suppress the link.

Comment thread components/CopyMessageButton.tsx Outdated
Comment on lines +19 to +21
await navigator.clipboard.writeText(message)
setCopied(true)
setTimeout(() => setCopied(false), 2000)

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

navigator.clipboard.writeText can throw/reject (non-HTTPS context, permissions denied, older browsers). Right now that rejection is unhandled, so the click may silently fail (or trigger an unhandled promise rejection). Wrap the call in try/catch and show a failure state or fallback copy mechanism when clipboard access isn’t available.

Suggested change
await navigator.clipboard.writeText(message)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
try {
if (navigator && 'clipboard' in navigator && navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(message)
} else {
// Fallback for older browsers or when Clipboard API is unavailable
const textarea = document.createElement('textarea')
textarea.value = message
textarea.style.position = 'fixed'
textarea.style.opacity = '0'
document.body.appendChild(textarea)
textarea.focus()
textarea.select()
try {
document.execCommand('copy')
} finally {
document.body.removeChild(textarea)
}
}
setCopied(true)
setTimeout(() => setCopied(false), 2000)
} catch (error) {
console.error('Failed to copy message to clipboard:', error)
setCopied(false)
}

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b138922. handleCopy now wraps everything in try/catch. It checks for the Clipboard API first, falls back to a hidden textarea + execCommand('copy') for older browsers, and on any error logs to console and resets the copied state without showing a false success.

Comment on lines 155 to +159
const timeDisplay = formatSessionTime(session.scheduled_start_utc, adminTimezone)
const scheduleTz = match?.schedule_pattern?.timezone ?? adminTimezone
const requestId = match?.request_id ?? ''
const durationMins = match?.schedule_pattern?.duration_mins ?? 60
const meetLink = match?.meet_link ?? ''

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The WhatsApp templates include tz: scheduleTz, but timeDisplay is formatted using adminTimezone. This can produce messages like “Time: 7:00 PM (Asia/Karachi)” where the time is actually in a different timezone. Use scheduleTz when formatting the time for WhatsApp messages (and optionally keep a separate admin-time display for the card UI).

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b138922. Added waTimeDisplay = formatSessionTime(session.scheduled_start_utc, scheduleTz) and used it in all WhatsApp templates (rem1h, lateJoin, studentNoShow) so the time in the message matches the timezone label shown to the student.

Comment thread app/admin/sessions/page.tsx Outdated
Comment on lines +179 to +185
const reschedConfirmedMsg = meetLink
? templates.reschedConfirmed({
day: '[Day]',
time: '[Time]',
tz: scheduleTz,
meetLink,
})

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reschedConfirmedMsg is built with placeholder values ([Day], [Time]), so the “Copy reschedule confirmed” button will copy an incomplete message. Either populate these fields from the actual reschedule data (if available) or remove/disable this button until the new day/time are known.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b138922. Removed the "Copy reschedule confirmed" button from the sessions page — the new day/time aren't available at session list render time, so sending a message with [Day]/[Time] placeholders would be harmful. The reschedule confirmed template remains available in lib/whatsapp/templates.ts for use in a dedicated reschedule confirmation flow later.

Comment on lines +135 to +141
const tutorAvailCheckMsg = templates.tutorAvailCheck({
tutorName,
level: levelLabel,
subject: subjectName,
slot1: '[Day] [Time] [TZ]',
slot2: '[Day] [Time] [TZ]',
})

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tutor availability check template is pre-filled with placeholder slots ([Day] [Time] [TZ]), which still requires manual editing before sending. If the goal is one-click “no manual typing,” consider passing real candidate slots (e.g., from request availability / schedule) or adding small UI inputs to set slot1/slot2 before copying.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in b138922. The slot placeholders are now [e.g. Mon 5:00 PM PKT] / [e.g. Wed 5:00 PM PKT] so it's immediately clear what format to use when editing before sending. Tutor name, level, and subject are still auto-filled from the DB.

Comment thread app/admin/payments/page.tsx Outdated
Comment on lines +181 to +194
<CopyMessageButton
message={templates.paid({ subject: subjectName })}
whatsappNumber={profile?.whatsapp_number ?? undefined}
label="Copy payment confirmed"
/>
<CopyMessageButton
message={templates.paybank({
accountTitle: PAYMENT_INSTRUCTIONS.accountTitle,
bank: PAYMENT_INSTRUCTIONS.bankName,
accountNumber: PAYMENT_INSTRUCTIONS.accountNumber,
studentName: profile?.display_name ?? '—',
level,
subject: subjectName,
})}

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These WhatsApp templates are always generated even when subjectName fell back to '—' (e.g., missing subjects). That would send user-facing messages like “verified — teacher”. Consider guarding these buttons/messages when required data (subject/level/student name) is missing, or provide a clearer fallback message prompting admins to fix the underlying data first.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b138922. The WhatsApp buttons section is now guarded with profile?.display_name && subjectName !== '—', so buttons only appear when the student name and subject are both resolved from the DB. Also switched studentName: profile?.display_name ?? '—' to profile.display_name inside the guard (no nullish fallback needed).

@Taleef7

Taleef7 commented Feb 25, 2026

Copy link
Copy Markdown
Owner

@copilot apply changes based on the comments in this thread

…k, timezone mismatch, remove placeholder reschedule button, guard payment templates

Co-authored-by: Taleef7 <89072337+Taleef7@users.noreply.github.com>
@Taleef7
Taleef7 merged commit f96d604 into main Feb 25, 2026
1 check passed
@Taleef7
Taleef7 deleted the copilot/implement-epic-e11-whatsapp-first-operations branch February 25, 2026 21:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants