feat: improve video conference UI in EventModal

- Replace 'Meeting link generated' text with 'Join Visio conference' button
- Button opens video conference link in new tab when clicked
- Use contained variant styling to match Save button appearance
- Maintain copy and delete functionality for meeting links
This commit is contained in:
lenhanphung
2025-10-03 14:50:37 +07:00
parent 04fa2d0629
commit f245991816
17 changed files with 25 additions and 82 deletions
@@ -1,5 +1,4 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { searchUsers } from "../../features/User/userAPI";
import { userAttendee } from "../../features/User/userDataTypes"; import { userAttendee } from "../../features/User/userDataTypes";
import { PeopleSearch, User } from "./PeopleSearch"; import { PeopleSearch, User } from "./PeopleSearch";
-10
View File
@@ -1,20 +1,10 @@
import CloseIcon from "@mui/icons-material/Close";
import Autocomplete from "@mui/material/Autocomplete"; import Autocomplete from "@mui/material/Autocomplete";
import Avatar from "@mui/material/Avatar"; import Avatar from "@mui/material/Avatar";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import Card from "@mui/material/Card";
import CardActions from "@mui/material/CardActions";
import CardContent from "@mui/material/CardContent";
import CardHeader from "@mui/material/CardHeader";
import CircularProgress from "@mui/material/CircularProgress"; import CircularProgress from "@mui/material/CircularProgress";
import IconButton from "@mui/material/IconButton";
import ListItem from "@mui/material/ListItem"; import ListItem from "@mui/material/ListItem";
import ListItemAvatar from "@mui/material/ListItemAvatar"; import ListItemAvatar from "@mui/material/ListItemAvatar";
import ListItemText from "@mui/material/ListItemText"; import ListItemText from "@mui/material/ListItemText";
import Modal from "@mui/material/Modal";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import Typography from "@mui/material/Typography";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { searchUsers } from "../../features/User/userAPI"; import { searchUsers } from "../../features/User/userAPI";
import PeopleOutlineOutlinedIcon from "@mui/icons-material/PeopleOutlineOutlined"; import PeopleOutlineOutlinedIcon from "@mui/icons-material/PeopleOutlineOutlined";
+1 -12
View File
@@ -11,29 +11,18 @@ import { useAppDispatch, useAppSelector } from "../../app/hooks";
import EventPopover from "../../features/Events/EventModal"; import EventPopover from "../../features/Events/EventModal";
import { CalendarEvent } from "../../features/Events/EventsTypes"; import { CalendarEvent } from "../../features/Events/EventsTypes";
import CalendarSelection from "./CalendarSelection"; import CalendarSelection from "./CalendarSelection";
import { import { getCalendarDetailAsync } from "../../features/Calendars/CalendarSlice";
getCalendarDetailAsync,
getEventAsync,
putEventAsync,
updateEventLocal,
} from "../../features/Calendars/CalendarSlice";
import ImportAlert from "../../features/Events/ImportAlert"; import ImportAlert from "../../features/Events/ImportAlert";
import { import {
computeStartOfTheWeek, computeStartOfTheWeek,
formatDateToYYYYMMDDTHHMMSS, formatDateToYYYYMMDDTHHMMSS,
getCalendarRange, getCalendarRange,
getDeltaInMilliseconds,
} from "../../utils/dateUtils"; } from "../../utils/dateUtils";
import { Calendars } from "../../features/Calendars/CalendarTypes"; import { Calendars } from "../../features/Calendars/CalendarTypes";
import { push } from "redux-first-history"; import { push } from "redux-first-history";
import EventPreviewModal from "../../features/Events/EventDisplayPreview"; import EventPreviewModal from "../../features/Events/EventDisplayPreview";
import { createSelector } from "@reduxjs/toolkit"; import { createSelector } from "@reduxjs/toolkit";
import HelpOutlineIcon from "@mui/icons-material/HelpOutline";
import ClearIcon from "@mui/icons-material/Clear";
import AddIcon from "@mui/icons-material/Add"; import AddIcon from "@mui/icons-material/Add";
import AccessTimeIcon from "@mui/icons-material/AccessTime";
import LockIcon from "@mui/icons-material/Lock";
import { userAttendee } from "../../features/User/userDataTypes";
import { TempCalendarsInput } from "./TempCalendarsInput"; import { TempCalendarsInput } from "./TempCalendarsInput";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import { import {
@@ -16,8 +16,6 @@ export default function CalendarLayout() {
const calendarRef = useRef<any>(null); const calendarRef = useRef<any>(null);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const selectedCalendars = useAppSelector((state) => state.calendars.list); const selectedCalendars = useAppSelector((state) => state.calendars.list);
const userId =
useAppSelector((state) => state.user.userData?.openpaasId) ?? "";
const [currentDate, setCurrentDate] = useState<Date>(new Date()); const [currentDate, setCurrentDate] = useState<Date>(new Date());
const [currentView, setCurrentView] = useState<string>("timeGridWeek"); const [currentView, setCurrentView] = useState<string>("timeGridWeek");
-14
View File
@@ -79,20 +79,6 @@ function CalendarPopover({
} }
}; };
const palette = [
"#D50000",
"#E67C73",
"#F4511E",
"#F6BF26",
"#33B679",
"#0B8043",
"#039BE5",
"#3F51B5",
"#7986CB",
"#8E24AA",
"#616161",
];
return ( return (
<Dialog open={open} onClose={(e, reason) => onClose(e, reason)}> <Dialog open={open} onClose={(e, reason) => onClose(e, reason)}>
<DialogTitle style={{ backgroundColor: color }}> <DialogTitle style={{ backgroundColor: color }}>
@@ -1,5 +1,4 @@
import CloseIcon from "@mui/icons-material/Close"; import CloseIcon from "@mui/icons-material/Close";
import Autocomplete from "@mui/material/Autocomplete";
import Avatar from "@mui/material/Avatar"; import Avatar from "@mui/material/Avatar";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
@@ -7,17 +6,11 @@ import Card from "@mui/material/Card";
import CardActions from "@mui/material/CardActions"; import CardActions from "@mui/material/CardActions";
import CardContent from "@mui/material/CardContent"; import CardContent from "@mui/material/CardContent";
import CardHeader from "@mui/material/CardHeader"; import CardHeader from "@mui/material/CardHeader";
import CircularProgress from "@mui/material/CircularProgress";
import IconButton from "@mui/material/IconButton"; import IconButton from "@mui/material/IconButton";
import ListItem from "@mui/material/ListItem";
import ListItemAvatar from "@mui/material/ListItemAvatar";
import ListItemText from "@mui/material/ListItemText";
import Modal from "@mui/material/Modal"; import Modal from "@mui/material/Modal";
import TextField from "@mui/material/TextField";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import { useState } from "react"; import { useState } from "react";
import { useAppDispatch, useAppSelector } from "../../app/hooks"; import { useAppDispatch, useAppSelector } from "../../app/hooks";
import { searchUsers } from "../../features/User/userAPI";
import { getCalendars } from "../../features/Calendars/CalendarApi"; import { getCalendars } from "../../features/Calendars/CalendarApi";
import { addSharedCalendarAsync } from "../../features/Calendars/CalendarSlice"; import { addSharedCalendarAsync } from "../../features/Calendars/CalendarSlice";
import { ColorPicker } from "./CalendarColorPicker"; import { ColorPicker } from "./CalendarColorPicker";
@@ -8,7 +8,6 @@ import { useState } from "react";
import CalendarPopover from "./CalendarModal"; import CalendarPopover from "./CalendarModal";
import { Calendars } from "../../features/Calendars/CalendarTypes"; import { Calendars } from "../../features/Calendars/CalendarTypes";
import MoreVertIcon from "@mui/icons-material/MoreVert"; import MoreVertIcon from "@mui/icons-material/MoreVert";
import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
import IconButton from "@mui/material/IconButton"; import IconButton from "@mui/material/IconButton";
import Checkbox from "@mui/material/Checkbox"; import Checkbox from "@mui/material/Checkbox";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
@@ -1,4 +1,4 @@
import { useState, useRef, useMemo } from "react"; import { useState, useRef } from "react";
import { useAppDispatch, useAppSelector } from "../../app/hooks"; import { useAppDispatch, useAppSelector } from "../../app/hooks";
import { import {
getTempCalendarsListAsync, getTempCalendarsListAsync,
@@ -1,5 +1,4 @@
import { useCallback } from "react"; import { useCallback } from "react";
import { CalendarApi } from "@fullcalendar/core";
import { import {
createEventHandlers, createEventHandlers,
EventHandlersProps, EventHandlersProps,
@@ -1,5 +1,4 @@
import { useCallback } from "react"; import { useCallback } from "react";
import { CalendarApi } from "@fullcalendar/core";
import { import {
createViewHandlers, createViewHandlers,
ViewHandlersProps, ViewHandlersProps,
-4
View File
@@ -98,10 +98,6 @@ export function Menubar({
} }
}; };
const handleRefresh = () => {
onRefresh();
};
const open = Boolean(anchorEl); const open = Boolean(anchorEl);
return ( return (
<> <>
+7 -10
View File
@@ -121,10 +121,7 @@ export const putEventAsync = createAsyncThunk<
{ calId: string; events: CalendarEvent[]; calType?: "temp" }, // Return type { calId: string; events: CalendarEvent[]; calType?: "temp" }, // Return type
{ cal: Calendars; newEvent: CalendarEvent; calType?: "temp" } // Arg type { cal: Calendars; newEvent: CalendarEvent; calType?: "temp" } // Arg type
>("calendars/putEvent", async ({ cal, newEvent, calType }) => { >("calendars/putEvent", async ({ cal, newEvent, calType }) => {
const response = await putEvent( await putEvent(newEvent, cal.ownerEmails ? cal.ownerEmails[0] : undefined);
newEvent,
cal.ownerEmails ? cal.ownerEmails[0] : undefined
);
const eventDate = new Date(newEvent.start); const eventDate = new Date(newEvent.start);
const weekStart = new Date(eventDate); const weekStart = new Date(eventDate);
@@ -184,7 +181,7 @@ export const patchCalendarAsync = createAsyncThunk<
patch: { name: string; desc: string; color: string }; patch: { name: string; desc: string; color: string };
} // Arg type } // Arg type
>("calendars/patchCalendar", async ({ calId, calLink, patch }) => { >("calendars/patchCalendar", async ({ calId, calLink, patch }) => {
const response = await proppatchCalendar(calLink, patch); await proppatchCalendar(calLink, patch);
return { return {
calId, calId,
calLink, calLink,
@@ -201,7 +198,7 @@ export const removeCalendarAsync = createAsyncThunk<
calLink: string; calLink: string;
} }
>("calendars/removeCalendar", async ({ calId, calLink }) => { >("calendars/removeCalendar", async ({ calId, calLink }) => {
const response = await removeCalendar(calLink); await removeCalendar(calLink);
return { return {
calId, calId,
calLink, calLink,
@@ -212,7 +209,7 @@ export const moveEventAsync = createAsyncThunk<
{ calId: string; events: CalendarEvent[] }, // Return type { calId: string; events: CalendarEvent[] }, // Return type
{ cal: Calendars; newEvent: CalendarEvent; newURL: string } // Arg type { cal: Calendars; newEvent: CalendarEvent; newURL: string } // Arg type
>("calendars/moveEvent", async ({ cal, newEvent, newURL }) => { >("calendars/moveEvent", async ({ cal, newEvent, newURL }) => {
const response = await moveEvent(newEvent, newURL); await moveEvent(newEvent, newURL);
const calEvents = (await getCalendar(cal.id, { const calEvents = (await getCalendar(cal.id, {
start: formatDateToYYYYMMDDTHHMMSS(new Date(newEvent.start)), start: formatDateToYYYYMMDDTHHMMSS(new Date(newEvent.start)),
end: formatDateToYYYYMMDDTHHMMSS( end: formatDateToYYYYMMDDTHHMMSS(
@@ -239,7 +236,7 @@ export const deleteEventAsync = createAsyncThunk<
{ calId: string; eventId: string }, // Return type { calId: string; eventId: string }, // Return type
{ calId: string; eventId: string; eventURL: string } // Arg type { calId: string; eventId: string; eventURL: string } // Arg type
>("calendars/delEvent", async ({ calId, eventId, eventURL }) => { >("calendars/delEvent", async ({ calId, eventId, eventURL }) => {
const response = await deleteEvent(eventURL); await deleteEvent(eventURL);
return { calId, eventId }; return { calId, eventId };
}); });
@@ -255,7 +252,7 @@ export const createCalendarAsync = createAsyncThunk<
}, // Return type }, // Return type
{ userId: string; calId: string; color: string; name: string; desc: string } // Arg type { userId: string; calId: string; color: string; name: string; desc: string } // Arg type
>("calendars/createCalendar", async ({ userId, calId, color, name, desc }) => { >("calendars/createCalendar", async ({ userId, calId, color, name, desc }) => {
const response = await postCalendar(userId, calId, color, name, desc); await postCalendar(userId, calId, color, name, desc);
const ownerData: any = await getUserDetails(userId.split("/")[0]); const ownerData: any = await getUserDetails(userId.split("/")[0]);
return { return {
@@ -283,7 +280,7 @@ export const addSharedCalendarAsync = createAsyncThunk<
}, // Return type }, // Return type
{ userId: string; calId: string; cal: Record<string, any> } // Arg type { userId: string; calId: string; cal: Record<string, any> } // Arg type
>("calendars/addSharedCalendar", async ({ userId, calId, cal }) => { >("calendars/addSharedCalendar", async ({ userId, calId, cal }) => {
const response = await addSharedCalendar(userId, calId, cal); await addSharedCalendar(userId, calId, cal);
const ownerData: any = await getUserDetails( const ownerData: any = await getUserDetails(
cal.cal._links.self.href cal.cal._links.self.href
.replace("/calendars/", "") .replace("/calendars/", "")
+1 -4
View File
@@ -1,7 +1,6 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { import {
deleteEventAsync, deleteEventAsync,
getEventAsync,
moveEventAsync, moveEventAsync,
putEventAsync, putEventAsync,
removeEvent, removeEvent,
@@ -9,7 +8,6 @@ import {
import { useAppDispatch, useAppSelector } from "../../app/hooks"; import { useAppDispatch, useAppSelector } from "../../app/hooks";
import AttendeeSelector from "../../components/Attendees/AttendeeSearch"; import AttendeeSelector from "../../components/Attendees/AttendeeSearch";
import { import {
Popover,
Button, Button,
Box, Box,
Typography, Typography,
@@ -41,7 +39,6 @@ import CircleIcon from "@mui/icons-material/Circle";
import CancelIcon from "@mui/icons-material/Cancel"; import CancelIcon from "@mui/icons-material/Cancel";
import CheckCircleIcon from "@mui/icons-material/CheckCircle"; import CheckCircleIcon from "@mui/icons-material/CheckCircle";
import { userAttendee } from "../User/userDataTypes"; import { userAttendee } from "../User/userDataTypes";
import { TIMEZONES } from "../../utils/timezone-data";
import { Calendars } from "../Calendars/CalendarTypes"; import { Calendars } from "../Calendars/CalendarTypes";
import { CalendarEvent, RepetitionObject } from "./EventsTypes"; import { CalendarEvent, RepetitionObject } from "./EventsTypes";
import { isValidUrl } from "../../utils/apiUtils"; import { isValidUrl } from "../../utils/apiUtils";
@@ -94,7 +91,7 @@ export default function EventDisplayModal({
const [alarm, setAlarm] = useState(event?.alarm?.trigger ?? ""); const [alarm, setAlarm] = useState(event?.alarm?.trigger ?? "");
const [busy, setBusy] = useState(event?.transp ?? "OPAQUE"); const [busy, setBusy] = useState(event?.transp ?? "OPAQUE");
const [eventClass, setEventClass] = useState(event?.class ?? "PUBLIC"); const [eventClass, setEventClass] = useState(event?.class ?? "PUBLIC");
const [timezone, setTimezone] = useState(event?.timezone ?? "UTC"); const [timezone] = useState(event?.timezone ?? "UTC");
const [newCalId, setNewCalId] = useState(event?.calId); const [newCalId, setNewCalId] = useState(event?.calId);
const [calendarid, setCalendarid] = useState( const [calendarid, setCalendarid] = useState(
calId.split("/")[0] === user.userData?.openpaasId calId.split("/")[0] === user.userData?.openpaasId
+1 -8
View File
@@ -15,8 +15,6 @@ import {
CardContent, CardContent,
Divider, Divider,
IconButton, IconButton,
Avatar,
Badge,
PopoverPosition, PopoverPosition,
} from "@mui/material"; } from "@mui/material";
import EditIcon from "@mui/icons-material/Edit"; import EditIcon from "@mui/icons-material/Edit";
@@ -29,18 +27,13 @@ import LocationOnIcon from "@mui/icons-material/LocationOn";
import VideocamIcon from "@mui/icons-material/Videocam"; import VideocamIcon from "@mui/icons-material/Videocam";
import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline"; import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline";
import CircleIcon from "@mui/icons-material/Circle"; import CircleIcon from "@mui/icons-material/Circle";
import CancelIcon from "@mui/icons-material/Cancel";
import FileDownloadOutlinedIcon from "@mui/icons-material/FileDownloadOutlined"; import FileDownloadOutlinedIcon from "@mui/icons-material/FileDownloadOutlined";
import { userAttendee } from "../User/userDataTypes";
import EventDisplayModal, { import EventDisplayModal, {
InfoRow, InfoRow,
renderAttendeeBadge, renderAttendeeBadge,
stringAvatar,
} from "./EventDisplay"; } from "./EventDisplay";
import { dlEvent, getEvent } from "./EventApi"; import { dlEvent } from "./EventApi";
import { CalendarEvent } from "./EventsTypes";
import EventDuplication from "../../components/Event/EventDuplicate"; import EventDuplication from "../../components/Event/EventDuplicate";
import { getCalendar } from "../Calendars/CalendarApi";
export default function EventPreviewModal({ export default function EventPreviewModal({
eventId, eventId,
+12 -3
View File
@@ -610,9 +610,18 @@ function EventPopover({
{hasVideoConference && meetingLink && ( {hasVideoConference && meetingLink && (
<> <>
<Typography sx={{ color: "text.secondary", mr: 1 }}> <Button
Meeting link generated startIcon={<VideocamIcon />}
</Typography> onClick={() => window.open(meetingLink, "_blank")}
size="medium"
variant="contained"
sx={{
textTransform: "none",
mr: 1,
}}
>
Join Visio conference
</Button>
<IconButton <IconButton
onClick={handleCopyMeetingLink} onClick={handleCopyMeetingLink}
size="small" size="small"
+1 -1
View File
@@ -1,6 +1,6 @@
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import { Callback } from "./oidcAuth"; import { Callback } from "./oidcAuth";
import { useAppDispatch, useAppSelector } from "../../app/hooks"; import { useAppDispatch } from "../../app/hooks";
import { push } from "redux-first-history"; import { push } from "redux-first-history";
import { getOpenPaasUserDataAsync, setTokens, setUserData } from "./userSlice"; import { getOpenPaasUserDataAsync, setTokens, setUserData } from "./userSlice";
import { Loading } from "../../components/Loading/Loading"; import { Loading } from "../../components/Loading/Loading";
+1 -2
View File
@@ -1,7 +1,6 @@
import "@testing-library/jest-dom"; import "@testing-library/jest-dom";
import { TextEncoder, TextDecoder } from "util"; import { TextEncoder } from "util";
import { clientConfig } from "./features/User/oidcAuth";
global.TextEncoder = TextEncoder; global.TextEncoder = TextEncoder;