feat: Update Event Preview Modal UI with custom MUI theme (#240)

- Add custom MUI theme
- Update ResponsiveDialog with actionsBorderTop prop for modal footer border
- Redesign EventDisplayPreview modal with improved typography and spacing
- Replace ButtonGroup with individual RSVP buttons (Accept/Maybe/Decline)
- Add pill-shaped buttons for RSVP actions
- Ensure backward compatibility with existing modals
This commit is contained in:
lenhanphung
2025-10-27 12:23:42 +07:00
committed by GitHub
parent 51c9d68be2
commit 4774e78543
12 changed files with 358 additions and 118 deletions
+119 -31
View File
@@ -12,13 +12,13 @@ import SubjectIcon from "@mui/icons-material/Subject";
import VideocamIcon from "@mui/icons-material/Videocam";
import RepeatIcon from "@mui/icons-material/Repeat";
import LockOutlineIcon from "@mui/icons-material/LockOutline";
import EventPopover from "./EventModal";
import { DateSelectArg } from "@fullcalendar/core";
import {
Box,
Button,
ButtonGroup,
Chip,
DialogActions,
Divider,
IconButton,
Menu,
MenuItem,
@@ -77,6 +77,7 @@ export default function EventPreviewModal({
const [showAllAttendees, setShowAllAttendees] = useState(false);
const [openFullDisplay, setOpenFullDisplay] = useState(false);
const [openUpdateModal, setOpenUpdateModal] = useState(false);
const [openDuplicateModal, setOpenDuplicateModal] = useState(false);
const [hidePreview, setHidePreview] = useState(false);
const [openEditModePopup, setOpenEditModePopup] = useState<string | null>(
null
@@ -136,6 +137,8 @@ export default function EventPreviewModal({
open={open && !hidePreview}
onClose={() => onClose({}, "backdropClick")}
showHeaderActions={false}
actionsBorderTop={true}
actionsJustifyContent="center"
style={{ overflow: "auto" }}
title={
event.title && (
@@ -210,7 +213,15 @@ export default function EventPreviewModal({
Email attendees
</MenuItem>
)}
<EventDuplication event={event} onClose={onClose} />
<EventDuplication
event={event}
onClose={() => setToggleActionMenu(null)}
onOpenDuplicate={() => {
setToggleActionMenu(null);
setHidePreview(true);
setOpenDuplicateModal(true);
}}
/>
{user.userData.email === event.organizer?.cal_address && (
<MenuItem
onClick={async () => {
@@ -270,10 +281,12 @@ export default function EventPreviewModal({
<LockOutlineIcon />
))}
<Typography
variant="inherit"
fontWeight="bold"
style={{
variant="h5"
sx={{
fontSize: "24px",
fontWeight: 600,
wordBreak: "break-word",
fontFamily: "Inter, sans-serif",
}}
gutterBottom
>
@@ -293,7 +306,7 @@ export default function EventPreviewModal({
</Tooltip>
)}
</Box>
<Typography variant="body2" color="textSecondary" gutterBottom>
<Typography color="text.secondaryContainer" gutterBottom>
{formatDate(event.start, event.allday)}
{event.end &&
formatEnd(event.start, event.end, event.allday) &&
@@ -305,15 +318,22 @@ export default function EventPreviewModal({
actions={
<>
{currentUserAttendee && (
<Box style={{ display: "flex", flexDirection: "row" }}>
<Typography variant="body2">Attending?</Typography>
<ButtonGroup size="small" fullWidth>
<>
<Typography sx={{ marginRight: 2 }}>Attending?</Typography>
<Box display="flex" gap="15px" alignItems="center">
<Button
variant={
currentUserAttendee?.partstat === "ACCEPTED"
? "contained"
: "outlined"
}
color={
currentUserAttendee?.partstat === "ACCEPTED"
? "success"
: "primary"
}
size="large"
sx={{ borderRadius: "50px" }}
onClick={() => {
if (isRecurring) {
setAfterChoiceFunc(
@@ -346,11 +366,18 @@ export default function EventPreviewModal({
Accept
</Button>
<Button
variant={
currentUserAttendee?.partstat === "TENTATIVE"
? "contained"
: "outlined"
}
color={
currentUserAttendee?.partstat === "TENTATIVE"
? "warning"
: "primary"
}
size="large"
sx={{ borderRadius: "50px" }}
onClick={() => {
if (isRecurring) {
setAfterChoiceFunc(
@@ -382,11 +409,18 @@ export default function EventPreviewModal({
Maybe
</Button>
<Button
variant={
currentUserAttendee?.partstat === "DECLINED"
? "contained"
: "outlined"
}
color={
currentUserAttendee?.partstat === "DECLINED"
? "error"
: "primary"
}
size="large"
sx={{ borderRadius: "50px" }}
onClick={() => {
if (isRecurring) {
setAfterChoiceFunc(
@@ -417,8 +451,8 @@ export default function EventPreviewModal({
>
Decline
</Button>
</ButtonGroup>
</Box>
</Box>
</>
)}
</>
}
@@ -428,7 +462,11 @@ export default function EventPreviewModal({
{/* Video */}
{event.x_openpass_videoconference && (
<InfoRow
icon={<VideocamIcon style={{ fontSize: 18 }} />}
icon={
<Box sx={{ minWidth: "25px", marginRight: 2 }}>
<VideocamIcon />
</Box>
}
content={
<Button
variant="contained"
@@ -445,7 +483,11 @@ export default function EventPreviewModal({
{attendees?.length > 0 && (
<>
<InfoRow
icon={<PeopleAltOutlinedIcon />}
icon={
<Box sx={{ minWidth: "25px", marginRight: 2 }}>
<PeopleAltOutlinedIcon />
</Box>
}
content={
<Box
style={{
@@ -454,9 +496,11 @@ export default function EventPreviewModal({
flexDirection: "row",
}}
>
<Box>
<Box sx={{ marginRight: 2 }}>
<Typography>{attendees.length} guests</Typography>
<Typography>
<Typography
sx={{ fontSize: "13px", color: "text.secondary" }}
>
{
attendees.filter((a) => a.partstat === "ACCEPTED")
.length
@@ -488,9 +532,13 @@ export default function EventPreviewModal({
</AvatarGroup>
)}
<Typography
variant="body2"
color="primary"
style={{ cursor: "pointer", marginTop: 0.5 }}
sx={{
cursor: "pointer",
marginLeft: 2,
fontSize: "14px",
color: "text.secondary",
alignSelf: "center",
}}
onClick={() => setShowAllAttendees(!showAllAttendees)}
>
{showAllAttendees ? "Show less" : `Show more `}
@@ -510,25 +558,44 @@ export default function EventPreviewModal({
{/* Location */}
{event.location && (
<InfoRow
icon={<LocationOnOutlinedIcon />}
icon={
<Box sx={{ minWidth: "25px", marginRight: 2 }}>
<LocationOnOutlinedIcon />
</Box>
}
text={event.location}
/>
)}
{/* Description */}
{event.description && (
<InfoRow icon={<SubjectIcon />} text={event.description} />
<InfoRow
icon={
<Box sx={{ minWidth: "25px", marginRight: 2 }}>
<SubjectIcon />
</Box>
}
text={event.description}
/>
)}
{/* ALARM */}
{event.alarm && (
<InfoRow
icon={<NotificationsNoneIcon />}
icon={
<Box sx={{ minWidth: "25px", marginRight: 2 }}>
<NotificationsNoneIcon />
</Box>
}
text={`${event.alarm.trigger} before by ${event.alarm.action}`}
/>
)}
{/* Repetition */}
{event.repetition && (
<InfoRow
icon={<RepeatIcon />}
icon={
<Box sx={{ minWidth: "25px", marginRight: 2 }}>
<RepeatIcon />
</Box>
}
text={makeRecurrenceString(event)}
/>
)}
@@ -560,7 +627,11 @@ export default function EventPreviewModal({
{/* Error */}
{event.error && (
<InfoRow
icon={<ErrorOutlineIcon color="error" style={{ fontSize: 18 }} />}
icon={
<Box sx={{ minWidth: "25px", marginRight: 2 }}>
<ErrorOutlineIcon color="error" />
</Box>
}
text={event.error}
error
/>
@@ -574,7 +645,9 @@ export default function EventPreviewModal({
marginBottom: 2,
}}
>
<CalendarTodayIcon style={{ fontSize: 16 }} />
<Box sx={{ minWidth: "25px", marginRight: 2 }}>
<CalendarTodayIcon />
</Box>
<CircleIcon
style={{
color: calendar.color?.light ?? "#3788D8",
@@ -582,13 +655,8 @@ export default function EventPreviewModal({
height: 12,
}}
/>
<Typography variant="body2">{calendar.name}</Typography>
<Typography>{calendar.name}</Typography>
</Box>
{currentUserAttendee && !event.repetition && (
<>
<Divider variant="fullWidth" />
</>
)}
</ResponsiveDialog>
<EditModeDialog
type={openEditModePopup}
@@ -620,6 +688,26 @@ export default function EventPreviewModal({
calId={calId}
typeOfAction={typeOfAction}
/>
<EventPopover
anchorEl={null}
open={openDuplicateModal}
selectedRange={
{
start: new Date(event.start),
startStr: event.start,
end: new Date(event.end ?? ""),
endStr: event.end ?? "",
allDay: event.allday ?? false,
} as DateSelectArg
}
setSelectedRange={() => {}}
calendarRef={{ current: null }}
onClose={() => {
setOpenDuplicateModal(false);
onClose({}, "backdropClick"); // Đóng cả preview modal
}}
event={event}
/>
</>
);
}
+3 -3
View File
@@ -104,7 +104,7 @@ function EventPopover({
);
const [attendees, setAttendees] = useState<userAttendee[]>(
event?.attendee
? event.attendee.filter((a) => a.cal_address !== organizer.cal_address)
? event.attendee.filter((a) => a.cal_address !== organizer?.cal_address)
: []
);
const [alarm, setAlarm] = useState(event?.alarm?.trigger ?? "");
@@ -482,8 +482,8 @@ function EventPopover({
timezone,
attendee: [
{
cn: organizer.cn,
cal_address: organizer.cal_address,
cn: organizer?.cn ?? "",
cal_address: organizer?.cal_address ?? "",
partstat: "ACCEPTED",
rsvp: "FALSE",
role: "CHAIR",