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
+}