2025-11-27 20:35:26 +00:00
"use client" ;
import { useState , useEffect } from "react" ;
import { useParams , useRouter } from "next/navigation" ;
import {
Calendar ,
Clock ,
User ,
Video ,
CalendarCheck ,
Loader2 ,
ArrowLeft ,
Mail ,
Phone as PhoneIcon ,
MessageSquare ,
CheckCircle2 ,
Copy ,
} from "lucide-react" ;
import { useAppTheme } from "@/components/ThemeProvider" ;
2025-12-05 18:35:20 +00:00
import { getAppointmentDetail , listAppointments } from "@/lib/actions/appointments" ;
2025-11-27 20:35:26 +00:00
import { Button } from "@/components/ui/button" ;
import { Navbar } from "@/components/Navbar" ;
import { toast } from "sonner" ;
import type { Appointment } from "@/lib/models/appointments" ;
export default function UserAppointmentDetailPage() {
const params = useParams ( ) ;
const router = useRouter ( ) ;
const appointmentId = params . id as string ;
const [ appointment , setAppointment ] = useState < Appointment | null > ( null ) ;
const [ loading , setLoading ] = useState ( true ) ;
const { theme } = useAppTheme ( ) ;
const isDark = theme === "dark" ;
useEffect ( ( ) = > {
const fetchAppointment = async ( ) = > {
if ( ! appointmentId ) return ;
setLoading ( true ) ;
try {
2025-12-03 18:27:09 +00:00
// Fetch both detail and list to get selected_slots from list endpoint
const [ detailData , listData ] = await Promise . all ( [
getAppointmentDetail ( appointmentId ) ,
listAppointments ( ) . catch ( ( ) = > [ ] ) // Fallback to empty array if list fails
] ) ;
// Find matching appointment in list to get selected_slots
const listAppointment = Array . isArray ( listData )
? listData . find ( ( apt : Appointment ) = > apt . id === appointmentId )
: null ;
// Merge selected_slots from list into detail data
if ( listAppointment && listAppointment . selected_slots && Array . isArray ( listAppointment . selected_slots ) && listAppointment . selected_slots . length > 0 ) {
detailData . selected_slots = listAppointment . selected_slots ;
}
setAppointment ( detailData ) ;
2025-11-27 20:35:26 +00:00
} catch ( error ) {
toast . error ( "Failed to load appointment details" ) ;
router . push ( "/user/dashboard" ) ;
} finally {
setLoading ( false ) ;
}
} ;
fetchAppointment ( ) ;
} , [ appointmentId , router ] ) ;
const formatDate = ( dateString : string ) = > {
const date = new Date ( dateString ) ;
return date . toLocaleDateString ( "en-US" , {
weekday : "long" ,
month : "long" ,
day : "numeric" ,
year : "numeric" ,
} ) ;
} ;
const formatTime = ( dateString : string ) = > {
const date = new Date ( dateString ) ;
return date . toLocaleTimeString ( "en-US" , {
hour : "numeric" ,
minute : "2-digit" ,
hour12 : true ,
} ) ;
} ;
const formatShortDate = ( dateString : string ) = > {
const date = new Date ( dateString ) ;
return date . toLocaleDateString ( "en-US" , {
month : "short" ,
day : "numeric" ,
year : "numeric" ,
} ) ;
} ;
const getStatusColor = ( status : string ) = > {
const normalized = status . toLowerCase ( ) ;
if ( isDark ) {
switch ( normalized ) {
case "scheduled" :
return "bg-blue-500/20 text-blue-300 border-blue-500/30" ;
case "completed" :
return "bg-green-500/20 text-green-300 border-green-500/30" ;
case "rejected" :
case "cancelled" :
return "bg-red-500/20 text-red-300 border-red-500/30" ;
case "pending_review" :
case "pending" :
return "bg-yellow-500/20 text-yellow-300 border-yellow-500/30" ;
default :
return "bg-gray-700 text-gray-200 border-gray-600" ;
}
}
switch ( normalized ) {
case "scheduled" :
return "bg-blue-50 text-blue-700 border-blue-200" ;
case "completed" :
return "bg-green-50 text-green-700 border-green-200" ;
case "rejected" :
case "cancelled" :
return "bg-red-50 text-red-700 border-red-200" ;
case "pending_review" :
case "pending" :
return "bg-yellow-50 text-yellow-700 border-yellow-200" ;
default :
return "bg-gray-100 text-gray-700 border-gray-300" ;
}
} ;
const formatStatus = ( status : string ) = > {
return status . replace ( "_" , " " ) . replace ( /\b\w/g , ( l ) = > l . toUpperCase ( ) ) ;
} ;
const copyToClipboard = ( text : string , label : string ) = > {
navigator . clipboard . writeText ( text ) ;
toast . success ( ` ${ label } copied to clipboard ` ) ;
} ;
if ( loading ) {
return (
< div className = { ` min-h-screen ${ isDark ? "bg-gray-900" : "bg-gray-50" } ` } >
< Navbar / >
< div className = "min-h-[calc(100vh-4rem)] flex items-center justify-center" >
< div className = "text-center" >
< Loader2 className = { ` w-12 h-12 animate-spin mx-auto mb-4 ${ isDark ? "text-rose-400" : "text-rose-600" } ` } / >
< p className = { ` text-sm ${ isDark ? "text-gray-400" : "text-gray-600" } ` } > Loading appointment details . . . < / p >
< / div >
< / div >
< / div >
) ;
}
if ( ! appointment ) {
return (
< div className = { ` min-h-screen ${ isDark ? "bg-gray-900" : "bg-gray-50" } ` } >
< Navbar / >
< div className = "min-h-[calc(100vh-4rem)] flex items-center justify-center" >
< div className = "text-center" >
< p className = { ` text-lg mb-4 ${ isDark ? "text-gray-400" : "text-gray-600" } ` } > Appointment not found < / p >
< Button
onClick = { ( ) = > router . push ( "/user/dashboard" ) }
className = "bg-rose-600 hover:bg-rose-700 text-white"
>
< ArrowLeft className = "w-4 h-4 mr-2" / >
Back to Dashboard
< / Button >
< / div >
< / div >
< / div >
) ;
}
return (
< div className = { ` min-h-screen ${ isDark ? "bg-gray-900" : "bg-gray-50" } ` } >
< Navbar / >
< main className = "container mx-auto px-4 sm:px-6 lg:px-8 space-y-6 pt-20 sm:pt-24 pb-8" >
{ /* Page Header */ }
< div className = "flex flex-col gap-3 sm:gap-4" >
< Button
variant = "ghost"
onClick = { ( ) = > router . push ( "/user/dashboard" ) }
className = { ` flex items-center gap-2 w-fit ${ isDark ? "text-gray-300 hover:bg-gray-800 hover:text-white" : "text-gray-600 hover:bg-gray-100" } ` }
>
< ArrowLeft className = "w-4 h-4" / >
Back to Dashboard
< / Button >
< div className = "flex flex-col sm:flex-row sm:items-start sm:justify-between gap-4" >
< div >
< div className = "flex items-center gap-3 mb-2" >
< div className = { ` h-12 w-12 sm:h-16 sm:w-16 rounded-full flex items-center justify-center text-xl sm:text-2xl font-bold ${ isDark ? "bg-gradient-to-br from-rose-500 to-pink-600 text-white" : "bg-gradient-to-br from-rose-100 to-pink-100 text-rose-600" } ` } >
< CalendarCheck className = "w-6 h-6 sm:w-8 sm:h-8" / >
< / div >
< div >
< h1 className = { ` text-2xl sm:text-3xl lg:text-4xl font-bold ${ isDark ? "text-white" : "text-gray-900" } ` } >
Appointment Details
< / h1 >
< p className = { ` text-xs sm:text-sm mt-1 ${ isDark ? "text-gray-400" : "text-gray-500" } ` } >
Request # { appointment . id . slice ( 0 , 8 ) }
< / p >
< / div >
< / div >
< / div >
< div className = "flex items-center gap-3" >
< span
className = { ` px-3 sm:px-4 py-2 inline-flex items-center gap-2 text-xs sm:text-sm font-semibold rounded-full border ${ getStatusColor (
appointment . status
) } ` }
>
{ appointment . status === "scheduled" && < CheckCircle2 className = "w-3 h-3 sm:w-4 sm:h-4" / > }
{ formatStatus ( appointment . status ) }
< / span >
< / div >
< / div >
< / div >
< div className = "grid grid-cols-1 lg:grid-cols-3 gap-6" >
{ /* Main Content - Left Column (2/3) */ }
< div className = "lg:col-span-2 space-y-6" >
{ /* Appointment Information Card */ }
< div className = { ` rounded-2xl border shadow-sm overflow-hidden ${ isDark ? "bg-gray-800 border-gray-700" : "bg-white border-gray-200" } ` } >
< div className = { ` px-6 py-4 border-b ${ isDark ? "border-gray-700 bg-gray-800/50" : "border-gray-200 bg-gray-50/50" } ` } >
< h2 className = { ` text-lg font-semibold flex items-center gap-2 ${ isDark ? "text-white" : "text-gray-900" } ` } >
< User className = { ` w-5 h-5 ${ isDark ? "text-rose-400" : "text-rose-600" } ` } / >
Appointment Information
< / h2 >
< / div >
< div className = "p-6" >
< div className = "grid grid-cols-1 sm:grid-cols-2 gap-6" >
< div className = "space-y-1" >
< p className = { ` text-xs font-medium uppercase tracking-wider ${ isDark ? "text-gray-400" : "text-gray-500" } ` } >
Full Name
< / p >
< p className = { ` text-base font-medium ${ isDark ? "text-white" : "text-gray-900" } ` } >
{ appointment . first_name } { appointment . last_name }
< / p >
< / div >
< div className = "space-y-1" >
< p className = { ` text-xs font-medium uppercase tracking-wider flex items-center gap-1 ${ isDark ? "text-gray-400" : "text-gray-500" } ` } >
< Mail className = "w-3 h-3" / >
Email Address
< / p >
< div className = "flex items-center gap-2" >
< p className = { ` text-base font-medium ${ isDark ? "text-white" : "text-gray-900" } ` } >
{ appointment . email }
< / p >
< button
onClick = { ( ) = > copyToClipboard ( appointment . email , "Email" ) }
className = { ` p-1.5 rounded-lg hover:bg-opacity-80 transition-colors ${ isDark ? "hover:bg-gray-700" : "hover:bg-gray-100" } ` }
title = "Copy email"
>
< Copy className = { ` w-4 h-4 ${ isDark ? "text-gray-400" : "text-gray-500" } ` } / >
< / button >
< / div >
< / div >
{ appointment . phone && (
< div className = "space-y-1" >
< p className = { ` text-xs font-medium uppercase tracking-wider flex items-center gap-1 ${ isDark ? "text-gray-400" : "text-gray-500" } ` } >
< PhoneIcon className = "w-3 h-3" / >
Phone Number
< / p >
< div className = "flex items-center gap-2" >
< p className = { ` text-base font-medium ${ isDark ? "text-white" : "text-gray-900" } ` } >
{ appointment . phone }
< / p >
< button
onClick = { ( ) = > copyToClipboard ( appointment . phone ! , "Phone" ) }
className = { ` p-1.5 rounded-lg hover:bg-opacity-80 transition-colors ${ isDark ? "hover:bg-gray-700" : "hover:bg-gray-100" } ` }
title = "Copy phone"
>
< Copy className = { ` w-4 h-4 ${ isDark ? "text-gray-400" : "text-gray-500" } ` } / >
< / button >
< / div >
< / div >
) }
< / div >
< / div >
< / div >
{ /* Scheduled Appointment Details */ }
{ appointment . scheduled_datetime && (
< div className = { ` rounded-2xl border shadow-sm overflow-hidden ${ isDark ? "bg-gray-800 border-gray-700" : "bg-white border-gray-200" } ` } >
< div className = { ` px-6 py-4 border-b ${ isDark ? "border-gray-700 bg-gray-800/50" : "border-gray-200 bg-gray-50/50" } ` } >
< h2 className = { ` text-lg font-semibold flex items-center gap-2 ${ isDark ? "text-white" : "text-gray-900" } ` } >
< Calendar className = { ` w-5 h-5 ${ isDark ? "text-rose-400" : "text-rose-600" } ` } / >
Scheduled Appointment
< / h2 >
< / div >
< div className = "p-6" >
< div className = "flex items-start gap-4" >
< div className = { ` p-4 rounded-xl ${ isDark ? "bg-blue-500/10 border border-blue-500/20" : "bg-blue-50 border border-blue-100" } ` } >
< Calendar className = { ` w-6 h-6 ${ isDark ? "text-blue-400" : "text-blue-600" } ` } / >
< / div >
< div className = "flex-1" >
< p className = { ` text-2xl font-bold mb-1 ${ isDark ? "text-white" : "text-gray-900" } ` } >
{ formatDate ( appointment . scheduled_datetime ) }
< / p >
< div className = "flex items-center gap-4 mt-2" >
< div className = "flex items-center gap-2" >
< Clock className = { ` w-4 h-4 ${ isDark ? "text-gray-400" : "text-gray-500" } ` } / >
< p className = { ` text-base ${ isDark ? "text-gray-300" : "text-gray-700" } ` } >
{ formatTime ( appointment . scheduled_datetime ) }
< / p >
< / div >
{ appointment . scheduled_duration && (
< div className = "flex items-center gap-2" >
< span className = { ` text-base ${ isDark ? "text-gray-400" : "text-gray-500" } ` } > • < / span >
< p className = { ` text-base ${ isDark ? "text-gray-300" : "text-gray-700" } ` } >
{ appointment . meeting_duration_display || ` ${ appointment . scheduled_duration } minutes ` }
< / p >
< / div >
) }
< / div >
< / div >
< / div >
< / div >
< / div >
) }
2025-12-03 18:27:09 +00:00
{ /* Selected Slots */ }
2025-12-03 11:03:01 +00:00
{ appointment . selected_slots && Array . isArray ( appointment . selected_slots ) && appointment . selected_slots . length > 0 && (
2025-11-27 20:35:26 +00:00
< div className = { ` rounded-2xl border shadow-sm overflow-hidden ${ isDark ? "bg-gray-800 border-gray-700" : "bg-white border-gray-200" } ` } >
< div className = { ` px-6 py-4 border-b ${ isDark ? "border-gray-700 bg-gray-800/50" : "border-gray-200 bg-gray-50/50" } ` } >
< h2 className = { ` text-lg font-semibold flex items-center gap-2 ${ isDark ? "text-white" : "text-gray-900" } ` } >
2025-12-03 16:25:28 +00:00
< CalendarCheck className = { ` w-5 h-5 ${ isDark ? "text-green-400" : "text-green-600" } ` } / >
2025-12-03 11:03:01 +00:00
Selected Time Slots
2025-12-03 16:25:28 +00:00
{ appointment . are_preferences_available !== undefined && (
< span className = { ` ml-auto px-3 py-1 text-xs font-medium rounded-full ${ appointment . are_preferences_available ? ( isDark ? "bg-green-500/20 text-green-300 border border-green-500/30" : "bg-green-50 text-green-700 border border-green-200" ) : ( isDark ? "bg-yellow-500/20 text-yellow-300 border border-yellow-500/30" : "bg-yellow-50 text-yellow-700 border border-yellow-200" ) } ` } >
{ appointment . are_preferences_available ? "All Available" : "Partially Available" }
< / span >
) }
2025-11-27 20:35:26 +00:00
< / h2 >
< / div >
< div className = "p-6" >
2025-12-03 18:27:09 +00:00
{ ( ( ) = > {
const dayNames : Record < number , string > = {
0 : "Monday" ,
1 : "Tuesday" ,
2 : "Wednesday" ,
3 : "Thursday" ,
4 : "Friday" ,
5 : "Saturday" ,
6 : "Sunday" ,
} ;
const timeSlotLabels : Record < string , string > = {
morning : "Morning" ,
afternoon : "Lunchtime" ,
evening : "Evening" ,
} ;
2025-12-04 10:48:14 +00:00
// Time slot order: morning, afternoon (lunchtime), evening
const timeSlotOrder : Record < string , number > = {
morning : 0 ,
afternoon : 1 ,
evening : 2 ,
} ;
2025-12-03 18:27:09 +00:00
// Group slots by date
const slotsByDate : Record < string , typeof appointment.selected_slots > = { } ;
appointment . selected_slots . forEach ( ( slot : any ) = > {
const date = slot . date || "" ;
if ( ! slotsByDate [ date ] ) {
slotsByDate [ date ] = [ ] ;
}
slotsByDate [ date ] . push ( slot ) ;
} ) ;
2025-12-04 10:48:14 +00:00
// Sort dates and slots within each date
const sortedDates = Object . keys ( slotsByDate ) . sort ( ( a , b ) = > {
return new Date ( a ) . getTime ( ) - new Date ( b ) . getTime ( ) ;
} ) ;
2025-12-03 18:27:09 +00:00
return (
< div className = "space-y-4" >
2025-12-04 10:48:14 +00:00
{ sortedDates . map ( ( date ) = > {
// Sort slots within this date by time slot order
const slots = slotsByDate [ date ] . sort ( ( a : any , b : any ) = > {
const aSlot = String ( a . time_slot ) . toLowerCase ( ) . trim ( ) ;
const bSlot = String ( b . time_slot ) . toLowerCase ( ) . trim ( ) ;
const aOrder = timeSlotOrder [ aSlot ] ? ? 999 ;
const bOrder = timeSlotOrder [ bSlot ] ? ? 999 ;
return aOrder - bOrder ;
} ) ;
return (
< div key = { date } className = { ` p-4 rounded-xl border ${ isDark ? "bg-gray-700/50 border-gray-600" : "bg-gray-50 border-gray-200" } ` } >
< div className = "mb-3" >
< p className = { ` text-base font-semibold ${ isDark ? "text-white" : "text-gray-900" } ` } >
{ formatShortDate ( date ) }
2025-12-03 18:27:09 +00:00
< / p >
2025-12-04 10:48:14 +00:00
{ slots . length > 0 && slots [ 0 ] ? . day !== undefined && (
< p className = { ` text-sm mt-1 ${ isDark ? "text-gray-400" : "text-gray-500" } ` } >
{ dayNames [ slots [ 0 ] . day ] || ` Day ${ slots [ 0 ] . day } ` }
< / p >
) }
< / div >
< div className = "flex flex-wrap gap-2" >
{ slots . map ( ( slot : any , idx : number ) = > {
const timeSlot = String ( slot . time_slot ) . toLowerCase ( ) . trim ( ) ;
const timeLabel = timeSlotLabels [ timeSlot ] || slot . time_slot ;
return (
< span
key = { idx }
className = { ` px-3 py-1.5 rounded-lg text-sm font-medium ${ isDark ? "bg-green-500/20 text-green-300 border border-green-500/30" : "bg-green-50 text-green-700 border border-green-200" } ` }
>
{ timeLabel }
< / span >
) ;
} ) }
< / div >
2025-12-03 18:27:09 +00:00
< / div >
2025-12-04 10:48:14 +00:00
) ;
} ) }
2025-12-03 18:27:09 +00:00
< / div >
) ;
} ) ( ) }
2025-11-27 20:35:26 +00:00
< / div >
< / div >
) }
{ /* Reason */ }
{ appointment . reason && (
< div className = { ` rounded-2xl border shadow-sm overflow-hidden ${ isDark ? "bg-gray-800 border-gray-700" : "bg-white border-gray-200" } ` } >
< div className = { ` px-6 py-4 border-b ${ isDark ? "border-gray-700 bg-gray-800/50" : "border-gray-200 bg-gray-50/50" } ` } >
< h2 className = { ` text-lg font-semibold flex items-center gap-2 ${ isDark ? "text-white" : "text-gray-900" } ` } >
< MessageSquare className = { ` w-5 h-5 ${ isDark ? "text-rose-400" : "text-rose-600" } ` } / >
Reason for Appointment
< / h2 >
< / div >
< div className = "p-6" >
< p className = { ` text-base leading-relaxed ${ isDark ? "text-gray-300" : "text-gray-700" } ` } >
{ appointment . reason }
< / p >
< / div >
< / div >
) }
{ /* Rejection Reason */ }
{ appointment . rejection_reason && (
< div className = { ` rounded-2xl border shadow-sm overflow-hidden ${ isDark ? "bg-red-900/20 border-red-800/50" : "bg-red-50 border-red-200" } ` } >
< div className = { ` px-6 py-4 border-b ${ isDark ? "border-red-800/50" : "border-red-200" } ` } >
< h2 className = { ` text-lg font-semibold ${ isDark ? "text-red-300" : "text-red-900" } ` } >
Rejection Reason
< / h2 >
< / div >
< div className = "p-6" >
< p className = { ` text-base leading-relaxed ${ isDark ? "text-red-200" : "text-red-800" } ` } >
{ appointment . rejection_reason }
< / p >
< / div >
< / div >
) }
{ /* Meeting Information */ }
2025-12-04 19:30:08 +00:00
{ /* Video Meeting card hidden - users can join via the Join Now button in sidebar */ }
{ / * { a p p o i n t m e n t . p a r t i c i p a n t _ j o i n _ u r l & & (
2025-11-27 20:35:26 +00:00
< div className = { ` rounded-2xl border shadow-sm overflow-hidden ${ isDark ? "bg-gradient-to-br from-blue-900/20 to-purple-900/20 border-blue-800/30" : "bg-gradient-to-br from-blue-50 to-purple-50 border-blue-200" } ` } >
< div className = { ` px-6 py-4 border-b ${ isDark ? "border-blue-800/30" : "border-blue-200" } ` } >
< h2 className = { ` text-lg font-semibold flex items-center gap-2 ${ isDark ? "text-white" : "text-gray-900" } ` } >
< Video className = { ` w-5 h-5 ${ isDark ? "text-blue-400" : "text-blue-600" } ` } / >
Video Meeting
< / h2 >
< / div >
< div className = "p-6 space-y-4" >
2025-12-03 18:39:30 +00:00
{ appointment . can_join_as_participant !== undefined && (
< div className = { ` flex items-center gap-2 px-4 py-3 rounded-lg ${ appointment . can_join_as_participant ? ( isDark ? "bg-green-500/20 border border-green-500/30" : "bg-green-50 border border-green-200" ) : ( isDark ? "bg-gray-800 border border-gray-700" : "bg-gray-50 border border-gray-200" ) } ` } >
< div className = { ` h-2 w-2 rounded-full ${ appointment . can_join_as_participant ? ( isDark ? "bg-green-400" : "bg-green-600" ) : ( isDark ? "bg-gray-500" : "bg-gray-400" ) } ` } / >
< p className = { ` text-sm font-medium ${ appointment . can_join_as_participant ? ( isDark ? "text-green-300" : "text-green-700" ) : ( isDark ? "text-gray-400" : "text-gray-500" ) } ` } >
2025-12-05 19:16:02 +00:00
{ appointment . can_join_as_participant ? "Meeting is active - You can join now" : "Click here to join" }
2025-11-27 20:35:26 +00:00
< / p >
< / div >
) }
< / div >
< / div >
2025-12-04 19:30:08 +00:00
) } * / }
2025-11-27 20:35:26 +00:00
< / div >
{ /* Sidebar - Right Column (1/3) */ }
< div className = "space-y-6" >
{ /* Quick Info Card */ }
< div className = { ` rounded-2xl border shadow-sm overflow-hidden ${ isDark ? "bg-gray-800 border-gray-700" : "bg-white border-gray-200" } ` } >
< div className = { ` px-6 py-4 border-b ${ isDark ? "border-gray-700 bg-gray-800/50" : "border-gray-200 bg-gray-50/50" } ` } >
< h2 className = { ` text-lg font-semibold ${ isDark ? "text-white" : "text-gray-900" } ` } >
Quick Info
< / h2 >
< / div >
< div className = "p-6 space-y-4" >
< div >
< p className = { ` text-xs font-medium mb-1 uppercase tracking-wider ${ isDark ? "text-gray-400" : "text-gray-500" } ` } >
Created
< / p >
< p className = { ` text-sm font-medium ${ isDark ? "text-white" : "text-gray-900" } ` } >
{ formatShortDate ( appointment . created_at ) }
< / p >
< p className = { ` text-xs mt-0.5 ${ isDark ? "text-gray-500" : "text-gray-500" } ` } >
{ formatTime ( appointment . created_at ) }
< / p >
< / div >
< div className = "pt-4 border-t border-gray-200 dark:border-gray-700" >
< p className = { ` text-xs font-medium mb-1 uppercase tracking-wider ${ isDark ? "text-gray-400" : "text-gray-500" } ` } >
Status
< / p >
< span
className = { ` inline-flex items-center gap-2 px-3 py-1.5 text-sm font-semibold rounded-lg border ${ getStatusColor (
appointment . status
) } ` }
>
{ appointment . status === "scheduled" && < CheckCircle2 className = "w-4 h-4" / > }
{ formatStatus ( appointment . status ) }
< / span >
< / div >
2025-12-04 19:01:16 +00:00
{ appointment . scheduled_datetime && (
< div className = "pt-4 border-t border-gray-200 dark:border-gray-700" >
< p className = { ` text-xs font-medium mb-2 uppercase tracking-wider ${ isDark ? "text-gray-400" : "text-gray-500" } ` } >
Meeting Information
< / p >
< div className = "space-y-3" >
< div >
< p className = { ` text-xs font-medium mb-1 ${ isDark ? "text-gray-400" : "text-gray-500" } ` } >
Meeting Start Time :
< / p >
< p className = { ` text-sm font-medium ${ isDark ? "text-white" : "text-gray-900" } ` } >
{ formatDate ( appointment . scheduled_datetime ) } at { formatTime ( appointment . scheduled_datetime ) }
< / p >
< / div >
< div >
< p className = { ` text-xs font-medium mb-1 ${ isDark ? "text-gray-400" : "text-gray-500" } ` } >
How to Access :
< / p >
< p className = { ` text-sm ${ isDark ? "text-gray-300" : "text-gray-700" } ` } >
2025-12-05 18:00:54 +00:00
{ ` Up to 10 minutes before the meeting is scheduled to begin, click the "Join Now" button below when the meeting becomes available. The meeting will be accessible starting at ${ formatTime ( appointment . scheduled_datetime ) } . ` }
2025-12-04 19:01:16 +00:00
< / p >
< / div >
< / div >
< / div >
) }
2025-11-27 20:35:26 +00:00
< / div >
< / div >
{ /* Join Meeting Button */ }
2025-12-03 18:39:30 +00:00
{ appointment . status === "scheduled" && appointment . participant_join_url && (
2025-11-27 20:35:26 +00:00
< div className = { ` rounded-2xl border shadow-sm overflow-hidden ${ isDark ? "bg-gradient-to-br from-blue-900/20 to-purple-900/20 border-blue-800/30" : "bg-gradient-to-br from-blue-50 to-purple-50 border-blue-200" } ` } >
< div className = "p-6" >
2025-12-04 15:56:53 +00:00
{ ( ( ) = > {
2025-12-05 13:03:58 +00:00
// Check if meeting has ended
const endedAt = appointment . meeting_ended_at ;
const hasEnded = endedAt != null && endedAt !== "" ;
// If meeting has ended, show "Meeting has ended"
if ( hasEnded ) {
return (
< button
disabled
className = { ` flex items-center justify-center gap-2 w-full cursor-not-allowed h-12 rounded-lg text-base font-medium transition-colors ${ isDark ? "bg-gray-700 text-gray-500" : "bg-gray-300 text-gray-500" } ` }
>
< Video className = "w-5 h-5" / >
Meeting has ended
< / button >
) ;
}
2025-12-04 15:56:53 +00:00
// Check if can join as participant (handle both boolean and string values)
const canJoinAsParticipant = appointment . can_join_as_participant === true || appointment . can_join_as_participant === "true" ;
// Check if meeting has started (handle both field names)
const startedAt = appointment . started_at || appointment . meeting_started_at ;
const hasStarted = startedAt != null && startedAt !== "" ;
2025-12-05 19:16:02 +00:00
// If can_join_as_participant != true, display "Click here to join"
2025-12-04 15:56:53 +00:00
if ( ! canJoinAsParticipant ) {
return (
< button
disabled
2025-12-05 19:16:02 +00:00
className = { ` flex items-center justify-center gap-2 w-full cursor-not-allowed h-12 rounded-lg text-sm sm:text-base font-medium transition-colors ${ isDark ? "bg-gray-700 text-gray-500" : "bg-gray-300 text-gray-500" } ` }
2025-12-04 15:56:53 +00:00
>
2025-12-05 19:16:02 +00:00
< Video className = "w-4 h-4 sm:w-5 sm:h-5 shrink-0" / >
< span className = "text-center" > Click here to join < / span >
2025-12-04 15:56:53 +00:00
< / button >
) ;
}
// If can_join_as_participant == true && started_at != null, show "Join Now" button
if ( hasStarted ) {
return (
< a
href = { appointment . participant_join_url }
target = "_blank"
rel = "noopener noreferrer"
className = { ` flex items-center justify-center gap-2 w-full bg-blue-600 hover:bg-blue-700 text-white h-12 rounded-lg text-base font-medium transition-colors ` }
>
< Video className = "w-5 h-5" / >
Join Now
< / a >
) ;
}
2025-12-05 19:16:02 +00:00
// If can_join_as_participant == true && started_at == null, show "Click here to join"
2025-12-04 15:56:53 +00:00
return (
< button
disabled
2025-12-05 19:16:02 +00:00
className = { ` flex items-center justify-center gap-2 w-full cursor-not-allowed h-12 rounded-lg text-sm sm:text-base font-medium transition-colors ${ isDark ? "bg-gray-700 text-gray-500" : "bg-gray-300 text-gray-500" } ` }
2025-12-04 15:56:53 +00:00
>
2025-12-05 19:16:02 +00:00
< Video className = "w-4 h-4 sm:w-5 sm:h-5 shrink-0" / >
< span className = "text-center" > Click here to join < / span >
2025-12-04 15:56:53 +00:00
< / button >
) ;
} ) ( ) }
2025-11-27 20:35:26 +00:00
< / div >
< / div >
) }
2025-12-05 18:19:19 +00:00
2025-11-27 20:35:26 +00:00
< / div >
< / div >
< / main >
< / div >
) ;
}