Skip to content

Commit 659142b

Browse files
lkostrowskiclaude
andauthored
Replace moment.js with native Date/Intl APIs (#6404)
* Replace moment.js with native Date/Intl APIs in 4 trivial cases - TaxAppLabel: use Intl.DateTimeFormat with user locale for date display - useNewUserCheck: use native Date for validation and comparison - handlers (preorder): use native Date for past-date check - GiftCard utils: use native Date for expiry comparison - Add TaxAppLabel unit tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * CR fixes * story for app label --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 142b8cf commit 659142b

File tree

8 files changed

+146
-15
lines changed

8 files changed

+146
-15
lines changed

.changeset/plenty-peaches-agree.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"saleor-dashboard": patch
3+
---
4+
5+
Replace a few moment.js invocations with native browser APIs

src/giftCards/GiftCardUpdate/providers/GiftCardDetailsProvider/utils.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
// @ts-strict-ignore
2-
import moment from "moment";
3-
41
import { type ExtendedGiftCard, type GiftCardBase } from "./types";
52

63
function isGiftCardExpired<T extends GiftCardBase>(giftCard: T): boolean {
74
if (!giftCard?.expiryDate) {
85
return false;
96
}
107

11-
return moment(giftCard?.expiryDate).isBefore(moment());
8+
return new Date(giftCard.expiryDate) < new Date();
129
}
1310

14-
export function getExtendedGiftCard<T extends GiftCardBase>(giftCard?: T): ExtendedGiftCard<T> {
11+
export function getExtendedGiftCard<T extends GiftCardBase>(
12+
giftCard?: T,
13+
): ExtendedGiftCard<T> | undefined {
14+
// todo do not accept optional value, check for existence higher
1515
if (!giftCard) {
1616
return undefined;
1717
}

src/giftCards/GiftCardsList/providers/GiftCardListProvider/GiftCardListProvider.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,10 @@ export const GiftCardsListProvider = ({ children, params }: GiftCardsListProvide
135135
variables: newQueryVariables,
136136
handleError: handleGiftCardListError,
137137
});
138-
const giftCards = mapEdgesToItems(data?.giftCards)?.map(getExtendedGiftCard) ?? [];
138+
const giftCards =
139+
mapEdgesToItems(data?.giftCards)
140+
?.map(getExtendedGiftCard)
141+
.filter((g): g is NonNullable<typeof g> => g !== undefined) ?? [];
139142
const providerValues: GiftCardsListConsumerProps = {
140143
onSort: handleSort,
141144
sort: getSortParams(params),

src/products/utils/handlers.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import {
1515
} from "@dashboard/graphql";
1616
import { type FormChange, type UseFormResult } from "@dashboard/hooks/useForm";
1717
import { diff } from "fast-array-diff";
18-
import moment from "moment";
1918

2019
export function createChannelsPriceChangeHandler(
2120
channelListings: ChannelData[],
@@ -113,7 +112,7 @@ export const createPreorderEndDateChangeHandler =
113112
event => {
114113
form.change(event);
115114

116-
if (moment(event.target.value).isSameOrBefore(Date.now())) {
115+
if (new Date(event.target.value) <= new Date()) {
117116
form.setError("preorderEndDateTime", preorderPastDateErrorMessage);
118117
} else {
119118
form.clearErrors("preorderEndDateTime");
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import type { Meta, StoryObj } from "@storybook/react-vite";
2+
3+
import { TaxAppLabel } from "./TaxAppLabel";
4+
5+
const meta: Meta<typeof TaxAppLabel> = {
6+
title: "Taxes/TaxAppLabel",
7+
component: TaxAppLabel,
8+
};
9+
10+
export default meta;
11+
12+
type Story = StoryObj<typeof TaxAppLabel>;
13+
14+
export const WithNameAndDate: Story = {
15+
args: {
16+
name: "Avalara",
17+
logoUrl: undefined,
18+
created: "2024-01-15T10:30:00Z",
19+
id: "app-1",
20+
},
21+
};
22+
23+
export const WithLogo: Story = {
24+
args: {
25+
name: "TaxJar",
26+
logoUrl: "https://placeholdit.com/128x128/dddddd/999999?text=app",
27+
created: "2024-06-20T14:00:00Z",
28+
id: "app-2",
29+
},
30+
};
31+
32+
export const WithoutDate: Story = {
33+
args: {
34+
name: "Tax App",
35+
logoUrl: undefined,
36+
created: null,
37+
id: "app-3",
38+
},
39+
};
40+
41+
export const WithoutName: Story = {
42+
args: {
43+
name: null,
44+
logoUrl: undefined,
45+
created: "2024-03-10T08:00:00Z",
46+
id: "app-4",
47+
},
48+
};
49+
50+
export const MinimalProps: Story = {
51+
args: {
52+
name: null,
53+
logoUrl: undefined,
54+
created: null,
55+
id: "app-5",
56+
},
57+
};
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { render, screen } from "@testing-library/react";
2+
3+
import { TaxAppLabel } from "./TaxAppLabel";
4+
5+
jest.mock("@dashboard/extensions/components/AppAvatar/AppAvatar", () => ({
6+
AppAvatar: () => <div data-testid="app-avatar" />,
7+
}));
8+
9+
jest.mock("@dashboard/hooks/useLocale", () => () => ({
10+
locale: "en",
11+
setLocale: () => undefined,
12+
}));
13+
14+
describe("TaxAppLabel", () => {
15+
it("renders locale-formatted date from ISO string", () => {
16+
// Arrange & Act
17+
render(
18+
<TaxAppLabel name="Tax App" logoUrl={undefined} created="2024-06-15T10:30:00Z" id="1" />,
19+
);
20+
21+
// Assert
22+
const expected = new Intl.DateTimeFormat("en", {
23+
year: "numeric",
24+
month: "2-digit",
25+
day: "2-digit",
26+
}).format(new Date("2024-06-15T10:30:00Z"));
27+
28+
expect(screen.getByText(`(${expected})`)).toBeInTheDocument();
29+
});
30+
31+
it("does not render date when created is null", () => {
32+
// Arrange & Act
33+
const { container } = render(
34+
<TaxAppLabel name="Tax App" logoUrl={undefined} created={null} id="1" />,
35+
);
36+
37+
// Assert
38+
expect(container.textContent).not.toContain("(");
39+
});
40+
41+
it("renders app name section when name is provided", () => {
42+
// Arrange & Act
43+
render(<TaxAppLabel name="Tax App" logoUrl={undefined} created={null} id="1" />);
44+
45+
// Assert
46+
expect(screen.getByText(/Use app/)).toBeInTheDocument();
47+
});
48+
49+
it("does not render name section when name is null", () => {
50+
// Arrange & Act
51+
render(<TaxAppLabel name={null} logoUrl={undefined} created="2024-01-01T00:00:00Z" id="1" />);
52+
53+
// Assert
54+
expect(screen.queryByText(/Use app/)).not.toBeInTheDocument();
55+
});
56+
});

src/taxes/components/TaxAppLabel.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { AppAvatar } from "@dashboard/extensions/components/AppAvatar/AppAvatar";
2+
import useLocale from "@dashboard/hooks/useLocale";
23
import { Box, Text } from "@saleor/macaw-ui-next";
3-
import moment from "moment";
44
import { FormattedMessage } from "react-intl";
55

66
interface TaxAppLabelProps {
@@ -10,7 +10,18 @@ interface TaxAppLabelProps {
1010
id: string;
1111
}
1212

13+
const formatDate = (locale: string, created: string): string => {
14+
const dateTimeString = new Intl.DateTimeFormat(locale, {
15+
year: "numeric",
16+
month: "2-digit",
17+
day: "2-digit",
18+
}).format(new Date(created));
19+
20+
return `(${dateTimeString})`;
21+
};
22+
1323
export const TaxAppLabel = ({ name, logoUrl, created }: TaxAppLabelProps) => {
24+
const { locale } = useLocale();
1425
const logo = logoUrl ? { source: logoUrl } : undefined;
1526

1627
return (
@@ -33,7 +44,7 @@ export const TaxAppLabel = ({ name, logoUrl, created }: TaxAppLabelProps) => {
3344
)}
3445
{created && (
3546
<Text size={2} color="default2">
36-
({moment(created).format("YYYY-MM-DD")})
47+
{formatDate(locale, created)}
3748
</Text>
3849
)}
3950
</Box>

src/welcomePage/WelcomePageOnboarding/hooks/useNewUserCheck.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { useUser } from "@dashboard/auth/useUser";
2-
import moment from "moment";
32

43
export const useNewUserCheck = () => {
54
const { user } = useUser();
@@ -19,18 +18,19 @@ export const useNewUserCheck = () => {
1918
};
2019
}
2120

22-
const userJoinedDate = moment(user.dateJoined);
23-
const thresholdDate = moment(thresholdDateString);
21+
const userJoinedDate = new Date(user.dateJoined);
22+
// Reset time, so timezone will not flip the day
23+
const thresholdDate = new Date(`${thresholdDateString}T00:00:00`);
2424

25-
if (!userJoinedDate.isValid() || !thresholdDate.isValid()) {
25+
if (isNaN(userJoinedDate.getTime()) || isNaN(thresholdDate.getTime())) {
2626
return {
2727
isNewUser: false,
2828
isUserLoading: false,
2929
};
3030
}
3131

3232
return {
33-
isNewUser: userJoinedDate.isAfter(thresholdDate),
33+
isNewUser: userJoinedDate > thresholdDate,
3434
isUserLoading: false,
3535
};
3636
};

0 commit comments

Comments
 (0)