fix(dashboard,types,js-sdk): Cleanup settings/store (#8336)

**What**
- Cleans up Store domain of all TS errors
- Adds layout component to Store domain
- Adds currencies types and js-sdk methods
- Fixes a bug that caused Table rows to render incorrectly when takings up more then the viewport height.
This commit is contained in:
Kasper Fabricius Kristensen
2024-07-29 19:44:40 +00:00
committed by GitHub
parent 24c105f288
commit 1d773c536f
19 changed files with 229 additions and 103 deletions
@@ -132,7 +132,7 @@ export const DataTableRoot = <TData,>({
})}
>
{!noResults ? (
<Table className="w-full">
<Table className="relative w-full">
<Table.Header className="border-t-0">
{table.getHeaderGroups().map((headerGroup) => {
return (
@@ -170,7 +170,7 @@ export const DataTableRoot = <TData,>({
: undefined,
}}
className={clx({
"bg-ui-bg-base sticky inset-y-0 left-0 after:absolute after:inset-y-0 after:right-0 after:h-full after:w-px after:bg-transparent after:content-['']":
"bg-ui-bg-base sticky left-0 after:absolute after:inset-y-0 after:right-0 after:h-full after:w-px after:bg-transparent after:content-['']":
isStickyHeader,
"left-[68px]":
isStickyHeader && hasSelect && !isSelectHeader,
@@ -203,13 +203,6 @@ export const DataTableRoot = <TData,>({
return (
<Table.Row
key={row.id}
onKeyDown={(e) => {
console.log("e.key", e.key, e.target)
if (e.key === "x") {
row.toggleSelected()
}
}}
data-selected={row.getIsSelected()}
className={clx(
"transition-fg group/row group relative [&_td:last-of-type]:w-[1%] [&_td:last-of-type]:whitespace-nowrap",
@@ -247,7 +240,7 @@ export const DataTableRoot = <TData,>({
? row.depth * 14 + 24
: undefined
const hasLeftOfsset =
const hasLeftOffset =
isStickyCell && hasSelect && !isSelectCell
const Inner = flexRender(
@@ -263,11 +256,11 @@ export const DataTableRoot = <TData,>({
key={cell.id}
className={clx({
"!pl-0 !pr-0": shouldRenderAsLink,
"bg-ui-bg-base group-data-[selected=true]/row:bg-ui-bg-highlight group-data-[selected=true]/row:group-hover/row:bg-ui-bg-highlight-hover group-hover/row:bg-ui-bg-base-hover transition-fg group-has-[[data-row-link]:focus-visible]:bg-ui-bg-base-hover sticky inset-y-0 left-0 after:absolute after:inset-y-0 after:right-0 after:h-full after:w-px after:bg-transparent after:content-['']":
"bg-ui-bg-base group-data-[selected=true]/row:bg-ui-bg-highlight group-data-[selected=true]/row:group-hover/row:bg-ui-bg-highlight-hover group-hover/row:bg-ui-bg-base-hover transition-fg group-has-[[data-row-link]:focus-visible]:bg-ui-bg-base-hover sticky left-0 after:absolute after:inset-y-0 after:right-0 after:h-full after:w-px after:bg-transparent after:content-['']":
isStickyCell,
"bg-ui-bg-subtle group-hover/row:bg-ui-bg-subtle-hover":
isOdd && isStickyCell,
"bottom-0 left-[68px] top-0": hasLeftOfsset,
"left-[68px]": hasLeftOffset,
"after:bg-ui-border-base":
showStickyBorder && isStickyCell && !isSelectCell,
"!bg-ui-bg-disabled !hover:bg-ui-bg-disabled":
@@ -290,7 +283,7 @@ export const DataTableRoot = <TData,>({
className={clx(
"flex size-full items-center pr-6",
{
"pl-6": isTabableLink && !hasLeftOfsset,
"pl-6": isTabableLink && !hasLeftOffset,
}
)}
>
@@ -1,20 +1,27 @@
import { FetchError } from "@medusajs/js-sdk"
import { HttpTypes } from "@medusajs/types"
import { QueryKey, UseQueryOptions, useQuery } from "@tanstack/react-query"
import { client } from "../../lib/client"
import { sdk } from "../../lib/client"
import { queryKeysFactory } from "../../lib/query-key-factory"
import { CurrencyListRes, CurrencyRes } from "../../types/api-responses"
const CURRENCIES_QUERY_KEY = "currencies" as const
const currenciesQueryKeys = queryKeysFactory(CURRENCIES_QUERY_KEY)
export const useCurrencies = (
query?: Record<string, any>,
query?: HttpTypes.AdminCurrencyListParams,
options?: Omit<
UseQueryOptions<CurrencyListRes, Error, CurrencyListRes, QueryKey>,
UseQueryOptions<
HttpTypes.AdminCurrencyListResponse,
FetchError,
HttpTypes.AdminCurrencyListResponse,
QueryKey
>,
"queryFn" | "queryKey"
>
) => {
const { data, ...rest } = useQuery({
queryFn: () => client.currencies.list(query),
queryFn: () => sdk.admin.currency.list(query),
queryKey: currenciesQueryKeys.list(query),
...options,
})
@@ -24,14 +31,20 @@ export const useCurrencies = (
export const useCurrency = (
id: string,
query?: HttpTypes.AdminCurrencyParams,
options?: Omit<
UseQueryOptions<CurrencyRes, Error, CurrencyRes, QueryKey>,
UseQueryOptions<
HttpTypes.AdminCurrencyResponse,
FetchError,
HttpTypes.AdminCurrencyResponse,
QueryKey
>,
"queryFn" | "queryKey"
>
) => {
const { data, ...rest } = useQuery({
queryKey: currenciesQueryKeys.detail(id),
queryFn: async () => client.currencies.retrieve(id),
queryFn: async () => sdk.admin.currency.retrieve(id, query),
...options,
})
@@ -19,7 +19,7 @@ export const storeQueryKeys = queryKeysFactory(STORE_QUERY_KEY)
/**
* Workaround to keep the V1 version of retrieving the store.
*/
async function retrieveActiveStore(
export async function retrieveActiveStore(
query?: HttpTypes.AdminStoreParams
): Promise<HttpTypes.AdminStoreResponse> {
const response = await sdk.admin.store.list(query)
@@ -34,7 +34,7 @@ async function retrieveActiveStore(
}
export const useStore = (
query?: Record<string, any>,
query?: HttpTypes.SelectParams,
options?: Omit<
UseQueryOptions<
HttpTypes.AdminStoreResponse,
@@ -704,6 +704,10 @@ export const RouteMap: RouteObject[] = [
path: "currencies",
lazy: () => import("../../routes/store/store-add-currencies"),
},
{
path: "metadata/edit",
lazy: () => import("../../routes/store/store-metadata"),
},
],
},
{
@@ -3,6 +3,11 @@ import { createColumnHelper } from "@tanstack/react-table"
import { useMemo } from "react"
import { useTranslation } from "react-i18next"
import {
TextCell,
TextHeader,
} from "../../../../components/table/table-cells/common/text-cell"
const columnHelper = createColumnHelper<HttpTypes.AdminCurrency>()
export const useCurrenciesTableColumns = () => {
@@ -11,12 +16,12 @@ export const useCurrenciesTableColumns = () => {
return useMemo(
() => [
columnHelper.accessor("code", {
header: t("fields.code"),
cell: ({ getValue }) => getValue().toUpperCase(),
header: () => <TextHeader text={t("fields.code")} />,
cell: ({ getValue }) => <TextCell text={getValue().toUpperCase()} />,
}),
columnHelper.accessor("name", {
header: t("fields.name"),
cell: ({ getValue }) => getValue(),
header: () => <TextHeader text={t("fields.name")} />,
cell: ({ getValue }) => <TextCell text={getValue()} />,
}),
],
[t]
@@ -4,7 +4,7 @@ import {
OnChangeFn,
RowSelectionState,
} from "@tanstack/react-table"
import { useCallback, useEffect, useMemo, useState } from "react"
import { useCallback, useMemo, useState } from "react"
import { useTranslation } from "react-i18next"
import * as zod from "zod"
@@ -18,14 +18,16 @@ import {
} from "../../../../../components/modals"
import { DataTable } from "../../../../../components/table/data-table"
import { useCurrencies } from "../../../../../hooks/api/currencies"
import { pricePreferencesQueryKeys } from "../../../../../hooks/api/price-preferences"
import { useUpdateStore } from "../../../../../hooks/api/store"
import { useDataTable } from "../../../../../hooks/use-data-table"
import { queryClient } from "../../../../../lib/query-client"
import { useCurrenciesTableColumns } from "../../../common/hooks/use-currencies-table-columns"
import { useCurrenciesTableQuery } from "../../../common/hooks/use-currencies-table-query"
import { usePricePreferences } from "../../../../../hooks/api/price-preferences"
type AddCurrenciesFormProps = {
store: HttpTypes.AdminStore
pricePreferences: HttpTypes.AdminPricePreference[]
}
const AddCurrenciesSchema = zod.object({
@@ -36,7 +38,10 @@ const AddCurrenciesSchema = zod.object({
const PAGE_SIZE = 50
const PREFIX = "ac"
export const AddCurrenciesForm = ({ store }: AddCurrenciesFormProps) => {
export const AddCurrenciesForm = ({
store,
pricePreferences,
}: AddCurrenciesFormProps) => {
const { t } = useTranslation()
const { handleSuccess } = useRouteModal()
@@ -55,20 +60,16 @@ export const AddCurrenciesForm = ({ store }: AddCurrenciesFormProps) => {
placeholderData: keepPreviousData,
})
const {
price_preferences: pricePreferences,
isPending: isPricePreferencesPending,
isError: isPricePreferencesError,
error: pricePreferencesError,
} = usePricePreferences({
attribute: "currency_code",
value: store.supported_currencies?.map((c) => c.currency_code),
})
const form = useForm<zod.infer<typeof AddCurrenciesSchema>>({
defaultValues: {
currencies: [],
pricePreferences: {},
pricePreferences: pricePreferences?.reduce((acc, curr) => {
if (curr.value) {
acc[curr.value] = curr.is_tax_inclusive
}
return acc
}, {} as Record<string, boolean>),
},
resolver: zodResolver(AddCurrenciesSchema),
})
@@ -100,15 +101,6 @@ export const AddCurrenciesForm = ({ store }: AddCurrenciesFormProps) => {
[setValue]
)
useEffect(() => {
setPricePreferences(
pricePreferences?.reduce((acc: Record<string, boolean>, curr) => {
acc[curr.value] = curr.is_tax_inclusive
return acc
}, {})
)
}, [pricePreferences, setPricePreferences])
const columns = useColumns(pricePreferenceValues, setPricePreferences)
const { table } = useDataTable({
@@ -152,6 +144,13 @@ export const AddCurrenciesForm = ({ store }: AddCurrenciesFormProps) => {
{
onSuccess: () => {
toast.success(t("store.toast.currenciesUpdated"))
// We invalidate all price preferences queries to ensure that if a currency is added
// as being tax inclusive, it will be reflected in the table view immediately.
queryClient.invalidateQueries({
queryKey: pricePreferencesQueryKeys.all,
})
handleSuccess()
},
onError: (error) => {
@@ -277,16 +276,18 @@ const useColumns = (
const isPreSelected = !row.getCanSelect()
const isTaxInclusive = pricePreferences[row.original.code]
return (
<Switch
disabled={isPreSelected}
checked={isTaxInclusive ?? false}
onCheckedChange={(val) => {
setPricePreferences({
...pricePreferences,
[row.original.code]: val,
})
}}
/>
<div className="flex items-center justify-end">
<Switch
disabled={isPreSelected}
checked={isTaxInclusive ?? false}
onCheckedChange={(val) => {
setPricePreferences({
...pricePreferences,
[row.original.code]: val,
})
}}
/>
</div>
)
},
}),
@@ -1,17 +1,42 @@
import { RouteFocusModal } from "../../../components/modals"
import { usePricePreferences } from "../../../hooks/api/price-preferences"
import { useStore } from "../../../hooks/api/store"
import { AddCurrenciesForm } from "./components/add-currencies-form/add-currencies-form"
export const StoreAddCurrencies = () => {
const { store, isPending: isLoading, isError, error } = useStore()
const { store, isPending, isError, error } = useStore()
const {
price_preferences: pricePreferences,
isPending: isPricePreferencesPending,
isError: isPricePreferencesError,
error: pricePreferencesError,
} = usePricePreferences(
{
attribute: "currency_code",
value: store?.supported_currencies?.map((c) => c.currency_code),
},
{
enabled: !!store,
}
)
const ready =
!!store && !isPending && !!pricePreferences && !isPricePreferencesPending
if (isError) {
throw error
}
if (isPricePreferencesError) {
throw pricePreferencesError
}
return (
<RouteFocusModal>
{!isLoading && store && <AddCurrenciesForm store={store} />}
{ready && (
<AddCurrenciesForm store={store} pricePreferences={pricePreferences} />
)}
</RouteFocusModal>
)
}
@@ -12,19 +12,19 @@ import { keepPreviousData } from "@tanstack/react-query"
import { RowSelectionState, createColumnHelper } from "@tanstack/react-table"
import { useMemo, useState } from "react"
import { useTranslation } from "react-i18next"
import { ActionMenu } from "../../../../../../components/common/action-menu"
import { DataTable } from "../../../../../../components/table/data-table"
import { StatusCell } from "../../../../../../components/table/table-cells/common/status-cell"
import { useCurrencies } from "../../../../../../hooks/api/currencies"
import { usePricePreferences } from "../../../../../../hooks/api/price-preferences"
import { useUpdateStore } from "../../../../../../hooks/api/store"
import { useDataTable } from "../../../../../../hooks/use-data-table"
import { ExtendedStoreDTO } from "../../../../../../types/api-responses"
import { useCurrenciesTableColumns } from "../../../../common/hooks/use-currencies-table-columns"
import { useCurrenciesTableQuery } from "../../../../common/hooks/use-currencies-table-query"
import { usePricePreferences } from "../../../../../../hooks/api/price-preferences"
import { StatusCell } from "../../../../../../components/table/table-cells/common/status-cell"
type StoreCurrencySectionProps = {
store: ExtendedStoreDTO
store: HttpTypes.AdminStore
}
const PAGE_SIZE = 10
@@ -274,12 +274,6 @@ const CurrencyActions = ({
groups={[
{
actions: [
{
icon: <Trash />,
label: t("actions.remove"),
onClick: handleRemove,
disabled: currency.code === defaultCurrencyCode,
},
{
icon: preferencesMap.get(currency.code)?.is_tax_inclusive ? (
<XCircle />
@@ -293,6 +287,16 @@ const CurrencyActions = ({
},
],
},
{
actions: [
{
icon: <Trash />,
label: t("actions.remove"),
onClick: handleRemove,
disabled: currency.code === defaultCurrencyCode,
},
],
},
]}
/>
)
@@ -2,6 +2,7 @@ import { PencilSquare } from "@medusajs/icons"
import { AdminStore } from "@medusajs/types"
import { Badge, Container, Heading, Text } from "@medusajs/ui"
import { useTranslation } from "react-i18next"
import { ActionMenu } from "../../../../../components/common/action-menu"
import { useRegion } from "../../../../../hooks/api/regions"
@@ -1,18 +1,18 @@
import { storeQueryKeys } from "../../../hooks/api/store"
import { client } from "../../../lib/client"
import { HttpTypes } from "@medusajs/types"
import { retrieveActiveStore, storeQueryKeys } from "../../../hooks/api/store"
import { queryClient } from "../../../lib/query-client"
import { StoreRes } from "../../../types/api-responses"
const storeDetailQuery = () => ({
queryKey: storeQueryKeys.details(),
queryFn: async () => client.stores.retrieve(),
queryFn: async () => retrieveActiveStore(),
})
export const storeLoader = async () => {
const query = storeDetailQuery()
return (
queryClient.getQueryData<StoreRes>(query.queryKey) ??
queryClient.getQueryData<HttpTypes.AdminStoreResponse>(query.queryKey) ??
(await queryClient.fetchQuery(query))
)
}
@@ -1,6 +1,5 @@
import { Outlet, useLoaderData } from "react-router-dom"
import { useLoaderData } from "react-router-dom"
import { JsonViewSection } from "../../../components/common/json-view-section/index.ts"
import { useStore } from "../../../hooks/api/store.tsx"
import { StoreCurrencySection } from "./components/store-currency-section/store-currencies-section.tsx/index.ts"
import { StoreGeneralSection } from "./components/store-general-section/index.ts"
@@ -8,21 +7,18 @@ import { storeLoader } from "./loader.ts"
import after from "virtual:medusa/widgets/store/details/after"
import before from "virtual:medusa/widgets/store/details/before"
import { SingleColumnPageSkeleton } from "../../../components/common/skeleton/skeleton.tsx"
import { SingleColumnPage } from "../../../components/layout/pages/index.ts"
export const StoreDetail = () => {
const initialData = useLoaderData() as Awaited<ReturnType<typeof storeLoader>>
const {
store,
isPending: isLoading,
isError,
error,
} = useStore(undefined, {
const { store, isPending, isError, error } = useStore(undefined, {
initialData,
})
if (isLoading || !store) {
return <div>Loading...</div>
if (isPending || !store) {
return <SingleColumnPageSkeleton sections={2} showJSON showMetadata />
}
if (isError) {
@@ -30,21 +26,18 @@ export const StoreDetail = () => {
}
return (
<div className="flex flex-col gap-y-2">
{before.widgets.map((w, i) => (
<div key={i}>
<w.Component data={store} />
</div>
))}
<SingleColumnPage
widgets={{
before,
after,
}}
data={store}
hasOutlet
showMetadata
showJSON
>
<StoreGeneralSection store={store} />
<StoreCurrencySection store={store} />
{after.widgets.map((w, i) => (
<div key={i}>
<w.Component data={store} />
</div>
))}
<JsonViewSection data={store} />
<Outlet />
</div>
</SingleColumnPage>
)
}
@@ -1,4 +1,5 @@
import { zodResolver } from "@hookform/resolvers/zod"
import { HttpTypes } from "@medusajs/types"
import { Button, Input, Select, toast } from "@medusajs/ui"
import { useForm } from "react-hook-form"
import { useTranslation } from "react-i18next"
@@ -8,10 +9,9 @@ import { Form } from "../../../../../components/common/form"
import { RouteDrawer, useRouteModal } from "../../../../../components/modals"
import { useRegions } from "../../../../../hooks/api/regions"
import { useUpdateStore } from "../../../../../hooks/api/store"
import { ExtendedStoreDTO } from "../../../../../types/api-responses"
type EditStoreFormProps = {
store: ExtendedStoreDTO
store: HttpTypes.AdminStore
}
const EditStoreSchema = z.object({
@@ -0,0 +1 @@
export { StoreMetadata as Component } from "./store-metadata"
@@ -0,0 +1,24 @@
import { MetadataForm } from "../../../components/forms/metadata-form"
import { RouteDrawer } from "../../../components/modals"
import { useStore, useUpdateStore } from "../../../hooks/api"
export const StoreMetadata = () => {
const { store, isPending, isError, error } = useStore()
const { mutateAsync, isPending: isMutating } = useUpdateStore(store?.id!)
if (isError) {
throw error
}
return (
<RouteDrawer>
<MetadataForm
isPending={isPending}
isMutating={isMutating}
hook={mutateAsync}
metadata={store?.metadata}
/>
</RouteDrawer>
)
}
@@ -0,0 +1,37 @@
import { HttpTypes } from "@medusajs/types"
import { Client } from "../client"
import { ClientHeaders } from "../types"
export class Currency {
private client: Client
constructor(client: Client) {
this.client = client
}
async list(
query?: HttpTypes.AdminCurrencyListParams,
headers?: ClientHeaders
) {
return this.client.fetch<HttpTypes.AdminCurrencyListResponse>(
`/admin/currencies`,
{
headers,
query,
}
)
}
async retrieve(
id: string,
query?: HttpTypes.AdminCurrencyParams,
headers?: ClientHeaders
) {
return this.client.fetch<HttpTypes.AdminCurrencyResponse>(
`/admin/currencies/${id}`,
{
headers,
query,
}
)
}
}
+4 -1
View File
@@ -1,4 +1,5 @@
import { Client } from "../client"
import { Currency } from "./currency"
import { Customer } from "./customer"
import { Fulfillment } from "./fulfillment"
import { FulfillmentProvider } from "./fulfillment-provider"
@@ -16,6 +17,7 @@ import { ProductTag } from "./product-tag"
import { ProductType } from "./product-type"
import { Region } from "./region"
import { Return } from "./return"
import { ReturnReason } from "./return-reason"
import { SalesChannel } from "./sales-channel"
import { ShippingOption } from "./shipping-option"
import { ShippingProfile } from "./shipping-profile"
@@ -25,7 +27,6 @@ import { TaxRate } from "./tax-rate"
import { TaxRegion } from "./tax-region"
import { Upload } from "./upload"
import { User } from "./user"
import { ReturnReason } from "./return-reason"
export class Admin {
public invite: Invite
@@ -55,6 +56,7 @@ export class Admin {
public store: Store
public productTag: ProductTag
public user: User
public currency: Currency
constructor(client: Client) {
this.invite = new Invite(client)
@@ -84,5 +86,6 @@ export class Admin {
this.store = new Store(client)
this.productTag = new ProductTag(client)
this.user = new User(client)
this.currency = new Currency(client)
}
}
@@ -1 +1,3 @@
export * from "./entities"
export * from "./queries"
export * from "./responses"
@@ -0,0 +1,11 @@
import { BaseFilterable } from "../../../dal"
import { FindParams, SelectParams } from "../../common"
export interface AdminCurrencyParams extends SelectParams {}
export interface AdminCurrencyListParams
extends FindParams,
BaseFilterable<AdminCurrencyListParams> {
q?: string
code?: string | string[]
}
@@ -0,0 +1,9 @@
import { PaginatedResponse } from "../../common"
import { AdminCurrency } from "./entities"
export interface AdminCurrencyResponse {
currency: AdminCurrency
}
export interface AdminCurrencyListResponse
extends PaginatedResponse<{ currencies: AdminCurrency[] }> {}