diff --git a/__test__/features/Events/EventDisplay.test.tsx b/__test__/features/Events/EventDisplay.test.tsx index 4e7ea8f..5127fec 100644 --- a/__test__/features/Events/EventDisplay.test.tsx +++ b/__test__/features/Events/EventDisplay.test.tsx @@ -82,6 +82,13 @@ describe("Event Preview Display", () => { end: day.toISOString(), organizer: { cn: "test", cal_address: "test@test.com" }, }, + event3: { + uid: "event3", + calId: "667037022b752d0026472254/cal1", + start: day.toISOString(), + end: day.toISOString(), + organizer: { cn: "test", cal_address: "test@test.com" }, + }, }, ownerEmails: ["test@test.com"], }, @@ -797,7 +804,21 @@ describe("Event Preview Display", () => { }).not.toThrow(); }); }); + it("renders correctly event data event with empty title", () => { + renderWithProviders( + , + preloadedState + ); + expect(screen.getByText("event.untitled")).toBeInTheDocument(); + + expect(screen.getByText("Calendar")).toBeInTheDocument(); + }); describe("BUGFIX", () => { it("doesnt render anything next to date of all day preview", () => { const allDayState = { diff --git a/__test__/features/Events/EventModal.test.tsx b/__test__/features/Events/EventModal.test.tsx index 1fa1822..57d518e 100644 --- a/__test__/features/Events/EventModal.test.tsx +++ b/__test__/features/Events/EventModal.test.tsx @@ -1,11 +1,9 @@ import { DateSelectArg } from "@fullcalendar/core"; import { act, fireEvent, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; -import preview from "jest-preview"; import * as eventThunks from "../../../src/features/Calendars/CalendarSlice"; import EventPopover from "../../../src/features/Events/EventModal"; import { api } from "../../../src/utils/apiUtils"; -import { formatDateToYYYYMMDDTHHMMSS } from "../../../src/utils/dateUtils"; import { renderWithProviders } from "../../utils/Renderwithproviders"; jest.mock("../../../src/utils/apiUtils"); diff --git a/src/components/Calendar/utils/calendarUtils.ts b/src/components/Calendar/utils/calendarUtils.ts index e0d1ddc..2147daf 100644 --- a/src/components/Calendar/utils/calendarUtils.ts +++ b/src/components/Calendar/utils/calendarUtils.ts @@ -6,6 +6,7 @@ import { SlotLabelContentArg } from "@fullcalendar/core"; import moment from "moment-timezone"; import { refreshSingularCalendar } from "../../Event/utils/eventUtils"; import { ThunkDispatch } from "@reduxjs/toolkit"; +import { useI18n } from "cozy-ui/transpiled/react/providers/I18n"; export const updateSlotLabelVisibility = ( currentTime: Date, @@ -58,13 +59,24 @@ export const eventToFullCalendarFormat = ( filteredTempEvents: CalendarEvent[], userId: string | undefined ) => { + const { t } = useI18n(); return filteredEvents .concat(filteredTempEvents.map((e) => ({ ...e, temp: true }))) .map((e) => { if (e.calId.split("/")[0] === userId) { - return { ...e, colors: e.color, editable: true }; + return { + ...e, + title: e.title ? e.title : t("event.untitled"), + colors: e.color, + editable: true, + }; } - return { ...e, colors: e.color, editable: false }; + return { + ...e, + title: e.title ? e.title : t("event.untitled"), + colors: e.color, + editable: false, + }; }); }; diff --git a/src/components/Event/EventFormFields.tsx b/src/components/Event/EventFormFields.tsx index 814703a..4d561d7 100644 --- a/src/components/Event/EventFormFields.tsx +++ b/src/components/Event/EventFormFields.tsx @@ -323,7 +323,6 @@ export default function EventFormFields({ // Validation logic const validateForm = React.useCallback(() => { return validateEventForm({ - title, startDate, startTime, endDate, @@ -410,14 +409,7 @@ export default function EventFormFields({ return ( <> - - {t("event.form.title")} * - - } - isExpanded={showMore} - > + { setTitle(e.target.value); }} - error={!!validation.errors.title} - helperText={validation.errors.title} size="small" margin="dense" inputRef={titleInputRef} diff --git a/src/components/Event/utils/formValidation.ts b/src/components/Event/utils/formValidation.ts index 45db7b6..1f3f9cf 100644 --- a/src/components/Event/utils/formValidation.ts +++ b/src/components/Event/utils/formValidation.ts @@ -4,7 +4,6 @@ import { combineDateTime } from "./dateTimeHelpers"; * Validation parameters for event form */ export interface ValidationParams { - title: string; startDate: string; startTime: string; endDate: string; @@ -21,7 +20,6 @@ export interface ValidationParams { export interface ValidationResult { isValid: boolean; errors: { - title: string; dateTime: string; }; } @@ -33,7 +31,6 @@ export interface ValidationResult { */ export function validateEventForm(params: ValidationParams): ValidationResult { const { - title, startDate, startTime, endDate, @@ -44,9 +41,6 @@ export function validateEventForm(params: ValidationParams): ValidationResult { showMore = false, } = params; - const isTitleValid = title.trim().length > 0; - const shouldShowTitleError = showValidationErrors && !isTitleValid; - let isDateTimeValid = true; let dateTimeError = ""; @@ -154,12 +148,11 @@ export function validateEventForm(params: ValidationParams): ValidationResult { } } - const isValid = isTitleValid && isDateTimeValid; + const isValid = isDateTimeValid; return { isValid, errors: { - title: shouldShowTitleError ? "Title is required" : "", dateTime: showValidationErrors ? dateTimeError : "", }, }; diff --git a/src/components/Menubar/Menubar.tsx b/src/components/Menubar/Menubar.tsx index 8708451..5c87feb 100644 --- a/src/components/Menubar/Menubar.tsx +++ b/src/components/Menubar/Menubar.tsx @@ -254,6 +254,7 @@ export function Menubar({ value={lang} onChange={(e) => { dispatch(setLanguage(e.target.value)); + handleLangClose(); }} variant="outlined" aria-label={t("menubar.languageSelector")} diff --git a/src/features/Events/EventDisplayPreview.tsx b/src/features/Events/EventDisplayPreview.tsx index b9d77de..1091738 100644 --- a/src/features/Events/EventDisplayPreview.tsx +++ b/src/features/Events/EventDisplayPreview.tsx @@ -141,175 +141,169 @@ export default function EventPreviewModal({ actionsJustifyContent="center" style={{ overflow: "auto" }} title={ - event.title && ( - <> - - - {(window as any).DEBUG && ( - { - const icsContent = await dlEvent(event); - const blob = new Blob([icsContent], { - type: "text/calendar", - }); - const url = URL.createObjectURL(blob); + <> + + + {(window as any).DEBUG && ( + { + const icsContent = await dlEvent(event); + const blob = new Blob([icsContent], { + type: "text/calendar", + }); + const url = URL.createObjectURL(blob); - const link = document.createElement("a"); - link.href = url; - link.download = `${eventId}.ics`; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - URL.revokeObjectURL(url); - }} - > - - - )} - {user.userData.email === event.organizer?.cal_address && - calendar.ownerEmails?.includes(user.userData.email) && ( - { - if (isRecurring) { - setAfterChoiceFunc(() => () => { - setHidePreview(true); - setOpenUpdateModal(true); - }); - setOpenEditModePopup("edit"); - } else { - setHidePreview(true); - setOpenUpdateModal(true); - } - }} - > - - - )} - {((event.class !== "PRIVATE" && !isOwn) || isOwn) && ( + const link = document.createElement("a"); + link.href = url; + link.download = `${eventId}.ics`; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); + }} + > + + + )} + {user.userData.email === event.organizer?.cal_address && + calendar.ownerEmails?.includes(user.userData.email) && ( setToggleActionMenu(e.currentTarget)} + onClick={() => { + if (isRecurring) { + setAfterChoiceFunc(() => () => { + setHidePreview(true); + setOpenUpdateModal(true); + }); + setOpenEditModePopup("edit"); + } else { + setHidePreview(true); + setOpenUpdateModal(true); + } + }} > - + )} - setToggleActionMenu(null)} - anchorEl={toggleActionMenu} - > - {mailSpaUrl && attendees.length > 0 && ( - - window.open( - `${mailSpaUrl}/mailto/?uri=mailto:${event.attendee - .map((a) => a.cal_address) - .filter((mail) => mail !== user.userData.email) - .join(",")}?subject=${event.title}` - ) - } - > - {t("eventPreview.emailAttendees")} - - )} - setToggleActionMenu(null)} - onOpenDuplicate={() => { - setToggleActionMenu(null); - setHidePreview(true); - setOpenDuplicateModal(true); - }} - /> - {user.userData.email === event.organizer?.cal_address && ( - { - if (isRecurring) { - setAfterChoiceFunc( - () => - (typeOfAction?: "solo" | "all" | undefined) => - handleDelete( - isRecurring, - typeOfAction, - onClose, - dispatch, - calendar, - event, - calId, - eventId - ) - ); - setOpenEditModePopup("edit"); - } else { - onClose({}, "backdropClick"); - await dispatch( - deleteEventAsync({ - calId, - eventId, - eventURL: event.URL, - }) - ); - } - updateTempList(); - }} - > - {t("eventPreview.deleteEvent")} - - )} - + {((event.class !== "PRIVATE" && !isOwn) || isOwn) && ( onClose({}, "backdropClick")} + onClick={(e) => setToggleActionMenu(e.currentTarget)} > - + - - - - {event.class === "PRIVATE" && - (isOwn ? ( - - - - ) : ( - - ))} - setToggleActionMenu(null)} + anchorEl={toggleActionMenu} > - {event.title} - - {event.transp === "TRANSPARENT" && ( + {mailSpaUrl && attendees.length > 0 && ( + + window.open( + `${mailSpaUrl}/mailto/?uri=mailto:${event.attendee + .map((a) => a.cal_address) + .filter((mail) => mail !== user.userData.email) + .join(",")}?subject=${event.title}` + ) + } + > + {t("eventPreview.emailAttendees")} + + )} + setToggleActionMenu(null)} + onOpenDuplicate={() => { + setToggleActionMenu(null); + setHidePreview(true); + setOpenDuplicateModal(true); + }} + /> + {user.userData.email === event.organizer?.cal_address && ( + { + if (isRecurring) { + setAfterChoiceFunc( + () => (typeOfAction?: "solo" | "all" | undefined) => + handleDelete( + isRecurring, + typeOfAction, + onClose, + dispatch, + calendar, + event, + calId, + eventId + ) + ); + setOpenEditModePopup("edit"); + } else { + onClose({}, "backdropClick"); + await dispatch( + deleteEventAsync({ + calId, + eventId, + eventURL: event.URL, + }) + ); + } + updateTempList(); + }} + > + {t("eventPreview.deleteEvent")} + + )} + + onClose({}, "backdropClick")} + > + + + + + + {event.class === "PRIVATE" && + (isOwn ? ( - } - label={t("eventPreview.free.label")} - /> + - )} - - - {formatDate(event.start, t, event.allday)} - {event.end && - formatEnd(event.start, event.end, t, event.allday) && - ` – ${formatEnd(event.start, event.end, t, event.allday)} ${!event.allday ? getTimezoneOffset(timezone) : ""}`} + ) : ( + + ))} + + {event.title ? event.title : t("event.untitled")} - - ) + {event.transp === "TRANSPARENT" && ( + + } + label={t("eventPreview.free.label")} + /> + + )} + + + {formatDate(event.start, t, event.allday)} + {event.end && + formatEnd(event.start, event.end, t, event.allday) && + ` – ${formatEnd(event.start, event.end, t, event.allday)} ${!event.allday ? getTimezoneOffset(timezone) : ""}`} + + } actions={ currentUserAttendee && diff --git a/src/features/Events/EventModal.tsx b/src/features/Events/EventModal.tsx index 91f441e..0eaf6f1 100644 --- a/src/features/Events/EventModal.tsx +++ b/src/features/Events/EventModal.tsx @@ -33,7 +33,6 @@ import { addDays } from "../../components/Event/utils/dateRules"; import { useI18n } from "cozy-ui/transpiled/react/providers/I18n"; function EventPopover({ - anchorEl, open, onClose, selectedRange, @@ -50,6 +49,7 @@ function EventPopover({ event?: CalendarEvent; }) { const dispatch = useAppDispatch(); + const { t, lang } = useI18n(); const organizer = useAppSelector((state) => state.user.organiserData); const userId = @@ -673,8 +673,6 @@ function EventPopover({ await updateTempCalendar(tempList, newEvent, dispatch, calendarRange); } }; - const { t } = useI18n(); - const dialogActions = ( {!showMore && ( diff --git a/src/locales/en.json b/src/locales/en.json index 6423faa..740c4c5 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -65,6 +65,7 @@ "createEvent": "Create Event", "updateEvent": "Update Event", "organizer": "Organizer", + "untitled": "Untitled", "repeat": { "repeatEvery": "Repeat every", "frequency": { diff --git a/src/locales/fr.json b/src/locales/fr.json index 1770bad..9cca5c5 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -65,6 +65,7 @@ "createEvent": "Créer un événement", "updateEvent": "Mettre à jour un événement", "organizer": "Organisateur", + "untitled": "Sans titre", "repeat": { "repeatEvery": "Répéter tous les", "frequency": {