From f8a5095f140b56ba4cc67f8b28721918398f60e4 Mon Sep 17 00:00:00 2001 From: rahulramesha Date: Tue, 19 Mar 2024 19:57:32 +0530 Subject: [PATCH] fix date related exceptions --- .../src/ui/components/editor-header.tsx | 3 +- .../src/ui/components/info-popover.tsx | 8 +++-- .../document-editor/src/utils/date-utils.ts | 22 +++++++++++++ packages/types/src/pages.d.ts | 4 +-- .../notifications/notification-card.tsx | 6 ++-- .../select-snooze-till-modal.tsx | 20 +++++++----- web/helpers/date-time.helper.ts | 7 +++-- .../projects/[projectId]/pages/[pageId].tsx | 10 +++--- web/store/page.store.ts | 14 ++++----- web/store/project-page.store.ts | 31 ++++++++++--------- 10 files changed, 81 insertions(+), 44 deletions(-) create mode 100644 packages/editor/document-editor/src/utils/date-utils.ts diff --git a/packages/editor/document-editor/src/ui/components/editor-header.tsx b/packages/editor/document-editor/src/ui/components/editor-header.tsx index aaa4c7be3..33ac4a0dc 100644 --- a/packages/editor/document-editor/src/ui/components/editor-header.tsx +++ b/packages/editor/document-editor/src/ui/components/editor-header.tsx @@ -7,6 +7,7 @@ import { AlertLabel } from "src/ui/components/alert-label"; import { IVerticalDropdownItemProps, VerticalDropdownMenu } from "src/ui/components/vertical-dropdown-menu"; import { SummaryPopover } from "src/ui/components/summary-popover"; import { InfoPopover } from "src/ui/components/info-popover"; +import { getDate } from "src/utils/date-utils"; interface IEditorHeader { editor: Editor; @@ -72,7 +73,7 @@ export const EditorHeader = (props: IEditorHeader) => { Icon={Archive} backgroundColor="bg-blue-500/20" textColor="text-blue-500" - label={`Archived at ${archivedAt.toLocaleString()}`} + label={`Archived at ${getDate(archivedAt)?.toLocaleString()}`} /> )} diff --git a/packages/editor/document-editor/src/ui/components/info-popover.tsx b/packages/editor/document-editor/src/ui/components/info-popover.tsx index 9a17a9376..16a3452a6 100644 --- a/packages/editor/document-editor/src/ui/components/info-popover.tsx +++ b/packages/editor/document-editor/src/ui/components/info-popover.tsx @@ -3,13 +3,15 @@ import { usePopper } from "react-popper"; import { Calendar, History, Info } from "lucide-react"; // types import { DocumentDetails } from "src/types/editor-types"; +//utils +import { getDate } from "src/utils/date-utils"; type Props = { documentDetails: DocumentDetails; }; // function to render a Date in the format- 25 May 2023 at 2:53PM -const renderDate = (date: Date): string => { +const renderDate = (date: Date | undefined): string => { const options: Intl.DateTimeFormatOptions = { day: "numeric", month: "long", @@ -52,14 +54,14 @@ export const InfoPopover: React.FC = (props) => {
Last updated on
- {renderDate(documentDetails.last_updated_at)} + {renderDate(getDate(documentDetails?.last_updated_at))}
Created on
- {renderDate(documentDetails.created_on)} + {renderDate(getDate(documentDetails?.created_on))}
diff --git a/packages/editor/document-editor/src/utils/date-utils.ts b/packages/editor/document-editor/src/utils/date-utils.ts new file mode 100644 index 000000000..5934d3709 --- /dev/null +++ b/packages/editor/document-editor/src/utils/date-utils.ts @@ -0,0 +1,22 @@ +function isNumber(value: any) { + return typeof value === "number"; +} + +/** + * This method returns a date from string of type yyyy-mm-dd + * This method is recommended to use instead of new Date() as this does not introduce any timezone offsets + * @param date + * @returns date or undefined + */ +export const getDate = (date: string | Date | undefined | null): Date | undefined => { + if (!date || date === "") return; + + if (typeof date !== "string" && !(date instanceof String)) return date; + const [yearString, monthString, dayString] = date.substring(0, 10).split("-"); + const year = parseInt(yearString); + const month = parseInt(monthString); + const day = parseInt(dayString); + if (!isNumber(year) || !isNumber(month) || !isNumber(day)) return; + + return new Date(year, month - 1, day); +}; diff --git a/packages/types/src/pages.d.ts b/packages/types/src/pages.d.ts index 29552b94c..28a1e96bc 100644 --- a/packages/types/src/pages.d.ts +++ b/packages/types/src/pages.d.ts @@ -6,7 +6,7 @@ export interface IPage { archived_at: string | null; blocks: IPageBlock[]; color: string; - created_at: Date; + created_at: string | null; created_by: string; description: string; description_html: string; @@ -20,7 +20,7 @@ export interface IPage { owned_by: string; project: string; project_detail: IProjectLite; - updated_at: Date; + updated_at: string | null; updated_by: string; workspace: string; workspace_detail: IWorkspaceLite; diff --git a/web/components/notifications/notification-card.tsx b/web/components/notifications/notification-card.tsx index c26407972..6ac8c67cd 100644 --- a/web/components/notifications/notification-card.tsx +++ b/web/components/notifications/notification-card.tsx @@ -13,7 +13,7 @@ import { ArchiveIcon, CustomMenu, Tooltip } from "@plane/ui"; import { snoozeOptions } from "constants/notification"; // helper import { replaceUnderscoreIfSnakeCase, truncateText, stripAndTruncateHTML } from "helpers/string.helper"; -import { calculateTimeAgo, renderFormattedTime, renderFormattedDate } from "helpers/date-time.helper"; +import { calculateTimeAgo, renderFormattedTime, renderFormattedDate, getDate } from "helpers/date-time.helper"; // type import type { IUserNotification, NotificationType } from "@plane/types"; // constants @@ -119,7 +119,9 @@ export const NotificationCard: React.FC = (props) => { const notificationField = notification.data.issue_activity.field; const notificationTriggeredBy = notification.triggered_by_details; - if (isSnoozedTabOpen && notification.snoozed_till! < new Date()) return null; + const snoozedTillDate = getDate(notification?.snoozed_till); + + if (snoozedTillDate && isSnoozedTabOpen && snoozedTillDate < new Date()) return null; return ( = (props) => { if (!formDataDate) return timeStamps; - const isToday = today.toDateString() === formDataDate.toDateString(); + const isToday = today.toDateString() === getDate(formDataDate)?.toDateString(); if (!isToday) return timeStamps; @@ -93,9 +95,9 @@ export const SnoozeNotificationModal: FC = (props) => { ); const minutes = parseInt(time[1]); - const dateTime = formData.date; - dateTime.setHours(hours); - dateTime.setMinutes(minutes); + const dateTime: Date | undefined = getDate(formData?.date); + dateTime?.setHours(hours); + dateTime?.setMinutes(minutes); await handleSubmitSnooze(notification.id, dateTime).then(() => { handleClose(); @@ -214,10 +216,11 @@ export const SnoozeNotificationModal: FC = (props) => { onClick={() => { setValue("period", "AM"); }} - className={`flex h-full w-1/2 cursor-pointer items-center justify-center text-center ${watch("period") === "AM" + className={`flex h-full w-1/2 cursor-pointer items-center justify-center text-center ${ + watch("period") === "AM" ? "bg-custom-primary-100/90 text-custom-primary-0" : "bg-custom-background-80" - }`} + }`} > AM @@ -225,10 +228,11 @@ export const SnoozeNotificationModal: FC = (props) => { onClick={() => { setValue("period", "PM"); }} - className={`flex h-full w-1/2 cursor-pointer items-center justify-center text-center ${watch("period") === "PM" + className={`flex h-full w-1/2 cursor-pointer items-center justify-center text-center ${ + watch("period") === "PM" ? "bg-custom-primary-100/90 text-custom-primary-0" : "bg-custom-background-80" - }`} + }`} > PM diff --git a/web/helpers/date-time.helper.ts b/web/helpers/date-time.helper.ts index 74d3b72fe..ff22fca37 100644 --- a/web/helpers/date-time.helper.ts +++ b/web/helpers/date-time.helper.ts @@ -8,7 +8,7 @@ import isNumber from "lodash/isNumber"; * @param {Date | string} date * @example renderFormattedDate("2024-01-01") // Jan 01, 2024 */ -export const renderFormattedDate = (date: string | Date | undefined): string | null => { +export const renderFormattedDate = (date: string | Date | undefined | null): string | null => { // Parse the date to check if it is valid const parsedDate = getDate(date); // return if undefined @@ -65,7 +65,10 @@ export const renderFormattedPayloadDate = (date: Date | string): string | null = * @example renderFormattedTime("2024-01-01 13:00:00") // 13:00 * @example renderFormattedTime("2024-01-01 13:00:00", "12-hour") // 01:00 PM */ -export const renderFormattedTime = (date: string | Date, timeFormat: "12-hour" | "24-hour" = "24-hour"): string => { +export const renderFormattedTime = ( + date: string | Date | undefined | null, + timeFormat: "12-hour" | "24-hour" = "24-hour" +): string => { // Parse the date to check if it is valid const parsedDate = getDate(date); // return if undefined diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx b/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx index b7041d7c6..afa37daa5 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx @@ -272,8 +272,8 @@ const PageDetailsPage: NextPageWithLayout = observer(() => { documentDetails={{ title: pageTitle, created_by: created_by, - created_on: created_at, - last_updated_at: updated_at, + created_on: getDate(created_at) ?? new Date(created_at ?? ""), + last_updated_at: getDate(updated_at) ?? new Date(created_at ?? ""), last_updated_by: updated_by, }} pageLockConfig={userCanLock && !archived_at ? { action: unlockPage, is_locked: is_locked } : undefined} @@ -283,7 +283,7 @@ const PageDetailsPage: NextPageWithLayout = observer(() => { ? { action: archived_at ? unArchivePage : archivePage, is_archived: archived_at ? true : false, - archived_at: archived_at ? getDate(archived_at) : undefined, + archived_at: getDate(archived_at), } : undefined } @@ -299,8 +299,8 @@ const PageDetailsPage: NextPageWithLayout = observer(() => { documentDetails={{ title: pageTitle, created_by: created_by, - created_on: created_at, - last_updated_at: updated_at, + created_on: getDate(created_at) ?? new Date(created_at ?? ""), + last_updated_at: getDate(updated_at) ?? new Date(created_at ?? ""), last_updated_by: updated_by, }} uploadFile={fileService.getUploadFileFunction(workspaceSlug as string)} diff --git a/web/store/page.store.ts b/web/store/page.store.ts index fa5970e49..3a95a9f69 100644 --- a/web/store/page.store.ts +++ b/web/store/page.store.ts @@ -10,7 +10,7 @@ export interface IPageStore { access: number; archived_at: string | null; color: string; - created_at: Date; + created_at: string | null; created_by: string; description: string; description_html: string; @@ -23,7 +23,7 @@ export interface IPageStore { name: string; owned_by: string; project: string; - updated_at: Date; + updated_at: string | null; updated_by: string; workspace: string; @@ -52,7 +52,7 @@ export class PageStore implements IPageStore { isSubmitting: "submitting" | "submitted" | "saved" = "saved"; archived_at: string | null; color: string; - created_at: Date; + created_at: string | null; created_by: string; description: string; description_html = ""; @@ -64,7 +64,7 @@ export class PageStore implements IPageStore { name = ""; owned_by: string; project: string; - updated_at: Date; + updated_at: string | null; updated_by: string; workspace: string; oldName = ""; @@ -94,9 +94,9 @@ export class PageStore implements IPageStore { cleanup: action, }); this.created_by = page?.created_by || ""; - this.created_at = page?.created_at || new Date(); + this.created_at = page?.created_at ?? ""; this.color = page?.color || ""; - this.archived_at = page?.archived_at || null; + this.archived_at = page?.archived_at ?? null; this.name = page?.name || ""; this.description = page?.description || ""; this.description_stripped = page?.description_stripped || ""; @@ -104,7 +104,7 @@ export class PageStore implements IPageStore { this.access = page?.access || 0; this.workspace = page?.workspace || ""; this.updated_by = page?.updated_by || ""; - this.updated_at = page?.updated_at || new Date(); + this.updated_at = page?.updated_at ?? ""; this.project = page?.project || ""; this.owned_by = page?.owned_by || ""; this.labels = page?.labels || []; diff --git a/web/store/project-page.store.ts b/web/store/project-page.store.ts index 8a739e473..34ac7ae38 100644 --- a/web/store/project-page.store.ts +++ b/web/store/project-page.store.ts @@ -8,6 +8,7 @@ import { PageStore, IPageStore } from "store/page.store"; import { IPage, IRecentPages } from "@plane/types"; import { RootStore } from "./root.store"; import { isThisWeek, isToday, isYesterday } from "date-fns"; +import { getDate } from "helpers/date-time.helper"; export interface IProjectPageStore { loader: boolean; @@ -73,8 +74,8 @@ export class ProjectPageStore implements IProjectPageStore { const allProjectIds = Object.keys(this.projectPageMap[projectId]); return allProjectIds.sort((a, b) => { - const dateA = this.projectPageMap[projectId][a].created_at.getTime(); - const dateB = this.projectPageMap[projectId][b].created_at.getTime(); + const dateA = getDate(this.projectPageMap[projectId]?.[a]?.created_at)?.getTime() ?? 0; + const dateB = getDate(this.projectPageMap[projectId]?.[b]?.created_at)?.getTime() ?? 0; return dateB - dateA; }); } @@ -84,8 +85,8 @@ export class ProjectPageStore implements IProjectPageStore { if (!projectId || !this.projectArchivedPageMap[projectId]) return []; const archivedPages = Object.keys(this.projectArchivedPageMap[projectId]); return archivedPages.sort((a, b) => { - const dateA = this.projectArchivedPageMap[projectId][a].created_at.getTime(); - const dateB = this.projectArchivedPageMap[projectId][b].created_at.getTime(); + const dateA = getDate(this.projectArchivedPageMap[projectId]?.[a]?.created_at)?.getTime() ?? 0; + const dateB = getDate(this.projectArchivedPageMap[projectId]?.[b]?.created_at)?.getTime() ?? 0; return dateB - dateA; }); } @@ -126,22 +127,24 @@ export class ProjectPageStore implements IProjectPageStore { const projectId = this.rootStore.app.router.projectId; if (!this.projectPageIds || !projectId) return; - const today: string[] = this.projectPageIds.filter((page) => - isToday(this.projectPageMap[projectId][page].updated_at) - ); + const today: string[] = this.projectPageIds.filter((page) => { + const updatedAt = getDate(this.projectPageMap[projectId]?.[page]?.updated_at); + return updatedAt && isToday(updatedAt); + }); - const yesterday: string[] = this.projectPageIds.filter((page) => - isYesterday(this.projectPageMap[projectId][page].updated_at) - ); + const yesterday: string[] = this.projectPageIds.filter((page) => { + const updatedAt = getDate(this.projectPageMap[projectId]?.[page]?.updated_at); + return updatedAt && isYesterday(updatedAt); + }); const this_week: string[] = this.projectPageIds.filter((page) => { - const pageUpdatedAt = this.projectPageMap[projectId][page].updated_at; - return isThisWeek(pageUpdatedAt) && !isToday(pageUpdatedAt) && !isYesterday(pageUpdatedAt); + const pageUpdatedAt = getDate(this.projectPageMap[projectId]?.[page]?.updated_at); + return pageUpdatedAt && isThisWeek(pageUpdatedAt) && !isToday(pageUpdatedAt) && !isYesterday(pageUpdatedAt); }); const older: string[] = this.projectPageIds.filter((page) => { - const pageUpdatedAt = this.projectPageMap[projectId][page].updated_at; - return !isThisWeek(pageUpdatedAt) && !isYesterday(pageUpdatedAt); + const pageUpdatedAt = getDate(this.projectPageMap[projectId]?.[page]?.updated_at); + return pageUpdatedAt && !isThisWeek(pageUpdatedAt) && !isYesterday(pageUpdatedAt); }); return { today, yesterday, this_week, older };