Refactor appointment components to replace Jitsi meeting URLs with moderator and participant join URLs. Update logic for joining meetings and copying room IDs based on user roles, enhancing clarity in the UI. Modify appointment model to support new join URL fields and permissions for moderators and participants.

This commit is contained in:
iamkiddy 2025-12-03 18:39:30 +00:00
parent 9930789d71
commit ce8383da89
5 changed files with 60 additions and 50 deletions

View File

@ -494,7 +494,7 @@ export default function AppointmentDetailPage() {
)}
{/* Meeting Information */}
{appointment.jitsi_meet_url && (
{appointment.moderator_join_url && (
<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"}`}>
@ -513,10 +513,10 @@ export default function AppointmentDetailPage() {
{appointment.jitsi_room_id}
</p>
<button
onClick={() => appointment.can_join_meeting && copyToClipboard(appointment.jitsi_room_id!, "Room ID")}
disabled={!appointment.can_join_meeting}
className={`p-2 rounded-lg transition-colors ${appointment.can_join_meeting ? (isDark ? "hover:bg-gray-700" : "hover:bg-gray-100") : (isDark ? "opacity-50 cursor-not-allowed" : "opacity-50 cursor-not-allowed")}`}
title={appointment.can_join_meeting ? "Copy room ID" : "Meeting not available"}
onClick={() => appointment.can_join_as_moderator && copyToClipboard(appointment.jitsi_room_id!, "Room ID")}
disabled={!appointment.can_join_as_moderator}
className={`p-2 rounded-lg transition-colors ${appointment.can_join_as_moderator ? (isDark ? "hover:bg-gray-700" : "hover:bg-gray-100") : (isDark ? "opacity-50 cursor-not-allowed" : "opacity-50 cursor-not-allowed")}`}
title={appointment.can_join_as_moderator ? "Copy room ID" : "Meeting not available"}
>
<Copy className={`w-4 h-4 ${isDark ? "text-gray-400" : "text-gray-500"}`} />
</button>
@ -525,21 +525,21 @@ export default function AppointmentDetailPage() {
)}
<div>
<p className={`text-xs font-medium mb-2 uppercase tracking-wider ${isDark ? "text-gray-400" : "text-gray-500"}`}>
Meeting Link
Moderator Meeting Link
</p>
<div className="flex items-center gap-2">
{appointment.can_join_meeting ? (
{appointment.can_join_as_moderator ? (
<>
<a
href={appointment.jitsi_meet_url}
href={appointment.moderator_join_url}
target="_blank"
rel="noopener noreferrer"
className={`flex-1 text-sm px-3 py-2 rounded-lg truncate ${isDark ? "bg-gray-800 text-blue-400 hover:bg-gray-700" : "bg-white text-blue-600 hover:bg-gray-50 border border-gray-200"}`}
>
{appointment.jitsi_meet_url}
{appointment.moderator_join_url}
</a>
<a
href={appointment.jitsi_meet_url}
href={appointment.moderator_join_url}
target="_blank"
rel="noopener noreferrer"
className={`px-4 py-2 rounded-lg font-medium transition-colors ${isDark ? "bg-blue-600 hover:bg-blue-700 text-white" : "bg-blue-600 hover:bg-blue-700 text-white"}`}
@ -550,7 +550,7 @@ export default function AppointmentDetailPage() {
) : (
<>
<div className={`flex-1 text-sm px-3 py-2 rounded-lg truncate ${isDark ? "bg-gray-800/50 text-gray-500 border border-gray-700" : "bg-gray-100 text-gray-400 border border-gray-300"}`}>
{appointment.jitsi_meet_url}
{appointment.moderator_join_url}
</div>
<button
disabled
@ -562,11 +562,11 @@ export default function AppointmentDetailPage() {
)}
</div>
</div>
{appointment.can_join_meeting !== undefined && (
<div className={`flex items-center gap-2 px-4 py-3 rounded-lg ${appointment.can_join_meeting ? (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_meeting ? (isDark ? "bg-green-400" : "bg-green-600") : (isDark ? "bg-gray-500" : "bg-gray-400")}`} />
<p className={`text-sm font-medium ${appointment.can_join_meeting ? (isDark ? "text-green-300" : "text-green-700") : (isDark ? "text-gray-400" : "text-gray-500")}`}>
{appointment.can_join_meeting ? "Meeting is active - You can join now" : "Meeting is not available yet"}
{appointment.can_join_as_moderator !== undefined && (
<div className={`flex items-center gap-2 px-4 py-3 rounded-lg ${appointment.can_join_as_moderator ? (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_moderator ? (isDark ? "bg-green-400" : "bg-green-600") : (isDark ? "bg-gray-500" : "bg-gray-400")}`} />
<p className={`text-sm font-medium ${appointment.can_join_as_moderator ? (isDark ? "text-green-300" : "text-green-700") : (isDark ? "text-gray-400" : "text-gray-500")}`}>
{appointment.can_join_as_moderator ? "Meeting is active - You can join as moderator" : "Meeting is not available yet"}
</p>
</div>
)}
@ -636,18 +636,18 @@ export default function AppointmentDetailPage() {
)}
{/* Join Meeting Button (if scheduled) */}
{appointment.status === "scheduled" && appointment.jitsi_meet_url && (
{appointment.status === "scheduled" && appointment.moderator_join_url && (
<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">
{appointment.can_join_meeting ? (
{appointment.can_join_as_moderator ? (
<a
href={appointment.jitsi_meet_url}
href={appointment.moderator_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 Meeting
Join Meeting as Moderator
</a>
) : (
<button

View File

@ -802,13 +802,13 @@ export default function Booking() {
</button>
</>
)}
{appointment.jitsi_meet_url && (
{appointment.moderator_join_url && (
<a
href={appointment.jitsi_meet_url}
href={appointment.moderator_join_url}
target="_blank"
rel="noopener noreferrer"
className={`p-1.5 sm:p-2 rounded-lg transition-colors ${isDark ? "text-gray-300 hover:text-white hover:bg-gray-700" : "text-gray-600 hover:text-gray-900 hover:bg-gray-100"}`}
title="Join Meeting"
title="Join Meeting as Moderator"
>
<Video className="w-4 h-4" />
</a>

View File

@ -433,7 +433,7 @@ export default function UserAppointmentDetailPage() {
)}
{/* Meeting Information */}
{appointment.jitsi_meet_url && (
{appointment.participant_join_url && (
<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"}`}>
@ -452,10 +452,10 @@ export default function UserAppointmentDetailPage() {
{appointment.jitsi_room_id}
</p>
<button
onClick={() => appointment.can_join_meeting && copyToClipboard(appointment.jitsi_room_id!, "Room ID")}
disabled={!appointment.can_join_meeting}
className={`p-2 rounded-lg transition-colors ${appointment.can_join_meeting ? (isDark ? "hover:bg-gray-700" : "hover:bg-gray-100") : (isDark ? "opacity-50 cursor-not-allowed" : "opacity-50 cursor-not-allowed")}`}
title={appointment.can_join_meeting ? "Copy room ID" : "Meeting not available"}
onClick={() => appointment.can_join_as_participant && copyToClipboard(appointment.jitsi_room_id!, "Room ID")}
disabled={!appointment.can_join_as_participant}
className={`p-2 rounded-lg transition-colors ${appointment.can_join_as_participant ? (isDark ? "hover:bg-gray-700" : "hover:bg-gray-100") : (isDark ? "opacity-50 cursor-not-allowed" : "opacity-50 cursor-not-allowed")}`}
title={appointment.can_join_as_participant ? "Copy room ID" : "Meeting not available"}
>
<Copy className={`w-4 h-4 ${isDark ? "text-gray-400" : "text-gray-500"}`} />
</button>
@ -467,18 +467,18 @@ export default function UserAppointmentDetailPage() {
Meeting Link
</p>
<div className="flex items-center gap-2">
{appointment.can_join_meeting ? (
{appointment.can_join_as_participant ? (
<>
<a
href={appointment.jitsi_meet_url}
href={appointment.participant_join_url}
target="_blank"
rel="noopener noreferrer"
className={`flex-1 text-sm px-3 py-2 rounded-lg truncate ${isDark ? "bg-gray-800 text-blue-400 hover:bg-gray-700" : "bg-white text-blue-600 hover:bg-gray-50 border border-gray-200"}`}
>
{appointment.jitsi_meet_url}
{appointment.participant_join_url}
</a>
<a
href={appointment.jitsi_meet_url}
href={appointment.participant_join_url}
target="_blank"
rel="noopener noreferrer"
className={`px-4 py-2 rounded-lg font-medium transition-colors ${isDark ? "bg-blue-600 hover:bg-blue-700 text-white" : "bg-blue-600 hover:bg-blue-700 text-white"}`}
@ -489,7 +489,7 @@ export default function UserAppointmentDetailPage() {
) : (
<>
<div className={`flex-1 text-sm px-3 py-2 rounded-lg truncate ${isDark ? "bg-gray-800/50 text-gray-500 border border-gray-700" : "bg-gray-100 text-gray-400 border border-gray-300"}`}>
{appointment.jitsi_meet_url}
{appointment.participant_join_url}
</div>
<button
disabled
@ -501,11 +501,11 @@ export default function UserAppointmentDetailPage() {
)}
</div>
</div>
{appointment.can_join_meeting !== undefined && (
<div className={`flex items-center gap-2 px-4 py-3 rounded-lg ${appointment.can_join_meeting ? (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_meeting ? (isDark ? "bg-green-400" : "bg-green-600") : (isDark ? "bg-gray-500" : "bg-gray-400")}`} />
<p className={`text-sm font-medium ${appointment.can_join_meeting ? (isDark ? "text-green-300" : "text-green-700") : (isDark ? "text-gray-400" : "text-gray-500")}`}>
{appointment.can_join_meeting ? "Meeting is active - You can join now" : "Meeting is not available yet"}
{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")}`}>
{appointment.can_join_as_participant ? "Meeting is active - You can join now" : "Meeting is not available yet"}
</p>
</div>
)}
@ -552,12 +552,12 @@ export default function UserAppointmentDetailPage() {
</div>
{/* Join Meeting Button */}
{appointment.status === "scheduled" && appointment.jitsi_meet_url && (
{appointment.status === "scheduled" && appointment.participant_join_url && (
<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">
{appointment.can_join_meeting ? (
{appointment.can_join_as_participant ? (
<a
href={appointment.jitsi_meet_url}
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`}

View File

@ -458,13 +458,13 @@ export default function UserDashboard() {
</td>
<td className="px-3 sm:px-4 md:px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<div className="flex items-center justify-end gap-1 sm:gap-2">
{appointment.jitsi_meet_url && (
{appointment.participant_join_url && (
<a
href={appointment.jitsi_meet_url}
href={appointment.participant_join_url}
target="_blank"
rel="noopener noreferrer"
className={`p-1.5 sm:p-2 rounded-lg transition-colors ${
appointment.can_join_meeting
appointment.can_join_as_participant
? isDark
? "bg-blue-600 hover:bg-blue-700 text-white"
: "bg-blue-600 hover:bg-blue-700 text-white"
@ -472,17 +472,17 @@ export default function UserDashboard() {
? "text-gray-400 hover:text-gray-300 hover:bg-gray-700"
: "text-gray-500 hover:text-gray-700 hover:bg-gray-100"
}`}
title={appointment.can_join_meeting ? "Join Meeting" : "Meeting Not Available"}
title={appointment.can_join_as_participant ? "Join Meeting" : "Meeting Not Available"}
onClick={(e) => {
e.stopPropagation();
if (!appointment.can_join_meeting) {
if (!appointment.can_join_as_participant) {
e.preventDefault();
}
}}
>
<Video className="w-4 h-4" />
</a>
)}
>
<Video className="w-4 h-4" />
</a>
)}
</div>
</td>
</tr>

View File

@ -18,8 +18,17 @@ export interface Appointment {
rejection_reason?: string;
jitsi_meet_url?: string;
jitsi_room_id?: string;
jitsi_meeting_created?: boolean;
meeting_started_at?: string;
meeting_ended_at?: string;
meeting_duration_actual?: number;
meeting_info?: any;
has_jitsi_meeting?: boolean | string;
can_join_meeting?: boolean | string;
can_join_as_moderator?: boolean | string;
can_join_as_participant?: boolean | string;
moderator_join_url?: string;
participant_join_url?: string;
meeting_status?: string;
matching_availability?: MatchingAvailability | Array<{
date: string;
@ -39,6 +48,7 @@ export interface Appointment {
export interface SelectedSlot {
day: number; // 0-6 (Monday-Sunday)
date?: string; // YYYY-MM-DD format
time_slot: "morning" | "afternoon" | "evening";
}