From 5724d80286188ae953dbd9f0ba488e3fd73ef0e4 Mon Sep 17 00:00:00 2001 From: Oli Juhl <59018053+olivermrbl@users.noreply.github.com> Date: Sat, 6 Apr 2024 18:30:27 +0200 Subject: [PATCH] feat: Bring back API key sales channel management (#6976) - Add API key sales channel management - Add HTTP responses for API keys and sales channels - Use HTTP responses in `dashboard` and remove now redundant types --- .../dashboard/src/hooks/api/api-keys.tsx | 70 ++- .../src/hooks/api/sales-channels.tsx | 42 +- .../dashboard/src/lib/client/api-keys.ts | 39 +- .../src/lib/client/sales-channels.ts | 28 +- .../src/providers/router-provider/v2.tsx | 5 +- .../dashboard/src/types/api-responses.ts | 12 +- .../api-key-management-add-sales-channels.tsx | 32 +- .../add-sales-channels-to-api-key-form.tsx | 224 ++----- .../api-key-management-detail.tsx | 3 +- .../api-key-sales-channel-section.tsx | 576 +++++++----------- .../api-key-management-detail/loader.ts | 4 +- .../use-api-key-management-table-columns.tsx | 29 +- .../sales-channel-detail/loader.ts | 4 +- .../types/src/http/api-key/admin/api-key.ts | 32 + .../types/src/http/api-key/admin/index.ts | 1 + packages/types/src/http/api-key/index.ts | 1 + packages/types/src/http/common/index.ts | 1 + .../src/http/common/paginated-response.ts | 5 + packages/types/src/http/index.ts | 4 +- .../src/http/sales-channel/admin/index.ts | 1 + .../http/sales-channel/admin/sales-channel.ts | 26 + .../types/src/http/sales-channel/index.ts | 1 + 22 files changed, 533 insertions(+), 607 deletions(-) create mode 100644 packages/types/src/http/api-key/admin/api-key.ts create mode 100644 packages/types/src/http/api-key/admin/index.ts create mode 100644 packages/types/src/http/api-key/index.ts create mode 100644 packages/types/src/http/common/index.ts create mode 100644 packages/types/src/http/common/paginated-response.ts create mode 100644 packages/types/src/http/sales-channel/admin/index.ts create mode 100644 packages/types/src/http/sales-channel/admin/sales-channel.ts create mode 100644 packages/types/src/http/sales-channel/index.ts diff --git a/packages/admin-next/dashboard/src/hooks/api/api-keys.tsx b/packages/admin-next/dashboard/src/hooks/api/api-keys.tsx index bfe31bd849..fb3d6746e3 100644 --- a/packages/admin-next/dashboard/src/hooks/api/api-keys.tsx +++ b/packages/admin-next/dashboard/src/hooks/api/api-keys.tsx @@ -10,11 +10,9 @@ import { client } from "../../lib/client" import { queryClient } from "../../lib/medusa" import { queryKeysFactory } from "../../lib/query-key-factory" import { CreateApiKeyReq, UpdateApiKeyReq } from "../../types/api-payloads" -import { - ApiKeyDeleteRes, - ApiKeyListRes, - ApiKeyRes, -} from "../../types/api-responses" +import { ApiKeyDeleteRes } from "../../types/api-responses" +import { AdminApiKeyResponse, AdminApiKeyListResponse } from "@medusajs/types" +import { salesChannelsQueryKeys } from "./sales-channels" const API_KEYS_QUERY_KEY = "api_keys" as const export const apiKeysQueryKeys = queryKeysFactory(API_KEYS_QUERY_KEY) @@ -23,7 +21,7 @@ export const useApiKey = ( id: string, query?: Record, options?: Omit< - UseQueryOptions, + UseQueryOptions, "queryKey" | "queryFn" > ) => { @@ -39,7 +37,12 @@ export const useApiKey = ( export const useApiKeys = ( query?: Record, options?: Omit< - UseQueryOptions, + UseQueryOptions< + AdminApiKeyListResponse, + Error, + AdminApiKeyListResponse, + QueryKey + >, "queryKey" | "queryFn" > ) => { @@ -53,7 +56,7 @@ export const useApiKeys = ( } export const useCreateApiKey = ( - options?: UseMutationOptions + options?: UseMutationOptions ) => { return useMutation({ mutationFn: (payload) => client.apiKeys.create(payload), @@ -68,7 +71,7 @@ export const useCreateApiKey = ( export const useUpdateApiKey = ( id: string, - options?: UseMutationOptions + options?: UseMutationOptions ) => { return useMutation({ mutationFn: (payload) => client.apiKeys.update(id, payload), @@ -84,7 +87,7 @@ export const useUpdateApiKey = ( export const useRevokeApiKey = ( id: string, - options?: UseMutationOptions + options?: UseMutationOptions ) => { return useMutation({ mutationFn: () => client.apiKeys.revoke(id), @@ -111,3 +114,50 @@ export const useDeleteApiKey = ( }, }) } + +export const useBatchRemoveSalesChannelsFromApiKey = ( + id: string, + options?: UseMutationOptions< + AdminApiKeyResponse, + Error, + { sales_channel_ids: string[] } + > +) => { + return useMutation({ + mutationFn: (payload) => + client.apiKeys.batchRemoveSalesChannels(id, payload), + onSuccess: (data, variables, context) => { + queryClient.invalidateQueries({ queryKey: apiKeysQueryKeys.lists() }) + queryClient.invalidateQueries({ queryKey: apiKeysQueryKeys.detail(id) }) + queryClient.invalidateQueries({ + queryKey: salesChannelsQueryKeys.lists(), + }) + + options?.onSuccess?.(data, variables, context) + }, + ...options, + }) +} + +export const useBatchAddSalesChannelsToApiKey = ( + id: string, + options?: UseMutationOptions< + AdminApiKeyResponse, + Error, + { sales_channel_ids: string[] } + > +) => { + return useMutation({ + mutationFn: (payload) => client.apiKeys.batchAddSalesChannels(id, payload), + onSuccess: (data, variables, context) => { + queryClient.invalidateQueries({ queryKey: apiKeysQueryKeys.lists() }) + queryClient.invalidateQueries({ queryKey: apiKeysQueryKeys.detail(id) }) + queryClient.invalidateQueries({ + queryKey: salesChannelsQueryKeys.lists(), + }) + + options?.onSuccess?.(data, variables, context) + }, + ...options, + }) +} diff --git a/packages/admin-next/dashboard/src/hooks/api/sales-channels.tsx b/packages/admin-next/dashboard/src/hooks/api/sales-channels.tsx index b8c98513a7..eeb55ff0c6 100644 --- a/packages/admin-next/dashboard/src/hooks/api/sales-channels.tsx +++ b/packages/admin-next/dashboard/src/hooks/api/sales-channels.tsx @@ -15,20 +15,25 @@ import { RemoveProductsSalesChannelReq, UpdateSalesChannelReq, } from "../../types/api-payloads" -import { - SalesChannelDeleteRes, - SalesChannelListRes, - SalesChannelRes, -} from "../../types/api-responses" +import { SalesChannelDeleteRes } from "../../types/api-responses" import { productsQueryKeys } from "./products" +import { + AdminSalesChannelListResponse, + AdminSalesChannelResponse, +} from "@medusajs/types" const SALES_CHANNELS_QUERY_KEY = "sales-channels" as const -const salesChannelsQueryKeys = queryKeysFactory(SALES_CHANNELS_QUERY_KEY) +export const salesChannelsQueryKeys = queryKeysFactory(SALES_CHANNELS_QUERY_KEY) export const useSalesChannel = ( id: string, options?: Omit< - UseQueryOptions, + UseQueryOptions< + AdminSalesChannelResponse, + Error, + AdminSalesChannelResponse, + QueryKey + >, "queryFn" | "queryKey" > ) => { @@ -44,7 +49,12 @@ export const useSalesChannel = ( export const useSalesChannels = ( query?: Record, options?: Omit< - UseQueryOptions, + UseQueryOptions< + AdminSalesChannelListResponse, + Error, + AdminSalesChannelListResponse, + QueryKey + >, "queryFn" | "queryKey" > ) => { @@ -58,7 +68,11 @@ export const useSalesChannels = ( } export const useCreateSalesChannel = ( - options?: UseMutationOptions + options?: UseMutationOptions< + AdminSalesChannelResponse, + Error, + CreateSalesChannelReq + > ) => { return useMutation({ mutationFn: (payload) => client.salesChannels.create(payload), @@ -74,7 +88,11 @@ export const useCreateSalesChannel = ( export const useUpdateSalesChannel = ( id: string, - options?: UseMutationOptions + options?: UseMutationOptions< + AdminSalesChannelResponse, + Error, + UpdateSalesChannelReq + > ) => { return useMutation({ mutationFn: (payload) => client.salesChannels.update(id, payload), @@ -120,7 +138,7 @@ export const useDeleteSalesChannel = ( export const useSalesChannelRemoveProducts = ( id: string, options?: UseMutationOptions< - SalesChannelRes, + AdminSalesChannelResponse, Error, RemoveProductsSalesChannelReq > @@ -156,7 +174,7 @@ export const useSalesChannelRemoveProducts = ( export const useSalesChannelAddProducts = ( id: string, options?: UseMutationOptions< - SalesChannelRes, + AdminSalesChannelResponse, Error, AddProductsSalesChannelReq > diff --git a/packages/admin-next/dashboard/src/lib/client/api-keys.ts b/packages/admin-next/dashboard/src/lib/client/api-keys.ts index c422f0cdc5..b3bb92427c 100644 --- a/packages/admin-next/dashboard/src/lib/client/api-keys.ts +++ b/packages/admin-next/dashboard/src/lib/client/api-keys.ts @@ -1,17 +1,14 @@ +import { AdminApiKeyListResponse, AdminApiKeyResponse } from "@medusajs/types" import { CreateApiKeyReq, UpdateApiKeyReq } from "../../types/api-payloads" -import { - ApiKeyDeleteRes, - ApiKeyListRes, - ApiKeyRes, -} from "../../types/api-responses" +import { ApiKeyDeleteRes } from "../../types/api-responses" import { deleteRequest, getRequest, postRequest } from "./common" const retrieveApiKey = async (id: string, query?: Record) => { - return getRequest(`/admin/api-keys/${id}`, query) + return getRequest(`/admin/api-keys/${id}`, query) } const listApiKeys = async (query?: Record) => { - return getRequest(`/admin/api-keys`, query) + return getRequest(`/admin/api-keys`, query) } const deleteApiKey = async (id: string) => { @@ -19,15 +16,35 @@ const deleteApiKey = async (id: string) => { } const revokeApiKey = async (id: string) => { - return postRequest(`/admin/api-keys/${id}/revoke`) + return postRequest(`/admin/api-keys/${id}/revoke`) } const createApiKey = async (payload: CreateApiKeyReq) => { - return postRequest(`/admin/api-keys`, payload) + return postRequest(`/admin/api-keys`, payload) } const updateApiKey = async (id: string, payload: UpdateApiKeyReq) => { - return postRequest(`/admin/api-keys/${id}`, payload) + return postRequest(`/admin/api-keys/${id}`, payload) +} + +const batchRemoveSalesChannelsFromApiKey = async ( + id: string, + payload: { sales_channel_ids: string[] } +) => { + return postRequest( + `/admin/api-keys/${id}/sales-channels/batch/remove`, + payload + ) +} + +const batchAddSalesChannelsFromApiKey = async ( + id: string, + payload: { sales_channel_ids: string[] } +) => { + return postRequest( + `/admin/api-keys/${id}/sales-channels/batch/add`, + payload + ) } export const apiKeys = { @@ -37,4 +54,6 @@ export const apiKeys = { create: createApiKey, update: updateApiKey, revoke: revokeApiKey, + batchRemoveSalesChannels: batchRemoveSalesChannelsFromApiKey, + batchAddSalesChannels: batchAddSalesChannelsFromApiKey, } diff --git a/packages/admin-next/dashboard/src/lib/client/sales-channels.ts b/packages/admin-next/dashboard/src/lib/client/sales-channels.ts index 82ba3fdd8d..79fe45eb16 100644 --- a/packages/admin-next/dashboard/src/lib/client/sales-channels.ts +++ b/packages/admin-next/dashboard/src/lib/client/sales-channels.ts @@ -1,36 +1,42 @@ +import { + AdminSalesChannelListResponse, + AdminSalesChannelResponse, +} from "@medusajs/types" import { AddProductsSalesChannelReq, CreateSalesChannelReq, RemoveProductsSalesChannelReq, UpdateSalesChannelReq, } from "../../types/api-payloads" -import { - SalesChannelDeleteRes, - SalesChannelListRes, - SalesChannelRes, -} from "../../types/api-responses" +import { SalesChannelDeleteRes } from "../../types/api-responses" import { deleteRequest, getRequest, postRequest } from "./common" async function retrieveSalesChannel(id: string, query?: Record) { - return getRequest>( + return getRequest>( `/admin/sales-channels/${id}`, query ) } async function listSalesChannels(query?: Record) { - return getRequest>( + return getRequest>( `/admin/sales-channels`, query ) } async function createSalesChannel(payload: CreateSalesChannelReq) { - return postRequest(`/admin/sales-channels`, payload) + return postRequest( + `/admin/sales-channels`, + payload + ) } async function updateSalesChannel(id: string, payload: UpdateSalesChannelReq) { - return postRequest(`/admin/sales-channels/${id}`, payload) + return postRequest( + `/admin/sales-channels/${id}`, + payload + ) } async function deleteSalesChannel(id: string) { @@ -41,7 +47,7 @@ async function batchRemoveProducts( id: string, payload: RemoveProductsSalesChannelReq ) { - return postRequest( + return postRequest( `/admin/sales-channels/${id}/products/batch/remove`, payload ) @@ -51,7 +57,7 @@ async function batchAddProducts( id: string, payload: AddProductsSalesChannelReq ) { - return postRequest( + return postRequest( `/admin/sales-channels/${id}/products/batch/add`, payload ) diff --git a/packages/admin-next/dashboard/src/providers/router-provider/v2.tsx b/packages/admin-next/dashboard/src/providers/router-provider/v2.tsx index 34e14b5691..e0f23a4007 100644 --- a/packages/admin-next/dashboard/src/providers/router-provider/v2.tsx +++ b/packages/admin-next/dashboard/src/providers/router-provider/v2.tsx @@ -7,7 +7,7 @@ import { ErrorBoundary } from "../../components/error/error-boundary" import { MainLayout } from "../../components/layout-v2/main-layout" import { SettingsLayout } from "../../components/layout/settings-layout" import { useMe } from "../../hooks/api/users" -import { ApiKeyRes } from "../../types/api-responses" +import { AdminApiKeyResponse } from "@medusajs/types" import { SearchProvider } from "../search-provider" import { SidebarProvider } from "../sidebar-provider" @@ -489,8 +489,7 @@ export const v2Routes: RouteObject[] = [ "../../v2-routes/api-key-management/api-key-management-detail" ), handle: { - crumb: (data: ApiKeyRes) => { - console.log("data", data) + crumb: (data: AdminApiKeyResponse) => { return data.api_key.title }, }, diff --git a/packages/admin-next/dashboard/src/types/api-responses.ts b/packages/admin-next/dashboard/src/types/api-responses.ts index 88c9226387..56a91e2bbe 100644 --- a/packages/admin-next/dashboard/src/types/api-responses.ts +++ b/packages/admin-next/dashboard/src/types/api-responses.ts @@ -3,7 +3,6 @@ */ import { - ApiKeyDTO, CampaignDTO, CurrencyDTO, CustomerDTO, @@ -22,8 +21,8 @@ import { StoreDTO, UserDTO, } from "@medusajs/types" -import { WorkflowExecutionDTO } from "../v2-routes/workflow-executions/types" import { ProductTagDTO } from "@medusajs/types/dist/product" +import { WorkflowExecutionDTO } from "../v2-routes/workflow-executions/types" type ListRes = { count: number @@ -77,18 +76,9 @@ export type CampaignListRes = { campaigns: CampaignDTO[] } & ListRes export type CampaignDeleteRes = DeleteRes // API Keys -export type ExtendedApiKeyDTO = ApiKeyDTO & { - sales_channels: SalesChannelDTO[] | null -} -export type ApiKeyRes = { api_key: ExtendedApiKeyDTO } -export type ApiKeyListRes = { api_keys: ExtendedApiKeyDTO[] } & ListRes export type ApiKeyDeleteRes = DeleteRes // Sales Channels -export type SalesChannelRes = { sales_channel: SalesChannelDTO } -export type SalesChannelListRes = { - sales_channels: SalesChannelDTO[] -} & ListRes export type SalesChannelDeleteRes = DeleteRes // Currencies diff --git a/packages/admin-next/dashboard/src/v2-routes/api-key-management/api-key-management-add-sales-channels/api-key-management-add-sales-channels.tsx b/packages/admin-next/dashboard/src/v2-routes/api-key-management/api-key-management-add-sales-channels/api-key-management-add-sales-channels.tsx index b6d1f43d99..b6815f8161 100644 --- a/packages/admin-next/dashboard/src/v2-routes/api-key-management/api-key-management-add-sales-channels/api-key-management-add-sales-channels.tsx +++ b/packages/admin-next/dashboard/src/v2-routes/api-key-management/api-key-management-add-sales-channels/api-key-management-add-sales-channels.tsx @@ -1,37 +1,27 @@ -// Missing Sales Channel endpoint for API Key domain - import { useParams } from "react-router-dom" import { RouteFocusModal } from "../../../components/route-modal" +import { useSalesChannels } from "../../../hooks/api/sales-channels" +import { AddSalesChannelsToApiKeyForm } from "./components" export const ApiKeyManagementAddSalesChannels = () => { const { id } = useParams() - // const { data, isLoading, isError, error } = useAdminCustomQuery( - // `/api-keys/${id}`, - // [adminPublishableApiKeysKeys.detailSalesChannels(id!)], - // { - // fields: "id,*sales_channels", - // }, - // { - // keepPreviousData: true, - // } - // ) + const { sales_channels, isLoading, isError, error } = useSalesChannels({ + publishable_key_id: id, + }) - // if (isError) { - // throw error - // } - - // const salesChannels = data?.api_key?.sales_channels + if (isError) { + throw error + } return ( - {/* {!isLoading && salesChannels && ( + {!isLoading && !!sales_channels && ( sc.id)} + preSelected={sales_channels.map((sc) => sc.id)} /> - )} */} - TODO + )} ) } diff --git a/packages/admin-next/dashboard/src/v2-routes/api-key-management/api-key-management-add-sales-channels/components/add-sales-channels-to-api-key-form.tsx b/packages/admin-next/dashboard/src/v2-routes/api-key-management/api-key-management-add-sales-channels/components/add-sales-channels-to-api-key-form.tsx index b94fde8aa1..3e2fc9383c 100644 --- a/packages/admin-next/dashboard/src/v2-routes/api-key-management/api-key-management-add-sales-channels/components/add-sales-channels-to-api-key-form.tsx +++ b/packages/admin-next/dashboard/src/v2-routes/api-key-management/api-key-management-add-sales-channels/components/add-sales-channels-to-api-key-form.tsx @@ -1,40 +1,25 @@ import { zodResolver } from "@hookform/resolvers/zod" -import { SalesChannel } from "@medusajs/medusa" +import { Button, Checkbox, Hint, StatusBadge, Tooltip } from "@medusajs/ui" import { - Button, - Checkbox, - Hint, - StatusBadge, - Table, - Tooltip, - clx, -} from "@medusajs/ui" -import { - PaginationState, + OnChangeFn, RowSelectionState, createColumnHelper, - flexRender, - getCoreRowModel, - useReactTable, } from "@tanstack/react-table" -import { - adminPublishableApiKeysKeys, - useAdminAddPublishableKeySalesChannelsBatch, - useAdminCustomPost, - useAdminSalesChannels, -} from "medusa-react" -import { useEffect, useMemo, useState } from "react" +import { useMemo, useState } from "react" import { useForm } from "react-hook-form" import { useTranslation } from "react-i18next" import * as zod from "zod" -import { OrderBy } from "../../../../components/filtering/order-by" -import { Query } from "../../../../components/filtering/query" -import { LocalizedTablePagination } from "../../../../components/localization/localized-table-pagination" import { RouteFocusModal, useRouteModal, } from "../../../../components/route-modal" -import { useQueryParams } from "../../../../hooks/use-query-params" +import { useBatchAddSalesChannelsToApiKey } from "../../../../hooks/api/api-keys" +import { useSalesChannelTableQuery } from "../../../../hooks/table/query/use-sales-channel-table-query" +import { useDataTable } from "../../../../hooks/use-data-table" +import { useSalesChannels } from "../../../../hooks/api/sales-channels" +import { keepPreviousData } from "@tanstack/react-query" +import { DataTable } from "../../../../components/table/data-table" +import { AdminSalesChannelResponse } from "@medusajs/types" type AddSalesChannelsToApiKeyFormProps = { apiKey: string @@ -63,69 +48,51 @@ export const AddSalesChannelsToApiKeyForm = ({ const { setValue } = form - const { mutateAsync, isLoading: isMutating } = useAdminCustomPost( - `/api-keys/${apiKey}/sales-channels/batch/add`, - [adminPublishableApiKeysKeys.detailSalesChannels(apiKey)] - ) + const [rowSelection, setRowSelection] = useState({}) - const [{ pageIndex, pageSize }, setPagination] = useState({ - pageIndex: 0, + // @ts-ignore + const { mutateAsync, isLoading: isMutating } = + useBatchAddSalesChannelsToApiKey(apiKey) + + const { raw, searchParams } = useSalesChannelTableQuery({ pageSize: PAGE_SIZE, }) - const pagination = useMemo( - () => ({ - pageIndex, - pageSize, - }), - [pageIndex, pageSize] - ) + const columns = useColumns() - const [rowSelection, setRowSelection] = useState({}) - - useEffect(() => { - setValue( - "sales_channel_ids", - Object.keys(rowSelection).filter((k) => rowSelection[k]), - { - shouldDirty: true, - shouldTouch: true, - } - ) - }, [rowSelection, setValue]) - - const params = useQueryParams(["q", "order"]) - const { sales_channels, count } = useAdminSalesChannels( + const { sales_channels, count, isLoading } = useSalesChannels( + { ...searchParams }, { - limit: PAGE_SIZE, - offset: PAGE_SIZE * pageIndex, - ...params, - }, - { - keepPreviousData: true, + placeholderData: keepPreviousData, } ) - const columns = useColumns() + const updater: OnChangeFn = (fn) => { + const state = typeof fn === "function" ? fn(rowSelection) : fn - const table = useReactTable({ + const ids = Object.keys(state) + + setValue("sales_channel_ids", ids, { + shouldDirty: true, + shouldTouch: true, + }) + + setRowSelection(state) + } + + const { table } = useDataTable({ data: sales_channels ?? [], columns, - pageCount: Math.ceil((count ?? 0) / PAGE_SIZE), - state: { - pagination, - rowSelection, - }, - onPaginationChange: setPagination, - onRowSelectionChange: setRowSelection, - getCoreRowModel: getCoreRowModel(), - manualPagination: true, - getRowId: (row) => row.id, + count, + enablePagination: true, enableRowSelection: (row) => { - return !preSelected.includes(row.id) + return !preSelected.includes(row.original.id!) }, - meta: { - preSelected, + getRowId: (row) => row.id, + pageSize: PAGE_SIZE, + rowSelection: { + state: rowSelection, + updater, }, }) @@ -165,85 +132,26 @@ export const AddSalesChannelsToApiKeyForm = ({ - -
-
-
- - -
-
-
- - - {table.getHeaderGroups().map((headerGroup) => { - return ( - - {headerGroup.headers.map((header) => { - return ( - - {flexRender( - header.column.columnDef.header, - header.getContext() - )} - - ) - })} - - ) - })} - - - {table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} - - ))} - - ))} - -
-
-
- -
+ + ) } -const columnHelper = createColumnHelper() +const columnHelper = + createColumnHelper() const useColumns = () => { const { t } = useTranslation() @@ -266,28 +174,24 @@ const useColumns = () => { /> ) }, - cell: ({ row, table }) => { - const { preSelected } = table.options.meta as { - preSelected: string[] - } - - const isAdded = preSelected.includes(row.original.id) - const isSelected = row.getIsSelected() || isAdded + cell: ({ row }) => { + const isPreSelected = !row.getCanSelect() + const isSelected = row.getIsSelected() || isPreSelected const Component = ( row.toggleSelected(!!value)} + onClick={(e) => { + e.stopPropagation() + }} /> ) - if (isAdded) { + if (isPreSelected) { return ( - + {Component} ) diff --git a/packages/admin-next/dashboard/src/v2-routes/api-key-management/api-key-management-detail/api-key-management-detail.tsx b/packages/admin-next/dashboard/src/v2-routes/api-key-management/api-key-management-detail/api-key-management-detail.tsx index caed18195b..af76bb6039 100644 --- a/packages/admin-next/dashboard/src/v2-routes/api-key-management/api-key-management-detail/api-key-management-detail.tsx +++ b/packages/admin-next/dashboard/src/v2-routes/api-key-management/api-key-management-detail/api-key-management-detail.tsx @@ -3,6 +3,7 @@ import { JsonViewSection } from "../../../components/common/json-view-section" import { useApiKey } from "../../../hooks/api/api-keys" import { ApiKeyGeneralSection } from "./components/api-key-general-section" import { apiKeyLoader } from "./loader" +import { ApiKeySalesChannelSection } from "./components/api-key-sales-channel-section" export const ApiKeyManagementDetail = () => { const initialData = useLoaderData() as Awaited< @@ -25,7 +26,7 @@ export const ApiKeyManagementDetail = () => { return (
- {/* */} +
diff --git a/packages/admin-next/dashboard/src/v2-routes/api-key-management/api-key-management-detail/components/api-key-sales-channel-section/api-key-sales-channel-section.tsx b/packages/admin-next/dashboard/src/v2-routes/api-key-management/api-key-management-detail/components/api-key-sales-channel-section/api-key-sales-channel-section.tsx index 416d21927f..666fd0d2a7 100644 --- a/packages/admin-next/dashboard/src/v2-routes/api-key-management/api-key-management-detail/components/api-key-sales-channel-section/api-key-sales-channel-section.tsx +++ b/packages/admin-next/dashboard/src/v2-routes/api-key-management/api-key-management-detail/components/api-key-sales-channel-section/api-key-sales-channel-section.tsx @@ -1,366 +1,254 @@ -// Api Key Sales Channel Endpoint is not part of v2 +import { PencilSquare, Trash } from "@medusajs/icons" +import { + Button, + Checkbox, + Container, + Heading, + StatusBadge, + usePrompt, +} from "@medusajs/ui" +import { RowSelectionState, createColumnHelper } from "@tanstack/react-table" +import { + adminPublishableApiKeysKeys, + useAdminCustomPost, + useAdminRemovePublishableKeySalesChannelsBatch, +} from "medusa-react" +import { useMemo, useState } from "react" +import { useTranslation } from "react-i18next" +import { Link, useNavigate } from "react-router-dom" +import { ActionMenu } from "../../../../../components/common/action-menu" +import { useSalesChannels } from "../../../../../hooks/api/sales-channels" +import { useSalesChannelTableQuery } from "../../../../../hooks/table/query/use-sales-channel-table-query" +import { useDataTable } from "../../../../../hooks/use-data-table" +import { DataTable } from "../../../../../components/table/data-table" +import { keepPreviousData } from "@tanstack/react-query" +import { AdminApiKeyResponse, AdminSalesChannelResponse } from "@medusajs/types" +import { useBatchRemoveSalesChannelsFromApiKey } from "../../../../../hooks/api/api-keys" -// import { PencilSquare, Trash } from "@medusajs/icons" -// import { SalesChannel } from "@medusajs/medusa" -// import { -// Button, -// Checkbox, -// CommandBar, -// Container, -// Heading, -// StatusBadge, -// Table, -// clx, -// usePrompt, -// } from "@medusajs/ui" -// import { -// PaginationState, -// RowSelectionState, -// createColumnHelper, -// flexRender, -// getCoreRowModel, -// useReactTable, -// } from "@tanstack/react-table" -// import { -// adminPublishableApiKeysKeys, -// useAdminCustomPost, -// useAdminRemovePublishableKeySalesChannelsBatch, -// } from "medusa-react" -// import { useMemo, useState } from "react" -// import { useTranslation } from "react-i18next" -// import { Link, useNavigate } from "react-router-dom" -// import { ActionMenu } from "../../../../../components/common/action-menu" -// import { -// NoRecords, -// NoResults, -// } from "../../../../../components/common/empty-table-content" -// import { Query } from "../../../../../components/filtering/query" -// import { LocalizedTablePagination } from "../../../../../components/localization/localized-table-pagination" -// import { useQueryParams } from "../../../../../hooks/use-query-params" -// import { ExtendedApiKeyDTO } from "../../../../../types/api-responses" -// import { SalesChannelDTO } from "@medusajs/types" +type ApiKeySalesChannelSectionProps = { + apiKey: AdminApiKeyResponse["api_key"] +} -// type ApiKeySalesChannelSectionProps = { -// apiKey: ExtendedApiKeyDTO -// } +const PAGE_SIZE = 10 -// const PAGE_SIZE = 10 +export const ApiKeySalesChannelSection = ({ + apiKey, +}: ApiKeySalesChannelSectionProps) => { + const [rowSelection, setRowSelection] = useState({}) + const { t } = useTranslation() + const prompt = usePrompt() -// export const ApiKeySalesChannelSection = ({ -// apiKey, -// }: ApiKeySalesChannelSectionProps) => { -// const { t } = useTranslation() -// const navigate = useNavigate() -// const prompt = usePrompt() + const { raw, searchParams } = useSalesChannelTableQuery({ + pageSize: PAGE_SIZE, + }) -// const [{ pageIndex, pageSize }, setPagination] = useState({ -// pageIndex: 0, -// pageSize: PAGE_SIZE, -// }) + const { sales_channels, count, isLoading } = useSalesChannels( + { ...searchParams, publishable_key_id: apiKey.id }, + { + placeholderData: keepPreviousData, + } + ) -// const pagination = useMemo( -// () => ({ -// pageIndex, -// pageSize, -// }), -// [pageIndex, pageSize] -// ) + const columns = useColumns({ apiKey: apiKey.id }) + // const filters = useProductTableFilters(["sales_channel_id"]) -// const [rowSelection, setRowSelection] = useState({}) + const { table } = useDataTable({ + data: sales_channels ?? [], + columns, + count, + enablePagination: true, + enableRowSelection: true, + getRowId: (row) => row.id, + pageSize: PAGE_SIZE, + rowSelection: { + state: rowSelection, + updater: setRowSelection, + }, + }) -// const params = useQueryParams(["q"]) + const { mutateAsync } = useBatchRemoveSalesChannelsFromApiKey(apiKey.id) -// const query = { -// ...params, -// fields: "id,*sales_channels", -// } + const handleRemove = async () => { + const keys = Object.keys(rowSelection) -// const salesChannels = apiKey?.sales_channels -// const count = salesChannels?.length || 0 + const res = await prompt({ + title: t("general.areYouSure"), + description: t("apiKeyManagement.removeSalesChannelsWarning", { + count: keys.length, + }), + confirmText: t("actions.continue"), + cancelText: t("actions.cancel"), + }) -// const columns = useColumns() + if (!res) { + return + } -// const table = useReactTable({ -// data: salesChannels ?? [], -// columns, -// pageCount: Math.ceil(count / PAGE_SIZE), -// state: { -// pagination, -// rowSelection, -// }, -// getRowId: (row) => row.id, -// onPaginationChange: setPagination, -// onRowSelectionChange: setRowSelection, -// getCoreRowModel: getCoreRowModel(), -// manualPagination: true, -// meta: { -// apiKey: apiKey.id, -// }, -// }) + await mutateAsync( + { + sales_channel_ids: keys, + }, + { + onSuccess: () => { + setRowSelection({}) + }, + } + ) + } -// const { mutateAsync } = useAdminCustomPost( -// `/api-keys/${apiKey.id}/sales-channels/batch/remove`, -// [adminPublishableApiKeysKeys.detailSalesChannels(apiKey.id)] -// ) + return ( + +
+ {t("salesChannels.domain")} + +
+ +
+ ) +} -// const handleRemove = async () => { -// const keys = Object.keys(rowSelection).filter((k) => rowSelection[k]) +const SalesChannelActions = ({ + salesChannel, + apiKey, +}: { + salesChannel: AdminSalesChannelResponse["sales_channel"] + apiKey: string +}) => { + const { t } = useTranslation() + const prompt = usePrompt() -// const res = await prompt({ -// title: t("general.areYouSure"), -// description: t("apiKeyManagement.removeSalesChannelsWarning", { -// count: keys.length, -// }), -// confirmText: t("actions.continue"), -// cancelText: t("actions.cancel"), -// }) + const { mutateAsync } = useBatchRemoveSalesChannelsFromApiKey(apiKey) -// if (!res) { -// return -// } + const handleDelete = async () => { + const res = await prompt({ + title: t("general.areYouSure"), + description: t("apiKeyManagement.removeSalesChannelWarning", { + name: salesChannel.name, + }), + confirmText: t("actions.delete"), + cancelText: t("actions.cancel"), + }) -// await mutateAsync( -// { -// sales_channel_ids: keys, -// }, -// { -// onSuccess: () => { -// setRowSelection({}) -// }, -// } -// ) -// } + if (!res) { + return + } -// const noRecords = !salesChannels?.length && !params.q + await mutateAsync({ + sales_channel_ids: [salesChannel.id], + }) + } -// return ( -// -//
-// {t("salesChannels.domain")} -// -//
-// {!noRecords && ( -//
-//
-//
-// -//
-//
-// )} -// {noRecords ? ( -// -// ) : ( -//
-// {salesChannels?.length !== 0 ? ( -// -// -// {table.getHeaderGroups().map((headerGroup) => { -// return ( -// -// {headerGroup.headers.map((header) => { -// return ( -// -// {flexRender( -// header.column.columnDef.header, -// header.getContext() -// )} -// -// ) -// })} -// -// ) -// })} -// -// -// {table.getRowModel().rows.map((row) => ( -// -// navigate(`/settings/sales-channels/${row.original.id}`) -// } -// > -// {row.getVisibleCells().map((cell) => ( -// -// {flexRender( -// cell.column.columnDef.cell, -// cell.getContext() -// )} -// -// ))} -// -// ))} -// -//
-// ) : ( -// -// )} -// -// -// -// -// {t("general.countSelected", { -// count: Object.keys(rowSelection).length, -// })} -// -// -// -// -// -//
-// )} -//
-// ) -// } + return ( + , + label: t("actions.edit"), + to: `/settings/sales-channels/${salesChannel.id}/edit`, + }, + ], + }, + { + actions: [ + { + icon: , + label: t("actions.delete"), + onClick: handleDelete, + }, + ], + }, + ]} + /> + ) +} -// const SalesChannelActions = ({ -// salesChannel, -// apiKey, -// }: { -// salesChannel: SalesChannelDTO -// apiKey: string -// }) => { -// const { t } = useTranslation() -// const prompt = usePrompt() +const columnHelper = + createColumnHelper() -// const { mutateAsync } = useAdminRemovePublishableKeySalesChannelsBatch(apiKey) +const useColumns = ({ apiKey }: { apiKey: string }) => { + const { t } = useTranslation() -// const handleDelete = async () => { -// const res = await prompt({ -// title: t("general.areYouSure"), -// description: t("apiKeyManagement.removeSalesChannelWarning"), -// confirmText: t("actions.delete"), -// cancelText: t("actions.cancel"), -// }) - -// if (!res) { -// return -// } - -// await mutateAsync({ -// sales_channel_ids: [{ id: salesChannel.id }], -// }) -// } - -// return ( -// , -// label: t("actions.edit"), -// to: `/settings/sales-channels/${salesChannel.id}/edit`, -// }, -// ], -// }, -// { -// actions: [ -// { -// icon: , -// label: t("actions.delete"), -// onClick: handleDelete, -// }, -// ], -// }, -// ]} -// /> -// ) -// } - -// const columnHelper = createColumnHelper() - -// const useColumns = () => { -// const { t } = useTranslation() - -// return useMemo( -// () => [ -// columnHelper.display({ -// id: "select", -// header: ({ table }) => { -// return ( -// -// table.toggleAllPageRowsSelected(!!value) -// } -// /> -// ) -// }, -// cell: ({ row }) => { -// return ( -// row.toggleSelected(!!value)} -// onClick={(e) => { -// e.stopPropagation() -// }} -// /> -// ) -// }, -// }), -// columnHelper.accessor("name", { -// header: t("fields.name"), -// cell: ({ getValue }) => getValue(), -// }), -// columnHelper.accessor("description", { -// header: t("fields.description"), -// cell: ({ getValue }) => getValue(), -// }), -// columnHelper.accessor("is_disabled", { -// header: t("fields.status"), -// cell: ({ getValue }) => { -// const value = getValue() -// return ( -//
-// -// {value ? t("general.disabled") : t("general.enabled")} -// -//
-// ) -// }, -// }), -// columnHelper.display({ -// id: "actions", -// cell: ({ row, table }) => { -// const { apiKey } = table.options.meta as { -// apiKey: string -// } - -// return ( -// -// ) -// }, -// }), -// ], -// [t] -// ) -// } + return useMemo( + () => [ + columnHelper.display({ + id: "select", + header: ({ table }) => { + return ( + + table.toggleAllPageRowsSelected(!!value) + } + /> + ) + }, + cell: ({ row }) => { + return ( + row.toggleSelected(!!value)} + onClick={(e) => { + e.stopPropagation() + }} + /> + ) + }, + }), + columnHelper.accessor("name", { + header: t("fields.name"), + cell: ({ getValue }) => getValue(), + }), + columnHelper.accessor("description", { + header: t("fields.description"), + cell: ({ getValue }) => getValue(), + }), + columnHelper.accessor("is_disabled", { + header: t("fields.status"), + cell: ({ getValue }) => { + const value = getValue() + return ( +
+ + {value ? t("general.disabled") : t("general.enabled")} + +
+ ) + }, + }), + columnHelper.display({ + id: "actions", + cell: ({ row, table }) => { + return ( + + ) + }, + }), + ], + [t] + ) +} diff --git a/packages/admin-next/dashboard/src/v2-routes/api-key-management/api-key-management-detail/loader.ts b/packages/admin-next/dashboard/src/v2-routes/api-key-management/api-key-management-detail/loader.ts index 7d991ef451..c5003551c5 100644 --- a/packages/admin-next/dashboard/src/v2-routes/api-key-management/api-key-management-detail/loader.ts +++ b/packages/admin-next/dashboard/src/v2-routes/api-key-management/api-key-management-detail/loader.ts @@ -1,9 +1,9 @@ import { LoaderFunctionArgs } from "react-router-dom" +import { AdminApiKeyResponse } from "@medusajs/types" import { apiKeysQueryKeys } from "../../../hooks/api/api-keys" import { client } from "../../../lib/client" import { queryClient } from "../../../lib/medusa" -import { ApiKeyRes } from "../../../types/api-responses" const apiKeyDetailQuery = (id: string) => ({ queryKey: apiKeysQueryKeys.detail(id), @@ -15,7 +15,7 @@ export const apiKeyLoader = async ({ params }: LoaderFunctionArgs) => { const query = apiKeyDetailQuery(id!) return ( - queryClient.getQueryData(query.queryKey) ?? + queryClient.getQueryData(query.queryKey) ?? (await queryClient.fetchQuery(query)) ) } diff --git a/packages/admin-next/dashboard/src/v2-routes/api-key-management/api-key-management-list/components/api-key-management-list-table/use-api-key-management-table-columns.tsx b/packages/admin-next/dashboard/src/v2-routes/api-key-management/api-key-management-list/components/api-key-management-list-table/use-api-key-management-table-columns.tsx index d025953c4f..6118d237d7 100644 --- a/packages/admin-next/dashboard/src/v2-routes/api-key-management/api-key-management-list/components/api-key-management-list-table/use-api-key-management-table-columns.tsx +++ b/packages/admin-next/dashboard/src/v2-routes/api-key-management/api-key-management-list/components/api-key-management-list-table/use-api-key-management-table-columns.tsx @@ -9,13 +9,16 @@ import { } from "medusa-react" import { useMemo } from "react" import { useTranslation } from "react-i18next" - -import { ApiKeyDTO } from "@medusajs/types" +import { AdminApiKeyResponse } from "@medusajs/types" import { ActionMenu } from "../../../../../components/common/action-menu" import { DateCell } from "../../../../../components/table/table-cells/common/date-cell" import { StatusCell } from "../../../../../components/table/table-cells/common/status-cell" +import { + useDeleteApiKey, + useRevokeApiKey, +} from "../../../../../hooks/api/api-keys" -const columnHelper = createColumnHelper() +const columnHelper = createColumnHelper() export const useApiKeyManagementTableColumns = () => { const { t } = useTranslation() @@ -83,21 +86,9 @@ export const useApiKeyManagementTableColumns = () => { ) } -const ApiKeyActions = ({ apiKey }: { apiKey: PublishableApiKey }) => { - const { mutateAsync: revokeAsync } = useAdminCustomPost( - `/api-keys/${apiKey.id}/revoke`, - [ - adminPublishableApiKeysKeys.lists(), - adminPublishableApiKeysKeys.detail(apiKey.id), - ] - ) - const { mutateAsync: deleteAsync } = useAdminCustomDelete( - `/api-keys/${apiKey.id}`, - [ - adminPublishableApiKeysKeys.lists(), - adminPublishableApiKeysKeys.detail(apiKey.id), - ] - ) +export const ApiKeyActions = ({ apiKey }: { apiKey: PublishableApiKey }) => { + const { mutateAsync: revokeAsync } = useRevokeApiKey(apiKey.id) + const { mutateAsync: deleteAsync } = useDeleteApiKey(apiKey.id) const { t } = useTranslation() const prompt = usePrompt() @@ -133,7 +124,7 @@ const ApiKeyActions = ({ apiKey }: { apiKey: PublishableApiKey }) => { return } - await revokeAsync({}) + await revokeAsync(undefined) } return ( diff --git a/packages/admin-next/dashboard/src/v2-routes/sales-channels/sales-channel-detail/loader.ts b/packages/admin-next/dashboard/src/v2-routes/sales-channels/sales-channel-detail/loader.ts index f74c0f7b30..8e805239a4 100644 --- a/packages/admin-next/dashboard/src/v2-routes/sales-channels/sales-channel-detail/loader.ts +++ b/packages/admin-next/dashboard/src/v2-routes/sales-channels/sales-channel-detail/loader.ts @@ -1,9 +1,9 @@ import { adminProductKeys } from "medusa-react" import { LoaderFunctionArgs } from "react-router-dom" +import { AdminSalesChannelResponse } from "@medusajs/types" import { client } from "../../../lib/client" import { queryClient } from "../../../lib/medusa" -import { SalesChannelRes } from "../../../types/api-responses" const salesChannelDetailQuery = (id: string) => ({ queryKey: adminProductKeys.detail(id), @@ -15,7 +15,7 @@ export const salesChannelLoader = async ({ params }: LoaderFunctionArgs) => { const query = salesChannelDetailQuery(id!) return ( - queryClient.getQueryData(query.queryKey) ?? + queryClient.getQueryData(query.queryKey) ?? (await queryClient.fetchQuery(query)) ) } diff --git a/packages/types/src/http/api-key/admin/api-key.ts b/packages/types/src/http/api-key/admin/api-key.ts new file mode 100644 index 0000000000..37044d42a6 --- /dev/null +++ b/packages/types/src/http/api-key/admin/api-key.ts @@ -0,0 +1,32 @@ +import { ApiKeyType } from "../../../api-key" +import { PaginatedResponse } from "../../../common" + +/** + * @experimental + */ +interface ApiKeyResponse { + id: string + token: string + redacted: string + title: string + type: ApiKeyType + last_used_at: Date | null + created_by: string + created_at: Date + revoked_by: string | null + revoked_at: Date | null +} + +/** + * @experimental + */ +export interface AdminApiKeyResponse { + api_key: ApiKeyResponse +} + +/** + * @experimental + */ +export interface AdminApiKeyListResponse extends PaginatedResponse { + api_keys: ApiKeyResponse[] +} diff --git a/packages/types/src/http/api-key/admin/index.ts b/packages/types/src/http/api-key/admin/index.ts new file mode 100644 index 0000000000..277e322691 --- /dev/null +++ b/packages/types/src/http/api-key/admin/index.ts @@ -0,0 +1 @@ +export * from "./api-key" diff --git a/packages/types/src/http/api-key/index.ts b/packages/types/src/http/api-key/index.ts new file mode 100644 index 0000000000..26b8eb9dad --- /dev/null +++ b/packages/types/src/http/api-key/index.ts @@ -0,0 +1 @@ +export * from "./admin" diff --git a/packages/types/src/http/common/index.ts b/packages/types/src/http/common/index.ts new file mode 100644 index 0000000000..0f115a0712 --- /dev/null +++ b/packages/types/src/http/common/index.ts @@ -0,0 +1 @@ +export * from "./paginated-response" diff --git a/packages/types/src/http/common/paginated-response.ts b/packages/types/src/http/common/paginated-response.ts new file mode 100644 index 0000000000..fa35997f8d --- /dev/null +++ b/packages/types/src/http/common/paginated-response.ts @@ -0,0 +1,5 @@ +export interface PaginatedResponse { + limit: number + offset: number + count: number +} diff --git a/packages/types/src/http/index.ts b/packages/types/src/http/index.ts index 350412d35d..e15e4461e7 100644 --- a/packages/types/src/http/index.ts +++ b/packages/types/src/http/index.ts @@ -1,2 +1,4 @@ -export * from "./stock-locations" +export * from "./api-key" export * from "./fulfillment" +export * from "./sales-channel" +export * from "./stock-locations" diff --git a/packages/types/src/http/sales-channel/admin/index.ts b/packages/types/src/http/sales-channel/admin/index.ts new file mode 100644 index 0000000000..ac40bbe6d6 --- /dev/null +++ b/packages/types/src/http/sales-channel/admin/index.ts @@ -0,0 +1 @@ +export * from "./sales-channel" diff --git a/packages/types/src/http/sales-channel/admin/sales-channel.ts b/packages/types/src/http/sales-channel/admin/sales-channel.ts new file mode 100644 index 0000000000..f82eb2af5f --- /dev/null +++ b/packages/types/src/http/sales-channel/admin/sales-channel.ts @@ -0,0 +1,26 @@ +import { PaginatedResponse } from "../../common" + +/** + * @experimental + */ +interface SalesChannelResponse { + id: string + name: string + description: string | null + is_disabled: boolean + metadata: Record | null +} + +/** + * @experimental + */ +export interface AdminSalesChannelListResponse extends PaginatedResponse { + sales_channels: SalesChannelResponse[] +} + +/** + * @experimental + */ +export interface AdminSalesChannelResponse { + sales_channel: SalesChannelResponse +} diff --git a/packages/types/src/http/sales-channel/index.ts b/packages/types/src/http/sales-channel/index.ts new file mode 100644 index 0000000000..26b8eb9dad --- /dev/null +++ b/packages/types/src/http/sales-channel/index.ts @@ -0,0 +1 @@ +export * from "./admin"