From 9a6df0941a1541c8e319b2ba2d3381d78dbfc084 Mon Sep 17 00:00:00 2001 From: Kasper Date: Thu, 8 Feb 2024 14:02:12 +0100 Subject: [PATCH 01/20] update style of route modal prompt --- .../dashboard/src/hooks/use-route-modal-state.tsx | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/admin-next/dashboard/src/hooks/use-route-modal-state.tsx b/packages/admin-next/dashboard/src/hooks/use-route-modal-state.tsx index ce208169516ae..34d91cfc5c1cd 100644 --- a/packages/admin-next/dashboard/src/hooks/use-route-modal-state.tsx +++ b/packages/admin-next/dashboard/src/hooks/use-route-modal-state.tsx @@ -3,13 +3,6 @@ import { useEffect, useState } from "react" import { useTranslation } from "react-i18next" import { useNavigate } from "react-router-dom" -type Prompt = { - title: string - description: string - cancelText: string - confirmText: string -} - /** * Hook for managing the state of route modals. */ @@ -30,11 +23,12 @@ export const useRouteModalState = (): [ const prompt = usePrompt() const { t } = useTranslation() - let promptValues: Prompt = { + const promptValues = { title: t("general.unsavedChangesTitle"), description: t("general.unsavedChangesDescription"), cancelText: t("general.cancel"), confirmText: t("general.continue"), + variant: "confirmation" as const, } useEffect(() => { From 17d983519720594b6101aab7bce4f9b16b44d693 Mon Sep 17 00:00:00 2001 From: Kasper Date: Thu, 8 Feb 2024 14:07:20 +0100 Subject: [PATCH 02/20] add common table cells --- .../common/money-amount-cell/index.ts | 1 + .../money-amount-cell/money-amount-cell.tsx | 45 +++++++++++++++++++ .../common/placeholder-cell/index.ts | 1 + .../placeholder-cell/placeholder-cell.tsx | 7 +++ 4 files changed, 54 insertions(+) create mode 100644 packages/admin-next/dashboard/src/components/table/table-cells/common/money-amount-cell/index.ts create mode 100644 packages/admin-next/dashboard/src/components/table/table-cells/common/money-amount-cell/money-amount-cell.tsx create mode 100644 packages/admin-next/dashboard/src/components/table/table-cells/common/placeholder-cell/index.ts create mode 100644 packages/admin-next/dashboard/src/components/table/table-cells/common/placeholder-cell/placeholder-cell.tsx diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/common/money-amount-cell/index.ts b/packages/admin-next/dashboard/src/components/table/table-cells/common/money-amount-cell/index.ts new file mode 100644 index 0000000000000..9c55954207290 --- /dev/null +++ b/packages/admin-next/dashboard/src/components/table/table-cells/common/money-amount-cell/index.ts @@ -0,0 +1 @@ +export * from "./money-amount-cell" diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/common/money-amount-cell/money-amount-cell.tsx b/packages/admin-next/dashboard/src/components/table/table-cells/common/money-amount-cell/money-amount-cell.tsx new file mode 100644 index 0000000000000..57a9cf0e6676a --- /dev/null +++ b/packages/admin-next/dashboard/src/components/table/table-cells/common/money-amount-cell/money-amount-cell.tsx @@ -0,0 +1,45 @@ +import { clx } from "@medusajs/ui" +import { getPresentationalAmount } from "../../../../../lib/money-amount-helpers" +import { PlaceholderCell } from "../placeholder-cell" + +type MoneyAmountCellProps = { + currencyCode: string + amount?: number | null + align?: "left" | "right" +} + +export const MoneyAmountCell = ({ + currencyCode, + amount, + align = "left", +}: MoneyAmountCellProps) => { + if (!amount) { + return + } + + const formatted = new Intl.NumberFormat(undefined, { + style: "currency", + currency: currencyCode, + currencyDisplay: "narrowSymbol", + }).format(0) + + const symbol = formatted.replace(/\d/g, "").replace(/[.,]/g, "").trim() + + const presentationAmount = getPresentationalAmount(amount, currencyCode) + const formattedTotal = new Intl.NumberFormat(undefined, { + style: "decimal", + }).format(presentationAmount) + + return ( +
+ + {symbol} {formattedTotal} {currencyCode.toUpperCase()} + +
+ ) +} diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/common/placeholder-cell/index.ts b/packages/admin-next/dashboard/src/components/table/table-cells/common/placeholder-cell/index.ts new file mode 100644 index 0000000000000..2991d7f09a72e --- /dev/null +++ b/packages/admin-next/dashboard/src/components/table/table-cells/common/placeholder-cell/index.ts @@ -0,0 +1 @@ +export * from "./placeholder-cell" diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/common/placeholder-cell/placeholder-cell.tsx b/packages/admin-next/dashboard/src/components/table/table-cells/common/placeholder-cell/placeholder-cell.tsx new file mode 100644 index 0000000000000..1deeb9bbd8537 --- /dev/null +++ b/packages/admin-next/dashboard/src/components/table/table-cells/common/placeholder-cell/placeholder-cell.tsx @@ -0,0 +1,7 @@ +export const PlaceholderCell = () => { + return ( +
+ - +
+ ) +} From 485134d3542ef7b18d20df86e095fcbd590fdd01 Mon Sep 17 00:00:00 2001 From: Kasper Date: Thu, 8 Feb 2024 14:07:56 +0100 Subject: [PATCH 03/20] update DisplayIdCel --- .../table-cells/order/display-id-cell/display-id-cell.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/order/display-id-cell/display-id-cell.tsx b/packages/admin-next/dashboard/src/components/table/table-cells/order/display-id-cell/display-id-cell.tsx index e4f2e48aeb6ab..97d7106b4d9ea 100644 --- a/packages/admin-next/dashboard/src/components/table/table-cells/order/display-id-cell/display-id-cell.tsx +++ b/packages/admin-next/dashboard/src/components/table/table-cells/order/display-id-cell/display-id-cell.tsx @@ -1,6 +1,11 @@ import { useTranslation } from "react-i18next" +import { PlaceholderCell } from "../../common/placeholder-cell" + +export const DisplayIdCell = ({ displayId }: { displayId?: number | null }) => { + if (!displayId) { + return + } -export const DisplayIdCell = ({ displayId }: { displayId: number }) => { return (
#{displayId} From 4da2c83fd6f238fabd0502c5e621224e524ce5ec Mon Sep 17 00:00:00 2001 From: Kasper Date: Thu, 8 Feb 2024 14:08:44 +0100 Subject: [PATCH 04/20] update TotalCell --- .../order/total-cell/total-cell.tsx | 24 ++++--------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/order/total-cell/total-cell.tsx b/packages/admin-next/dashboard/src/components/table/table-cells/order/total-cell/total-cell.tsx index 58646ee9339f4..f9543ad99d277 100644 --- a/packages/admin-next/dashboard/src/components/table/table-cells/order/total-cell/total-cell.tsx +++ b/packages/admin-next/dashboard/src/components/table/table-cells/order/total-cell/total-cell.tsx @@ -1,5 +1,6 @@ import { useTranslation } from "react-i18next" -import { getPresentationalAmount } from "../../../../../lib/money-amount-helpers" +import { MoneyAmountCell } from "../../common/money-amount-cell" +import { PlaceholderCell } from "../../common/placeholder-cell" type TotalCellProps = { currencyCode: string @@ -8,28 +9,11 @@ type TotalCellProps = { export const TotalCell = ({ currencyCode, total }: TotalCellProps) => { if (!total) { - return - + return } - const formatted = new Intl.NumberFormat(undefined, { - style: "currency", - currency: currencyCode, - currencyDisplay: "narrowSymbol", - }).format(0) - - const symbol = formatted.replace(/\d/g, "").replace(/[.,]/g, "").trim() - - const presentationAmount = getPresentationalAmount(total, currencyCode) - const formattedTotal = new Intl.NumberFormat(undefined, { - style: "decimal", - }).format(presentationAmount) - return ( -
- - {symbol} {formattedTotal} {currencyCode.toUpperCase()} - -
+ ) } From da6959edd0d04ef72506ce3d6c1d6826c606826c Mon Sep 17 00:00:00 2001 From: Kasper Date: Thu, 8 Feb 2024 14:13:09 +0100 Subject: [PATCH 05/20] add product table cells --- .../public/locales/en/translation.json | 3 +- .../collection-cell/collection-cell.tsx | 30 +++++++++ .../product/collection-cell/index.ts | 1 + .../product/product-status-cell/index.ts | 1 + .../product-status-cell.tsx | 31 +++++++++ .../product/sales-channels-cell/index.ts | 1 + .../sales-channels-cell.tsx | 63 +++++++++++++++++++ .../table-cells/product/variant-cell/index.ts | 1 + .../product/variant-cell/variant-cell.tsx | 34 ++++++++++ 9 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 packages/admin-next/dashboard/src/components/table/table-cells/product/collection-cell/collection-cell.tsx create mode 100644 packages/admin-next/dashboard/src/components/table/table-cells/product/collection-cell/index.ts create mode 100644 packages/admin-next/dashboard/src/components/table/table-cells/product/product-status-cell/index.ts create mode 100644 packages/admin-next/dashboard/src/components/table/table-cells/product/product-status-cell/product-status-cell.tsx create mode 100644 packages/admin-next/dashboard/src/components/table/table-cells/product/sales-channels-cell/index.ts create mode 100644 packages/admin-next/dashboard/src/components/table/table-cells/product/sales-channels-cell/sales-channels-cell.tsx create mode 100644 packages/admin-next/dashboard/src/components/table/table-cells/product/variant-cell/index.ts create mode 100644 packages/admin-next/dashboard/src/components/table/table-cells/product/variant-cell/variant-cell.tsx diff --git a/packages/admin-next/dashboard/public/locales/en/translation.json b/packages/admin-next/dashboard/public/locales/en/translation.json index b84fd893ee27b..582c89d7d618a 100644 --- a/packages/admin-next/dashboard/public/locales/en/translation.json +++ b/packages/admin-next/dashboard/public/locales/en/translation.json @@ -337,6 +337,7 @@ "salesChannel": "Sales Channel", "region": "Region", "role": "Role", - "sent": "Sent" + "sent": "Sent", + "salesChannels": "Sales Channels" } } diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/product/collection-cell/collection-cell.tsx b/packages/admin-next/dashboard/src/components/table/table-cells/product/collection-cell/collection-cell.tsx new file mode 100644 index 0000000000000..3d714b29b11b5 --- /dev/null +++ b/packages/admin-next/dashboard/src/components/table/table-cells/product/collection-cell/collection-cell.tsx @@ -0,0 +1,30 @@ +import type { ProductCollection } from "@medusajs/medusa" +import { useTranslation } from "react-i18next" + +import { PlaceholderCell } from "../../common/placeholder-cell" + +type CollectionCellProps = { + collection?: ProductCollection | null +} + +export const CollectionCell = ({ collection }: CollectionCellProps) => { + if (!collection) { + return + } + + return ( +
+ {collection.title} +
+ ) +} + +export const CollectionHeader = () => { + const { t } = useTranslation() + + return ( +
+ {t("fields.collection")} +
+ ) +} diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/product/collection-cell/index.ts b/packages/admin-next/dashboard/src/components/table/table-cells/product/collection-cell/index.ts new file mode 100644 index 0000000000000..c6184e9938250 --- /dev/null +++ b/packages/admin-next/dashboard/src/components/table/table-cells/product/collection-cell/index.ts @@ -0,0 +1 @@ +export * from "./collection-cell" diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/product/product-status-cell/index.ts b/packages/admin-next/dashboard/src/components/table/table-cells/product/product-status-cell/index.ts new file mode 100644 index 0000000000000..c4fd9163f0073 --- /dev/null +++ b/packages/admin-next/dashboard/src/components/table/table-cells/product/product-status-cell/index.ts @@ -0,0 +1 @@ +export * from "./product-status-cell" diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/product/product-status-cell/product-status-cell.tsx b/packages/admin-next/dashboard/src/components/table/table-cells/product/product-status-cell/product-status-cell.tsx new file mode 100644 index 0000000000000..770a1da9321be --- /dev/null +++ b/packages/admin-next/dashboard/src/components/table/table-cells/product/product-status-cell/product-status-cell.tsx @@ -0,0 +1,31 @@ +import { ProductStatus } from "@medusajs/types" +import { useTranslation } from "react-i18next" + +import { StatusCell } from "../../common/status-cell" + +type ProductStatusCellProps = { + status: ProductStatus +} + +export const ProductStatusCell = ({ status }: ProductStatusCellProps) => { + const { t } = useTranslation() + + const [color, text] = { + draft: ["grey", t("products.productStatus.draft")], + proposed: ["orange", t("products.productStatus.proposed")], + published: ["green", t("products.productStatus.published")], + rejected: ["red", t("products.productStatus.rejected")], + }[status] as ["grey" | "orange" | "green" | "red", string] + + return {text} +} + +export const ProductStatusHeader = () => { + const { t } = useTranslation() + + return ( +
+ {t("fields.status")} +
+ ) +} diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/product/sales-channels-cell/index.ts b/packages/admin-next/dashboard/src/components/table/table-cells/product/sales-channels-cell/index.ts new file mode 100644 index 0000000000000..9beda9262a1e8 --- /dev/null +++ b/packages/admin-next/dashboard/src/components/table/table-cells/product/sales-channels-cell/index.ts @@ -0,0 +1 @@ +export * from "./sales-channels-cell" diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/product/sales-channels-cell/sales-channels-cell.tsx b/packages/admin-next/dashboard/src/components/table/table-cells/product/sales-channels-cell/sales-channels-cell.tsx new file mode 100644 index 0000000000000..bd8ea4cd6e172 --- /dev/null +++ b/packages/admin-next/dashboard/src/components/table/table-cells/product/sales-channels-cell/sales-channels-cell.tsx @@ -0,0 +1,63 @@ +import type { SalesChannel } from "@medusajs/medusa" +import { Tooltip } from "@medusajs/ui" +import { useTranslation } from "react-i18next" + +import { PlaceholderCell } from "../../common/placeholder-cell" + +type SalesChannelCellProps = { + salesChannels?: SalesChannel[] | null +} + +export const SalesChannelCell = ({ salesChannels }: SalesChannelCellProps) => { + const { t } = useTranslation() + + if (!salesChannels || !salesChannels.length) { + return + } + + if (salesChannels.length > 2) { + return ( +
+ + {salesChannels + .slice(0, 2) + .map((sc) => sc.name) + .join(", ")} + + + {salesChannels.slice(2).map((sc) => ( +
  • {sc.name}
  • + ))} + + } + > + + {t("general.plusCountMore", { + count: salesChannels.length - 2, + })} + +
    +
    + ) + } + + return ( +
    + + {salesChannels.map((sc) => sc.name).join(", ")} + +
    + ) +} + +export const SalesChannelHeader = () => { + const { t } = useTranslation() + + return ( +
    + {t("fields.salesChannels")} +
    + ) +} diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/product/variant-cell/index.ts b/packages/admin-next/dashboard/src/components/table/table-cells/product/variant-cell/index.ts new file mode 100644 index 0000000000000..3a565aa39139d --- /dev/null +++ b/packages/admin-next/dashboard/src/components/table/table-cells/product/variant-cell/index.ts @@ -0,0 +1 @@ +export * from "./variant-cell" diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/product/variant-cell/variant-cell.tsx b/packages/admin-next/dashboard/src/components/table/table-cells/product/variant-cell/variant-cell.tsx new file mode 100644 index 0000000000000..dce5d0a80f0ac --- /dev/null +++ b/packages/admin-next/dashboard/src/components/table/table-cells/product/variant-cell/variant-cell.tsx @@ -0,0 +1,34 @@ +import { ProductVariant } from "@medusajs/medusa" +import { useTranslation } from "react-i18next" + +import { PlaceholderCell } from "../../common/placeholder-cell" + +type VariantCellProps = { + variants?: ProductVariant[] | null +} + +export const VariantCell = ({ variants }: VariantCellProps) => { + const { t } = useTranslation() + + if (!variants || !variants.length) { + return + } + + return ( +
    + + {t("products.variantCount", { count: variants.length })} + +
    + ) +} + +export const VariantHeader = () => { + const { t } = useTranslation() + + return ( +
    + {t("fields.variants")} +
    + ) +} From 66d0becae6c85dd9c0a66d069bcee7a40a7d4f5e Mon Sep 17 00:00:00 2001 From: Kasper Date: Thu, 8 Feb 2024 14:15:17 +0100 Subject: [PATCH 06/20] add more Badge sizes --- .../ui/src/components/badge/badge.stories.tsx | 12 ++++ .../ui/src/components/badge/badge.tsx | 62 +++++++++++++++++-- 2 files changed, 70 insertions(+), 4 deletions(-) diff --git a/packages/design-system/ui/src/components/badge/badge.stories.tsx b/packages/design-system/ui/src/components/badge/badge.stories.tsx index 63a1103d69d78..b96f492c0269a 100644 --- a/packages/design-system/ui/src/components/badge/badge.stories.tsx +++ b/packages/design-system/ui/src/components/badge/badge.stories.tsx @@ -66,6 +66,18 @@ export const Rounded: Story = { }, } +export const XXSmall: Story = { + args: { + size: "2xsmall", + }, +} + +export const XSmall: Story = { + args: { + size: "xsmall", + }, +} + export const Small: Story = { args: { size: "small", diff --git a/packages/design-system/ui/src/components/badge/badge.tsx b/packages/design-system/ui/src/components/badge/badge.tsx index aa890285738d7..e14b2a72fa13d 100644 --- a/packages/design-system/ui/src/components/badge/badge.tsx +++ b/packages/design-system/ui/src/components/badge/badge.tsx @@ -24,18 +24,72 @@ const badgeColorVariants = cva({ }) const badgeSizeVariants = cva({ - base: "inline-flex items-center gap-x-0.5 border", + base: "inline-flex items-center gap-x-0.5 border box-border", variants: { size: { - small: "txt-compact-xsmall-plus px-1.5", - base: "txt-compact-small-plus px-2 py-0.5", - large: "txt-compact-medium-plus px-2.5 py-1", + "2xsmall": "txt-compact-xsmall-plus h-5", + xsmall: "txt-compact-xsmall-plus py-px h-6", + small: "txt-compact-xsmall-plus py-[3px] h-7", + base: "txt-compact-small-plus py-[5px] h-8", + large: "txt-compact-medium-plus py-[7px] h-10", }, rounded: { base: "rounded-md", full: "rounded-full", }, }, + compoundVariants: [ + { + size: "2xsmall", + rounded: "full", + className: "px-1.5", + }, + { + size: "2xsmall", + rounded: "base", + className: "px-1", + }, + { + size: "xsmall", + rounded: "full", + className: "px-2", + }, + { + size: "xsmall", + rounded: "base", + className: "px-1.5", + }, + { + size: "small", + rounded: "full", + className: "px-2.5", + }, + { + size: "small", + rounded: "base", + className: "px-2", + }, + { + size: "base", + rounded: "full", + className: "px-3", + }, + { + size: "base", + rounded: "base", + className: "px-2.5", + }, + { + size: "large", + rounded: "full", + className: "px-3.5", + }, + { + size: "large", + rounded: "base", + className: "px-3", + }, + ], defaultVariants: { size: "base", rounded: "base", From 88465467bd5cd9d3181966a5358510babadc00e7 Mon Sep 17 00:00:00 2001 From: Kasper Date: Thu, 8 Feb 2024 14:49:10 +0100 Subject: [PATCH 07/20] use datatable in collection product section --- .../public/locales/en/translation.json | 9 +- .../json-view-section/json-view-section.tsx | 4 +- .../data-table-root/data-table-root.tsx | 10 +- .../table-cells/product/product-cell/index.ts | 1 + .../product/product-cell/product-cell.tsx | 30 +++ .../sales-channels-cell.tsx | 6 +- .../columns/use-product-table-columns.tsx | 59 +++++ .../filters/use-product-table-filters.tsx | 190 +++++++++++++ .../table/query/use-order-table-query.tsx | 2 +- .../table/query/use-product-table-query.tsx | 68 +++++ .../dashboard/src/hooks/use-data-table.tsx | 3 + .../collection-product-section.tsx | 249 +++--------------- 12 files changed, 417 insertions(+), 214 deletions(-) create mode 100644 packages/admin-next/dashboard/src/components/table/table-cells/product/product-cell/index.ts create mode 100644 packages/admin-next/dashboard/src/components/table/table-cells/product/product-cell/product-cell.tsx create mode 100644 packages/admin-next/dashboard/src/hooks/table/columns/use-product-table-columns.tsx create mode 100644 packages/admin-next/dashboard/src/hooks/table/filters/use-product-table-filters.tsx create mode 100644 packages/admin-next/dashboard/src/hooks/table/query/use-product-table-query.tsx diff --git a/packages/admin-next/dashboard/public/locales/en/translation.json b/packages/admin-next/dashboard/public/locales/en/translation.json index 582c89d7d618a..f81def9d53b74 100644 --- a/packages/admin-next/dashboard/public/locales/en/translation.json +++ b/packages/admin-next/dashboard/public/locales/en/translation.json @@ -338,6 +338,13 @@ "region": "Region", "role": "Role", "sent": "Sent", - "salesChannels": "Sales Channels" + "salesChannels": "Sales Channels", + "product": "Product", + "createdAt": "Created at", + "updatedAt": "Updated at", + "true": "True", + "false": "False", + "giftCard": "Gift Card", + "tag": "Tag" } } diff --git a/packages/admin-next/dashboard/src/components/common/json-view-section/json-view-section.tsx b/packages/admin-next/dashboard/src/components/common/json-view-section/json-view-section.tsx index 7727b9f663ef6..18b5ffaa1cad9 100644 --- a/packages/admin-next/dashboard/src/components/common/json-view-section/json-view-section.tsx +++ b/packages/admin-next/dashboard/src/components/common/json-view-section/json-view-section.tsx @@ -28,7 +28,7 @@ export const JsonViewSection = ({ data, root }: JsonViewProps) => {
    JSON - {numberOfKeys} keys + {numberOfKeys} keys
    @@ -44,7 +44,7 @@ export const JsonViewSection = ({ data, root }: JsonViewProps) => {
    JSON - {numberOfKeys} keys + {numberOfKeys} keys
    esc diff --git a/packages/admin-next/dashboard/src/components/table/data-table/data-table-root/data-table-root.tsx b/packages/admin-next/dashboard/src/components/table/data-table/data-table-root/data-table-root.tsx index 374cc0b2be178..1fc5d08b684e0 100644 --- a/packages/admin-next/dashboard/src/components/table/data-table/data-table-root/data-table-root.tsx +++ b/packages/admin-next/dashboard/src/components/table/data-table/data-table-root/data-table-root.tsx @@ -13,7 +13,7 @@ import { NoResults } from "../../../common/empty-table-content" type BulkCommand = { label: string shortcut: string - action: (selection: Record) => void + action: (selection: Record) => Promise } export interface DataTableRootProps { @@ -94,6 +94,12 @@ export const DataTableRoot = ({ } } + const handleAction = async (action: BulkCommand["action"]) => { + await action(rowSelection).then(() => { + table.resetRowSelection() + }) + } + return (
    @@ -239,7 +245,7 @@ export const DataTableRoot = ({ command.action(rowSelection)} + action={() => handleAction(command.action)} /> {index < commands.length - 1 && } diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/product/product-cell/index.ts b/packages/admin-next/dashboard/src/components/table/table-cells/product/product-cell/index.ts new file mode 100644 index 0000000000000..d2d7b28355bed --- /dev/null +++ b/packages/admin-next/dashboard/src/components/table/table-cells/product/product-cell/index.ts @@ -0,0 +1 @@ +export * from "./product-cell" diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/product/product-cell/product-cell.tsx b/packages/admin-next/dashboard/src/components/table/table-cells/product/product-cell/product-cell.tsx new file mode 100644 index 0000000000000..d6e974993b1ca --- /dev/null +++ b/packages/admin-next/dashboard/src/components/table/table-cells/product/product-cell/product-cell.tsx @@ -0,0 +1,30 @@ +import type { Product } from "@medusajs/medusa" +import type { PricedProduct } from "@medusajs/medusa/dist/types/pricing" +import { useTranslation } from "react-i18next" + +import { Thumbnail } from "../../../../common/thumbnail" + +type ProductCellProps = { + product: Product | PricedProduct +} + +export const ProductCell = ({ product }: ProductCellProps) => { + return ( +
    +
    + +
    + {product.title} +
    + ) +} + +export const ProductHeader = () => { + const { t } = useTranslation() + + return ( +
    + {t("fields.product")} +
    + ) +} diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/product/sales-channels-cell/sales-channels-cell.tsx b/packages/admin-next/dashboard/src/components/table/table-cells/product/sales-channels-cell/sales-channels-cell.tsx index bd8ea4cd6e172..af1408c386418 100644 --- a/packages/admin-next/dashboard/src/components/table/table-cells/product/sales-channels-cell/sales-channels-cell.tsx +++ b/packages/admin-next/dashboard/src/components/table/table-cells/product/sales-channels-cell/sales-channels-cell.tsx @@ -4,11 +4,13 @@ import { useTranslation } from "react-i18next" import { PlaceholderCell } from "../../common/placeholder-cell" -type SalesChannelCellProps = { +type SalesChannelsCellProps = { salesChannels?: SalesChannel[] | null } -export const SalesChannelCell = ({ salesChannels }: SalesChannelCellProps) => { +export const SalesChannelsCell = ({ + salesChannels, +}: SalesChannelsCellProps) => { const { t } = useTranslation() if (!salesChannels || !salesChannels.length) { diff --git a/packages/admin-next/dashboard/src/hooks/table/columns/use-product-table-columns.tsx b/packages/admin-next/dashboard/src/hooks/table/columns/use-product-table-columns.tsx new file mode 100644 index 0000000000000..f8e990320961a --- /dev/null +++ b/packages/admin-next/dashboard/src/hooks/table/columns/use-product-table-columns.tsx @@ -0,0 +1,59 @@ +import { Product } from "@medusajs/medusa" +import { ColumnDef, createColumnHelper } from "@tanstack/react-table" +import { useMemo } from "react" + +import { + CollectionCell, + CollectionHeader, +} from "../../../components/table/table-cells/product/collection-cell/collection-cell" +import { + ProductCell, + ProductHeader, +} from "../../../components/table/table-cells/product/product-cell" +import { + ProductStatusCell, + ProductStatusHeader, +} from "../../../components/table/table-cells/product/product-status-cell" +import { + SalesChannelHeader, + SalesChannelsCell, +} from "../../../components/table/table-cells/product/sales-channels-cell" +import { + VariantCell, + VariantHeader, +} from "../../../components/table/table-cells/product/variant-cell" + +const columnHelper = createColumnHelper() + +export const useProductTableColumns = () => { + return useMemo( + () => [ + columnHelper.display({ + id: "product", + header: () => , + cell: ({ row }) => , + }), + columnHelper.accessor("collection", { + header: () => , + cell: ({ row }) => ( + + ), + }), + columnHelper.accessor("sales_channels", { + header: () => , + cell: ({ row }) => ( + + ), + }), + columnHelper.accessor("variants", { + header: () => , + cell: ({ row }) => , + }), + columnHelper.accessor("status", { + header: () => , + cell: ({ row }) => , + }), + ], + [] + ) as ColumnDef[] +} diff --git a/packages/admin-next/dashboard/src/hooks/table/filters/use-product-table-filters.tsx b/packages/admin-next/dashboard/src/hooks/table/filters/use-product-table-filters.tsx new file mode 100644 index 0000000000000..df9b58cb36b06 --- /dev/null +++ b/packages/admin-next/dashboard/src/hooks/table/filters/use-product-table-filters.tsx @@ -0,0 +1,190 @@ +import { + useAdminCollections, + useAdminProductCategories, + useAdminProductTags, + useAdminProductTypes, + useAdminSalesChannels, +} from "medusa-react" +import { useTranslation } from "react-i18next" +import { Filter } from "../../../components/table/data-table" + +const excludeableFields = ["sales_channel_id", "collections"] as const + +export const useProductTableFilters = ( + exclude?: (typeof excludeableFields)[number][] +) => { + const { t } = useTranslation() + + const { product_types } = useAdminProductTypes({ + limit: 1000, + offset: 0, + }) + + const { product_tags } = useAdminProductTags({ + limit: 1000, + offset: 0, + }) + + const isSalesChannelExcluded = exclude?.includes("sales_channel_id") + + const { sales_channels } = useAdminSalesChannels( + { + limit: 1000, + fields: "id,name", + expand: "", + }, + { + enabled: !isSalesChannelExcluded, + } + ) + + const { product_categories } = useAdminProductCategories({ + limit: 1000, + offset: 0, + fields: "id,name", + expand: "", + }) + + const isCollectionExcluded = exclude?.includes("collections") + + const { collections } = useAdminCollections( + { + limit: 1000, + offset: 0, + }, + { + enabled: !isCollectionExcluded, + } + ) + + let filters: Filter[] = [] + + if (product_types) { + const typeFilter: Filter = { + key: "type_id", + label: t("fields.type"), + type: "select", + multiple: true, + options: product_types.map((t) => ({ + label: t.value, + value: t.id, + })), + } + + filters = [...filters, typeFilter] + } + + if (product_tags) { + const tagFilter: Filter = { + key: "tags", + label: t("fields.tag"), + type: "select", + multiple: true, + options: product_tags.map((t) => ({ + label: t.value, + value: t.id, + })), + } + + filters = [...filters, tagFilter] + } + + if (sales_channels) { + const salesChannelFilter: Filter = { + key: "sales_channel_id", + label: t("fields.salesChannel"), + type: "select", + multiple: true, + options: sales_channels.map((s) => ({ + label: s.name, + value: s.id, + })), + } + + filters = [...filters, salesChannelFilter] + } + + if (product_categories) { + const categoryFilter: Filter = { + key: "category_id", + label: t("fields.category"), + type: "select", + multiple: true, + options: product_categories.map((c) => ({ + label: c.name, + value: c.id, + })), + } + + filters = [...filters, categoryFilter] + } + + if (collections) { + const collectionFilter: Filter = { + key: "collection_id", + label: t("fields.collection"), + type: "select", + multiple: true, + options: collections.map((c) => ({ + label: c.title, + value: c.id, + })), + } + + filters = [...filters, collectionFilter] + } + + const giftCardFilter: Filter = { + key: "is_giftcard", + label: t("fields.giftCard"), + type: "select", + options: [ + { + label: t("fields.true"), + value: "true", + }, + { + label: t("fields.false"), + value: "false", + }, + ], + } + + const statusFilter: Filter = { + key: "status", + label: t("fields.status"), + type: "select", + multiple: true, + options: [ + { + label: t("products.productStatus.draft"), + value: "draft", + }, + { + label: t("products.productStatus.proposed"), + value: "proposed", + }, + { + label: t("products.productStatus.published"), + value: "published", + }, + { + label: t("products.productStatus.rejected"), + value: "rejected", + }, + ], + } + + const dateFilters: Filter[] = [ + { label: t("fields.createdAt"), key: "created_at" }, + { label: t("fields.updatedAt"), key: "updated_at" }, + ].map((f) => ({ + key: f.key, + label: f.label, + type: "date", + })) + + filters = [...filters, statusFilter, giftCardFilter, ...dateFilters] + + return filters +} diff --git a/packages/admin-next/dashboard/src/hooks/table/query/use-order-table-query.tsx b/packages/admin-next/dashboard/src/hooks/table/query/use-order-table-query.tsx index bcf52dad14c1d..e43e6a1ad1a2b 100644 --- a/packages/admin-next/dashboard/src/hooks/table/query/use-order-table-query.tsx +++ b/packages/admin-next/dashboard/src/hooks/table/query/use-order-table-query.tsx @@ -12,7 +12,7 @@ type UseOrderTableQueryProps = { export const useOrderTableQuery = ({ prefix, - pageSize = 50, + pageSize = 20, }: UseOrderTableQueryProps) => { const queryObject = useQueryParams( [ diff --git a/packages/admin-next/dashboard/src/hooks/table/query/use-product-table-query.tsx b/packages/admin-next/dashboard/src/hooks/table/query/use-product-table-query.tsx new file mode 100644 index 0000000000000..5e0720dda514b --- /dev/null +++ b/packages/admin-next/dashboard/src/hooks/table/query/use-product-table-query.tsx @@ -0,0 +1,68 @@ +import { AdminGetProductsParams } from "@medusajs/medusa" +import { ProductStatus } from "@medusajs/types" + +import { useQueryParams } from "../../use-query-params" + +type UseProductTableQueryProps = { + prefix?: string + pageSize?: number +} + +export const useProductTableQuery = ({ + prefix, + pageSize = 20, +}: UseProductTableQueryProps) => { + const queryObject = useQueryParams( + [ + "offset", + "order", + "q", + "created_at", + "updated_at", + "sales_channel_id", + "category_id", + "collection_id", + "is_giftcard", + "tags", + "type_id", + "status", + ], + prefix + ) + + const { + offset, + sales_channel_id, + created_at, + updated_at, + category_id, + collection_id, + tags, + type_id, + is_giftcard, + status, + order, + q, + } = queryObject + + const searchParams: AdminGetProductsParams = { + limit: pageSize, + offset: offset ? Number(offset) : 0, + sales_channel_id: sales_channel_id?.split(","), + created_at: created_at ? JSON.parse(created_at) : undefined, + updated_at: updated_at ? JSON.parse(updated_at) : undefined, + category_id: category_id?.split(","), + collection_id: collection_id?.split(","), + is_giftcard: is_giftcard ? is_giftcard === "true" : undefined, + order: order, + tags: tags?.split(","), + type_id: type_id?.split(","), + status: status?.split(",") as ProductStatus[], + q, + } + + return { + searchParams, + raw: queryObject, + } +} diff --git a/packages/admin-next/dashboard/src/hooks/use-data-table.tsx b/packages/admin-next/dashboard/src/hooks/use-data-table.tsx index 5be85018f4e4c..253d2f49c8761 100644 --- a/packages/admin-next/dashboard/src/hooks/use-data-table.tsx +++ b/packages/admin-next/dashboard/src/hooks/use-data-table.tsx @@ -18,6 +18,7 @@ type UseDataTableProps = { enableRowSelection?: boolean | ((row: Row) => boolean) enablePagination?: boolean getRowId?: (original: TData, index: number) => string + meta: Record prefix?: string } @@ -29,6 +30,7 @@ export const useDataTable = ({ enablePagination = true, enableRowSelection = false, getRowId, + meta, prefix, }: UseDataTableProps) => { const [searchParams, setSearchParams] = useSearchParams() @@ -106,6 +108,7 @@ export const useDataTable = ({ ? getPaginationRowModel() : undefined, manualPagination: enablePagination ? true : undefined, + meta, }) return { table } diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-detail/components/collection-product-section/collection-product-section.tsx b/packages/admin-next/dashboard/src/routes/collections/collection-detail/components/collection-product-section/collection-product-section.tsx index fd0edc6a4b1fb..77b6b1c1bc2c7 100644 --- a/packages/admin-next/dashboard/src/routes/collections/collection-detail/components/collection-product-section/collection-product-section.tsx +++ b/packages/admin-next/dashboard/src/routes/collections/collection-detail/components/collection-product-section/collection-product-section.tsx @@ -1,47 +1,21 @@ import { PencilSquare, Trash } from "@medusajs/icons" import type { Product, ProductCollection } from "@medusajs/medusa" -import { - Button, - Checkbox, - CommandBar, - Container, - Heading, - Table, - clx, - usePrompt, -} from "@medusajs/ui" -import { - PaginationState, - RowSelectionState, - createColumnHelper, - flexRender, - getCoreRowModel, - useReactTable, -} from "@tanstack/react-table" +import { Button, Checkbox, Container, Heading, usePrompt } from "@medusajs/ui" +import { createColumnHelper } from "@tanstack/react-table" import { adminProductKeys, useAdminProducts, useAdminRemoveProductsFromCollection, } from "medusa-react" -import { useMemo, useState } from "react" +import { useMemo } from "react" import { useTranslation } from "react-i18next" -import { Link, useNavigate } from "react-router-dom" +import { Link } from "react-router-dom" import { ActionMenu } from "../../../../../components/common/action-menu" -import { - NoRecords, - NoResults, -} from "../../../../../components/common/empty-table-content" -import { - ProductAvailabilityCell, - ProductCollectionCell, - ProductStatusCell, - ProductTitleCell, - ProductVariantCell, -} from "../../../../../components/common/product-table-cells" -import { OrderBy } from "../../../../../components/filtering/order-by" -import { Query } from "../../../../../components/filtering/query" -import { LocalizedTablePagination } from "../../../../../components/localization/localized-table-pagination" -import { useQueryParams } from "../../../../../hooks/use-query-params" +import { DataTable } from "../../../../../components/table/data-table" +import { useProductTableColumns } from "../../../../../hooks/table/columns/use-product-table-columns" +import { useProductTableFilters } from "../../../../../hooks/table/filters/use-product-table-filters" +import { useProductTableQuery } from "../../../../../hooks/table/query/use-product-table-query" +import { useDataTable } from "../../../../../hooks/use-data-table" import { queryClient } from "../../../../../lib/medusa" type CollectionProductSectionProps = { @@ -54,51 +28,30 @@ export const CollectionProductSection = ({ collection, }: CollectionProductSectionProps) => { const { t } = useTranslation() - const navigate = useNavigate() - const [{ pageIndex, pageSize }, setPagination] = useState({ - pageIndex: 0, - pageSize: PAGE_SIZE, - }) - - const pagination = useMemo( - () => ({ - pageIndex, - pageSize, - }), - [pageIndex, pageSize] - ) - - const [rowSelection, setRowSelection] = useState({}) - - const params = useQueryParams(["q", "order"]) + const { searchParams, raw } = useProductTableQuery({ pageSize: PAGE_SIZE }) const { products, count, isLoading, isError, error } = useAdminProducts( { limit: PAGE_SIZE, - offset: pageIndex * PAGE_SIZE, + ...searchParams, collection_id: [collection.id], - ...params, }, { keepPreviousData: true, } ) + const filters = useProductTableFilters(["collections"]) const columns = useColumns() - const table = useReactTable({ + const { table } = useDataTable({ data: (products ?? []) as Product[], columns, - pageCount: Math.ceil((count ?? 0) / PAGE_SIZE), - state: { - pagination, - rowSelection, - }, getRowId: (row) => row.id, - onPaginationChange: setPagination, - onRowSelectionChange: setRowSelection, - getCoreRowModel: getCoreRowModel(), - manualPagination: true, + count, + enablePagination: true, + enableRowSelection: true, + pageSize: PAGE_SIZE, meta: { collectionId: collection.id, }, @@ -107,8 +60,8 @@ export const CollectionProductSection = ({ const prompt = usePrompt() const { mutateAsync } = useAdminRemoveProductsFromCollection(collection.id) - const handleRemove = async () => { - const ids = Object.keys(rowSelection) + const handleRemove = async (selection: Record) => { + const ids = Object.keys(selection) const res = await prompt({ title: t("general.areYouSure"), @@ -130,23 +83,17 @@ export const CollectionProductSection = ({ { onSuccess: () => { queryClient.invalidateQueries(adminProductKeys.lists()) - setRowSelection({}) }, } ) } - const noRecords = - !isLoading && - products?.length === 0 && - !Object.values(params).filter((v) => v).length - if (isError) { throw error } return ( - +
    {t("products.domain")} @@ -155,99 +102,26 @@ export const CollectionProductSection = ({
    - {!noRecords && ( -
    -
    -
    - - -
    -
    - )} - {noRecords ? ( - - ) : ( -
    - {!isLoading && !products?.length ? ( -
    - -
    - ) : ( - - - {table.getHeaderGroups().map((headerGroup) => { - return ( - - {headerGroup.headers.map((header) => { - return ( - - {flexRender( - header.column.columnDef.header, - header.getContext() - )} - - ) - })} - - ) - })} - - - {table.getRowModel().rows.map((row) => ( - navigate(`/products/${row.original.id}`)} - > - {row.getVisibleCells().map((cell) => ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} - - ))} - - ))} - -
    - )} - - - - - {t("general.countSelected", { - count: Object.keys(rowSelection).length, - })} - - - - - -
    - )} + `/products/${original.id}`} + count={count} + filters={filters} + isLoading={isLoading} + orderBy={["title", "created_at", "updated_at"]} + queryObject={raw} + commands={[ + { + action: handleRemove, + label: t("general.remove"), + shortcut: "r", + }, + ]} + />
    ) } @@ -311,7 +185,7 @@ const ProductActions = ({ const columnHelper = createColumnHelper() const useColumns = () => { - const { t } = useTranslation() + const columns = useProductTableColumns() return useMemo( () => [ @@ -343,44 +217,7 @@ const useColumns = () => { ) }, }), - columnHelper.accessor("title", { - header: t("fields.title"), - cell: ({ row }) => { - return - }, - }), - columnHelper.accessor("collection", { - header: t("fields.collection"), - cell: (cell) => { - const collection = cell.getValue() - - return - }, - }), - columnHelper.accessor("sales_channels", { - header: t("fields.availability"), - cell: (cell) => { - const salesChannels = cell.getValue() - - return - }, - }), - columnHelper.accessor("variants", { - header: t("fields.variants"), - cell: (cell) => { - const variants = cell.getValue() - - return - }, - }), - columnHelper.accessor("status", { - header: t("fields.status"), - cell: (cell) => { - const value = cell.getValue() - - return - }, - }), + ...columns, columnHelper.display({ id: "actions", cell: ({ row, table }) => { @@ -397,6 +234,6 @@ const useColumns = () => { }, }), ], - [t] + [columns] ) } From 17a7263cba6d1b99451885efa3fe62c7fc497715 Mon Sep 17 00:00:00 2001 From: Kasper Date: Thu, 8 Feb 2024 14:50:25 +0100 Subject: [PATCH 08/20] update style of grid --- .../collection-general-section.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-detail/components/collection-general-section/collection-general-section.tsx b/packages/admin-next/dashboard/src/routes/collections/collection-detail/components/collection-general-section/collection-general-section.tsx index c9ed9ee84f70a..8af7e29f5b160 100644 --- a/packages/admin-next/dashboard/src/routes/collections/collection-detail/components/collection-general-section/collection-general-section.tsx +++ b/packages/admin-next/dashboard/src/routes/collections/collection-detail/components/collection-general-section/collection-general-section.tsx @@ -33,7 +33,7 @@ export const CollectionGeneralSection = ({ } return ( - +
    {collection.title}
    -
    +
    {t("fields.handle")} /{collection.handle}
    -
    +
    {t("fields.products")} From d6ac7a6291f499db113a8a0cf4a43a878d7e27be Mon Sep 17 00:00:00 2001 From: Kasper Date: Thu, 8 Feb 2024 14:58:56 +0100 Subject: [PATCH 09/20] add gc list table --- .../public/locales/en/translation.json | 5 +- .../dashboard/src/hooks/use-data-table.tsx | 2 +- .../router-provider/router-provider.tsx | 4 +- .../src/routes/gift-cards/details/details.tsx | 3 - .../src/routes/gift-cards/details/index.ts | 1 - .../gift-card-detail/gift-card-detail.tsx | 3 + .../gift-cards/gift-card-detail/index.ts | 1 + .../gift-card-list-table.tsx | 68 +++++++++++++++++ .../components/gift-card-list-table/index.ts | 1 + .../use-gift-card-table-columns.tsx | 75 +++++++++++++++++++ .../use-gift-card-table-filters.tsx | 21 ++++++ .../use-gift-card-table-query.tsx | 19 +++++ .../gift-card-list/gift-card-list.tsx | 9 +++ .../routes/gift-cards/gift-card-list/index.ts | 1 + .../src/routes/gift-cards/list/index.ts | 1 - .../src/routes/gift-cards/list/list.tsx | 9 --- 16 files changed, 205 insertions(+), 18 deletions(-) delete mode 100644 packages/admin-next/dashboard/src/routes/gift-cards/details/details.tsx delete mode 100644 packages/admin-next/dashboard/src/routes/gift-cards/details/index.ts create mode 100644 packages/admin-next/dashboard/src/routes/gift-cards/gift-card-detail/gift-card-detail.tsx create mode 100644 packages/admin-next/dashboard/src/routes/gift-cards/gift-card-detail/index.ts create mode 100644 packages/admin-next/dashboard/src/routes/gift-cards/gift-card-list/components/gift-card-list-table/gift-card-list-table.tsx create mode 100644 packages/admin-next/dashboard/src/routes/gift-cards/gift-card-list/components/gift-card-list-table/index.ts create mode 100644 packages/admin-next/dashboard/src/routes/gift-cards/gift-card-list/components/gift-card-list-table/use-gift-card-table-columns.tsx create mode 100644 packages/admin-next/dashboard/src/routes/gift-cards/gift-card-list/components/gift-card-list-table/use-gift-card-table-filters.tsx create mode 100644 packages/admin-next/dashboard/src/routes/gift-cards/gift-card-list/components/gift-card-list-table/use-gift-card-table-query.tsx create mode 100644 packages/admin-next/dashboard/src/routes/gift-cards/gift-card-list/gift-card-list.tsx create mode 100644 packages/admin-next/dashboard/src/routes/gift-cards/gift-card-list/index.ts delete mode 100644 packages/admin-next/dashboard/src/routes/gift-cards/list/index.ts delete mode 100644 packages/admin-next/dashboard/src/routes/gift-cards/list/list.tsx diff --git a/packages/admin-next/dashboard/public/locales/en/translation.json b/packages/admin-next/dashboard/public/locales/en/translation.json index f81def9d53b74..741df70f7ea7f 100644 --- a/packages/admin-next/dashboard/public/locales/en/translation.json +++ b/packages/admin-next/dashboard/public/locales/en/translation.json @@ -345,6 +345,9 @@ "true": "True", "false": "False", "giftCard": "Gift Card", - "tag": "Tag" + "tag": "Tag", + "dateIssued": "Date issued", + "balance": "Balance", + "originalAmount": "Original amount" } } diff --git a/packages/admin-next/dashboard/src/hooks/use-data-table.tsx b/packages/admin-next/dashboard/src/hooks/use-data-table.tsx index 253d2f49c8761..072b7fc1db56e 100644 --- a/packages/admin-next/dashboard/src/hooks/use-data-table.tsx +++ b/packages/admin-next/dashboard/src/hooks/use-data-table.tsx @@ -18,7 +18,7 @@ type UseDataTableProps = { enableRowSelection?: boolean | ((row: Row) => boolean) enablePagination?: boolean getRowId?: (original: TData, index: number) => string - meta: Record + meta?: Record prefix?: string } diff --git a/packages/admin-next/dashboard/src/providers/router-provider/router-provider.tsx b/packages/admin-next/dashboard/src/providers/router-provider/router-provider.tsx index d0e47ebaa5c37..25113b39384fe 100644 --- a/packages/admin-next/dashboard/src/providers/router-provider/router-provider.tsx +++ b/packages/admin-next/dashboard/src/providers/router-provider/router-provider.tsx @@ -274,11 +274,11 @@ const router = createBrowserRouter([ children: [ { index: true, - lazy: () => import("../../routes/gift-cards/list"), + lazy: () => import("../../routes/gift-cards/gift-card-list"), }, { path: ":id", - lazy: () => import("../../routes/gift-cards/details"), + lazy: () => import("../../routes/gift-cards/gift-card-detail"), }, ], }, diff --git a/packages/admin-next/dashboard/src/routes/gift-cards/details/details.tsx b/packages/admin-next/dashboard/src/routes/gift-cards/details/details.tsx deleted file mode 100644 index a3fb2af4c76d7..0000000000000 --- a/packages/admin-next/dashboard/src/routes/gift-cards/details/details.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export const GiftCardDetails = () => { - return
    Gift Card Details
    ; -}; diff --git a/packages/admin-next/dashboard/src/routes/gift-cards/details/index.ts b/packages/admin-next/dashboard/src/routes/gift-cards/details/index.ts deleted file mode 100644 index efdfebaf78488..0000000000000 --- a/packages/admin-next/dashboard/src/routes/gift-cards/details/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { GiftCardDetails as Component } from "./details"; diff --git a/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-detail/gift-card-detail.tsx b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-detail/gift-card-detail.tsx new file mode 100644 index 0000000000000..d99a27df3350c --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-detail/gift-card-detail.tsx @@ -0,0 +1,3 @@ +export const GiftCardDetail = () => { + return
    Gift Card Details
    +} diff --git a/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-detail/index.ts b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-detail/index.ts new file mode 100644 index 0000000000000..1e4288a8114d3 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-detail/index.ts @@ -0,0 +1 @@ +export { GiftCardDetail as Component } from "./gift-card-detail" diff --git a/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-list/components/gift-card-list-table/gift-card-list-table.tsx b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-list/components/gift-card-list-table/gift-card-list-table.tsx new file mode 100644 index 0000000000000..6105ca98a9ea7 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-list/components/gift-card-list-table/gift-card-list-table.tsx @@ -0,0 +1,68 @@ +import { Button, Container, Heading } from "@medusajs/ui" +import { useAdminGiftCards } from "medusa-react" +import { useTranslation } from "react-i18next" +import { Link } from "react-router-dom" +import { DataTable } from "../../../../../components/table/data-table" +import { useDataTable } from "../../../../../hooks/use-data-table" +import { useGiftCardTableColumns } from "./use-gift-card-table-columns" +import { useGiftCardTableFilters } from "./use-gift-card-table-filters" +import { useGiftCardTableQuery } from "./use-gift-card-table-query" + +const PAGE_SIZE = 20 + +export const GiftCardListTable = () => { + const { t } = useTranslation() + const { searchParams, raw } = useGiftCardTableQuery({ + pageSize: PAGE_SIZE, + }) + + const { gift_cards, count, isError, error, isLoading } = useAdminGiftCards( + { + ...searchParams, + }, + { + keepPreviousData: true, + } + ) + + const filters = useGiftCardTableFilters() + const columns = useGiftCardTableColumns() + + const { table } = useDataTable({ + data: gift_cards ?? [], + columns, + enablePagination: true, + count, + pageSize: PAGE_SIZE, + }) + + if (isError) { + throw error + } + + return ( + +
    + {t("giftCards.domain")} +
    + +
    +
    + `/gift-cards/${row.original.id}`} + filters={filters} + count={count} + search + isLoading={isLoading} + rowCount={PAGE_SIZE} + orderBy={["created_at", "updated_at"]} + queryObject={raw} + /> +
    + ) +} diff --git a/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-list/components/gift-card-list-table/index.ts b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-list/components/gift-card-list-table/index.ts new file mode 100644 index 0000000000000..04cfe3662adcd --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-list/components/gift-card-list-table/index.ts @@ -0,0 +1 @@ +export * from "./gift-card-list-table" diff --git a/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-list/components/gift-card-list-table/use-gift-card-table-columns.tsx b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-list/components/gift-card-list-table/use-gift-card-table-columns.tsx new file mode 100644 index 0000000000000..24139bab7e07a --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-list/components/gift-card-list-table/use-gift-card-table-columns.tsx @@ -0,0 +1,75 @@ +import { GiftCard } from "@medusajs/medusa" +import { Badge } from "@medusajs/ui" +import { ColumnDef, createColumnHelper } from "@tanstack/react-table" +import { useMemo } from "react" +import { useTranslation } from "react-i18next" +import { DateCell } from "../../../../../components/table/table-cells/common/date-cell" +import { MoneyAmountCell } from "../../../../../components/table/table-cells/common/money-amount-cell" +import { StatusCell } from "../../../../../components/table/table-cells/common/status-cell" +import { DisplayIdCell } from "../../../../../components/table/table-cells/order/display-id-cell" + +const columnHelper = createColumnHelper() + +export const useGiftCardTableColumns = () => { + const { t } = useTranslation() + + return useMemo( + () => [ + columnHelper.accessor("code", { + header: t("fields.code"), + cell: ({ getValue }) => { + return {getValue()} + }, + }), + columnHelper.accessor("order.display_id", { + header: t("fields.order"), + cell: ({ getValue }) => { + return + }, + }), + columnHelper.accessor("region", { + header: t("fields.region"), + cell: ({ row }) => { + return row.original.region.name + }, + }), + columnHelper.accessor("is_disabled", { + header: t("fields.status"), + cell: ({ getValue }) => { + const isDisabled = getValue() + + return ( + + {isDisabled ? t("general.disabled") : t("general.enabled")} + + ) + }, + }), + columnHelper.accessor("created_at", { + header: t("fields.dateIssued"), + cell: ({ getValue }) => { + return + }, + }), + columnHelper.accessor("value", { + header: t("fields.originalAmount"), + cell: ({ getValue, row }) => { + const currencyCode = row.original.region.currency_code + const value = getValue() + + return + }, + }), + columnHelper.accessor("balance", { + header: t("fields.balance"), + cell: ({ getValue, row }) => { + const currencyCode = row.original.region.currency_code + const value = getValue() + + return + }, + }), + ], + [t] + ) as ColumnDef[] +} diff --git a/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-list/components/gift-card-list-table/use-gift-card-table-filters.tsx b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-list/components/gift-card-list-table/use-gift-card-table-filters.tsx new file mode 100644 index 0000000000000..b01f57d1fd86b --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-list/components/gift-card-list-table/use-gift-card-table-filters.tsx @@ -0,0 +1,21 @@ +import { useTranslation } from "react-i18next" +import { Filter } from "../../../../../components/table/data-table" + +export const useGiftCardTableFilters = () => { + const { t } = useTranslation() + + let filters: Filter[] = [] + + const dateFilters: Filter[] = [ + { label: t("fields.createdAt"), key: "created_at" }, + { label: t("fields.updatedAt"), key: "updated_at" }, + ].map((f) => ({ + key: f.key, + label: f.label, + type: "date", + })) + + filters = [...filters, ...dateFilters] + + return filters +} diff --git a/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-list/components/gift-card-list-table/use-gift-card-table-query.tsx b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-list/components/gift-card-list-table/use-gift-card-table-query.tsx new file mode 100644 index 0000000000000..c5f9e1a436db7 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-list/components/gift-card-list-table/use-gift-card-table-query.tsx @@ -0,0 +1,19 @@ +import { AdminGetGiftCardsParams } from "@medusajs/medusa" +import { useQueryParams } from "../../../../../hooks/use-query-params" + +export const useGiftCardTableQuery = ({ pageSize }: { pageSize: number }) => { + const queryObject = useQueryParams(["offset", "q"]) + + const { offset, q } = queryObject + + const searchParams: AdminGetGiftCardsParams = { + limit: pageSize, + offset: offset ? Number(offset) : 0, + q, + } + + return { + searchParams, + raw: queryObject, + } +} diff --git a/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-list/gift-card-list.tsx b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-list/gift-card-list.tsx new file mode 100644 index 0000000000000..fccb1e9388f5b --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-list/gift-card-list.tsx @@ -0,0 +1,9 @@ +import { GiftCardListTable } from "./components/gift-card-list-table" + +export const GiftCardList = () => { + return ( +
    + +
    + ) +} diff --git a/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-list/index.ts b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-list/index.ts new file mode 100644 index 0000000000000..45b40904e9821 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-list/index.ts @@ -0,0 +1 @@ +export { GiftCardList as Component } from "./gift-card-list" diff --git a/packages/admin-next/dashboard/src/routes/gift-cards/list/index.ts b/packages/admin-next/dashboard/src/routes/gift-cards/list/index.ts deleted file mode 100644 index b28d751cab5f8..0000000000000 --- a/packages/admin-next/dashboard/src/routes/gift-cards/list/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { GiftCardList as Component } from "./list"; diff --git a/packages/admin-next/dashboard/src/routes/gift-cards/list/list.tsx b/packages/admin-next/dashboard/src/routes/gift-cards/list/list.tsx deleted file mode 100644 index bde0081772b36..0000000000000 --- a/packages/admin-next/dashboard/src/routes/gift-cards/list/list.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { Container, Heading } from "@medusajs/ui"; - -export const GiftCardList = () => { - return ( - - Gift Card List - - ); -}; From b1e84a888b43c252f608b7ad4e21ac9a66777c3c Mon Sep 17 00:00:00 2001 From: Kasper Date: Thu, 8 Feb 2024 15:07:27 +0100 Subject: [PATCH 10/20] add support for order on gc list endpoint --- .../src/lib/models/AdminGetGiftCardsParams.ts | 4 ++++ .../api/routes/admin/gift-cards/list-gift-cards.ts | 12 ++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/generated/client-types/src/lib/models/AdminGetGiftCardsParams.ts b/packages/generated/client-types/src/lib/models/AdminGetGiftCardsParams.ts index 4c2505c73e5bf..1a6cd49cb5765 100644 --- a/packages/generated/client-types/src/lib/models/AdminGetGiftCardsParams.ts +++ b/packages/generated/client-types/src/lib/models/AdminGetGiftCardsParams.ts @@ -16,4 +16,8 @@ export interface AdminGetGiftCardsParams { * a term to search gift cards' code or display ID */ q?: string + /** + * A gift card field to sort-order the retrieved gift cards by. + */ + order?: string } diff --git a/packages/medusa/src/api/routes/admin/gift-cards/list-gift-cards.ts b/packages/medusa/src/api/routes/admin/gift-cards/list-gift-cards.ts index ac1fed962e1c6..1ef305856b39c 100644 --- a/packages/medusa/src/api/routes/admin/gift-cards/list-gift-cards.ts +++ b/packages/medusa/src/api/routes/admin/gift-cards/list-gift-cards.ts @@ -1,10 +1,10 @@ import { IsInt, IsOptional, IsString } from "class-validator" -import { GiftCardService } from "../../../../services" import { Type } from "class-transformer" import { pickBy } from "lodash" -import { validator } from "../../../../utils/validator" import { isDefined } from "medusa-core-utils" +import { GiftCardService } from "../../../../services" +import { validator } from "../../../../utils/validator" /** * @oas [get] /admin/gift-cards @@ -16,6 +16,7 @@ import { isDefined } from "medusa-core-utils" * - (query) offset=0 {number} The number of gift cards to skip when retrieving the gift cards. * - (query) limit=50 {number} Limit the number of gift cards returned. * - (query) q {string} a term to search gift cards' code or display ID + * - (query) order {string} A gift card field to sort-order the retrieved gift cards by. * x-codegen: * method: list * queryParams: AdminGetGiftCardsParams @@ -135,4 +136,11 @@ export class AdminGetGiftCardsParams { @IsOptional() @IsString() q?: string + + /** + * The field to sort the data by. By default, the sort order is ascending. To change the order to descending, prefix the field name with `-`. + */ + @IsOptional() + @IsString() + order?: string } From 84c78e5762c153c2f300ce54a8dcbb04b1a16cb3 Mon Sep 17 00:00:00 2001 From: Kasper Date: Thu, 8 Feb 2024 15:18:14 +0100 Subject: [PATCH 11/20] add gc details page --- .../public/locales/en/translation.json | 5 +- .../gift-card-general-section.tsx | 125 ++++++++++++++++++ .../gift-card-general-section/index.ts | 1 + .../gift-card-detail/gift-card-detail.tsx | 24 +++- 4 files changed, 153 insertions(+), 2 deletions(-) create mode 100644 packages/admin-next/dashboard/src/routes/gift-cards/gift-card-detail/components/gift-card-general-section/gift-card-general-section.tsx create mode 100644 packages/admin-next/dashboard/src/routes/gift-cards/gift-card-detail/components/gift-card-general-section/index.ts diff --git a/packages/admin-next/dashboard/public/locales/en/translation.json b/packages/admin-next/dashboard/public/locales/en/translation.json index 741df70f7ea7f..ccbc3c186475f 100644 --- a/packages/admin-next/dashboard/public/locales/en/translation.json +++ b/packages/admin-next/dashboard/public/locales/en/translation.json @@ -82,7 +82,8 @@ "domain": "Inventory" }, "giftCards": { - "domain": "Gift Cards" + "domain": "Gift Cards", + "deleteGiftCardWarning": "You are about to delete the gift card {{code}}. This action cannot be undone." }, "customers": { "domain": "Customers", @@ -347,6 +348,8 @@ "giftCard": "Gift Card", "tag": "Tag", "dateIssued": "Date issued", + "issuedDate": "Issued date", + "expiryDate": "Expiry date", "balance": "Balance", "originalAmount": "Original amount" } diff --git a/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-detail/components/gift-card-general-section/gift-card-general-section.tsx b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-detail/components/gift-card-general-section/gift-card-general-section.tsx new file mode 100644 index 0000000000000..91dc4c74cf619 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-detail/components/gift-card-general-section/gift-card-general-section.tsx @@ -0,0 +1,125 @@ +import { PencilSquare, Trash } from "@medusajs/icons" +import { GiftCard } from "@medusajs/medusa" +import { + Container, + Copy, + Heading, + StatusBadge, + Text, + usePrompt, +} from "@medusajs/ui" +import format from "date-fns/format" +import { useAdminDeleteGiftCard } from "medusa-react" +import { useTranslation } from "react-i18next" +import { useNavigate } from "react-router-dom" + +import { ActionMenu } from "../../../../../components/common/action-menu" + +type GiftCardGeneralSectionProps = { + giftCard: GiftCard +} + +export const GiftCardGeneralSection = ({ + giftCard, +}: GiftCardGeneralSectionProps) => { + const { t } = useTranslation() + const prompt = usePrompt() + const navigate = useNavigate() + + const { mutateAsync } = useAdminDeleteGiftCard(giftCard.id) + + const handleDelete = async () => { + const res = await prompt({ + title: t("general.areYouSure"), + description: t("giftCards.deleteGiftCardWarning", { + code: giftCard.code, + }), + confirmText: t("general.delete"), + cancelText: t("general.cancel"), + }) + + if (!res) { + return + } + + await mutateAsync(undefined, { + onSuccess: () => { + navigate("..") + }, + }) + } + + return ( + +
    +
    + {giftCard.code} + +
    +
    + + {giftCard.is_disabled + ? t("general.disabled") + : t("general.enabled")} + + , + label: t("general.edit"), + to: "edit", + }, + ], + }, + { + actions: [ + { + label: t("general.delete"), + icon: , + onClick: handleDelete, + }, + ], + }, + ]} + /> +
    +
    +
    + + {t("fields.region")} + + + {giftCard.region.name} + +
    +
    + + {t("fields.order")} + + + {giftCard.order?.display_id ? `#${giftCard.order.display_id}` : "-"} + +
    +
    + + {t("fields.issuedDate")} + + + {format(new Date(giftCard.created_at), "dd MMM yyyy")} + +
    +
    + + {t("fields.expiryDate")} + + + {giftCard.ends_at + ? format(new Date(giftCard.ends_at), "dd MMM yyyy") + : "Doesn't expire"} + +
    +
    + ) +} diff --git a/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-detail/components/gift-card-general-section/index.ts b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-detail/components/gift-card-general-section/index.ts new file mode 100644 index 0000000000000..51d282413ea2c --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-detail/components/gift-card-general-section/index.ts @@ -0,0 +1 @@ +export * from "./gift-card-general-section" diff --git a/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-detail/gift-card-detail.tsx b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-detail/gift-card-detail.tsx index d99a27df3350c..a45cd27b8c914 100644 --- a/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-detail/gift-card-detail.tsx +++ b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-detail/gift-card-detail.tsx @@ -1,3 +1,25 @@ +import { useAdminGiftCard } from "medusa-react" +import { useParams } from "react-router-dom" +import { JsonViewSection } from "../../../components/common/json-view-section" +import { GiftCardGeneralSection } from "./components/gift-card-general-section" + export const GiftCardDetail = () => { - return
    Gift Card Details
    + const { id } = useParams() + + const { gift_card, isLoading, isError, error } = useAdminGiftCard(id!) + + if (isLoading || !gift_card) { + return null + } + + if (isError) { + throw error + } + + return ( +
    + + +
    + ) } From 21f40fdd29e4df4cad9f836d76f0df817b76f04e Mon Sep 17 00:00:00 2001 From: Kasper Date: Thu, 8 Feb 2024 15:24:19 +0100 Subject: [PATCH 12/20] add expired status --- .../public/locales/en/translation.json | 1 + .../gift-card-general-section.tsx | 27 +++++++++++++++---- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/packages/admin-next/dashboard/public/locales/en/translation.json b/packages/admin-next/dashboard/public/locales/en/translation.json index ccbc3c186475f..1958cfd8854ec 100644 --- a/packages/admin-next/dashboard/public/locales/en/translation.json +++ b/packages/admin-next/dashboard/public/locales/en/translation.json @@ -30,6 +30,7 @@ "details": "Details", "enabled": "Enabled", "disabled": "Disabled", + "expired": "Expired", "active": "Active", "revoke": "Revoke", "revoked": "Revoked", diff --git a/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-detail/components/gift-card-general-section/gift-card-general-section.tsx b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-detail/components/gift-card-general-section/gift-card-general-section.tsx index 91dc4c74cf619..43389932a7bb3 100644 --- a/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-detail/components/gift-card-general-section/gift-card-general-section.tsx +++ b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-detail/components/gift-card-general-section/gift-card-general-section.tsx @@ -49,6 +49,27 @@ export const GiftCardGeneralSection = ({ }) } + let color: "green" | "red" + + if ( + giftCard.is_disabled || + (giftCard.ends_at && new Date(giftCard.ends_at) < new Date()) + ) { + color = "red" + } else { + color = "green" + } + + let text: string = t("general.enabled") + + if (giftCard.is_disabled) { + text = t("general.disabled") + } + + if (giftCard.ends_at && new Date(giftCard.ends_at) < new Date()) { + text = t("general.expired") + } + return (
    @@ -57,11 +78,7 @@ export const GiftCardGeneralSection = ({
    - - {giftCard.is_disabled - ? t("general.disabled") - : t("general.enabled")} - + {text} Date: Thu, 8 Feb 2024 17:13:50 +0100 Subject: [PATCH 13/20] progress --- .../public/locales/en/translation.json | 9 +- .../router-provider/router-provider.tsx | 9 +- .../edit-collection-form.tsx | 2 +- .../gift-card-general-section.tsx | 2 +- .../gift-card-detail/gift-card-detail.tsx | 3 +- .../edit-gift-card-form.tsx | 271 ++++++++++++++++++ .../components/edit-gift-card-form/index.ts | 1 + .../gift-card-edit/gift-card-edit.tsx | 39 +++ .../routes/gift-cards/gift-card-edit/index.ts | 1 + .../currency-input/currency-input.tsx | 4 + 10 files changed, 336 insertions(+), 5 deletions(-) create mode 100644 packages/admin-next/dashboard/src/routes/gift-cards/gift-card-edit/components/edit-gift-card-form/edit-gift-card-form.tsx create mode 100644 packages/admin-next/dashboard/src/routes/gift-cards/gift-card-edit/components/edit-gift-card-form/index.ts create mode 100644 packages/admin-next/dashboard/src/routes/gift-cards/gift-card-edit/gift-card-edit.tsx create mode 100644 packages/admin-next/dashboard/src/routes/gift-cards/gift-card-edit/index.ts diff --git a/packages/admin-next/dashboard/public/locales/en/translation.json b/packages/admin-next/dashboard/public/locales/en/translation.json index 1958cfd8854ec..89d8f0b5e2db4 100644 --- a/packages/admin-next/dashboard/public/locales/en/translation.json +++ b/packages/admin-next/dashboard/public/locales/en/translation.json @@ -84,7 +84,14 @@ }, "giftCards": { "domain": "Gift Cards", - "deleteGiftCardWarning": "You are about to delete the gift card {{code}}. This action cannot be undone." + "editGiftCard": "Edit Gift Card", + "deleteGiftCardWarning": "You are about to delete the gift card {{code}}. This action cannot be undone.", + "balanceHigherThanValue": "The balance cannot be higher than the original amount.", + "balanceLowerThanZero": "The balance cannot be negative.", + "expiryDateHint": "Countries have different laws regarding gift card expiry dates. Make sure to check local regulations before setting an expiry date.", + "enabledHint": "Specify if the gift card is enabled or disabled.", + "currentBalance": "Current balance", + "initialBalance": "Initial balance" }, "customers": { "domain": "Customers", diff --git a/packages/admin-next/dashboard/src/providers/router-provider/router-provider.tsx b/packages/admin-next/dashboard/src/providers/router-provider/router-provider.tsx index 25113b39384fe..05a9235d31064 100644 --- a/packages/admin-next/dashboard/src/providers/router-provider/router-provider.tsx +++ b/packages/admin-next/dashboard/src/providers/router-provider/router-provider.tsx @@ -273,12 +273,19 @@ const router = createBrowserRouter([ }, children: [ { - index: true, + path: "", lazy: () => import("../../routes/gift-cards/gift-card-list"), }, { path: ":id", lazy: () => import("../../routes/gift-cards/gift-card-detail"), + children: [ + { + path: "edit", + lazy: () => + import("../../routes/gift-cards/gift-card-edit"), + }, + ], }, ], }, diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-edit/components/edit-collection-form/edit-collection-form.tsx b/packages/admin-next/dashboard/src/routes/collections/collection-edit/components/edit-collection-form/edit-collection-form.tsx index 3b720a8db37a6..32ae884cea0d3 100644 --- a/packages/admin-next/dashboard/src/routes/collections/collection-edit/components/edit-collection-form/edit-collection-form.tsx +++ b/packages/admin-next/dashboard/src/routes/collections/collection-edit/components/edit-collection-form/edit-collection-form.tsx @@ -83,7 +83,7 @@ export const EditCollectionForm = ({
    -
    +
    {giftCard.ends_at ? format(new Date(giftCard.ends_at), "dd MMM yyyy") - : "Doesn't expire"} + : "-"}
    diff --git a/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-detail/gift-card-detail.tsx b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-detail/gift-card-detail.tsx index a45cd27b8c914..36664d5a0bf56 100644 --- a/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-detail/gift-card-detail.tsx +++ b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-detail/gift-card-detail.tsx @@ -1,5 +1,5 @@ import { useAdminGiftCard } from "medusa-react" -import { useParams } from "react-router-dom" +import { Outlet, useParams } from "react-router-dom" import { JsonViewSection } from "../../../components/common/json-view-section" import { GiftCardGeneralSection } from "./components/gift-card-general-section" @@ -20,6 +20,7 @@ export const GiftCardDetail = () => {
    +
    ) } diff --git a/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-edit/components/edit-gift-card-form/edit-gift-card-form.tsx b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-edit/components/edit-gift-card-form/edit-gift-card-form.tsx new file mode 100644 index 0000000000000..2f548b39ce2aa --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-edit/components/edit-gift-card-form/edit-gift-card-form.tsx @@ -0,0 +1,271 @@ +import { zodResolver } from "@hookform/resolvers/zod" +import { GiftCard } from "@medusajs/medusa" +import { + Button, + CurrencyInput, + DatePicker, + Drawer, + Select, + Switch, + Text, +} from "@medusajs/ui" +import * as Collapsible from "@radix-ui/react-collapsible" +import { useAdminRegions, useAdminUpdateGiftCard } from "medusa-react" +import { useEffect, useState } from "react" +import { useForm } from "react-hook-form" +import { useTranslation } from "react-i18next" +import * as zod from "zod" + +import { Form } from "../../../../../components/common/form" +import { currencies } from "../../../../../lib/currencies" +import { isAxiosError } from "../../../../../lib/is-axios-error" +import { + getDbAmount, + getPresentationalAmount, +} from "../../../../../lib/money-amount-helpers" + +type EditGiftCardFormProps = { + giftCard: GiftCard + onSuccessfulSubmit: () => void + subscribe: (state: boolean) => void +} + +const EditGiftCardSchema = zod.object({ + region_id: zod.string(), + balance: zod.string(), + is_enabled: zod.boolean(), + ends_at: zod.date().optional(), +}) + +export const EditGiftCardForm = ({ + giftCard, + onSuccessfulSubmit, + subscribe, +}: EditGiftCardFormProps) => { + const { t } = useTranslation() + const [showDateFields, setShowDateFields] = useState(!!giftCard.ends_at) + const form = useForm>({ + defaultValues: { + region_id: giftCard.region_id, + balance: getPresentationalAmount( + giftCard.balance, + giftCard.region.currency_code + ).toString(), + is_enabled: !giftCard.is_disabled, + }, + resolver: zodResolver(EditGiftCardSchema), + }) + + const { + formState: { isDirty }, + } = form + + useEffect(() => { + subscribe(isDirty) + }, [isDirty, subscribe]) + + const { regions } = useAdminRegions({ + limit: 1000, + fields: "id,name", + }) + + const { mutateAsync, isLoading } = useAdminUpdateGiftCard(giftCard.id) + + const handleSubmit = form.handleSubmit(async (data) => { + const newBalance = getDbAmount( + parseFloat(data.balance), + giftCard.region.currency_code + ) + + if (newBalance > giftCard.value) { + form.setError("balance", { + type: "manual", + message: t("giftCards.balanceHigherThanValue"), + }) + return + } + + if (newBalance < 0) { + form.setError("balance", { + type: "manual", + message: t("giftCards.balanceLowerThanZero"), + }) + return + } + + await mutateAsync( + { + region_id: data.region_id, + balance: getDbAmount( + parseFloat(data.balance), + giftCard.region.currency_code + ), + is_disabled: !data.is_enabled, + }, + { + onSuccess: () => { + onSuccessfulSubmit() + }, + onError: (error) => { + if (isAxiosError(error)) { + form.setError("balance", { + type: "manual", + message: error.response?.data.message, + }) + } + }, + } + ) + }) + + return ( +
    + + +
    + { + return ( + + {t("fields.balance")} + + + + + + ) + }} + /> + { + return ( + + {t("fields.region")} + + + + + + ) + }} + /> +
    + + { + return ( + +
    + {t("general.enabled")} + + + +
    + + {t("giftCards.enabledHint")} + + +
    + ) + }} + /> +
    + +
    +
    + + {t("fields.expiryDate")} + + +
    + + {t("giftCards.expiryDateHint")} + +
    + +
    + { + return ( + + + { + onChange(val) + }} + placeholder="DD/MM/YYYY HH:MM" + showTimePicker + {...field} + /> + + + ) + }} + /> +
    +
    +
    +
    +
    + +
    + + + + +
    +
    +
    + + ) +} diff --git a/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-edit/components/edit-gift-card-form/index.ts b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-edit/components/edit-gift-card-form/index.ts new file mode 100644 index 0000000000000..4a64fef913c2d --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-edit/components/edit-gift-card-form/index.ts @@ -0,0 +1 @@ +export * from "./edit-gift-card-form" diff --git a/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-edit/gift-card-edit.tsx b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-edit/gift-card-edit.tsx new file mode 100644 index 0000000000000..a888ff1b64de4 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-edit/gift-card-edit.tsx @@ -0,0 +1,39 @@ +import { Drawer, Heading } from "@medusajs/ui" +import { useAdminGiftCard } from "medusa-react" +import { useTranslation } from "react-i18next" +import { useParams } from "react-router-dom" +import { useRouteModalState } from "../../../hooks/use-route-modal-state" +import { EditGiftCardForm } from "./components/edit-gift-card-form" + +export const GiftCardEdit = () => { + const { id } = useParams() + const { t } = useTranslation() + const [open, onOpenChange, subscribe] = useRouteModalState() + + const { gift_card, isLoading, isError, error } = useAdminGiftCard(id!) + + const handleSuccessfulSubmit = () => { + onOpenChange(false, true) + } + + if (isError) { + throw error + } + + return ( + + + + {t("giftCards.editGiftCard")} + + {!isLoading && gift_card && ( + + )} + + + ) +} diff --git a/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-edit/index.ts b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-edit/index.ts new file mode 100644 index 0000000000000..50e9681db4471 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-edit/index.ts @@ -0,0 +1 @@ +export { GiftCardEdit as Component } from "./gift-card-edit" diff --git a/packages/design-system/ui/src/components/currency-input/currency-input.tsx b/packages/design-system/ui/src/components/currency-input/currency-input.tsx index 1d31516250e2d..64dfe14f4a68e 100644 --- a/packages/design-system/ui/src/components/currency-input/currency-input.tsx +++ b/packages/design-system/ui/src/components/currency-input/currency-input.tsx @@ -108,6 +108,8 @@ const CurrencyInput = React.forwardRef( role="presentation" > ( role="presentation" > Date: Fri, 9 Feb 2024 11:44:20 +0100 Subject: [PATCH 14/20] cleanup gc --- .../public/locales/en/translation.json | 1 + .../gift-card-general-section.tsx | 27 ++++ .../edit-gift-card-form.tsx | 144 +++++++++--------- .../use-gift-card-table-columns.tsx | 63 +++++++- .../order-list-table/order-list-table.tsx | 2 +- 5 files changed, 164 insertions(+), 73 deletions(-) diff --git a/packages/admin-next/dashboard/public/locales/en/translation.json b/packages/admin-next/dashboard/public/locales/en/translation.json index 89d8f0b5e2db4..9bbf58fe035ef 100644 --- a/packages/admin-next/dashboard/public/locales/en/translation.json +++ b/packages/admin-next/dashboard/public/locales/en/translation.json @@ -89,6 +89,7 @@ "balanceHigherThanValue": "The balance cannot be higher than the original amount.", "balanceLowerThanZero": "The balance cannot be negative.", "expiryDateHint": "Countries have different laws regarding gift card expiry dates. Make sure to check local regulations before setting an expiry date.", + "regionHint": "Changing the region of the gift card will also change its currency, potentially affecting its monetary value.", "enabledHint": "Specify if the gift card is enabled or disabled.", "currentBalance": "Current balance", "initialBalance": "Initial balance" diff --git a/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-detail/components/gift-card-general-section/gift-card-general-section.tsx b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-detail/components/gift-card-general-section/gift-card-general-section.tsx index b67bffb01b985..53fe7b31a0579 100644 --- a/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-detail/components/gift-card-general-section/gift-card-general-section.tsx +++ b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-detail/components/gift-card-general-section/gift-card-general-section.tsx @@ -14,6 +14,8 @@ import { useTranslation } from "react-i18next" import { useNavigate } from "react-router-dom" import { ActionMenu } from "../../../../../components/common/action-menu" +import { currencies } from "../../../../../lib/currencies" +import { getPresentationalAmount } from "../../../../../lib/money-amount-helpers" type GiftCardGeneralSectionProps = { giftCard: GiftCard @@ -70,6 +72,9 @@ export const GiftCardGeneralSection = ({ text = t("general.expired") } + const currencyCode = giftCard.region.currency_code.toUpperCase() + const nativeSymbol = currencies[currencyCode].symbol_native + return (
    @@ -103,6 +108,28 @@ export const GiftCardGeneralSection = ({ />
    +
    + + {t("giftCards.currentBalance")} + + + {`${nativeSymbol} ${getPresentationalAmount( + giftCard.balance, + currencyCode + )} ${currencyCode}`} + +
    +
    + + {t("giftCards.initialBalance")} + + + {`${nativeSymbol} ${getPresentationalAmount( + giftCard.value, + currencyCode + )} ${currencyCode}`} + +
    {t("fields.region")} diff --git a/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-edit/components/edit-gift-card-form/edit-gift-card-form.tsx b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-edit/components/edit-gift-card-form/edit-gift-card-form.tsx index 2f548b39ce2aa..e7fa7ade434a9 100644 --- a/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-edit/components/edit-gift-card-form/edit-gift-card-form.tsx +++ b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-edit/components/edit-gift-card-form/edit-gift-card-form.tsx @@ -34,7 +34,7 @@ const EditGiftCardSchema = zod.object({ region_id: zod.string(), balance: zod.string(), is_enabled: zod.boolean(), - ends_at: zod.date().optional(), + ends_at: zod.date().nullable(), }) export const EditGiftCardForm = ({ @@ -52,12 +52,14 @@ export const EditGiftCardForm = ({ giftCard.region.currency_code ).toString(), is_enabled: !giftCard.is_disabled, + ends_at: giftCard.ends_at ? new Date(giftCard.ends_at) : null, }, resolver: zodResolver(EditGiftCardSchema), }) const { formState: { isDirty }, + setValue, } = form useEffect(() => { @@ -71,6 +73,16 @@ export const EditGiftCardForm = ({ const { mutateAsync, isLoading } = useAdminUpdateGiftCard(giftCard.id) + const handleOpenChange = (open: boolean) => { + if (!open) { + setValue("ends_at", null, { + shouldDirty: true, + }) + } + + setShowDateFields(open) + } + const handleSubmit = form.handleSubmit(async (data) => { const newBalance = getDbAmount( parseFloat(data.balance), @@ -101,6 +113,7 @@ export const EditGiftCardForm = ({ giftCard.region.currency_code ), is_disabled: !data.is_enabled, + ends_at: data.ends_at, }, { onSuccess: () => { @@ -125,62 +138,59 @@ export const EditGiftCardForm = ({ className="flex flex-1 flex-col overflow-hidden" > -
    - { - return ( - - {t("fields.balance")} - - - - - - ) - }} - /> - { - return ( - - {t("fields.region")} - - - - - - ) - }} - /> -
    - + { + return ( + + {t("fields.balance")} + + + + + + ) + }} + /> + { + return ( + + {t("fields.region")} + + + + {t("giftCards.regionHint")} + + + ) + }} + />
    - +
    @@ -217,7 +224,7 @@ export const EditGiftCardForm = ({
    @@ -229,18 +236,17 @@ export const EditGiftCardForm = ({ { + render={({ + field: { value, onChange, ref: _ref, ...field }, + }) => { return ( { - onChange(val) + value={value ?? undefined} + onChange={(v) => { + onChange(v ?? null) }} - placeholder="DD/MM/YYYY HH:MM" - showTimePicker {...field} /> diff --git a/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-list/components/gift-card-list-table/use-gift-card-table-columns.tsx b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-list/components/gift-card-list-table/use-gift-card-table-columns.tsx index 24139bab7e07a..c3b4d42ab4eef 100644 --- a/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-list/components/gift-card-list-table/use-gift-card-table-columns.tsx +++ b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-list/components/gift-card-list-table/use-gift-card-table-columns.tsx @@ -1,8 +1,11 @@ +import { PencilSquare, Trash } from "@medusajs/icons" import { GiftCard } from "@medusajs/medusa" -import { Badge } from "@medusajs/ui" +import { Badge, usePrompt } from "@medusajs/ui" import { ColumnDef, createColumnHelper } from "@tanstack/react-table" +import { useAdminDeleteGiftCard } from "medusa-react" import { useMemo } from "react" import { useTranslation } from "react-i18next" +import { ActionMenu } from "../../../../../components/common/action-menu" import { DateCell } from "../../../../../components/table/table-cells/common/date-cell" import { MoneyAmountCell } from "../../../../../components/table/table-cells/common/money-amount-cell" import { StatusCell } from "../../../../../components/table/table-cells/common/status-cell" @@ -21,10 +24,11 @@ export const useGiftCardTableColumns = () => { return {getValue()} }, }), - columnHelper.accessor("order.display_id", { + columnHelper.accessor("order", { header: t("fields.order"), cell: ({ getValue }) => { - return + const order = getValue() + return }, }), columnHelper.accessor("region", { @@ -69,7 +73,60 @@ export const useGiftCardTableColumns = () => { return }, }), + columnHelper.display({ + id: "actions", + cell: ({ row }) => , + }), ], [t] ) as ColumnDef[] } + +const GiftCardActions = ({ giftCard }: { giftCard: GiftCard }) => { + const { t } = useTranslation() + const prompt = usePrompt() + + const { mutateAsync } = useAdminDeleteGiftCard(giftCard.id) + + const handleDelete = async () => { + const res = await prompt({ + title: t("general.areYouSure"), + description: t("giftCards.deleteGiftCardWarning", { + code: giftCard.code, + }), + confirmText: t("general.delete"), + cancelText: t("general.cancel"), + }) + + if (!res) { + return + } + + await mutateAsync() + } + + return ( + , + label: t("general.edit"), + to: `/gift-cards/${giftCard.id}/edit`, + }, + ], + }, + { + actions: [ + { + icon: , + label: t("general.delete"), + onClick: handleDelete, + }, + ], + }, + ]} + /> + ) +} diff --git a/packages/admin-next/dashboard/src/routes/orders/order-list/components/order-list-table/order-list-table.tsx b/packages/admin-next/dashboard/src/routes/orders/order-list/components/order-list-table/order-list-table.tsx index 05ba27416a2b2..45267be53c3d1 100644 --- a/packages/admin-next/dashboard/src/routes/orders/order-list/components/order-list-table/order-list-table.tsx +++ b/packages/admin-next/dashboard/src/routes/orders/order-list/components/order-list-table/order-list-table.tsx @@ -7,7 +7,7 @@ import { useOrderTableFilters } from "../../../../../hooks/table/filters/use-ord import { useOrderTableQuery } from "../../../../../hooks/table/query/use-order-table-query" import { useDataTable } from "../../../../../hooks/use-data-table" -const PAGE_SIZE = 50 +const PAGE_SIZE = 20 const DEFAULT_RELATIONS = "customer,items,sales_channel" const DEFAULT_FIELDS = "id,status,display_id,created_at,email,fulfillment_status,payment_status,total,currency_code" From 153508e9d5a5f7fa957542f6bd2edcddc85fa09e Mon Sep 17 00:00:00 2001 From: Kasper Date: Fri, 9 Feb 2024 14:25:10 +0100 Subject: [PATCH 15/20] create manual cg --- .../locales/{en => en-US}/translation.json | 6 +- .../admin-next/dashboard/src/i18n/config.ts | 13 +- .../admin-next/dashboard/src/i18n/types.ts | 2 +- .../admin-next/dashboard/src/i18next.d.ts | 4 +- .../router-provider/router-provider.tsx | 11 + .../create-gift-card-form.tsx | 311 ++++++++++++++++++ .../components/create-gift-card-form/index.ts | 1 + .../gift-card-create/gift-card-create.tsx | 15 + .../gift-cards/gift-card-create/index.ts | 1 + .../gift-card-general-section.tsx | 36 +- .../gift-card-detail/gift-card-detail.tsx | 11 +- .../gift-cards/gift-card-detail/index.ts | 1 + .../gift-cards/gift-card-detail/loader.ts | 21 ++ .../edit-gift-card-form.tsx | 88 ++--- .../gift-card-list/gift-card-list.tsx | 2 + .../create-region-form/create-region-form.tsx | 4 +- 16 files changed, 464 insertions(+), 63 deletions(-) rename packages/admin-next/dashboard/public/locales/{en => en-US}/translation.json (98%) create mode 100644 packages/admin-next/dashboard/src/routes/gift-cards/gift-card-create/components/create-gift-card-form/create-gift-card-form.tsx create mode 100644 packages/admin-next/dashboard/src/routes/gift-cards/gift-card-create/components/create-gift-card-form/index.ts create mode 100644 packages/admin-next/dashboard/src/routes/gift-cards/gift-card-create/gift-card-create.tsx create mode 100644 packages/admin-next/dashboard/src/routes/gift-cards/gift-card-create/index.ts create mode 100644 packages/admin-next/dashboard/src/routes/gift-cards/gift-card-detail/loader.ts diff --git a/packages/admin-next/dashboard/public/locales/en/translation.json b/packages/admin-next/dashboard/public/locales/en-US/translation.json similarity index 98% rename from packages/admin-next/dashboard/public/locales/en/translation.json rename to packages/admin-next/dashboard/public/locales/en-US/translation.json index 9bbf58fe035ef..8647032575b20 100644 --- a/packages/admin-next/dashboard/public/locales/en/translation.json +++ b/packages/admin-next/dashboard/public/locales/en-US/translation.json @@ -85,6 +85,9 @@ "giftCards": { "domain": "Gift Cards", "editGiftCard": "Edit Gift Card", + "createGiftCard": "Create Gift Card", + "createGiftCardHint": "Manually create a gift card that can be used as a payment method in your store.", + "selectRegionFirst": "Select a region first", "deleteGiftCardWarning": "You are about to delete the gift card {{code}}. This action cannot be undone.", "balanceHigherThanValue": "The balance cannot be higher than the original amount.", "balanceLowerThanZero": "The balance cannot be negative.", @@ -92,7 +95,8 @@ "regionHint": "Changing the region of the gift card will also change its currency, potentially affecting its monetary value.", "enabledHint": "Specify if the gift card is enabled or disabled.", "currentBalance": "Current balance", - "initialBalance": "Initial balance" + "initialBalance": "Initial balance", + "personalMessage": "Personal message" }, "customers": { "domain": "Customers", diff --git a/packages/admin-next/dashboard/src/i18n/config.ts b/packages/admin-next/dashboard/src/i18n/config.ts index ff128d9e5ef5e..f8bdd59d9e86f 100644 --- a/packages/admin-next/dashboard/src/i18n/config.ts +++ b/packages/admin-next/dashboard/src/i18n/config.ts @@ -6,22 +6,27 @@ import { initReactI18next } from "react-i18next" import { Language } from "./types" -i18n +void i18n .use(Backend) .use(LanguageDetector) .use(initReactI18next) .init({ - fallbackLng: "en", + fallbackLng: "en-US", + load: "languageOnly", debug: process.env.NODE_ENV === "development", interpolation: { escapeValue: false, }, + backend: { + // for all available options read the backend's repository readme file + loadPath: "/locales/{{lng}}/{{ns}}.json", + }, }) export const languages: Language[] = [ { - code: "en", - display_name: "English", + code: "en-US", + display_name: "English (US)", ltr: true, date_locale: enUS, }, diff --git a/packages/admin-next/dashboard/src/i18n/types.ts b/packages/admin-next/dashboard/src/i18n/types.ts index 14e59028fd422..35ebd4b6777f8 100644 --- a/packages/admin-next/dashboard/src/i18n/types.ts +++ b/packages/admin-next/dashboard/src/i18n/types.ts @@ -1,5 +1,5 @@ import type { Locale } from "date-fns" -import en from "../../public/locales/en/translation.json" +import en from "../../public/locales/en-US/translation.json" const resources = { translation: en, diff --git a/packages/admin-next/dashboard/src/i18next.d.ts b/packages/admin-next/dashboard/src/i18next.d.ts index bd7cf8079ab10..e504f2f99cb31 100644 --- a/packages/admin-next/dashboard/src/i18next.d.ts +++ b/packages/admin-next/dashboard/src/i18next.d.ts @@ -1,7 +1,7 @@ -import { Resources } from "./i18n/types"; +import { Resources } from "./i18n/types" declare module "i18next" { interface CustomTypeOptions { - resources: Resources; + resources: Resources } } diff --git a/packages/admin-next/dashboard/src/providers/router-provider/router-provider.tsx b/packages/admin-next/dashboard/src/providers/router-provider/router-provider.tsx index 05a9235d31064..43ac0c9c2d732 100644 --- a/packages/admin-next/dashboard/src/providers/router-provider/router-provider.tsx +++ b/packages/admin-next/dashboard/src/providers/router-provider/router-provider.tsx @@ -2,6 +2,7 @@ import type { AdminCollectionsRes, AdminCustomerGroupsRes, AdminCustomersRes, + AdminGiftCardsRes, AdminProductsRes, AdminPublishableApiKeysRes, AdminRegionsRes, @@ -275,10 +276,20 @@ const router = createBrowserRouter([ { path: "", lazy: () => import("../../routes/gift-cards/gift-card-list"), + children: [ + { + path: "create", + lazy: () => + import("../../routes/gift-cards/gift-card-create"), + }, + ], }, { path: ":id", lazy: () => import("../../routes/gift-cards/gift-card-detail"), + handle: { + crumb: (data: AdminGiftCardsRes) => data.gift_card.code, + }, children: [ { path: "edit", diff --git a/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-create/components/create-gift-card-form/create-gift-card-form.tsx b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-create/components/create-gift-card-form/create-gift-card-form.tsx new file mode 100644 index 0000000000000..10611b64368e5 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/gift-cards/gift-card-create/components/create-gift-card-form/create-gift-card-form.tsx @@ -0,0 +1,311 @@ +import { zodResolver } from "@hookform/resolvers/zod" +import { + Button, + CurrencyInput, + DatePicker, + FocusModal, + Heading, + Input, + Select, + Switch, + Text, + Textarea, + Tooltip, +} from "@medusajs/ui" +import * as Collapsible from "@radix-ui/react-collapsible" +import { useAdminCreateGiftCard, useAdminRegions } from "medusa-react" +import { useEffect, useState } from "react" +import { useForm, useWatch } from "react-hook-form" +import { useTranslation } from "react-i18next" +import * as zod from "zod" + +import { useNavigate } from "react-router-dom" +import { Form } from "../../../../../components/common/form" +import { currencies } from "../../../../../lib/currencies" +import { getDbAmount } from "../../../../../lib/money-amount-helpers" + +type CreateGiftCardFormProps = { + subscribe: (state: boolean) => void +} + +const CreateGiftCardSchema = zod.object({ + region_id: zod.string(), + value: zod.string(), + is_enabled: zod.boolean(), + ends_at: zod.date().nullable(), + email: zod.string().email(), + personal_message: zod.string().optional(), +}) + +export const CreateGiftCardForm = ({ subscribe }: CreateGiftCardFormProps) => { + const [showDateFields, setShowDateFields] = useState(false) + + const { regions } = useAdminRegions({ + limit: 1000, + fields: "id,name,currency_code", + }) + const { t } = useTranslation() + const navigate = useNavigate() + + const form = useForm>({ + defaultValues: { + region_id: regions?.[0]?.id ?? "", + value: "", + is_enabled: true, + ends_at: null, + email: "", + personal_message: "", + }, + resolver: zodResolver(CreateGiftCardSchema), + }) + + const { + formState: { isDirty }, + setValue, + setError, + } = form + + const regionId = useWatch({ + control: form.control, + name: "region_id", + }) + + const currencyCode = regions?.find((r) => r.id === regionId)?.currency_code + const nativeSymbol = currencyCode + ? currencies[currencyCode.toUpperCase()].symbol_native + : undefined + + useEffect(() => { + subscribe(isDirty) + }, [isDirty, subscribe]) + + const { mutateAsync, isLoading } = useAdminCreateGiftCard() + + const handleOpenChange = (open: boolean) => { + if (!open) { + setValue("ends_at", null, { + shouldDirty: true, + }) + } + + setShowDateFields(open) + } + + const handleSubmit = form.handleSubmit(async (data) => { + if (!currencyCode) { + setError("region_id", { + type: "manual", + message: "Region not found", + }) + + return + } + + await mutateAsync( + { + region_id: data.region_id, + value: getDbAmount(parseFloat(data.value), currencyCode), + is_disabled: !data.is_enabled, + ends_at: data.ends_at ?? undefined, + metadata: { + email: data.email, + personal_message: data.personal_message, + }, + }, + { + onSuccess: ({ gift_card }) => { + navigate(`../${gift_card.id}`, { replace: true }) + }, + } + ) + }) + + return ( +
    + + +
    + + + + +
    +
    + +
    +
    + {t("giftCards.createGiftCard")} + + {t("giftCards.createGiftCardHint")} + +
    +
    + { + return ( + + {t("fields.region")} + + + + + + ) + }} + /> + { + return ( + + {t("fields.balance")} + + {!currencyCode || !nativeSymbol ? ( + + + + ) : ( + + )} + + + + ) + }} + /> +
    + { + return ( + +
    + {t("general.enabled")} + + + +
    + + {t("giftCards.enabledHint")} + + +
    + ) + }} + /> + { + return ( + + +
    +
    + + {t("fields.expiryDate")} + + +
    + + {t("giftCards.expiryDateHint")} + +
    + +
    + + { + onChange(v ?? null) + }} + {...field} + /> + +
    +
    +
    +
    + ) + }} + /> +
    +
    + { + return ( + + {t("fields.email")} + + + + + + ) + }} + /> +
    + { + return ( + + + {t("giftCards.personalMessage")} + + +