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 joi… #43

Merged
Hammond merged 1 commits from feat/booking-panel into master 2025-12-03 18:41:04 +00:00
5 changed files with 60 additions and 50 deletions
Showing only changes of commit ce8383da89 - Show all commits

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,10 +472,10 @@ 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();
}
}}

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";
}