diff --git a/README.md b/README.md index 795bb8c..48dfc4a 100644 --- a/README.md +++ b/README.md @@ -121,7 +121,7 @@ Open [http://localhost:3000](http://localhost:3000). You'll see the CorvEd landi --- -### What the app can do right now (after E10) +### What the app can do right now (after E11) | Area | Status | |---|---| @@ -194,7 +194,16 @@ Open [http://localhost:3000](http://localhost:3000). You'll see the CorvEd landi | **Tutor: sessions list** | ✅ `app/tutor/sessions/page.tsx` — upcoming and past sessions in tutor's timezone; student name, subject, Meet link; `SessionCompleteForm` inline on each upcoming session card | | **Tutor: session completion form** | ✅ `components/dashboards/SessionCompleteForm.tsx` — radio buttons (Done / Student No-show / My No-show), notes textarea, calls `tutor_update_session` RPC via server action; error state; success state | | **DB: increment_sessions_used guard** | ✅ `supabase/migrations/20260225000003_increment_sessions_used_guard.sql` — adds `sessions_used < sessions_total` safety guard to prevent `sessions_used` from exceeding `sessions_total` (over-incrementing); restricts direct RPC access to `service_role` only | -| WhatsApp templates (E11) | 🚧 Coming in E11 | +| WhatsApp templates (E11) | ✅ `lib/whatsapp/templates.ts` — 14 typed template functions (greeting, intake, packages, paybank, paid, tutorAvailCheck, matched, rem1h, reschedAck, reschedConfirmed, lateJoin, studentNoShow, tutorNoShow, renewalReminder) | +| WhatsApp link builder (E11) | ✅ `lib/whatsapp/buildLink.ts` — `buildWaLink(number, message?)` strips non-digits, returns `wa.me/` URL with optional `?text=` parameter | +| WhatsApp `CopyMessageButton` component (E11) | ✅ `components/CopyMessageButton.tsx` — "📋 Copy message" button with ✅ Copied! toast + optional "💬 Open WhatsApp" link to `wa.me` with pre-filled text | +| WhatsApp `WhatsAppLink` component (E11) | ✅ `components/WhatsAppLink.tsx` — standalone "💬 Open WhatsApp" link; graceful fallback when number is absent | +| Admin: WhatsApp actions on match detail (E11) | ✅ `/admin/matches/[id]` — "Copy matched message", "Copy 1-hour reminder (student/tutor)", "Copy tutor availability check" buttons; "Open chat" links next to student/tutor numbers | +| Admin: WhatsApp actions on payments (E11) | ✅ `/admin/payments` — "Copy payment confirmed" and "Copy payment instructions" buttons + "Open chat" link per payment row | +| Admin: WhatsApp actions on sessions (E11) | ✅ `/admin/sessions` — per session: "Copy 1-hour reminder", "Copy late join follow-up", "Copy student no-show notice", "Copy tutor no-show apology", "Copy reschedule confirmed" buttons | +| Admin: WhatsApp link on users page (E11) | ✅ `/admin/users` — "Open chat" link next to each user's WhatsApp number | +| Admin: WhatsApp link on request detail (E11) | ✅ `/admin/requests/[id]` — "Open chat" link next to student's WhatsApp number | +| Admin: WhatsApp link on tutor detail (E11) | ✅ `/admin/tutors/[id]` — "Open chat" link next to tutor's WhatsApp number | --- diff --git a/app/admin/matches/[id]/page.tsx b/app/admin/matches/[id]/page.tsx index cdd1917..e76df56 100644 --- a/app/admin/matches/[id]/page.tsx +++ b/app/admin/matches/[id]/page.tsx @@ -1,5 +1,5 @@ -// E7 T7.4 S7.2 E8 T8.1: Admin match detail page — view match, reassign tutor, edit schedule, generate sessions -// Closes #50 #46 #54 +// E7 T7.4 S7.2 E8 T8.1 E11 T11.2: Admin match detail page — view match, reassign tutor, edit schedule, generate sessions, WhatsApp actions +// Closes #50 #46 #54 #75 export const dynamic = 'force-dynamic' @@ -9,6 +9,9 @@ import { createAdminClient } from '@/lib/supabase/admin' import { fetchApprovedTutors } from '@/lib/services/matching' import { LEVEL_LABELS } from '@/lib/utils/request' import { ReassignTutorForm, EditMatchForm, GenerateSessionsForm } from './MatchActions' +import { CopyMessageButton } from '@/components/CopyMessageButton' +import { WhatsAppLink } from '@/components/WhatsAppLink' +import { templates } from '@/lib/whatsapp/templates' const DAY_NAMES = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] @@ -97,6 +100,46 @@ export default async function AdminMatchDetailPage({ ? request.for_student_name : (studentProfile?.display_name ?? '—') + const tutorName = tutorUserProfile?.display_name ?? '—' + const schedDays = schedule?.days?.length + ? schedule.days.map((d) => DAY_NAMES[d]).join(', ') + : '' + const schedTime = schedule?.time ?? '' + const schedTz = schedule?.timezone ?? '' + const meetLink = match.meet_link ?? '' + + // Pre-built template strings for WhatsApp buttons + const matchedMsg = + schedDays && schedTime && schedTz && meetLink + ? templates.matched({ + tutorName, + days: schedDays, + time: schedTime, + tz: schedTz, + meetLink, + }) + : null + + const rem1hStudentMsg = + schedTime && schedTz && meetLink + ? templates.rem1h({ + level: levelLabel, + subject: subjectName, + tutorName, + time: schedTime, + tz: schedTz, + meetLink, + }) + : null + + const tutorAvailCheckMsg = templates.tutorAvailCheck({ + tutorName, + level: levelLabel, + subject: subjectName, + slot1: '[e.g. Mon 5:00 PM PKT]', + slot2: '[e.g. Wed 5:00 PM PKT]', + }) + const assignedDate = new Date(match.assigned_at).toLocaleDateString('en-GB', { day: 'numeric', month: 'long', @@ -150,7 +193,10 @@ export default async function AdminMatchDetailPage({
Student
{studentName}
{studentProfile?.whatsapp_number && ( -
📱 {studentProfile.whatsapp_number}
+
+ 📱 {studentProfile.whatsapp_number} + +
)} @@ -161,7 +207,10 @@ export default async function AdminMatchDetailPage({
{tutorProfile?.timezone}
{tutorUserProfile?.whatsapp_number && ( -
📱 {tutorUserProfile.whatsapp_number}
+
+ 📱 {tutorUserProfile.whatsapp_number} + +
)} @@ -233,6 +282,60 @@ export default async function AdminMatchDetailPage({ )} + {/* WhatsApp Actions */} +
+

+ WhatsApp Messages +

+
+ {matchedMsg ? ( +
+

Match confirmed (to student)

+ +
+ ) : ( +

+ Set schedule and Meet link to enable match confirmation template. +

+ )} + + {rem1hStudentMsg && ( +
+

1-hour reminder (to student)

+ +
+ )} + + {rem1hStudentMsg && ( +
+

1-hour reminder (to tutor)

+ +
+ )} + +
+

Tutor availability check

+ +
+
+
+ {/* Audit info */}

Match ID: {match.id} · Last updated:{' '} diff --git a/app/admin/payments/page.tsx b/app/admin/payments/page.tsx index ed9800d..218b1d5 100644 --- a/app/admin/payments/page.tsx +++ b/app/admin/payments/page.tsx @@ -1,5 +1,5 @@ -// E5 T5.3 S5.2: Admin payments list — view, mark paid, mark rejected -// Closes #35 #32 +// E5 T5.3 S5.2 E11 T11.2 T11.3: Admin payments list — view, mark paid, mark rejected, WhatsApp actions +// Closes #35 #32 #75 #76 export const dynamic = 'force-dynamic' @@ -7,6 +7,10 @@ import { createAdminClient } from '@/lib/supabase/admin' import { LEVEL_LABELS } from '@/lib/utils/request' import { MarkPaidForm, RejectForm } from './PaymentActions' import Link from 'next/link' +import { CopyMessageButton } from '@/components/CopyMessageButton' +import { WhatsAppLink } from '@/components/WhatsAppLink' +import { templates } from '@/lib/whatsapp/templates' +import { PAYMENT_INSTRUCTIONS } from '@/lib/config/pricing' const STATUS_COLOURS: Record = { pending: 'bg-yellow-100 text-yellow-800', @@ -170,6 +174,30 @@ export default async function AdminPaymentsPage({ )} + + {/* WhatsApp message buttons — only when student and subject data are available */} + {profile?.display_name && subjectName !== '—' && ( +

+ + + +
+ )} ) })} diff --git a/app/admin/requests/[id]/page.tsx b/app/admin/requests/[id]/page.tsx index f55bba9..3eca557 100644 --- a/app/admin/requests/[id]/page.tsx +++ b/app/admin/requests/[id]/page.tsx @@ -1,5 +1,5 @@ -// E7 T7.2: Admin request detail + matching screen -// Closes #48 +// E7 T7.2 E11 T11.3: Admin request detail + matching screen + WhatsApp link +// Closes #48 #76 export const dynamic = 'force-dynamic' @@ -9,6 +9,7 @@ import { createAdminClient } from '@/lib/supabase/admin' import { fetchApprovedTutors } from '@/lib/services/matching' import { STATUS_COLOURS, STATUS_LABELS, LEVEL_LABELS } from '@/lib/utils/request' import { AssignTutorForm } from './AssignTutorForm' +import { WhatsAppLink } from '@/components/WhatsAppLink' const EXAM_BOARD_LABELS: Record = { cambridge: 'Cambridge', @@ -160,7 +161,10 @@ export default async function AdminRequestDetailPage({ )}

{profile?.whatsapp_number && ( -

📱 {profile.whatsapp_number}

+

+ 📱 {profile.whatsapp_number} + +

)} diff --git a/app/admin/sessions/page.tsx b/app/admin/sessions/page.tsx index bbb19a3..2c2ecf2 100644 --- a/app/admin/sessions/page.tsx +++ b/app/admin/sessions/page.tsx @@ -1,5 +1,5 @@ -// E8 T8.4 S8.2: Admin sessions overview — list all sessions, update status, reschedule -// Closes #57 #53 +// E8 T8.4 S8.2 E11 T11.2: Admin sessions overview — list all sessions, update status, reschedule, WhatsApp actions +// Closes #57 #53 #75 export const dynamic = 'force-dynamic' @@ -7,6 +7,8 @@ import { createAdminClient } from '@/lib/supabase/admin' import { SESSION_STATUS_LABELS, SESSION_STATUS_COLOURS, formatSessionTime, type SessionStatus } from '@/lib/utils/session' import { SessionStatusForm, RescheduleForm } from './SessionActions' import Link from 'next/link' +import { CopyMessageButton } from '@/components/CopyMessageButton' +import { templates } from '@/lib/whatsapp/templates' const ADMIN_TIMEZONE = 'Asia/Karachi' @@ -21,15 +23,15 @@ type SessionRow = { meet_link: string | null request_id: string tutor_user_id: string - schedule_pattern: { duration_mins?: number } | null + schedule_pattern: { duration_mins?: number; timezone?: string } | null tutor_profiles: { - user_profiles: { display_name: string } | null + user_profiles: { display_name: string; whatsapp_number: string | null } | null } | null requests: { id: string level: string subjects: { name: string } | null - user_profiles: { display_name: string } | null + user_profiles: { display_name: string; whatsapp_number: string | null } | null } | null } | null } @@ -44,12 +46,12 @@ export default async function AdminSessionsPage() { matches!sessions_match_id_fkey ( meet_link, request_id, tutor_user_id, schedule_pattern, tutor_profiles!matches_tutor_user_id_fkey ( - user_profiles!tutor_user_id ( display_name ) + user_profiles!tutor_user_id ( display_name, whatsapp_number ) ), requests!matches_request_id_fkey ( id, level, subjects ( name ), - user_profiles!requests_created_by_user_id_fkey ( display_name ) + user_profiles!requests_created_by_user_id_fkey ( display_name, whatsapp_number ) ) )` ) @@ -140,12 +142,42 @@ function SessionCard({ const tutorProfile = match?.tutor_profiles const studentName = (req?.user_profiles as { display_name: string } | null)?.display_name ?? '—' + const studentWhatsApp = + (req?.user_profiles as { display_name: string; whatsapp_number: string | null } | null) + ?.whatsapp_number ?? null const tutorName = (tutorProfile?.user_profiles as { display_name: string } | null)?.display_name ?? '—' + const tutorWhatsApp = + (tutorProfile?.user_profiles as { display_name: string; whatsapp_number: string | null } | null) + ?.whatsapp_number ?? null const subjectName = (req?.subjects as { name: string } | null)?.name ?? '—' + const levelLabel = req?.level ?? '' const timeDisplay = formatSessionTime(session.scheduled_start_utc, adminTimezone) + const scheduleTz = match?.schedule_pattern?.timezone ?? adminTimezone + // Format session time in the schedule/student timezone for WhatsApp messages + const waTimeDisplay = formatSessionTime(session.scheduled_start_utc, scheduleTz) const requestId = match?.request_id ?? '' const durationMins = match?.schedule_pattern?.duration_mins ?? 60 + const meetLink = match?.meet_link ?? '' + + // Template strings for WhatsApp buttons (use waTimeDisplay so tz label matches the time) + const rem1hMsg = meetLink + ? templates.rem1h({ + level: levelLabel, + subject: subjectName, + tutorName, + time: waTimeDisplay, + tz: scheduleTz, + meetLink, + }) + : null + + const lateJoinMsg = meetLink + ? templates.lateJoin({ name: studentName, time: waTimeDisplay, meetLink }) + : null + + const studentNoShowMsg = templates.studentNoShow({ name: studentName, time: waTimeDisplay }) + const tutorNoShowMsg = templates.tutorNoShow({ name: studentName }) return (
@@ -192,6 +224,41 @@ function SessionCard({ />
)} + + {/* WhatsApp message buttons */} +
+ {rem1hMsg && ( + + )} + {lateJoinMsg && ( + + )} + + + {tutorWhatsApp && rem1hMsg && ( + + )} +
) } diff --git a/app/admin/tutors/[id]/page.tsx b/app/admin/tutors/[id]/page.tsx index 259a601..a031bf2 100644 --- a/app/admin/tutors/[id]/page.tsx +++ b/app/admin/tutors/[id]/page.tsx @@ -1,5 +1,5 @@ -// E6 T6.3 T6.4 S6.2: Admin tutor detail page — full profile view with approve/revoke -// Closes #42 #43 #39 +// E6 T6.3 T6.4 S6.2 E11 T11.3: Admin tutor detail page — full profile view with approve/revoke + WhatsApp link +// Closes #42 #43 #39 #76 export const dynamic = 'force-dynamic' @@ -7,6 +7,7 @@ import { notFound } from 'next/navigation' import Link from 'next/link' import { createAdminClient } from '@/lib/supabase/admin' import { ApproveButton, RevokeButton } from '../TutorActions' +import { WhatsAppLink } from '@/components/WhatsAppLink' const DAY_NAMES = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] @@ -118,7 +119,12 @@ export default async function AdminTutorDetailPage({

WhatsApp:{' '} - {profile?.whatsapp_number ?? ( + {profile?.whatsapp_number ? ( + + {profile.whatsapp_number} + + + ) : ( not provided )}

diff --git a/app/admin/users/page.tsx b/app/admin/users/page.tsx index e2c353a..76bcd11 100644 --- a/app/admin/users/page.tsx +++ b/app/admin/users/page.tsx @@ -1,8 +1,9 @@ -// E3 T3.4: admin user role management screen -// Closes #19 #23 +// E3 T3.4 E11 T11.3: admin user role management screen + WhatsApp links +// Closes #19 #23 #76 import { createAdminClient } from '@/lib/supabase/admin' import { assignRole, removeRole, setPrimaryRole } from '../actions' +import { WhatsAppLink } from '@/components/WhatsAppLink' const ALL_ROLES = ['student', 'parent', 'tutor', 'admin'] as const type Role = (typeof ALL_ROLES)[number] @@ -128,7 +129,16 @@ export default async function AdminUsersPage() { - {user.whatsapp_number ?? '—'} +
+ {user.whatsapp_number ? ( + <> + {user.whatsapp_number} + + + ) : ( + '—' + )} +
diff --git a/components/CopyMessageButton.tsx b/components/CopyMessageButton.tsx new file mode 100644 index 0000000..d85344e --- /dev/null +++ b/components/CopyMessageButton.tsx @@ -0,0 +1,71 @@ +'use client' + +// T11.2 / S11.2: "Copy message" and optional "Open WhatsApp" button component +// Closes #75 #73 + +import { useState } from 'react' +import { buildWaLink } from '@/lib/whatsapp/buildLink' + +interface CopyMessageButtonProps { + message: string + whatsappNumber?: string + label?: string +} + +export function CopyMessageButton({ message, whatsappNumber, label }: CopyMessageButtonProps) { + const [copied, setCopied] = useState(false) + + async function handleCopy() { + 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) + } + } + + const waHref = whatsappNumber ? buildWaLink(whatsappNumber, message) : null + + return ( +
+ + + {waHref && ( + + 💬 Open WhatsApp + + )} +
+ ) +} diff --git a/components/WhatsAppLink.tsx b/components/WhatsAppLink.tsx new file mode 100644 index 0000000..f1c0e33 --- /dev/null +++ b/components/WhatsAppLink.tsx @@ -0,0 +1,30 @@ +// T11.3: Standalone "Open WhatsApp" link component +// Closes #76 + +import { buildWaLink } from '@/lib/whatsapp/buildLink' + +interface WhatsAppLinkProps { + number: string | null | undefined + message?: string + label?: string +} + +export function WhatsAppLink({ number, message, label = 'Open WhatsApp' }: WhatsAppLinkProps) { + if (!number) { + return No WhatsApp number + } + + const href = buildWaLink(number, message) + + return ( + + 💬 {label} + + ) +} diff --git a/lib/whatsapp/buildLink.ts b/lib/whatsapp/buildLink.ts new file mode 100644 index 0000000..19cc255 --- /dev/null +++ b/lib/whatsapp/buildLink.ts @@ -0,0 +1,16 @@ +// T11.3: wa.me deep link builder utility +// Closes #76 + +/** + * Builds a wa.me WhatsApp deep link for the given number and optional pre-filled message. + * Strips all non-digit characters from the number. + * Returns an empty string if no digits remain (caller should treat this as "no link"). + * 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, '') + if (!digits) return '' + const base = `https://wa.me/${digits}` + if (!message) return base + return `${base}?text=${encodeURIComponent(message)}` +} diff --git a/lib/whatsapp/templates.ts b/lib/whatsapp/templates.ts new file mode 100644 index 0000000..70344cf --- /dev/null +++ b/lib/whatsapp/templates.ts @@ -0,0 +1,129 @@ +// T11.1: All 14 standard WhatsApp message templates from docs/OPS.md section 6 +// Closes #74 + +export const templates = { + // 6.1 Greeting (auto-greeting in WhatsApp Business) + greeting: () => + `Hello! Welcome to CorvEd 👋\n` + + `We provide 1:1 online tutoring for O Levels and A Levels with verified teachers.\n` + + `To get started, please share:\n` + + `1) Student or Parent?\n2) Level (O / A)\n3) Subject\n` + + `4) City + Timezone\n5) Availability (days + times)\n6) Your goal (exam date or weak areas)`, + + // 6.2 Lead intake + intake: () => + `To match you with the right teacher, please reply with:\n` + + `1) Student or Parent?\n` + + `2) Level: O Levels or A Levels\n` + + `3) Subject: Math/Physics/Chemistry/Biology/English/CS/Pak Studies/Islamiyat/Urdu\n` + + `4) Exam board (Cambridge/Edexcel/Other) (optional)\n` + + `5) Your availability (days + time windows) + your timezone\n` + + `6) Goal (target grade, weak topics, exam date)`, + + // 6.3 Package options + packages: () => + `We offer monthly packages per subject (60-minute sessions):\n` + + `- 8 sessions/month (~2x per week)\n` + + `- 12 sessions/month (~3x per week)\n` + + `- 20 sessions/month (~5x per week)\n\n` + + `Share your preferred package and we'll send payment details.`, + + // 6.4 Payment instructions + paybank: (p: { + accountTitle: string + bank: string + accountNumber: string + studentName: string + level: string + subject: string + }) => + `Bank transfer details:\n` + + `Account Title: ${p.accountTitle}\n` + + `Bank: ${p.bank}\n` + + `Account/IBAN: ${p.accountNumber}\n\n` + + `Reference: CorvEd | ${p.studentName} | ${p.level} ${p.subject}\n\n` + + `After payment, send a screenshot or transaction reference and we'll confirm.`, + + // 6.5 Payment confirmed + paid: (p: { subject: string }) => + `Payment received ✅ Thank you.\n` + + `Next step: we'll match you with a verified ${p.subject} teacher and confirm your schedule shortly.\n\n` + + `To finalize scheduling, please confirm:\n- preferred days/times (with timezone)\n- start date (if any preference)`, + + // 6.6 Tutor availability check + tutorAvailCheck: (p: { + tutorName: string + level: string + subject: string + slot1: string + slot2: string + }) => + `Hi ${p.tutorName}, I hope you're well.\n` + + `We have a new ${p.level} ${p.subject} student. Are you available for:\n` + + `Option 1: ${p.slot1}\nOption 2: ${p.slot2}\n\n` + + `If yes, please confirm which option works. If not, share 2–3 available slots.`, + + // 6.7 Match confirmed to student + matched: (p: { + tutorName: string + days: string + time: string + tz: string + meetLink: string + }) => + `You're matched ✅\n` + + `Teacher: ${p.tutorName}\n` + + `Schedule: ${p.days} at ${p.time} (${p.tz})\n` + + `Session duration: 60 minutes\n` + + `Google Meet link (recurring): ${p.meetLink}\n\n` + + `Reschedule policy: please request reschedules at least 24 hours before the class time.`, + + // 6.8 1-hour reminder + rem1h: (p: { + level: string + subject: string + tutorName: string + time: string + tz: string + meetLink: string + }) => + `Reminder ⏰ Your class starts in 1 hour:\n` + + `${p.level} ${p.subject} with ${p.tutorName}\n` + + `Time: ${p.time} (${p.tz})\n` + + `Meet link: ${p.meetLink}`, + + // 6.9 Reschedule request acknowledgement + reschedAck: () => + `Got it — I can help you reschedule.\n` + + `Please share 2–3 alternate time slots (days + times + your timezone).\n` + + `Note: reschedules are allowed if requested at least 24 hours before class.`, + + // 6.10 Reschedule confirmed + reschedConfirmed: (p: { day: string; time: string; tz: string; meetLink: string }) => + `Reschedule confirmed ✅\n` + + `New time: ${p.day} at ${p.time} (${p.tz})\n` + + `Meet link (same): ${p.meetLink}`, + + // 6.11 Late join follow-up + lateJoin: (p: { name: string; time: string; meetLink: string }) => + `Hi ${p.name}, your class started at ${p.time}. Are you joining?\nMeet link: ${p.meetLink}`, + + // 6.12 Student no-show policy notice + studentNoShow: (p: { name: string; time: string }) => + `Hi ${p.name}, we waited 10 minutes and couldn't connect today.\n` + + `As per our policy, a no-show counts as a used session.\n` + + `If you'd like, share your availability and we'll continue with the remaining sessions.`, + + // 6.13 Tutor no-show apology + tutorNoShow: (p: { name: string }) => + `Hi ${p.name}, we're sorry — the teacher could not join today.\n` + + `This session will not be deducted.\n` + + `Please share 2–3 alternate slots and we'll reschedule immediately.`, + + // 6.14 Renewal reminder + renewalReminder: (p: { subject: string }) => + `Your monthly package is ending soon.\n` + + `Would you like to renew for next month for ${p.subject}?\n\n` + + `Packages:\n- 8 sessions\n- 12 sessions\n- 20 sessions\n\n` + + `Reply with your package choice and we'll share payment details.`, +} diff --git a/package-lock.json b/package-lock.json index 88727f3..8d26359 100644 --- a/package-lock.json +++ b/package-lock.json @@ -76,7 +76,6 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -1355,7 +1354,6 @@ "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.97.0.tgz", "integrity": "sha512-kTD91rZNO4LvRUHv4x3/4hNmsEd2ofkYhuba2VMUPRVef1RCmnHtm7rIws38Fg0yQnOSZOplQzafn0GSiy6GVg==", "license": "MIT", - "peer": true, "dependencies": { "@supabase/auth-js": "2.97.0", "@supabase/functions-js": "2.97.0", @@ -1706,7 +1704,6 @@ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -1775,7 +1772,6 @@ "integrity": "sha512-IgSWvLobTDOjnaxAfDTIHaECbkNlAlKv2j5SjpB2v7QHKv1FIfjwMy8FsDbVfDX/KjmCmYICcw7uGaXLhtsLNg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.56.0", "@typescript-eslint/types": "8.56.0", @@ -2301,7 +2297,6 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2672,7 +2667,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -3283,7 +3277,6 @@ "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -3469,7 +3462,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -5819,7 +5811,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -5829,7 +5820,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -5842,7 +5832,6 @@ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.71.2.tgz", "integrity": "sha512-1CHvcDYzuRUNOflt4MOq3ZM46AronNJtQ1S7tnX6YN4y72qhgiUItpacZUAQ0TyWYci3yz1X+rXaSxiuEm86PA==", "license": "MIT", - "peer": true, "engines": { "node": ">=18.0.0" }, @@ -6611,7 +6600,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -6774,7 +6762,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -7093,7 +7080,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" }