diff --git a/.changeset/plenty-peaches-agree.md b/.changeset/plenty-peaches-agree.md new file mode 100644 index 00000000000..035b75d21c3 --- /dev/null +++ b/.changeset/plenty-peaches-agree.md @@ -0,0 +1,5 @@ +--- +"saleor-dashboard": patch +--- + +Replace a few moment.js invocations with native browser APIs diff --git a/src/giftCards/GiftCardUpdate/providers/GiftCardDetailsProvider/utils.ts b/src/giftCards/GiftCardUpdate/providers/GiftCardDetailsProvider/utils.ts index d8b1be91209..966199f7528 100644 --- a/src/giftCards/GiftCardUpdate/providers/GiftCardDetailsProvider/utils.ts +++ b/src/giftCards/GiftCardUpdate/providers/GiftCardDetailsProvider/utils.ts @@ -1,6 +1,3 @@ -// @ts-strict-ignore -import moment from "moment"; - import { type ExtendedGiftCard, type GiftCardBase } from "./types"; function isGiftCardExpired(giftCard: T): boolean { @@ -8,10 +5,13 @@ function isGiftCardExpired(giftCard: T): boolean { return false; } - return moment(giftCard?.expiryDate).isBefore(moment()); + return new Date(giftCard.expiryDate) < new Date(); } -export function getExtendedGiftCard(giftCard?: T): ExtendedGiftCard { +export function getExtendedGiftCard( + giftCard?: T, +): ExtendedGiftCard | undefined { + // todo do not accept optional value, check for existence higher if (!giftCard) { return undefined; } diff --git a/src/giftCards/GiftCardsList/providers/GiftCardListProvider/GiftCardListProvider.tsx b/src/giftCards/GiftCardsList/providers/GiftCardListProvider/GiftCardListProvider.tsx index 444bda17a10..86a37329ea1 100644 --- a/src/giftCards/GiftCardsList/providers/GiftCardListProvider/GiftCardListProvider.tsx +++ b/src/giftCards/GiftCardsList/providers/GiftCardListProvider/GiftCardListProvider.tsx @@ -135,7 +135,10 @@ export const GiftCardsListProvider = ({ children, params }: GiftCardsListProvide variables: newQueryVariables, handleError: handleGiftCardListError, }); - const giftCards = mapEdgesToItems(data?.giftCards)?.map(getExtendedGiftCard) ?? []; + const giftCards = + mapEdgesToItems(data?.giftCards) + ?.map(getExtendedGiftCard) + .filter((g): g is NonNullable => g !== undefined) ?? []; const providerValues: GiftCardsListConsumerProps = { onSort: handleSort, sort: getSortParams(params), diff --git a/src/products/utils/handlers.ts b/src/products/utils/handlers.ts index 48e60d4ae4e..939a81f35dc 100644 --- a/src/products/utils/handlers.ts +++ b/src/products/utils/handlers.ts @@ -15,7 +15,6 @@ import { } from "@dashboard/graphql"; import { type FormChange, type UseFormResult } from "@dashboard/hooks/useForm"; import { diff } from "fast-array-diff"; -import moment from "moment"; export function createChannelsPriceChangeHandler( channelListings: ChannelData[], @@ -113,7 +112,7 @@ export const createPreorderEndDateChangeHandler = event => { form.change(event); - if (moment(event.target.value).isSameOrBefore(Date.now())) { + if (new Date(event.target.value) <= new Date()) { form.setError("preorderEndDateTime", preorderPastDateErrorMessage); } else { form.clearErrors("preorderEndDateTime"); diff --git a/src/taxes/components/TaxAppLabel.stories.tsx b/src/taxes/components/TaxAppLabel.stories.tsx new file mode 100644 index 00000000000..753b2415063 --- /dev/null +++ b/src/taxes/components/TaxAppLabel.stories.tsx @@ -0,0 +1,57 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; + +import { TaxAppLabel } from "./TaxAppLabel"; + +const meta: Meta = { + title: "Taxes/TaxAppLabel", + component: TaxAppLabel, +}; + +export default meta; + +type Story = StoryObj; + +export const WithNameAndDate: Story = { + args: { + name: "Avalara", + logoUrl: undefined, + created: "2024-01-15T10:30:00Z", + id: "app-1", + }, +}; + +export const WithLogo: Story = { + args: { + name: "TaxJar", + logoUrl: "https://placeholdit.com/128x128/dddddd/999999?text=app", + created: "2024-06-20T14:00:00Z", + id: "app-2", + }, +}; + +export const WithoutDate: Story = { + args: { + name: "Tax App", + logoUrl: undefined, + created: null, + id: "app-3", + }, +}; + +export const WithoutName: Story = { + args: { + name: null, + logoUrl: undefined, + created: "2024-03-10T08:00:00Z", + id: "app-4", + }, +}; + +export const MinimalProps: Story = { + args: { + name: null, + logoUrl: undefined, + created: null, + id: "app-5", + }, +}; diff --git a/src/taxes/components/TaxAppLabel.test.tsx b/src/taxes/components/TaxAppLabel.test.tsx new file mode 100644 index 00000000000..3d5357d0b34 --- /dev/null +++ b/src/taxes/components/TaxAppLabel.test.tsx @@ -0,0 +1,56 @@ +import { render, screen } from "@testing-library/react"; + +import { TaxAppLabel } from "./TaxAppLabel"; + +jest.mock("@dashboard/extensions/components/AppAvatar/AppAvatar", () => ({ + AppAvatar: () =>
, +})); + +jest.mock("@dashboard/hooks/useLocale", () => () => ({ + locale: "en", + setLocale: () => undefined, +})); + +describe("TaxAppLabel", () => { + it("renders locale-formatted date from ISO string", () => { + // Arrange & Act + render( + , + ); + + // Assert + const expected = new Intl.DateTimeFormat("en", { + year: "numeric", + month: "2-digit", + day: "2-digit", + }).format(new Date("2024-06-15T10:30:00Z")); + + expect(screen.getByText(`(${expected})`)).toBeInTheDocument(); + }); + + it("does not render date when created is null", () => { + // Arrange & Act + const { container } = render( + , + ); + + // Assert + expect(container.textContent).not.toContain("("); + }); + + it("renders app name section when name is provided", () => { + // Arrange & Act + render(); + + // Assert + expect(screen.getByText(/Use app/)).toBeInTheDocument(); + }); + + it("does not render name section when name is null", () => { + // Arrange & Act + render(); + + // Assert + expect(screen.queryByText(/Use app/)).not.toBeInTheDocument(); + }); +}); diff --git a/src/taxes/components/TaxAppLabel.tsx b/src/taxes/components/TaxAppLabel.tsx index 0d89f96f249..0f57f178e32 100644 --- a/src/taxes/components/TaxAppLabel.tsx +++ b/src/taxes/components/TaxAppLabel.tsx @@ -1,6 +1,6 @@ import { AppAvatar } from "@dashboard/extensions/components/AppAvatar/AppAvatar"; +import useLocale from "@dashboard/hooks/useLocale"; import { Box, Text } from "@saleor/macaw-ui-next"; -import moment from "moment"; import { FormattedMessage } from "react-intl"; interface TaxAppLabelProps { @@ -10,7 +10,18 @@ interface TaxAppLabelProps { id: string; } +const formatDate = (locale: string, created: string): string => { + const dateTimeString = new Intl.DateTimeFormat(locale, { + year: "numeric", + month: "2-digit", + day: "2-digit", + }).format(new Date(created)); + + return `(${dateTimeString})`; +}; + export const TaxAppLabel = ({ name, logoUrl, created }: TaxAppLabelProps) => { + const { locale } = useLocale(); const logo = logoUrl ? { source: logoUrl } : undefined; return ( @@ -33,7 +44,7 @@ export const TaxAppLabel = ({ name, logoUrl, created }: TaxAppLabelProps) => { )} {created && ( - ({moment(created).format("YYYY-MM-DD")}) + {formatDate(locale, created)} )} diff --git a/src/welcomePage/WelcomePageOnboarding/hooks/useNewUserCheck.ts b/src/welcomePage/WelcomePageOnboarding/hooks/useNewUserCheck.ts index ef5ed25dcef..8503dfac87c 100644 --- a/src/welcomePage/WelcomePageOnboarding/hooks/useNewUserCheck.ts +++ b/src/welcomePage/WelcomePageOnboarding/hooks/useNewUserCheck.ts @@ -1,5 +1,4 @@ import { useUser } from "@dashboard/auth/useUser"; -import moment from "moment"; export const useNewUserCheck = () => { const { user } = useUser(); @@ -19,10 +18,11 @@ export const useNewUserCheck = () => { }; } - const userJoinedDate = moment(user.dateJoined); - const thresholdDate = moment(thresholdDateString); + const userJoinedDate = new Date(user.dateJoined); + // Reset time, so timezone will not flip the day + const thresholdDate = new Date(`${thresholdDateString}T00:00:00`); - if (!userJoinedDate.isValid() || !thresholdDate.isValid()) { + if (isNaN(userJoinedDate.getTime()) || isNaN(thresholdDate.getTime())) { return { isNewUser: false, isUserLoading: false, @@ -30,7 +30,7 @@ export const useNewUserCheck = () => { } return { - isNewUser: userJoinedDate.isAfter(thresholdDate), + isNewUser: userJoinedDate > thresholdDate, isUserLoading: false, }; };