diff --git a/frontends/main/package.json b/frontends/main/package.json index 9cf7375c7a..6511220ed6 100644 --- a/frontends/main/package.json +++ b/frontends/main/package.json @@ -3,10 +3,10 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "PORT=${PORT:-8062} next dev", + "dev": "PORT=${PORT:-8062} TZ=UTC next dev", "build": "next build", "build:no-lint": "next build --no-lint", - "start": "next start", + "start": "TZ=UTC next start", "lint": "next lint" }, "dependencies": { diff --git a/frontends/main/src/app-pages/HomePage/NewsEventsSection.tsx b/frontends/main/src/app-pages/HomePage/NewsEventsSection.tsx index bcf7d7227f..2b9b37abbb 100644 --- a/frontends/main/src/app-pages/HomePage/NewsEventsSection.tsx +++ b/frontends/main/src/app-pages/HomePage/NewsEventsSection.tsx @@ -13,7 +13,7 @@ import { NewsEventsListFeedTypeEnum, } from "api/hooks/newsEvents" import type { NewsFeedItem, EventFeedItem } from "api/v0" -import { formatDate } from "ol-utilities" +import { LocalDate } from "ol-utilities" import { RiArrowRightSLine } from "@remixicon/react" import Link from "next/link" @@ -196,7 +196,7 @@ const Story: React.FC<{ item: NewsFeedItem; mobile: boolean }> = ({ {item.title} - Published: {formatDate(item.news_details?.publish_date)} + Published: ) @@ -226,16 +226,16 @@ const NewsEventsSection: React.FC = () => { - {formatDate( - (item as EventFeedItem).event_details?.event_datetime, - "D", - )} + - {formatDate( - (item as EventFeedItem).event_details?.event_datetime, - "MMM", - )} + diff --git a/frontends/ol-components/src/components/LearningResourceCard/LearningResourceCard.tsx b/frontends/ol-components/src/components/LearningResourceCard/LearningResourceCard.tsx index 8db2fffc68..c0aec628f1 100644 --- a/frontends/ol-components/src/components/LearningResourceCard/LearningResourceCard.tsx +++ b/frontends/ol-components/src/components/LearningResourceCard/LearningResourceCard.tsx @@ -9,7 +9,7 @@ import { } from "@remixicon/react" import { LearningResource } from "api" import { - formatDate, + LocalDate, getReadableResourceType, DEFAULT_RESOURCE_IMG, getLearningResourcePrices, @@ -149,7 +149,8 @@ const StartDate: React.FC<{ resource: LearningResource; size?: Size }> = ({ const format = size === "small" ? "MMM DD, YYYY" : "MMMM DD, YYYY" const formatted = anytime ? "Anytime" - : startDate && formatDate(startDate, format) + : startDate && + if (!formatted) return null const showLabel = size !== "small" || anytime diff --git a/frontends/ol-components/src/components/LearningResourceCard/LearningResourceListCard.tsx b/frontends/ol-components/src/components/LearningResourceCard/LearningResourceListCard.tsx index f68b78baa4..65b08b0cdb 100644 --- a/frontends/ol-components/src/components/LearningResourceCard/LearningResourceListCard.tsx +++ b/frontends/ol-components/src/components/LearningResourceCard/LearningResourceListCard.tsx @@ -9,7 +9,7 @@ import { } from "@remixicon/react" import { ResourceTypeEnum, LearningResource } from "api" import { - formatDate, + LocalDate, getReadableResourceType, DEFAULT_RESOURCE_IMG, pluralize, @@ -151,7 +151,7 @@ export const StartDate: React.FC<{ resource: LearningResource }> = ({ const startDate = getResourceDate(resource) const formatted = anytime ? "Anytime" - : startDate && formatDate(startDate, "MMMM DD, YYYY") + : startDate && if (!formatted) return null return ( diff --git a/frontends/ol-components/src/components/LearningResourceExpanded/DifferingRunsTable.tsx b/frontends/ol-components/src/components/LearningResourceExpanded/DifferingRunsTable.tsx index 57bdaf0843..ea1663f41c 100644 --- a/frontends/ol-components/src/components/LearningResourceExpanded/DifferingRunsTable.tsx +++ b/frontends/ol-components/src/components/LearningResourceExpanded/DifferingRunsTable.tsx @@ -8,6 +8,7 @@ import { getDisplayPrice, getRunPrices, showStartAnytime, + NoSSR, } from "ol-utilities" const DifferingRuns = styled.div({ @@ -103,7 +104,9 @@ const DifferingRunsTable: React.FC<{ resource: LearningResource }> = ({ {resource.runs?.map((run, index) => ( - {formatRunDate(run, asTaughtIn)} + + {formatRunDate(run, asTaughtIn)} + {run.resource_prices && ( {getDisplayPrice(getRunPrices(run)["course"])} diff --git a/frontends/ol-components/src/components/LearningResourceExpanded/InfoSectionV2.tsx b/frontends/ol-components/src/components/LearningResourceExpanded/InfoSectionV2.tsx index 6908649c75..2141d8c52e 100644 --- a/frontends/ol-components/src/components/LearningResourceExpanded/InfoSectionV2.tsx +++ b/frontends/ol-components/src/components/LearningResourceExpanded/InfoSectionV2.tsx @@ -24,6 +24,7 @@ import { formatRunDate, getLearningResourcePrices, showStartAnytime, + NoSSR, } from "ol-utilities" import { theme } from "../ThemeProvider/ThemeProvider" import DifferingRunsTable from "./DifferingRunsTable" @@ -255,7 +256,11 @@ const INFO_ITEMS: InfoItemConfig = [ const totalDatesWithRuns = resource.runs?.filter((run) => run.start_date !== null).length || 0 if (allRunsAreIdentical(resource) && totalDatesWithRuns > 0) { - return + return ( + + + + ) } else return null }, }, diff --git a/frontends/ol-components/src/components/LearningResourceExpanded/LearningResourceExpandedV1.tsx b/frontends/ol-components/src/components/LearningResourceExpanded/LearningResourceExpandedV1.tsx index 09005f595a..ed27f0ef29 100644 --- a/frontends/ol-components/src/components/LearningResourceExpanded/LearningResourceExpandedV1.tsx +++ b/frontends/ol-components/src/components/LearningResourceExpanded/LearningResourceExpandedV1.tsx @@ -7,6 +7,7 @@ import { ButtonLink } from "../Button/Button" import type { LearningResource, LearningResourceRun } from "api" import { ResourceTypeEnum, PlatformEnum } from "api" import { + NoSSR, formatDate, capitalize, DEFAULT_RESOURCE_IMG, @@ -299,7 +300,7 @@ const ResourceDescription = ({ resource }: { resource?: LearningResource }) => { return ( = ({ .map((run) => { return { value: run.id.toString(), - label: formatRunDate(run, asTaughtIn), + label: {formatRunDate(run, asTaughtIn)}, } }) ?? [] @@ -415,7 +416,7 @@ const LearningResourceExpandedV1: React.FC = ({ return ( {label} - {formatted ?? ""} + {formatted ?? ""} ) } diff --git a/frontends/ol-utilities/src/date/LocalDate.tsx b/frontends/ol-utilities/src/date/LocalDate.tsx new file mode 100644 index 0000000000..1ebdb9c636 --- /dev/null +++ b/frontends/ol-utilities/src/date/LocalDate.tsx @@ -0,0 +1,20 @@ +import React from "react" +import { NoSSR } from "../ssr/NoSSR" +import { formatDate } from "./format" + +type LocalDateProps = { + date?: string | Date | null + /** + * A Moment.js format string. See https://momentjs.com/docs/#/displaying/format/ + */ + format?: string +} + +/* Component to render dates only on the client as these are displayed + * according to the user's locale (generally, not all Moment.js format tokens + * are localized) causing an error due to hydration mismatch. + */ +export const LocalDate = ({ date, format = "MMM D, YYYY" }: LocalDateProps) => { + if (!date) return null + return {formatDate(date, format)} +} diff --git a/frontends/ol-utilities/src/date/format.ts b/frontends/ol-utilities/src/date/format.ts index 3bd6206487..706d5cbef6 100644 --- a/frontends/ol-utilities/src/date/format.ts +++ b/frontends/ol-utilities/src/date/format.ts @@ -1,12 +1,14 @@ import moment from "moment" +/* Instances must be wrapped in to avoid SSR hydration mismatches. + */ export const formatDate = ( /** * Date string or date. */ date: string | Date, /** - * A momentjs format string. See https://momentjs.com/docs/#/displaying/format/ + * A Moment.js format string. See https://momentjs.com/docs/#/displaying/format/ */ format = "MMM D, YYYY", ) => { diff --git a/frontends/ol-utilities/src/index.ts b/frontends/ol-utilities/src/index.ts index eefe535703..39f05c6eb6 100644 --- a/frontends/ol-utilities/src/index.ts +++ b/frontends/ol-utilities/src/index.ts @@ -6,6 +6,7 @@ export * from "./styles" export * from "./date/format" +export * from "./date/LocalDate" export * from "./learning-resources/learning-resources" export * from "./learning-resources/pricing" export * from "./strings/html" @@ -14,3 +15,4 @@ export * from "./hooks" export * from "./querystrings" export * from "./lib" export * from "./images/backgroundImages" +export * from "./ssr/NoSSR" diff --git a/frontends/ol-utilities/src/ssr/NoSSR.tsx b/frontends/ol-utilities/src/ssr/NoSSR.tsx new file mode 100644 index 0000000000..ca93c8596f --- /dev/null +++ b/frontends/ol-utilities/src/ssr/NoSSR.tsx @@ -0,0 +1,16 @@ +import React, { useState, useEffect, ReactNode } from "react" + +type NoSSRProps = { + children: ReactNode + onSSR?: ReactNode +} + +export const NoSSR: React.FC = ({ children, onSSR = null }) => { + const [isClient, setClient] = useState(false) + + useEffect(() => { + setClient(true) + }, []) + + return isClient ? children : onSSR +}