feat(dashboard): Update v2 store domain to follow conventions, and abstract some logic (#6927)
**What** - Updates the V2 store domain. - Abstracts some of the API requests logic into re-usable hooks **Note** - Partial PR as we need to add support for setting a default currency, region and location. Currently the `q` param is missing on all V2 endpoints, so we can't use our combobox component to list these options. Will be added later when that has been fixed in core. - The PR includes a generic hook for fetching combobox data, that I added before realising that `q` is not currently supported. But keeping it as it will be useful once support is added.
This commit is contained in:
@@ -618,7 +618,10 @@
|
|||||||
"addCurrencies": "Add currencies",
|
"addCurrencies": "Add currencies",
|
||||||
"removeCurrencyWarning_one": "You are about to remove {{count}} currency from your store. Ensure that you have removed all prices using the currency before proceeding.",
|
"removeCurrencyWarning_one": "You are about to remove {{count}} currency from your store. Ensure that you have removed all prices using the currency before proceeding.",
|
||||||
"removeCurrencyWarning_other": "You are about to remove {{count}} currencies from your store. Ensure that you have removed all prices using the currencies before proceeding.",
|
"removeCurrencyWarning_other": "You are about to remove {{count}} currencies from your store. Ensure that you have removed all prices using the currencies before proceeding.",
|
||||||
"currencyAlreadyAdded": "The currency has already been added to your store."
|
"currencyAlreadyAdded": "The currency has already been added to your store.",
|
||||||
|
"edit": {
|
||||||
|
"header": "Edit Store"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"regions": {
|
"regions": {
|
||||||
"domain": "Regions",
|
"domain": "Regions",
|
||||||
@@ -1002,4 +1005,4 @@
|
|||||||
"seconds_one": "Second",
|
"seconds_one": "Second",
|
||||||
"seconds_other": "Seconds"
|
"seconds_other": "Seconds"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import { QueryKey, useInfiniteQuery } from "@tanstack/react-query"
|
||||||
|
import debounce from "lodash/debounce"
|
||||||
|
import { useCallback, useEffect, useState } from "react"
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
q: string
|
||||||
|
limit: number
|
||||||
|
offset: number
|
||||||
|
}
|
||||||
|
|
||||||
|
type Page = {
|
||||||
|
count: number
|
||||||
|
offset: number
|
||||||
|
limit: number
|
||||||
|
}
|
||||||
|
|
||||||
|
type UseComboboxDataProps<TParams extends Params, TRes extends Page> = {
|
||||||
|
fetcher: (params: TParams) => Promise<TRes>
|
||||||
|
params?: Omit<TParams, "q" | "limit" | "offset">
|
||||||
|
queryKey: QueryKey
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook for fetching infinite data for a combobox.
|
||||||
|
*/
|
||||||
|
export const useComboboxData = <TParams extends Params, TRes extends Page>({
|
||||||
|
fetcher,
|
||||||
|
params,
|
||||||
|
queryKey,
|
||||||
|
}: UseComboboxDataProps<TParams, TRes>) => {
|
||||||
|
const [query, setQuery] = useState("")
|
||||||
|
const [debouncedQuery, setDebouncedQuery] = useState("")
|
||||||
|
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
const debouncedUpdate = useCallback(
|
||||||
|
debounce((query) => setDebouncedQuery(query), 300),
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
debouncedUpdate(query)
|
||||||
|
|
||||||
|
return () => debouncedUpdate.cancel()
|
||||||
|
}, [query, debouncedUpdate])
|
||||||
|
|
||||||
|
const data = useInfiniteQuery(
|
||||||
|
[...queryKey, debouncedQuery],
|
||||||
|
async ({ pageParam = 0 }) => {
|
||||||
|
const res = await fetcher({
|
||||||
|
q: debouncedQuery,
|
||||||
|
limit: 10,
|
||||||
|
offset: pageParam,
|
||||||
|
...params,
|
||||||
|
} as TParams)
|
||||||
|
return res
|
||||||
|
},
|
||||||
|
{
|
||||||
|
getNextPageParam: (lastPage) => {
|
||||||
|
const morePages = lastPage.count > lastPage.offset + lastPage.limit
|
||||||
|
return morePages ? lastPage.offset + lastPage.limit : undefined
|
||||||
|
},
|
||||||
|
keepPreviousData: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
...data,
|
||||||
|
query,
|
||||||
|
setQuery,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { CurrencyDTO } from "@medusajs/types"
|
||||||
|
import { adminCurrenciesKeys, useAdminCustomQuery } from "medusa-react"
|
||||||
|
import { V2ListRes } from "./types/common"
|
||||||
|
|
||||||
|
// TODO: Add types once we export V2 API types
|
||||||
|
export const useV2Currencies = (query?: any, options?: any) => {
|
||||||
|
const { data, ...rest } = useAdminCustomQuery(
|
||||||
|
`/admin/currencies`,
|
||||||
|
adminCurrenciesKeys.list(query),
|
||||||
|
query,
|
||||||
|
options
|
||||||
|
)
|
||||||
|
|
||||||
|
const typedData: {
|
||||||
|
currencies: CurrencyDTO[] | undefined
|
||||||
|
} & V2ListRes = {
|
||||||
|
currencies: data?.currencies,
|
||||||
|
count: data?.count,
|
||||||
|
offset: data?.offset,
|
||||||
|
limit: data?.limit,
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...typedData, ...rest }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useV2Currency = (id: string, options?: any) => {
|
||||||
|
const { data, ...rest } = useAdminCustomQuery(
|
||||||
|
`/admin/currencies/${id}`,
|
||||||
|
adminCurrenciesKeys.detail(id),
|
||||||
|
undefined,
|
||||||
|
options
|
||||||
|
)
|
||||||
|
|
||||||
|
const currency: CurrencyDTO | undefined = data?.currency
|
||||||
|
|
||||||
|
return { currency, ...rest }
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { RegionDTO } from "@medusajs/types"
|
||||||
|
import { adminRegionKeys, useAdminCustomQuery } from "medusa-react"
|
||||||
|
import { V2ListRes } from "./types/common"
|
||||||
|
|
||||||
|
export const useV2Regions = (query?: any, options?: any) => {
|
||||||
|
const { data, ...rest } = useAdminCustomQuery(
|
||||||
|
"/regions",
|
||||||
|
adminRegionKeys.list(query),
|
||||||
|
query,
|
||||||
|
options
|
||||||
|
)
|
||||||
|
|
||||||
|
const typedData: {
|
||||||
|
regions: RegionDTO[] | undefined
|
||||||
|
} & V2ListRes = {
|
||||||
|
regions: data?.regions,
|
||||||
|
count: data?.count,
|
||||||
|
offset: data?.offset,
|
||||||
|
limit: data?.limit,
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...typedData, ...rest }
|
||||||
|
}
|
||||||
@@ -1,18 +1,32 @@
|
|||||||
import { adminStoreKeys, useAdminCustomQuery } from "medusa-react"
|
import {
|
||||||
|
adminStoreKeys,
|
||||||
|
useAdminCustomPost,
|
||||||
|
useAdminCustomQuery,
|
||||||
|
} from "medusa-react"
|
||||||
|
import { Store } from "./types/store"
|
||||||
|
|
||||||
export const useV2Store = ({ initialData }: { initialData?: any }) => {
|
// TODO: Add types once we export V2 API types
|
||||||
const { data, isLoading, isError, error } = useAdminCustomQuery(
|
export const useV2Store = (options?: any) => {
|
||||||
|
const { data, isLoading, isError, error, ...rest } = useAdminCustomQuery(
|
||||||
"/admin/stores",
|
"/admin/stores",
|
||||||
adminStoreKeys.details(),
|
adminStoreKeys.details(),
|
||||||
{},
|
undefined,
|
||||||
{ initialData }
|
options
|
||||||
)
|
)
|
||||||
|
|
||||||
const store = data?.stores[0]
|
const store = data?.stores[0] as Store | undefined
|
||||||
|
|
||||||
|
let hasError = isError
|
||||||
|
let err: Error | null = error
|
||||||
|
|
||||||
if (!isLoading && !isError && typeof store === "undefined") {
|
if (!isLoading && !isError && typeof store === "undefined") {
|
||||||
throw new Error("Store does not exist")
|
hasError = true
|
||||||
|
err = new Error("Store not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
return { store, isLoading, isError, error }
|
return { store, isLoading, isError: hasError, error: err, ...rest }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useV2UpdateStore = (id: string) => {
|
||||||
|
return useAdminCustomPost(`/admin/stores/${id}`, adminStoreKeys.detail(id))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export type V2ListRes = {
|
||||||
|
count: number | undefined
|
||||||
|
offset: number | undefined
|
||||||
|
limit: number | undefined
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { CurrencyDTO, StoreDTO } from "@medusajs/types"
|
||||||
|
|
||||||
|
export type Store = StoreDTO & {
|
||||||
|
default_currency: CurrencyDTO | null
|
||||||
|
}
|
||||||
@@ -5,10 +5,9 @@ import { SettingsLayout } from "../../components/layout/settings-layout"
|
|||||||
import { Outlet } from "react-router-dom"
|
import { Outlet } from "react-router-dom"
|
||||||
|
|
||||||
import { Spinner } from "@medusajs/icons"
|
import { Spinner } from "@medusajs/icons"
|
||||||
|
import { SalesChannelDTO, UserDTO } from "@medusajs/types"
|
||||||
import { ErrorBoundary } from "../../components/error/error-boundary"
|
import { ErrorBoundary } from "../../components/error/error-boundary"
|
||||||
import { useV2Session } from "../../lib/api-v2"
|
import { useV2Session } from "../../lib/api-v2"
|
||||||
import { SalesChannelDTO } from "@medusajs/types"
|
|
||||||
import { UserDTO } from "@medusajs/types"
|
|
||||||
import { SearchProvider } from "../search-provider"
|
import { SearchProvider } from "../search-provider"
|
||||||
import { SidebarProvider } from "../sidebar-provider"
|
import { SidebarProvider } from "../sidebar-provider"
|
||||||
|
|
||||||
@@ -135,7 +134,7 @@ export const v2Routes: RouteObject[] = [
|
|||||||
lazy: () => import("../../v2-routes/store/store-edit"),
|
lazy: () => import("../../v2-routes/store/store-edit"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "add-currencies",
|
path: "currencies",
|
||||||
lazy: () =>
|
lazy: () =>
|
||||||
import("../../v2-routes/store/store-add-currencies"),
|
import("../../v2-routes/store/store-add-currencies"),
|
||||||
},
|
},
|
||||||
|
|||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
import { CurrencyDTO } from "@medusajs/types"
|
||||||
|
import { createColumnHelper } from "@tanstack/react-table"
|
||||||
|
import { useMemo } from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
|
const columnHelper = createColumnHelper<CurrencyDTO>()
|
||||||
|
|
||||||
|
export const useCurrenciesTableColumns = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() => [
|
||||||
|
columnHelper.accessor("code", {
|
||||||
|
header: t("fields.code"),
|
||||||
|
cell: ({ getValue }) => getValue().toUpperCase(),
|
||||||
|
}),
|
||||||
|
columnHelper.accessor("name", {
|
||||||
|
header: t("fields.name"),
|
||||||
|
cell: ({ getValue }) => getValue(),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
[t]
|
||||||
|
)
|
||||||
|
}
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
import { useQueryParams } from "../../../../hooks/use-query-params"
|
||||||
|
|
||||||
|
export const useCurrenciesTableQuery = ({
|
||||||
|
pageSize = 10,
|
||||||
|
prefix,
|
||||||
|
}: {
|
||||||
|
pageSize?: number
|
||||||
|
prefix?: string
|
||||||
|
}) => {
|
||||||
|
const raw = useQueryParams(["order", "q", "offset"], prefix)
|
||||||
|
|
||||||
|
const { offset, ...rest } = raw
|
||||||
|
|
||||||
|
const searchParams = {
|
||||||
|
limit: pageSize,
|
||||||
|
offset: offset ? parseInt(offset) : 0,
|
||||||
|
...rest,
|
||||||
|
}
|
||||||
|
|
||||||
|
return { searchParams, raw }
|
||||||
|
}
|
||||||
+56
-172
@@ -1,43 +1,27 @@
|
|||||||
import { Currency } from "@medusajs/medusa"
|
import { Currency } from "@medusajs/medusa"
|
||||||
|
import { Button, Checkbox, Hint, Tooltip } from "@medusajs/ui"
|
||||||
import {
|
import {
|
||||||
Badge,
|
OnChangeFn,
|
||||||
Button,
|
|
||||||
Checkbox,
|
|
||||||
Hint,
|
|
||||||
StatusBadge,
|
|
||||||
Table,
|
|
||||||
Tooltip,
|
|
||||||
clx,
|
|
||||||
} from "@medusajs/ui"
|
|
||||||
import {
|
|
||||||
PaginationState,
|
|
||||||
RowSelectionState,
|
RowSelectionState,
|
||||||
createColumnHelper,
|
createColumnHelper,
|
||||||
flexRender,
|
|
||||||
getCoreRowModel,
|
|
||||||
useReactTable,
|
|
||||||
} from "@tanstack/react-table"
|
} from "@tanstack/react-table"
|
||||||
import {
|
import { adminCurrenciesKeys, useAdminCustomQuery } from "medusa-react"
|
||||||
adminCurrenciesKeys,
|
import { useMemo, useState } from "react"
|
||||||
adminStoreKeys,
|
|
||||||
useAdminCustomPost,
|
|
||||||
useAdminCustomQuery,
|
|
||||||
} from "medusa-react"
|
|
||||||
import { useEffect, useMemo, useState } from "react"
|
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import * as zod from "zod"
|
import * as zod from "zod"
|
||||||
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod"
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
|
import { CurrencyDTO, StoreDTO } from "@medusajs/types"
|
||||||
import { useForm } from "react-hook-form"
|
import { useForm } from "react-hook-form"
|
||||||
import { OrderBy } from "../../../../../components/filtering/order-by"
|
|
||||||
import { LocalizedTablePagination } from "../../../../../components/localization/localized-table-pagination"
|
|
||||||
import {
|
import {
|
||||||
RouteFocusModal,
|
RouteFocusModal,
|
||||||
useRouteModal,
|
useRouteModal,
|
||||||
} from "../../../../../components/route-modal"
|
} from "../../../../../components/route-modal"
|
||||||
import { useHandleTableScroll } from "../../../../../hooks/use-handle-table-scroll"
|
import { DataTable } from "../../../../../components/table/data-table"
|
||||||
import { useQueryParams } from "../../../../../hooks/use-query-params"
|
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||||
import { StoreDTO } from "@medusajs/types"
|
import { useV2UpdateStore } from "../../../../../lib/api-v2"
|
||||||
|
import { useCurrenciesTableColumns } from "../../../common/hooks/use-currencies-table-columns"
|
||||||
|
import { useCurrenciesTableQuery } from "../../../common/hooks/use-currencies-table-query"
|
||||||
|
|
||||||
type AddCurrenciesFormProps = {
|
type AddCurrenciesFormProps = {
|
||||||
store: StoreDTO
|
store: StoreDTO
|
||||||
@@ -48,6 +32,7 @@ const AddCurrenciesSchema = zod.object({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const PAGE_SIZE = 50
|
const PAGE_SIZE = 50
|
||||||
|
const PREFIX = "ac"
|
||||||
|
|
||||||
export const AddCurrenciesForm = ({ store }: AddCurrenciesFormProps) => {
|
export const AddCurrenciesForm = ({ store }: AddCurrenciesFormProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@@ -62,68 +47,55 @@ export const AddCurrenciesForm = ({ store }: AddCurrenciesFormProps) => {
|
|||||||
|
|
||||||
const { setValue } = form
|
const { setValue } = form
|
||||||
|
|
||||||
const [{ pageIndex, pageSize }, setPagination] = useState<PaginationState>({
|
|
||||||
pageIndex: 0,
|
|
||||||
pageSize: PAGE_SIZE,
|
|
||||||
})
|
|
||||||
|
|
||||||
const pagination = useMemo(
|
|
||||||
() => ({
|
|
||||||
pageIndex,
|
|
||||||
pageSize,
|
|
||||||
}),
|
|
||||||
[pageIndex, pageSize]
|
|
||||||
)
|
|
||||||
|
|
||||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({})
|
const [rowSelection, setRowSelection] = useState<RowSelectionState>({})
|
||||||
|
|
||||||
useEffect(() => {
|
const updater: OnChangeFn<RowSelectionState> = (fn) => {
|
||||||
const ids = Object.keys(rowSelection)
|
const updated = typeof fn === "function" ? fn(rowSelection) : fn
|
||||||
|
|
||||||
|
const ids = Object.keys(updated)
|
||||||
|
|
||||||
setValue("currencies", ids, {
|
setValue("currencies", ids, {
|
||||||
shouldDirty: true,
|
shouldDirty: true,
|
||||||
shouldTouch: true,
|
shouldTouch: true,
|
||||||
})
|
})
|
||||||
}, [rowSelection, setValue])
|
|
||||||
|
|
||||||
const params = useQueryParams(["order"])
|
setRowSelection(updated)
|
||||||
const filter = {
|
|
||||||
limit: PAGE_SIZE,
|
|
||||||
offset: pageIndex * PAGE_SIZE,
|
|
||||||
...params,
|
|
||||||
}
|
}
|
||||||
// @ts-ignore
|
|
||||||
const { data, count, isError, error } = useAdminCustomQuery(
|
const { raw, searchParams } = useCurrenciesTableQuery({
|
||||||
|
pageSize: 50,
|
||||||
|
prefix: PREFIX,
|
||||||
|
})
|
||||||
|
|
||||||
|
const { data, isLoading, isError, error } = useAdminCustomQuery(
|
||||||
"/admin/currencies",
|
"/admin/currencies",
|
||||||
adminCurrenciesKeys.list(filter),
|
adminCurrenciesKeys.list(raw),
|
||||||
filter
|
searchParams,
|
||||||
|
{
|
||||||
|
keepPreviousData: true,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const preSelectedRows = store.supported_currency_codes.map((c) => c)
|
const preSelectedRows = store.supported_currency_codes.map((c) => c)
|
||||||
|
|
||||||
const columns = useColumns()
|
const columns = useColumns()
|
||||||
|
|
||||||
const table = useReactTable({
|
const { table } = useDataTable({
|
||||||
data: data?.currencies ?? [],
|
data: (data?.currencies ?? []) as CurrencyDTO[],
|
||||||
columns,
|
columns,
|
||||||
pageCount: Math.ceil((count ?? 0) / PAGE_SIZE),
|
count: data?.count,
|
||||||
state: {
|
|
||||||
pagination,
|
|
||||||
rowSelection,
|
|
||||||
},
|
|
||||||
getRowId: (row) => row.code,
|
getRowId: (row) => row.code,
|
||||||
onPaginationChange: setPagination,
|
|
||||||
onRowSelectionChange: setRowSelection,
|
|
||||||
getCoreRowModel: getCoreRowModel(),
|
|
||||||
enableRowSelection: (row) => !preSelectedRows.includes(row.original.code),
|
enableRowSelection: (row) => !preSelectedRows.includes(row.original.code),
|
||||||
manualPagination: true,
|
enablePagination: true,
|
||||||
|
pageSize: PAGE_SIZE,
|
||||||
|
prefix: PREFIX,
|
||||||
|
rowSelection: {
|
||||||
|
state: rowSelection,
|
||||||
|
updater,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const { mutateAsync, isLoading: isMutating } = useAdminCustomPost(
|
const { mutateAsync, isLoading: isMutating } = useV2UpdateStore(store.id)
|
||||||
`/admin/stores/${store.id}`,
|
|
||||||
adminStoreKeys.details()
|
|
||||||
)
|
|
||||||
|
|
||||||
const { handleScroll, isScrolled, tableContainerRef } = useHandleTableScroll()
|
|
||||||
|
|
||||||
const handleSubmit = form.handleSubmit(async (data) => {
|
const handleSubmit = form.handleSubmit(async (data) => {
|
||||||
const currencies = Array.from(
|
const currencies = Array.from(
|
||||||
@@ -174,87 +146,19 @@ export const AddCurrenciesForm = ({ store }: AddCurrenciesFormProps) => {
|
|||||||
</div>
|
</div>
|
||||||
</RouteFocusModal.Header>
|
</RouteFocusModal.Header>
|
||||||
<RouteFocusModal.Body className="flex flex-1 flex-col overflow-hidden">
|
<RouteFocusModal.Body className="flex flex-1 flex-col overflow-hidden">
|
||||||
<div className="flex items-center justify-between border-b px-6 py-4">
|
<DataTable
|
||||||
<div></div>
|
table={table}
|
||||||
<div className="flex items-center gap-x-2">
|
pageSize={PAGE_SIZE}
|
||||||
<OrderBy keys={["code"]} />
|
count={data?.count}
|
||||||
</div>
|
columns={columns}
|
||||||
</div>
|
layout="fill"
|
||||||
<div
|
pagination
|
||||||
className="flex-1 overflow-y-auto"
|
search
|
||||||
ref={tableContainerRef}
|
prefix={PREFIX}
|
||||||
onScroll={handleScroll}
|
orderBy={["code", "name"]}
|
||||||
>
|
isLoading={isLoading}
|
||||||
<Table className="relative">
|
queryObject={raw}
|
||||||
<Table.Header
|
/>
|
||||||
className={clx(
|
|
||||||
"bg-ui-bg-base transition-fg sticky inset-x-0 top-0 z-10 border-t-0",
|
|
||||||
{
|
|
||||||
"shadow-elevation-card-hover": isScrolled,
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{table.getHeaderGroups().map((headerGroup) => {
|
|
||||||
return (
|
|
||||||
<Table.Row
|
|
||||||
key={headerGroup.id}
|
|
||||||
className="[&_th:first-of-type]:w-[1%] [&_th:first-of-type]:whitespace-nowrap [&_th]:w-1/3"
|
|
||||||
>
|
|
||||||
{headerGroup.headers.map((header) => {
|
|
||||||
return (
|
|
||||||
<Table.HeaderCell key={header.id}>
|
|
||||||
{flexRender(
|
|
||||||
header.column.columnDef.header,
|
|
||||||
header.getContext()
|
|
||||||
)}
|
|
||||||
</Table.HeaderCell>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</Table.Row>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</Table.Header>
|
|
||||||
<Table.Body className="border-b-0">
|
|
||||||
{table.getRowModel().rows.map((row) => (
|
|
||||||
<Table.Row
|
|
||||||
key={row.id}
|
|
||||||
className={clx(
|
|
||||||
"transition-fg last-of-type:border-b-0",
|
|
||||||
{
|
|
||||||
"bg-ui-bg-highlight hover:bg-ui-bg-highlight-hover":
|
|
||||||
row.getIsSelected(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bg-ui-bg-disabled hover:bg-ui-bg-disabled":
|
|
||||||
!row.getCanSelect(),
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{row.getVisibleCells().map((cell) => (
|
|
||||||
<Table.Cell key={cell.id}>
|
|
||||||
{flexRender(
|
|
||||||
cell.column.columnDef.cell,
|
|
||||||
cell.getContext()
|
|
||||||
)}
|
|
||||||
</Table.Cell>
|
|
||||||
))}
|
|
||||||
</Table.Row>
|
|
||||||
))}
|
|
||||||
</Table.Body>
|
|
||||||
</Table>
|
|
||||||
</div>
|
|
||||||
<div className="w-full border-t">
|
|
||||||
<LocalizedTablePagination
|
|
||||||
canNextPage={table.getCanNextPage()}
|
|
||||||
canPreviousPage={table.getCanPreviousPage()}
|
|
||||||
nextPage={table.nextPage}
|
|
||||||
previousPage={table.previousPage}
|
|
||||||
count={count ?? 0}
|
|
||||||
pageIndex={pageIndex}
|
|
||||||
pageCount={table.getPageCount()}
|
|
||||||
pageSize={PAGE_SIZE}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</RouteFocusModal.Body>
|
</RouteFocusModal.Body>
|
||||||
</form>
|
</form>
|
||||||
</RouteFocusModal.Form>
|
</RouteFocusModal.Form>
|
||||||
@@ -265,6 +169,7 @@ const columnHelper = createColumnHelper<Currency>()
|
|||||||
|
|
||||||
const useColumns = () => {
|
const useColumns = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const base = useCurrenciesTableColumns()
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => [
|
() => [
|
||||||
@@ -310,29 +215,8 @@ const useColumns = () => {
|
|||||||
return Component
|
return Component
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor("name", {
|
...base,
|
||||||
header: t("fields.name"),
|
|
||||||
cell: ({ getValue }) => getValue(),
|
|
||||||
}),
|
|
||||||
columnHelper.accessor("code", {
|
|
||||||
header: t("fields.code"),
|
|
||||||
cell: ({ getValue }) => (
|
|
||||||
<Badge size="small">{getValue().toUpperCase()}</Badge>
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
columnHelper.accessor("includes_tax", {
|
|
||||||
header: t("fields.taxInclusivePricing"),
|
|
||||||
cell: ({ getValue }) => {
|
|
||||||
const value = getValue()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<StatusBadge color={value ? "green" : "red"}>
|
|
||||||
{value ? t("general.enabled") : t("general.disabled")}
|
|
||||||
</StatusBadge>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
[t]
|
[t, base]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+64
-117
@@ -1,72 +1,66 @@
|
|||||||
import { Trash } from "@medusajs/icons"
|
import { Plus, Trash } from "@medusajs/icons"
|
||||||
import { Currency } from "@medusajs/medusa"
|
import { CurrencyDTO, StoreDTO } from "@medusajs/types"
|
||||||
import {
|
import {
|
||||||
Button,
|
|
||||||
Checkbox,
|
Checkbox,
|
||||||
CommandBar,
|
CommandBar,
|
||||||
Container,
|
Container,
|
||||||
Heading,
|
Heading,
|
||||||
StatusBadge,
|
|
||||||
Table,
|
|
||||||
clx,
|
|
||||||
usePrompt,
|
usePrompt,
|
||||||
} from "@medusajs/ui"
|
} from "@medusajs/ui"
|
||||||
import {
|
import { RowSelectionState, createColumnHelper } from "@tanstack/react-table"
|
||||||
RowSelectionState,
|
import { adminStoreKeys, useAdminCustomPost } from "medusa-react"
|
||||||
createColumnHelper,
|
|
||||||
flexRender,
|
|
||||||
getCoreRowModel,
|
|
||||||
getPaginationRowModel,
|
|
||||||
useReactTable,
|
|
||||||
} from "@tanstack/react-table"
|
|
||||||
import {
|
|
||||||
adminStoreKeys,
|
|
||||||
useAdminCustomPost,
|
|
||||||
useAdminCustomQuery,
|
|
||||||
} from "medusa-react"
|
|
||||||
import { useMemo, useState } from "react"
|
import { useMemo, useState } from "react"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { Link } from "react-router-dom"
|
|
||||||
import { ActionMenu } from "../../../../../../components/common/action-menu"
|
import { ActionMenu } from "../../../../../../components/common/action-menu"
|
||||||
import { LocalizedTablePagination } from "../../../../../../components/localization/localized-table-pagination"
|
import { DataTable } from "../../../../../../components/table/data-table"
|
||||||
import { StoreDTO } from "@medusajs/types"
|
import { useDataTable } from "../../../../../../hooks/use-data-table"
|
||||||
|
import { useV2UpdateStore } from "../../../../../../lib/api-v2"
|
||||||
|
import { useV2Currencies } from "../../../../../../lib/api-v2/currencies"
|
||||||
|
import { useCurrenciesTableColumns } from "../../../../common/hooks/use-currencies-table-columns"
|
||||||
|
import { useCurrenciesTableQuery } from "../../../../common/hooks/use-currencies-table-query"
|
||||||
|
|
||||||
type StoreCurrencySectionProps = {
|
type StoreCurrencySectionProps = {
|
||||||
store: StoreDTO
|
store: StoreDTO
|
||||||
}
|
}
|
||||||
|
|
||||||
const PAGE_SIZE = 20
|
const PAGE_SIZE = 10
|
||||||
|
|
||||||
export const StoreCurrencySection = ({ store }: StoreCurrencySectionProps) => {
|
export const StoreCurrencySection = ({ store }: StoreCurrencySectionProps) => {
|
||||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({})
|
const [rowSelection, setRowSelection] = useState<RowSelectionState>({})
|
||||||
const { data } = useAdminCustomQuery(
|
|
||||||
`/admin/currencies?code[]=${store.supported_currency_codes.join(",")}`,
|
const { searchParams, raw } = useCurrenciesTableQuery({ pageSize: PAGE_SIZE })
|
||||||
adminStoreKeys.details()
|
|
||||||
|
const { currencies, count, isLoading, isError, error } = useV2Currencies(
|
||||||
|
{
|
||||||
|
code: store.supported_currency_codes,
|
||||||
|
...searchParams,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
keepPreviousData: true,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const columns = useColumns()
|
const columns = useColumns()
|
||||||
|
|
||||||
const table = useReactTable({
|
const { table } = useDataTable({
|
||||||
data: data?.currencies ?? [],
|
data: currencies ?? [],
|
||||||
columns,
|
columns,
|
||||||
getCoreRowModel: getCoreRowModel(),
|
count: count,
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
|
||||||
onRowSelectionChange: setRowSelection,
|
|
||||||
getRowId: (row) => row.code,
|
getRowId: (row) => row.code,
|
||||||
pageCount: Math.ceil(store.supported_currency_codes.length / PAGE_SIZE),
|
rowSelection: {
|
||||||
state: {
|
state: rowSelection,
|
||||||
rowSelection,
|
updater: setRowSelection,
|
||||||
},
|
},
|
||||||
|
enablePagination: true,
|
||||||
|
enableRowSelection: true,
|
||||||
|
pageSize: PAGE_SIZE,
|
||||||
meta: {
|
meta: {
|
||||||
currencyCodes: store.supported_currency_codes,
|
currencyCodes: store.supported_currency_codes,
|
||||||
storeId: store.id,
|
storeId: store.id,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const { mutateAsync } = useAdminCustomPost(
|
const { mutateAsync } = useV2UpdateStore(store.id)
|
||||||
`/admin/stores/${store.id}`,
|
|
||||||
adminStoreKeys.details()
|
|
||||||
)
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const prompt = usePrompt()
|
const prompt = usePrompt()
|
||||||
|
|
||||||
@@ -100,67 +94,38 @@ export const StoreCurrencySection = ({ store }: StoreCurrencySectionProps) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isError) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className="p-0">
|
<Container className="divide-y p-0">
|
||||||
<div className="flex items-center justify-between px-6 py-4">
|
<div className="flex items-center justify-between px-6 py-4">
|
||||||
<Heading level="h2">{t("store.currencies")}</Heading>
|
<Heading level="h2">{t("store.currencies")}</Heading>
|
||||||
<div>
|
<ActionMenu
|
||||||
<Link to="/settings/store/add-currencies">
|
groups={[
|
||||||
<Button size="small" variant="secondary">
|
{
|
||||||
{t("general.add")}
|
actions: [
|
||||||
</Button>
|
{
|
||||||
</Link>
|
icon: <Plus />,
|
||||||
</div>
|
label: t("actions.add"),
|
||||||
|
to: "currencies",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Table>
|
<DataTable
|
||||||
<Table.Header>
|
orderBy={["code", "name"]}
|
||||||
{table.getHeaderGroups().map((headerGroup) => {
|
search
|
||||||
return (
|
pagination
|
||||||
<Table.Row
|
table={table}
|
||||||
key={headerGroup.id}
|
|
||||||
className="[&_th:first-of-type]:w-[1%] [&_th:first-of-type]:whitespace-nowrap [&_th]:w-1/3"
|
|
||||||
>
|
|
||||||
{headerGroup.headers.map((header) => {
|
|
||||||
return (
|
|
||||||
<Table.HeaderCell key={header.id}>
|
|
||||||
{flexRender(
|
|
||||||
header.column.columnDef.header,
|
|
||||||
header.getContext()
|
|
||||||
)}
|
|
||||||
</Table.HeaderCell>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</Table.Row>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</Table.Header>
|
|
||||||
<Table.Body className="border-b-0">
|
|
||||||
{table.getRowModel().rows.map((row) => (
|
|
||||||
<Table.Row
|
|
||||||
key={row.id}
|
|
||||||
className={clx("transition-fg", {
|
|
||||||
"bg-ui-bg-highlight hover:bg-ui-bg-highlight-hover":
|
|
||||||
row.getIsSelected(),
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{row.getVisibleCells().map((cell) => (
|
|
||||||
<Table.Cell key={cell.id}>
|
|
||||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
||||||
</Table.Cell>
|
|
||||||
))}
|
|
||||||
</Table.Row>
|
|
||||||
))}
|
|
||||||
</Table.Body>
|
|
||||||
</Table>
|
|
||||||
<LocalizedTablePagination
|
|
||||||
canNextPage={table.getCanNextPage()}
|
|
||||||
canPreviousPage={table.getCanPreviousPage()}
|
|
||||||
nextPage={table.nextPage}
|
|
||||||
previousPage={table.previousPage}
|
|
||||||
count={store.supported_currency_codes.length}
|
|
||||||
pageIndex={table.getState().pagination.pageIndex}
|
|
||||||
pageCount={table.getPageCount()}
|
|
||||||
pageSize={PAGE_SIZE}
|
pageSize={PAGE_SIZE}
|
||||||
|
columns={columns}
|
||||||
|
count={count}
|
||||||
|
isLoading={isLoading}
|
||||||
|
queryObject={raw}
|
||||||
/>
|
/>
|
||||||
<CommandBar open={!!Object.keys(rowSelection).length}>
|
<CommandBar open={!!Object.keys(rowSelection).length}>
|
||||||
<CommandBar.Bar>
|
<CommandBar.Bar>
|
||||||
@@ -187,7 +152,7 @@ const CurrencyActions = ({
|
|||||||
currencyCodes,
|
currencyCodes,
|
||||||
}: {
|
}: {
|
||||||
storeId: string
|
storeId: string
|
||||||
currency: Currency
|
currency: CurrencyDTO
|
||||||
currencyCodes: string[]
|
currencyCodes: string[]
|
||||||
}) => {
|
}) => {
|
||||||
const { mutateAsync } = useAdminCustomPost(
|
const { mutateAsync } = useAdminCustomPost(
|
||||||
@@ -238,10 +203,10 @@ const CurrencyActions = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const columnHelper = createColumnHelper<Currency>()
|
const columnHelper = createColumnHelper<CurrencyDTO>()
|
||||||
|
|
||||||
const useColumns = () => {
|
const useColumns = () => {
|
||||||
const { t } = useTranslation()
|
const base = useCurrenciesTableColumns()
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => [
|
() => [
|
||||||
@@ -273,25 +238,7 @@ const useColumns = () => {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor("code", {
|
...base,
|
||||||
header: t("fields.code"),
|
|
||||||
cell: ({ getValue }) => getValue().toUpperCase(),
|
|
||||||
}),
|
|
||||||
columnHelper.accessor("name", {
|
|
||||||
header: t("fields.name"),
|
|
||||||
cell: ({ getValue }) => getValue(),
|
|
||||||
}),
|
|
||||||
columnHelper.accessor("includes_tax", {
|
|
||||||
header: "Tax Inclusive Prices",
|
|
||||||
cell: ({ getValue }) => {
|
|
||||||
const value = getValue()
|
|
||||||
const text = value ? t("general.enabled") : t("general.disabled")
|
|
||||||
|
|
||||||
return (
|
|
||||||
<StatusBadge color={value ? "green" : "red"}>{text}</StatusBadge>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
columnHelper.display({
|
columnHelper.display({
|
||||||
id: "actions",
|
id: "actions",
|
||||||
cell: ({ row, table }) => {
|
cell: ({ row, table }) => {
|
||||||
@@ -310,6 +257,6 @@ const useColumns = () => {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
[t]
|
[base]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-82
@@ -1,7 +1,8 @@
|
|||||||
import { Store } from "@medusajs/medusa"
|
import { PencilSquare } from "@medusajs/icons"
|
||||||
import { Badge, Button, Container, Copy, Heading, Text } from "@medusajs/ui"
|
import { Badge, Container, Heading, Text } from "@medusajs/ui"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { Link } from "react-router-dom"
|
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||||
|
import { Store } from "../../../../../lib/api-v2/types/store"
|
||||||
|
|
||||||
type StoreGeneralSectionProps = {
|
type StoreGeneralSectionProps = {
|
||||||
store: Store
|
store: Store
|
||||||
@@ -19,11 +20,19 @@ export const StoreGeneralSection = ({ store }: StoreGeneralSectionProps) => {
|
|||||||
{t("store.manageYourStoresDetails")}
|
{t("store.manageYourStoresDetails")}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
<Link to={"/settings/store/edit"}>
|
<ActionMenu
|
||||||
<Button size="small" variant="secondary">
|
groups={[
|
||||||
{t("actions.edit")}
|
{
|
||||||
</Button>
|
actions: [
|
||||||
</Link>
|
{
|
||||||
|
icon: <PencilSquare />,
|
||||||
|
label: t("actions.edit"),
|
||||||
|
to: "edit",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 px-6 py-4">
|
<div className="grid grid-cols-2 px-6 py-4">
|
||||||
<Text size="small" leading="compact" weight="plus">
|
<Text size="small" leading="compact" weight="plus">
|
||||||
@@ -33,82 +42,21 @@ export const StoreGeneralSection = ({ store }: StoreGeneralSectionProps) => {
|
|||||||
{store.name}
|
{store.name}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 px-6 py-4">
|
{store.default_currency && (
|
||||||
<Text size="small" leading="compact" weight="plus">
|
<div className="grid grid-cols-2 px-6 py-4">
|
||||||
{t("store.defaultCurrency")}
|
<Text size="small" leading="compact" weight="plus">
|
||||||
</Text>
|
{t("store.defaultCurrency")}
|
||||||
<div className="flex items-center gap-x-2">
|
|
||||||
<Badge size="2xsmall">
|
|
||||||
{store.default_currency_code.toUpperCase()}
|
|
||||||
</Badge>
|
|
||||||
<Text size="small" leading="compact">
|
|
||||||
{store.default_currency.name}
|
|
||||||
</Text>
|
</Text>
|
||||||
|
<div className="flex items-center gap-x-2">
|
||||||
|
<Badge size="2xsmall">
|
||||||
|
{store.default_currency.code.toUpperCase()}
|
||||||
|
</Badge>
|
||||||
|
<Text size="small" leading="compact">
|
||||||
|
{store.default_currency.name}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
<div className="grid grid-cols-2 px-6 py-4">
|
|
||||||
<Text size="small" leading="compact" weight="plus">
|
|
||||||
{t("store.swapLinkTemplate")}
|
|
||||||
</Text>
|
|
||||||
{store.swap_link_template ? (
|
|
||||||
<div className="bg-ui-bg-subtle border-ui-border-base box-border flex w-fit cursor-default items-center gap-x-0.5 overflow-hidden rounded-full border pl-2 pr-1">
|
|
||||||
<Text size="xsmall" leading="compact" className="truncate">
|
|
||||||
{store.swap_link_template}
|
|
||||||
</Text>
|
|
||||||
<Copy
|
|
||||||
content={store.swap_link_template}
|
|
||||||
variant="mini"
|
|
||||||
className="text-ui-fg-subtle"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<Text size="small" leading="compact">
|
|
||||||
-
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-2 px-6 py-4">
|
|
||||||
<Text size="small" leading="compact" weight="plus">
|
|
||||||
{t("store.paymentLinkTemplate")}
|
|
||||||
</Text>
|
|
||||||
{store.payment_link_template ? (
|
|
||||||
<div className="bg-ui-bg-subtle border-ui-border-base box-border flex w-fit cursor-default items-center gap-x-0.5 overflow-hidden rounded-full border pl-2 pr-1">
|
|
||||||
<Text size="xsmall" leading="compact" className="truncate">
|
|
||||||
{store.payment_link_template}
|
|
||||||
</Text>
|
|
||||||
<Copy
|
|
||||||
content={store.payment_link_template}
|
|
||||||
variant="mini"
|
|
||||||
className="text-ui-fg-subtle"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<Text size="small" leading="compact">
|
|
||||||
-
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-2 px-6 py-4">
|
|
||||||
<Text size="small" leading="compact" weight="plus">
|
|
||||||
{t("store.inviteLinkTemplate")}
|
|
||||||
</Text>
|
|
||||||
{store.invite_link_template ? (
|
|
||||||
<div className="bg-ui-bg-subtle border-ui-border-base box-border flex w-fit cursor-default items-center gap-x-0.5 overflow-hidden rounded-full border pl-2 pr-1">
|
|
||||||
<Text size="xsmall" leading="compact" className="truncate">
|
|
||||||
{store.invite_link_template}
|
|
||||||
</Text>
|
|
||||||
<Copy
|
|
||||||
content={store.invite_link_template}
|
|
||||||
variant="mini"
|
|
||||||
className="text-ui-fg-subtle"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<Text size="small" leading="compact">
|
|
||||||
-
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { AdminExtendedStoresRes } from "@medusajs/medusa"
|
|
||||||
import { Response } from "@medusajs/medusa-js"
|
import { Response } from "@medusajs/medusa-js"
|
||||||
import { adminStoreKeys } from "medusa-react"
|
import { adminStoreKeys } from "medusa-react"
|
||||||
import { redirect } from "react-router-dom"
|
import { redirect } from "react-router-dom"
|
||||||
|
|
||||||
|
import { StoreDTO } from "@medusajs/types"
|
||||||
import { FetchQueryOptions } from "@tanstack/react-query"
|
import { FetchQueryOptions } from "@tanstack/react-query"
|
||||||
import { medusa, queryClient } from "../../../lib/medusa"
|
import { medusa, queryClient } from "../../../lib/medusa"
|
||||||
|
|
||||||
const storeDetailQuery = () => ({
|
const storeDetailQuery = () => ({
|
||||||
queryKey: adminStoreKeys.details(),
|
queryKey: adminStoreKeys.details(),
|
||||||
queryFn: async () => medusa.client.request("GET", "/admin/stores"),
|
queryFn: async () => medusa.admin.custom.get("/stores"),
|
||||||
})
|
})
|
||||||
|
|
||||||
const fetchQuery = async (
|
const fetchQuery = async (
|
||||||
@@ -31,7 +31,7 @@ export const storeLoader = async () => {
|
|||||||
const query = storeDetailQuery()
|
const query = storeDetailQuery()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
queryClient.getQueryData<Response<AdminExtendedStoresRes>>(
|
queryClient.getQueryData<Response<{ stores: StoreDTO[] }>>(
|
||||||
query.queryKey
|
query.queryKey
|
||||||
) ?? (await fetchQuery(query))
|
) ?? (await fetchQuery(query))
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { Outlet, useLoaderData } from "react-router-dom"
|
import { Outlet, useLoaderData } from "react-router-dom"
|
||||||
|
|
||||||
import { JsonViewSection } from "../../../components/common/json-view-section/json-view-section.tsx"
|
import { JsonViewSection } from "../../../components/common/json-view-section"
|
||||||
import { StoreCurrencySection } from "./components/store-currency-section/store-currencies-section.tsx/index.ts"
|
import { useV2Store } from "../../../lib/api-v2"
|
||||||
import { StoreGeneralSection } from "./components/store-general-section/index.ts"
|
import { StoreCurrencySection } from "./components/store-currency-section/store-currencies-section.tsx"
|
||||||
import { storeLoader } from "./loader.ts"
|
import { StoreGeneralSection } from "./components/store-general-section"
|
||||||
import { useV2Store } from "../../../lib/api-v2/index.ts"
|
import { storeLoader } from "./loader"
|
||||||
|
|
||||||
export const StoreDetail = () => {
|
export const StoreDetail = () => {
|
||||||
const initialData = useLoaderData() as Awaited<ReturnType<typeof storeLoader>>
|
const initialData = useLoaderData() as Awaited<ReturnType<typeof storeLoader>>
|
||||||
@@ -13,16 +13,12 @@ export const StoreDetail = () => {
|
|||||||
initialData: initialData,
|
initialData: initialData,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading || !store) {
|
||||||
return <div>Loading...</div>
|
return <div>Loading...</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isError || !store) {
|
if (isError) {
|
||||||
if (error) {
|
throw error
|
||||||
throw error
|
|
||||||
}
|
|
||||||
|
|
||||||
return <div>{JSON.stringify(error, null, 2)}</div>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
+18
-84
@@ -1,63 +1,47 @@
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod"
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
import type { Store } from "@medusajs/medusa"
|
import { StoreDTO } from "@medusajs/types"
|
||||||
import { Button, Input } from "@medusajs/ui"
|
import { Button, Input } from "@medusajs/ui"
|
||||||
import { adminStoreKeys, useAdminCustomPost } from "medusa-react"
|
|
||||||
import { useForm } from "react-hook-form"
|
import { useForm } from "react-hook-form"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import * as zod from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
import { Form } from "../../../../../components/common/form"
|
import { Form } from "../../../../../components/common/form"
|
||||||
import {
|
import {
|
||||||
RouteDrawer,
|
RouteDrawer,
|
||||||
useRouteModal,
|
useRouteModal,
|
||||||
} from "../../../../../components/route-modal"
|
} from "../../../../../components/route-modal"
|
||||||
|
import { useV2UpdateStore } from "../../../../../lib/api-v2"
|
||||||
|
|
||||||
type EditStoreFormProps = {
|
type EditStoreFormProps = {
|
||||||
store: Store
|
store: StoreDTO
|
||||||
}
|
}
|
||||||
|
|
||||||
const EditStoreSchema = zod.object({
|
const EditStoreSchema = z.object({
|
||||||
name: zod.string().optional(),
|
name: z.string().min(1),
|
||||||
swap_link_template: zod.union([zod.literal(""), zod.string().trim().url()]),
|
// default_currency_code: z.string().optional(),
|
||||||
payment_link_template: zod.union([
|
// default_region_id: z.string().optional(),
|
||||||
zod.literal(""),
|
// default_location_id: z.string().optional(),
|
||||||
zod.string().trim().url(),
|
|
||||||
]),
|
|
||||||
invite_link_template: zod.union([zod.literal(""), zod.string().trim().url()]),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export const EditStoreForm = ({ store }: EditStoreFormProps) => {
|
export const EditStoreForm = ({ store }: EditStoreFormProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { handleSuccess } = useRouteModal()
|
const { handleSuccess } = useRouteModal()
|
||||||
|
|
||||||
const form = useForm<zod.infer<typeof EditStoreSchema>>({
|
const form = useForm<z.infer<typeof EditStoreSchema>>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: store.name,
|
name: store.name,
|
||||||
swap_link_template: store.swap_link_template ?? "",
|
|
||||||
payment_link_template: store.payment_link_template ?? "",
|
|
||||||
invite_link_template: store.invite_link_template ?? "",
|
|
||||||
},
|
},
|
||||||
resolver: zodResolver(EditStoreSchema),
|
resolver: zodResolver(EditStoreSchema),
|
||||||
})
|
})
|
||||||
|
|
||||||
const { mutateAsync, isLoading } = useAdminCustomPost(
|
const { mutateAsync, isLoading } = useV2UpdateStore(store.id)
|
||||||
`/admin/stores/${store.id}`,
|
|
||||||
adminStoreKeys.details()
|
|
||||||
)
|
|
||||||
|
|
||||||
const handleSubmit = form.handleSubmit(async (values) => {
|
const handleSubmit = form.handleSubmit(async (values) => {
|
||||||
mutateAsync(
|
mutateAsync(values, {
|
||||||
{
|
onSuccess: () => {
|
||||||
name: values.name,
|
handleSuccess()
|
||||||
// invite_link_template: values.invite_link_template || undefined,
|
|
||||||
// swap_link_template: values.swap_link_template || undefined,
|
|
||||||
// payment_link_template: values.payment_link_template || undefined,
|
|
||||||
},
|
},
|
||||||
{
|
})
|
||||||
onSuccess: () => {
|
|
||||||
handleSuccess()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -72,63 +56,13 @@ export const EditStoreForm = ({ store }: EditStoreFormProps) => {
|
|||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Form.Label>{t("fields.name")}</Form.Label>
|
<Form.Label>{t("fields.name")}</Form.Label>
|
||||||
<Form.Control>
|
<Form.Control>
|
||||||
<Input size="small" {...field} placeholder="ACME" />
|
<Input placeholder="ACME" {...field} />
|
||||||
</Form.Control>
|
|
||||||
<Form.ErrorMessage />
|
|
||||||
</Form.Item>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Form.Field
|
|
||||||
control={form.control}
|
|
||||||
name="swap_link_template"
|
|
||||||
render={({ field }) => (
|
|
||||||
<Form.Item>
|
|
||||||
<Form.Label>{t("store.swapLinkTemplate")}</Form.Label>
|
|
||||||
<Form.Control>
|
|
||||||
<Input
|
|
||||||
size="small"
|
|
||||||
{...field}
|
|
||||||
placeholder="https://www.store.com/swap={id}"
|
|
||||||
/>
|
|
||||||
</Form.Control>
|
|
||||||
<Form.ErrorMessage />
|
|
||||||
</Form.Item>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Form.Field
|
|
||||||
control={form.control}
|
|
||||||
name="payment_link_template"
|
|
||||||
render={({ field }) => (
|
|
||||||
<Form.Item>
|
|
||||||
<Form.Label>{t("store.paymentLinkTemplate")}</Form.Label>
|
|
||||||
<Form.Control>
|
|
||||||
<Input
|
|
||||||
size="small"
|
|
||||||
{...field}
|
|
||||||
placeholder="https://www.store.com/payment={id}"
|
|
||||||
/>
|
|
||||||
</Form.Control>
|
|
||||||
<Form.ErrorMessage />
|
|
||||||
</Form.Item>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Form.Field
|
|
||||||
control={form.control}
|
|
||||||
name="invite_link_template"
|
|
||||||
render={({ field }) => (
|
|
||||||
<Form.Item>
|
|
||||||
<Form.Label>{t("store.inviteLinkTemplate")}</Form.Label>
|
|
||||||
<Form.Control>
|
|
||||||
<Input
|
|
||||||
size="small"
|
|
||||||
{...field}
|
|
||||||
placeholder="https://www.admin.com/invite?token={invite_token}"
|
|
||||||
/>
|
|
||||||
</Form.Control>
|
</Form.Control>
|
||||||
<Form.ErrorMessage />
|
<Form.ErrorMessage />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
{/* TODO: Add comboboxes for default region, location, and currency. `q` is currently missing on all v2 endpoints */}
|
||||||
</div>
|
</div>
|
||||||
</RouteDrawer.Body>
|
</RouteDrawer.Body>
|
||||||
<RouteDrawer.Footer>
|
<RouteDrawer.Footer>
|
||||||
|
|||||||
@@ -1,28 +1,25 @@
|
|||||||
import { Heading } from "@medusajs/ui"
|
import { Heading } from "@medusajs/ui"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { json } from "react-router-dom"
|
|
||||||
import { RouteDrawer } from "../../../components/route-modal"
|
import { RouteDrawer } from "../../../components/route-modal"
|
||||||
import { EditStoreForm } from "./components/edit-store-form/edit-store-form"
|
|
||||||
import { useV2Store } from "../../../lib/api-v2"
|
import { useV2Store } from "../../../lib/api-v2"
|
||||||
|
import { EditStoreForm } from "./components/edit-store-form/edit-store-form"
|
||||||
|
|
||||||
export const StoreEdit = () => {
|
export const StoreEdit = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { store, isLoading, isError, error } = useV2Store({})
|
const { store, isLoading, isError, error } = useV2Store()
|
||||||
|
|
||||||
if (isError) {
|
if (isError) {
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!store && !isLoading) {
|
const ready = !!store && !isLoading
|
||||||
throw json("An unknown error has occured", 500)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RouteDrawer>
|
<RouteDrawer>
|
||||||
<RouteDrawer.Header>
|
<RouteDrawer.Header>
|
||||||
<Heading className="capitalize">{t("store.editStore")}</Heading>
|
<Heading>{t("store.edit.header")}</Heading>
|
||||||
</RouteDrawer.Header>
|
</RouteDrawer.Header>
|
||||||
{store && <EditStoreForm store={store} />}
|
{ready && <EditStoreForm store={store} />}
|
||||||
</RouteDrawer>
|
</RouteDrawer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user