-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Replace moment.js with Intl.DateTimeFormat - batch #6406
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "saleor-dashboard": patch | ||
| --- | ||
|
|
||
| Replaced a few instanced of moment.js usage with native Intl API | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| import { customer } from "@dashboard/customers/fixtures"; | ||
| import type { Meta, StoryObj } from "@storybook/react-vite"; | ||
| import { fn } from "storybook/test"; | ||
|
|
||
| import CustomerDetails from "./CustomerDetails"; | ||
|
|
||
| const meta: Meta<typeof CustomerDetails> = { | ||
| title: "Customers/CustomerDetails", | ||
| component: CustomerDetails, | ||
| }; | ||
|
|
||
| export default meta; | ||
| type Story = StoryObj<typeof CustomerDetails>; | ||
|
|
||
| const defaultProps = { | ||
| customer: customer, | ||
| data: { | ||
| isActive: true, | ||
| note: "Very important customer", | ||
| }, | ||
| disabled: false, | ||
| errors: [], | ||
| onChange: fn(), | ||
| }; | ||
|
|
||
| export const Default: Story = { | ||
| args: defaultProps, | ||
| }; | ||
|
|
||
| export const Loading: Story = { | ||
| args: { | ||
| ...defaultProps, | ||
| customer: null, | ||
| }, | ||
| }; | ||
|
|
||
| export const Inactive: Story = { | ||
| args: { | ||
| ...defaultProps, | ||
| data: { | ||
| isActive: false, | ||
| note: "", | ||
| }, | ||
| }, | ||
| }; | ||
|
|
||
| export const Disabled: Story = { | ||
| args: { | ||
| ...defaultProps, | ||
| disabled: true, | ||
| }, | ||
| }; | ||
|
|
||
| export const WithError: Story = { | ||
| args: { | ||
| ...defaultProps, | ||
| errors: [ | ||
| { | ||
| __typename: "AccountError" as const, | ||
| code: "INVALID" as any, | ||
| field: "note", | ||
| addressType: null, | ||
| message: "This field is required", | ||
| }, | ||
| ], | ||
| }, | ||
| }; | ||
|
|
||
| export const EmptyNote: Story = { | ||
| args: { | ||
| ...defaultProps, | ||
| data: { | ||
| isActive: true, | ||
| note: "", | ||
| }, | ||
| }, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| import { formatMonthYear } from "./CustomerDetails"; | ||
|
|
||
| describe("formatMonthYear", () => { | ||
| it("formats date as abbreviated month and year", () => { | ||
| // Arrange & Act | ||
| const result = formatMonthYear("en-US")("2024-01-15T14:30:00Z"); | ||
|
|
||
| // Assert | ||
| expect(result).toBe("Jan 2024"); | ||
| }); | ||
|
|
||
| it("formats mid-year date correctly", () => { | ||
| // Arrange & Act | ||
| const result = formatMonthYear("en-US")("2024-06-01T00:00:00Z"); | ||
|
|
||
| // Assert | ||
| expect(result).toBe("Jun 2024"); | ||
| }); | ||
|
|
||
| it("formats end-of-year date correctly", () => { | ||
| // Arrange & Act | ||
| const result = formatMonthYear("en-US")("2023-12-31T23:59:59Z"); | ||
|
|
||
| // Assert | ||
| expect(result).toBe("Dec 2023"); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,13 +1,13 @@ | ||||||||||||||||||||||||||||||||||||||
| // @ts-strict-ignore | ||||||||||||||||||||||||||||||||||||||
| import { DashboardCard } from "@dashboard/components/Card"; | ||||||||||||||||||||||||||||||||||||||
| import { type AccountErrorFragment, type CustomerDetailsQuery } from "@dashboard/graphql"; | ||||||||||||||||||||||||||||||||||||||
| import useLocale from "@dashboard/hooks/useLocale"; | ||||||||||||||||||||||||||||||||||||||
| import { maybe } from "@dashboard/misc"; | ||||||||||||||||||||||||||||||||||||||
| import { getFormErrors } from "@dashboard/utils/errors"; | ||||||||||||||||||||||||||||||||||||||
| import getAccountErrorMessage from "@dashboard/utils/errors/account"; | ||||||||||||||||||||||||||||||||||||||
| import { TextField } from "@material-ui/core"; | ||||||||||||||||||||||||||||||||||||||
| import { makeStyles } from "@saleor/macaw-ui"; | ||||||||||||||||||||||||||||||||||||||
| import { Checkbox, Skeleton, Text } from "@saleor/macaw-ui-next"; | ||||||||||||||||||||||||||||||||||||||
| import moment from "moment-timezone"; | ||||||||||||||||||||||||||||||||||||||
| import type * as React from "react"; | ||||||||||||||||||||||||||||||||||||||
| import { FormattedMessage, useIntl } from "react-intl"; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
|
@@ -29,6 +29,14 @@ const useStyles = makeStyles( | |||||||||||||||||||||||||||||||||||||
| { name: "CustomerDetails" }, | ||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| export const formatMonthYear = | ||||||||||||||||||||||||||||||||||||||
| (locale: string) => | ||||||||||||||||||||||||||||||||||||||
| (date: string): string => | ||||||||||||||||||||||||||||||||||||||
| new Intl.DateTimeFormat(locale, { | ||||||||||||||||||||||||||||||||||||||
| month: "short", | ||||||||||||||||||||||||||||||||||||||
| year: "numeric", | ||||||||||||||||||||||||||||||||||||||
| }).format(new Date(date)); | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+34
to
+38
|
||||||||||||||||||||||||||||||||||||||
| (date: string): string => | |
| new Intl.DateTimeFormat(locale, { | |
| month: "short", | |
| year: "numeric", | |
| }).format(new Date(date)); | |
| (date: string): string => { | |
| const parsed = new Date(date); | |
| if (isNaN(parsed.getTime())) { | |
| // Fallback for invalid dates, matching Moment's "Invalid date" behavior | |
| return "Invalid date"; | |
| } | |
| return new Intl.DateTimeFormat(locale, { | |
| month: "short", | |
| year: "numeric", | |
| }).format(parsed); | |
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,16 +1,38 @@ | ||
| import { LocaleContext } from "@dashboard/components/Locale"; | ||
| import moment from "moment-timezone"; | ||
| import { useContext } from "react"; | ||
|
|
||
| export type LocalizeDate = (date: string, format?: string) => string; | ||
|
|
||
| /** | ||
| * Backwards compat with old moment.js format. | ||
| */ | ||
| const FORMAT_OPTIONS: Record<string, Intl.DateTimeFormatOptions> = { | ||
| ll: { dateStyle: "medium" }, | ||
| lll: { dateStyle: "medium", timeStyle: "short" }, | ||
| llll: { | ||
| weekday: "short", | ||
| year: "numeric", | ||
| month: "short", | ||
| day: "numeric", | ||
| hour: "numeric", | ||
| minute: "numeric", | ||
| }, | ||
| }; | ||
lkostrowski marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| function useDateLocalize(): LocalizeDate { | ||
| const { locale } = useContext(LocaleContext); | ||
|
|
||
| return (date: string, format?: string) => | ||
| moment(date) | ||
| .locale(locale) | ||
| .format(format || "ll"); | ||
| return (date: string, format?: "ll" | "lll" | "llll" | string) => { | ||
| const parsed = new Date(date); | ||
|
|
||
| if (isNaN(parsed.getTime())) { | ||
| return "Invalid date"; | ||
| } | ||
|
|
||
| const options = FORMAT_OPTIONS[format || "ll"] ?? FORMAT_OPTIONS.ll; | ||
lkostrowski marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| return new Intl.DateTimeFormat(locale, options).format(parsed); | ||
| }; | ||
| } | ||
|
|
||
| export default useDateLocalize; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,11 +5,16 @@ import { type OrderDraft } from "@dashboard/orders/types"; | |
| import { type Sort } from "@dashboard/types"; | ||
| import { getColumnSortDirectionIcon } from "@dashboard/utils/columns/getColumnSortDirectionIcon"; | ||
| import { type GridCell, type Item } from "@glideapps/glide-data-grid"; | ||
| import moment from "moment"; | ||
| import { type IntlShape } from "react-intl"; | ||
|
|
||
| import { columnsMessages } from "./messages"; | ||
|
|
||
| export function formatDateTime(date: string, locale: Locale): string { | ||
| return new Intl.DateTimeFormat(locale, { dateStyle: "medium", timeStyle: "short" }).format( | ||
| new Date(date), | ||
lkostrowski marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ); | ||
lkostrowski marked this conversation as resolved.
Show resolved
Hide resolved
lkostrowski marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
Comment on lines
+12
to
+16
|
||
|
|
||
| export const orderDraftListStaticColumnsAdapter = ( | ||
| intl: IntlShape, | ||
| sort: Sort, | ||
|
|
@@ -67,7 +72,11 @@ export const createGetCellContent = | |
| case "number": | ||
| return readonlyTextCell(`#${rowData.number}`); | ||
| case "date": | ||
| return readonlyTextCell(moment(rowData.created).locale(locale).format("lll")); | ||
| try { | ||
| return readonlyTextCell(formatDateTime(rowData.created, locale)); | ||
| } catch (e) { | ||
| return readonlyTextCell("-"); | ||
| } | ||
| case "customer": | ||
| return readonlyTextCell(getCustomerName(rowData)); | ||
| case "total": | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo: “instanced” → “instances”.