From 6b5cc4a2c74c3bdc64a6e09fe8badcad847cfdd4 Mon Sep 17 00:00:00 2001 From: Kasper Date: Mon, 22 Jan 2024 15:25:32 +0100 Subject: [PATCH 1/9] finalize api key domain --- .../public/locales/en/translation.json | 13 +- .../router-provider/router-provider.tsx | 17 + .../api-key-management-add-sales-channels.tsx | 36 ++ .../add-sales-channels-to-api-key-form.tsx | 326 +++++++++++++++ .../components/index.ts | 1 + .../index.ts | 1 + .../api-key-management-create.tsx | 2 +- .../api-key-management-detail.tsx | 38 +- .../api-key-general-section.tsx | 189 +++++++++ .../api-key-general-section/index.ts | 1 + .../api-key-sales-channel-section.tsx | 370 ++++++++++++++++++ .../api-key-sales-channel-section/index.ts | 1 + .../api-key-management-detail/index.ts | 1 + .../api-key-management-detail/loader.ts | 22 ++ .../api-key-management-edit.tsx | 34 +- .../edit-api-key-form/edit-api-key-form.tsx | 90 +++++ .../components/edit-api-key-form/index.ts | 1 + .../api-key-management-list-table.tsx | 58 ++- .../location-sales-channel-section.tsx | 2 +- .../sales-channel-detail/index.ts | 1 + .../sales-channel-detail/loader.ts | 21 + .../sales-channel-detail.tsx | 11 +- .../ui/src/components/copy/copy.tsx | 142 ++++--- 23 files changed, 1312 insertions(+), 66 deletions(-) create mode 100644 packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-add-sales-channels/api-key-management-add-sales-channels.tsx create mode 100644 packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-add-sales-channels/components/add-sales-channels-to-api-key-form.tsx create mode 100644 packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-add-sales-channels/components/index.ts create mode 100644 packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-add-sales-channels/index.ts create mode 100644 packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/components/api-key-general-section/api-key-general-section.tsx create mode 100644 packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/components/api-key-general-section/index.ts create mode 100644 packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/components/api-key-sales-channel-section/api-key-sales-channel-section.tsx create mode 100644 packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/components/api-key-sales-channel-section/index.ts create mode 100644 packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/loader.ts create mode 100644 packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-edit/components/edit-api-key-form/edit-api-key-form.tsx create mode 100644 packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-edit/components/edit-api-key-form/index.ts create mode 100644 packages/admin-next/dashboard/src/routes/sales-channels/sales-channel-detail/loader.ts diff --git a/packages/admin-next/dashboard/public/locales/en/translation.json b/packages/admin-next/dashboard/public/locales/en/translation.json index 57dfc15fafc57..4ce5ae032c383 100644 --- a/packages/admin-next/dashboard/public/locales/en/translation.json +++ b/packages/admin-next/dashboard/public/locales/en/translation.json @@ -27,6 +27,8 @@ "details": "Details", "enabled": "Enabled", "disabled": "Disabled", + "active": "Active", + "revoked": "Revoked", "remove": "Remove", "admin": "Admin", "store": "Store", @@ -183,10 +185,15 @@ "domain": "API Key Management", "createKey": "Create key", "createPublishableApiKey": "Create Publishable API Key", + "editKey": "Edit key", "revoke": "Revoke", "publishableApiKeyHint": "Publishable API keys are used to limit the scope of requests to specific sales channels.", "deleteKeyWarning": "You are about to delete the API key {{title}}. This action cannot be undone.", - "revokeKeyWarning": "You are about to revoke the API key {{title}}." + "revokeKeyWarning": "You are about to revoke the API key {{title}}. This action cannot be undone, and the key cannot be used in future requests.", + "removeSalesChannelsWarning_one": "You are about to remove {{count}} sales channel from the API key. This action cannot be undone.", + "removeSalesChannelsWarning_other": "You are about to remove {{count}} sales channels from the API key. This action cannot be undone.", + "createdBy": "Created by", + "revokedBy": "Revoked by" }, "fields": { "name": "Name", @@ -234,6 +241,8 @@ "variants": "Variants", "orders": "Orders", "account": "Account", - "total": "Total" + "total": "Total", + "created": "Created", + "key": "Key" } } diff --git a/packages/admin-next/dashboard/src/providers/router-provider/router-provider.tsx b/packages/admin-next/dashboard/src/providers/router-provider/router-provider.tsx index 6ccd1178e7ba3..c1ccf72385b13 100644 --- a/packages/admin-next/dashboard/src/providers/router-provider/router-provider.tsx +++ b/packages/admin-next/dashboard/src/providers/router-provider/router-provider.tsx @@ -3,7 +3,9 @@ import type { AdminCustomerGroupsRes, AdminCustomersRes, AdminProductsRes, + AdminPublishableApiKeysRes, AdminRegionsRes, + AdminSalesChannelsRes, } from "@medusajs/medusa" import { Outlet, @@ -487,6 +489,10 @@ const router = createBrowserRouter([ path: ":id", lazy: () => import("../../routes/sales-channels/sales-channel-detail"), + handle: { + crumb: (data: AdminSalesChannelsRes) => + data.sales_channel.name, + }, children: [ { path: "edit", @@ -533,6 +539,10 @@ const router = createBrowserRouter([ import( "../../routes/api-key-management/api-key-management-detail" ), + handle: { + crumb: (data: AdminPublishableApiKeysRes) => + data.publishable_api_key.title, + }, children: [ { path: "edit", @@ -541,6 +551,13 @@ const router = createBrowserRouter([ "../../routes/api-key-management/api-key-management-edit" ), }, + { + path: "add-sales-channels", + lazy: () => + import( + "../../routes/api-key-management/api-key-management-add-sales-channels" + ), + }, ], }, ], diff --git a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-add-sales-channels/api-key-management-add-sales-channels.tsx b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-add-sales-channels/api-key-management-add-sales-channels.tsx new file mode 100644 index 0000000000000..9e8a62caf7b4a --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-add-sales-channels/api-key-management-add-sales-channels.tsx @@ -0,0 +1,36 @@ +import { FocusModal } from "@medusajs/ui" +import { useAdminPublishableApiKeySalesChannels } from "medusa-react" +import { useParams } from "react-router-dom" +import { useRouteModalState } from "../../../hooks/use-route-modal-state" +import { AddSalesChannelsToApiKeyForm } from "./components" + +export const ApiKeyManagementAddSalesChannels = () => { + const { id } = useParams() + const [open, onOpenChange, subscribe] = useRouteModalState() + + const { sales_channels, isLoading, isError, error } = + useAdminPublishableApiKeySalesChannels(id!) + + const handleSuccessfulSubmit = () => { + onOpenChange(false, true) + } + + if (isError) { + throw error + } + + return ( + + + {!isLoading && sales_channels && ( + sc.id)} + onSuccessfulSubmit={handleSuccessfulSubmit} + subscribe={subscribe} + /> + )} + + + ) +} diff --git a/packages/admin-next/dashboard/src/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/routes/api-key-management/api-key-management-add-sales-channels/components/add-sales-channels-to-api-key-form.tsx new file mode 100644 index 0000000000000..902dee4fc9a4d --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-add-sales-channels/components/add-sales-channels-to-api-key-form.tsx @@ -0,0 +1,326 @@ +import { zodResolver } from "@hookform/resolvers/zod" +import { SalesChannel } from "@medusajs/medusa" +import { + Button, + Checkbox, + FocusModal, + Hint, + StatusBadge, + Table, + Tooltip, + clx, +} from "@medusajs/ui" +import { + PaginationState, + RowSelectionState, + createColumnHelper, + flexRender, + getCoreRowModel, + useReactTable, +} from "@tanstack/react-table" +import { + useAdminAddPublishableKeySalesChannelsBatch, + useAdminSalesChannels, +} from "medusa-react" +import { useEffect, useMemo, useState } from "react" +import { useForm } from "react-hook-form" +import { useTranslation } from "react-i18next" +import * as zod from "zod" +import { Form } from "../../../../components/common/form" +import { OrderBy } from "../../../../components/filtering/order-by" +import { Query } from "../../../../components/filtering/query" +import { LocalizedTablePagination } from "../../../../components/localization/localized-table-pagination" +import { useQueryParams } from "../../../../hooks/use-query-params" + +type AddSalesChannelsToApiKeyFormProps = { + apiKey: string + preSelected: string[] + subscribe: (state: boolean) => void + onSuccessfulSubmit: () => void +} + +const AddSalesChannelsToApiKeySchema = zod.object({ + sales_channel_ids: zod.array(zod.string()).min(1), +}) + +const PAGE_SIZE = 50 + +export const AddSalesChannelsToApiKeyForm = ({ + apiKey, + preSelected, + subscribe, + onSuccessfulSubmit, +}: AddSalesChannelsToApiKeyFormProps) => { + const { t } = useTranslation() + + const form = useForm>({ + defaultValues: { + sales_channel_ids: [], + }, + resolver: zodResolver(AddSalesChannelsToApiKeySchema), + }) + + const { + formState: { isDirty }, + } = form + + useEffect(() => { + subscribe(isDirty) + }, [isDirty]) + + const { mutateAsync, isLoading: isMutating } = + useAdminAddPublishableKeySalesChannelsBatch(apiKey) + + const [{ pageIndex, pageSize }, setPagination] = useState({ + pageIndex: 0, + pageSize: PAGE_SIZE, + }) + + const pagination = useMemo( + () => ({ + pageIndex, + pageSize, + }), + [pageIndex, pageSize] + ) + + const [rowSelection, setRowSelection] = useState({}) + + useEffect(() => { + form.setValue( + "sales_channel_ids", + Object.keys(rowSelection).filter((k) => rowSelection[k]) + ) + }, [rowSelection]) + + const params = useQueryParams(["q", "order"]) + const { sales_channels, count } = useAdminSalesChannels( + { + limit: PAGE_SIZE, + offset: PAGE_SIZE * pageIndex, + ...params, + }, + { + keepPreviousData: true, + } + ) + + const columns = useColumns() + + const table = useReactTable({ + 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, + enableRowSelection: (row) => { + return !preSelected.includes(row.id) + }, + meta: { + preSelected, + }, + }) + + const handleSubmit = form.handleSubmit(async (values) => { + await mutateAsync( + { + sales_channel_ids: values.sales_channel_ids.map((p) => ({ id: p })), + }, + { + onSuccess: () => { + onSuccessfulSubmit() + }, + } + ) + }) + + return ( +
+ + +
+ {form.formState.errors.sales_channel_ids && ( + + {form.formState.errors.sales_channel_ids.message} + + )} + + + + +
+
+ +
+
+
+ + +
+
+
+ + + {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 useColumns = () => { + const { t } = useTranslation() + + return useMemo( + () => [ + columnHelper.display({ + id: "select", + header: ({ table }) => { + return ( + + table.toggleAllPageRowsSelected(!!value) + } + /> + ) + }, + cell: ({ row, table }) => { + const { preSelected } = table.options.meta as { + preSelected: string[] + } + + const isAdded = preSelected.includes(row.original.id) + const isSelected = row.getIsSelected() || isAdded + + const Component = ( + row.toggleSelected(!!value)} + /> + ) + + if (isAdded) { + return ( + + {Component} + + ) + } + + return Component + }, + }), + 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")} + +
+ ) + }, + }), + ], + [t] + ) +} diff --git a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-add-sales-channels/components/index.ts b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-add-sales-channels/components/index.ts new file mode 100644 index 0000000000000..4e4781c229fba --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-add-sales-channels/components/index.ts @@ -0,0 +1 @@ +export * from "./add-sales-channels-to-api-key-form" diff --git a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-add-sales-channels/index.ts b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-add-sales-channels/index.ts new file mode 100644 index 0000000000000..8c278126e7c1d --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-add-sales-channels/index.ts @@ -0,0 +1 @@ +export { ApiKeyManagementAddSalesChannels as Component } from "./api-key-management-add-sales-channels" diff --git a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-create/api-key-management-create.tsx b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-create/api-key-management-create.tsx index ef7c98898e7c8..cfc8522b0c719 100644 --- a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-create/api-key-management-create.tsx +++ b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-create/api-key-management-create.tsx @@ -8,7 +8,7 @@ export const ApiKeyManagementCreate = () => { return ( - + ) diff --git a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/api-key-management-detail.tsx b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/api-key-management-detail.tsx index 169888b96e631..358886d640435 100644 --- a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/api-key-management-detail.tsx +++ b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/api-key-management-detail.tsx @@ -1,3 +1,39 @@ +import { useAdminPublishableApiKey } from "medusa-react" +import { Outlet, json, useLoaderData, useParams } from "react-router-dom" +import { JsonViewSection } from "../../../components/common/json-view-section" +import { ApiKeyGeneralSection } from "./components/api-key-general-section" +import { ApiKeySalesChannelSection } from "./components/api-key-sales-channel-section" +import { apiKeyLoader } from "./loader" + export const ApiKeyManagementDetail = () => { - return
+ const initialData = useLoaderData() as Awaited< + ReturnType + > + + const { id } = useParams() + const { publishable_api_key, isLoading, isError, error } = + useAdminPublishableApiKey(id!, { + initialData, + }) + + if (isLoading) { + return
Loading...
+ } + + if (isError || !publishable_api_key) { + if (error) { + throw error + } + + throw json("An unknown error occurred", 500) + } + + return ( +
+ + + + +
+ ) } diff --git a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/components/api-key-general-section/api-key-general-section.tsx b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/components/api-key-general-section/api-key-general-section.tsx new file mode 100644 index 0000000000000..c19f63f454775 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/components/api-key-general-section/api-key-general-section.tsx @@ -0,0 +1,189 @@ +import { + ArrowUpRightOnBox, + PencilSquare, + Trash, + XCircle, +} from "@medusajs/icons" +import { PublishableApiKey } from "@medusajs/medusa" +import { + Container, + Copy, + Heading, + StatusBadge, + Text, + usePrompt, +} from "@medusajs/ui" +import { + useAdminDeletePublishableApiKey, + useAdminRevokePublishableApiKey, + useAdminUser, +} from "medusa-react" +import { useTranslation } from "react-i18next" +import { Link } from "react-router-dom" +import { ActionMenu } from "../../../../../components/common/action-menu" +import { Skeleton } from "../../../../../components/common/skeleton" + +type ApiKeyGeneralSectionProps = { + apiKey: PublishableApiKey +} + +export const ApiKeyGeneralSection = ({ apiKey }: ApiKeyGeneralSectionProps) => { + const { t } = useTranslation() + const prompt = usePrompt() + + const { mutateAsync: revokeAsync } = useAdminRevokePublishableApiKey( + apiKey.id + ) + const { mutateAsync: deleteAsync } = useAdminDeletePublishableApiKey( + apiKey.id + ) + + const handleDelete = async () => { + const res = await prompt({ + title: t("general.areYouSure"), + description: t("apiKeyManagement.deleteKeyWarning", { + title: apiKey.title, + }), + confirmText: t("general.delete"), + cancelText: t("general.cancel"), + }) + + if (!res) { + return + } + + await deleteAsync() + } + + const handleRevoke = async () => { + const res = await prompt({ + title: t("general.areYouSure"), + description: t("apiKeyManagement.revokeKeyWarning", { + title: apiKey.title, + }), + confirmText: t("apiKeyManagement.revoke"), + cancelText: t("general.cancel"), + }) + + if (!res) { + return + } + + await revokeAsync() + } + + return ( + +
+ {apiKey.title} +
+ + {apiKey.revoked_at ? t("general.revoked") : t("general.active")} + + , + to: `/settings/api-key-management/${apiKey.id}/edit`, + }, + ], + }, + { + actions: [ + { + icon: , + label: t("apiKeyManagement.revoke"), + onClick: handleRevoke, + }, + { + icon: , + label: t("general.delete"), + onClick: handleDelete, + }, + ], + }, + ]} + /> +
+
+
+ + {t("fields.key")} + +
e.stopPropagation()} + > + + {apiKey.id} + + +
+
+
+ + {t("apiKeyManagement.createdBy")} + + +
+ {apiKey.revoked_at && ( +
+ + {t("apiKeyManagement.revokedBy")} + + +
+ )} +
+ ) +} + +const ActionBy = ({ userId }: { userId: string | null }) => { + const { user, isLoading, isError, error } = useAdminUser(userId!, { + enabled: !!userId, + }) + + if (!userId) { + return ( + + - + + ) + } + + if (isError) { + throw error + } + + if (isLoading) { + return + } + + if (!user) { + return ( + + - + + ) + } + + const name = [user.first_name, user.last_name].filter(Boolean).join(" ") + + return ( + + {name || user.email} + + + ) +} diff --git a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/components/api-key-general-section/index.ts b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/components/api-key-general-section/index.ts new file mode 100644 index 0000000000000..eb2a2a50ed698 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/components/api-key-general-section/index.ts @@ -0,0 +1 @@ +export * from "./api-key-general-section" diff --git a/packages/admin-next/dashboard/src/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/routes/api-key-management/api-key-management-detail/components/api-key-sales-channel-section/api-key-sales-channel-section.tsx new file mode 100644 index 0000000000000..d2385fa94644d --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/components/api-key-sales-channel-section/api-key-sales-channel-section.tsx @@ -0,0 +1,370 @@ +import { PencilSquare, Trash } from "@medusajs/icons" +import { PublishableApiKey, 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 { + useAdminPublishableApiKeySalesChannels, + 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" + +type ApiKeySalesChannelSectionProps = { + apiKey: PublishableApiKey +} + +const PAGE_SIZE = 10 + +export const ApiKeySalesChannelSection = ({ + apiKey, +}: ApiKeySalesChannelSectionProps) => { + const { t } = useTranslation() + const navigate = useNavigate() + const prompt = usePrompt() + + const [{ pageIndex, pageSize }, setPagination] = useState({ + pageIndex: 0, + pageSize: PAGE_SIZE, + }) + + const pagination = useMemo( + () => ({ + pageIndex, + pageSize, + }), + [pageIndex, pageSize] + ) + + const [rowSelection, setRowSelection] = useState({}) + + const params = useQueryParams(["q"]) + const { sales_channels, isLoading, isError, error } = + useAdminPublishableApiKeySalesChannels( + apiKey.id, + { + ...params, + }, + { + keepPreviousData: true, + } + ) + + const count = sales_channels?.length || 0 + + const columns = useColumns() + + const table = useReactTable({ + data: sales_channels ?? [], + 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, + }, + }) + + const { mutateAsync } = useAdminRemovePublishableKeySalesChannelsBatch( + apiKey.id + ) + + const handleRemove = async () => { + const keys = Object.keys(rowSelection).filter((k) => rowSelection[k]) + + const res = await prompt({ + title: t("general.areYouSure"), + description: t("apiKeyManagement.removeSalesChannelsWarning", { + count: keys.length, + }), + confirmText: t("general.continue"), + cancelText: t("general.cancel"), + }) + + if (!res) { + return + } + + await mutateAsync( + { + sales_channel_ids: keys.map((k) => ({ id: k })), + }, + { + onSuccess: () => { + setRowSelection({}) + }, + } + ) + } + + const noRecords = !isLoading && !sales_channels?.length && !params.q + + if (isError) { + throw error + } + + return ( + +
+ {t("salesChannels.domain")} + + + +
+ {!noRecords && ( +
+
+
+ +
+
+ )} + {noRecords ? ( + + ) : ( +
+ {!isLoading && sales_channels?.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, + })} + + + + + +
+ )} +
+ ) +} + +const SalesChannelActions = ({ + salesChannel, + apiKey, +}: { + salesChannel: SalesChannel + apiKey: string +}) => { + const { t } = useTranslation() + const prompt = usePrompt() + + const { mutateAsync } = useAdminRemovePublishableKeySalesChannelsBatch(apiKey) + + const handleDelete = async () => { + const res = await prompt({ + title: t("general.areYouSure"), + description: t("locations.removeSalesChannelsWarning", { count: 1 }), + confirmText: t("general.delete"), + cancelText: t("general.cancel"), + }) + + if (!res) { + return + } + + await mutateAsync({ + sales_channel_ids: [{ id: salesChannel.id }], + }) + } + + return ( + , + label: t("general.edit"), + to: `/settings/sales-channels/${salesChannel.id}/edit`, + }, + ], + }, + { + actions: [ + { + icon: , + label: t("general.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] + ) +} diff --git a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/components/api-key-sales-channel-section/index.ts b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/components/api-key-sales-channel-section/index.ts new file mode 100644 index 0000000000000..a9b2850391b8f --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/components/api-key-sales-channel-section/index.ts @@ -0,0 +1 @@ +export * from "./api-key-sales-channel-section" diff --git a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/index.ts b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/index.ts index 2c193a6bd8137..710a7b5175286 100644 --- a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/index.ts +++ b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/index.ts @@ -1 +1,2 @@ export { ApiKeyManagementDetail as Component } from "./api-key-management-detail" +export { apiKeyLoader as loader } from "./loader" diff --git a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/loader.ts b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/loader.ts new file mode 100644 index 0000000000000..66034bcebb6a9 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/loader.ts @@ -0,0 +1,22 @@ +import { AdminPublishableApiKeysRes } from "@medusajs/medusa" +import { Response } from "@medusajs/medusa-js" +import { adminProductKeys } from "medusa-react" +import { LoaderFunctionArgs } from "react-router-dom" + +import { medusa, queryClient } from "../../../lib/medusa" + +const apiKeyDetailQuery = (id: string) => ({ + queryKey: adminProductKeys.detail(id), + queryFn: async () => medusa.admin.publishableApiKeys.retrieve(id), +}) + +export const apiKeyLoader = async ({ params }: LoaderFunctionArgs) => { + const id = params.id + const query = apiKeyDetailQuery(id!) + + return ( + queryClient.getQueryData>( + query.queryKey + ) ?? (await queryClient.fetchQuery(query)) + ) +} diff --git a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-edit/api-key-management-edit.tsx b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-edit/api-key-management-edit.tsx index 5c4125b5dd679..61c1fcaeb57a0 100644 --- a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-edit/api-key-management-edit.tsx +++ b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-edit/api-key-management-edit.tsx @@ -1,12 +1,40 @@ -import { Drawer } from "@medusajs/ui" +import { Drawer, Heading } from "@medusajs/ui" +import { useAdminPublishableApiKey } from "medusa-react" +import { useTranslation } from "react-i18next" +import { useParams } from "react-router-dom" import { useRouteModalState } from "../../../hooks/use-route-modal-state" +import { EditApiKeyForm } from "./components/edit-api-key-form" export const ApiKeyManagementEdit = () => { - const [open, onOpenChange] = useRouteModalState() + const [open, onOpenChange, subscribe] = useRouteModalState() + const { id } = useParams() + const { t } = useTranslation() + + const { publishable_api_key, isLoading, isError, error } = + useAdminPublishableApiKey(id!) + + const handleSuccessfulSubmit = () => { + onOpenChange(false, true) + } + + if (isError) { + throw error + } return ( - + + + {t("apiKeyManagement.editKey")} + + {!isLoading && publishable_api_key && ( + + )} + ) } diff --git a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-edit/components/edit-api-key-form/edit-api-key-form.tsx b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-edit/components/edit-api-key-form/edit-api-key-form.tsx new file mode 100644 index 0000000000000..0a061c564c24d --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-edit/components/edit-api-key-form/edit-api-key-form.tsx @@ -0,0 +1,90 @@ +import { zodResolver } from "@hookform/resolvers/zod" +import type { PublishableApiKey } from "@medusajs/medusa" +import { Button, Drawer, Input } from "@medusajs/ui" +import { useAdminUpdatePublishableApiKey } from "medusa-react" +import { useEffect } from "react" +import { useForm } from "react-hook-form" +import { useTranslation } from "react-i18next" +import * as zod from "zod" +import { Form } from "../../../../../components/common/form" + +type EditApiKeyFormProps = { + apiKey: PublishableApiKey + onSuccessfulSubmit: () => void + subscribe: (state: boolean) => void +} + +const EditApiKeySchema = zod.object({ + title: zod.string().min(1), +}) + +export const EditApiKeyForm = ({ + apiKey, + onSuccessfulSubmit, + subscribe, +}: EditApiKeyFormProps) => { + const { t } = useTranslation() + + const form = useForm>({ + defaultValues: { + title: apiKey.title, + }, + resolver: zodResolver(EditApiKeySchema), + }) + + const { + formState: { isDirty }, + } = form + + useEffect(() => { + subscribe(isDirty) + }, [isDirty]) + + const { mutateAsync, isLoading } = useAdminUpdatePublishableApiKey(apiKey.id) + + const handleSubmit = form.handleSubmit(async (data) => { + await mutateAsync(data, { + onSuccess: () => { + onSuccessfulSubmit() + }, + }) + }) + + return ( +
+ + +
+ { + return ( + + {t("fields.title")} + + + + + + ) + }} + /> +
+
+ +
+ + + + +
+
+
+ + ) +} diff --git a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-edit/components/edit-api-key-form/index.ts b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-edit/components/edit-api-key-form/index.ts new file mode 100644 index 0000000000000..441fcefa9f79f --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-edit/components/edit-api-key-form/index.ts @@ -0,0 +1 @@ +export * from "./edit-api-key-form" diff --git a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-list/components/api-key-management-list-table/api-key-management-list-table.tsx b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-list/components/api-key-management-list-table/api-key-management-list-table.tsx index 4edd5e6a1c16b..5b6a0c2ea1499 100644 --- a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-list/components/api-key-management-list-table/api-key-management-list-table.tsx +++ b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-list/components/api-key-management-list-table/api-key-management-list-table.tsx @@ -1,6 +1,16 @@ import { PencilSquare, Trash, XCircle } from "@medusajs/icons" import { PublishableApiKey } from "@medusajs/medusa" -import { Button, Container, Heading, Table, clx, usePrompt } from "@medusajs/ui" +import { + Button, + Container, + Copy, + Heading, + StatusBadge, + Table, + Text, + clx, + usePrompt, +} from "@medusajs/ui" import { PaginationState, RowSelectionState, @@ -9,6 +19,7 @@ import { getCoreRowModel, useReactTable, } from "@tanstack/react-table" +import { format } from "date-fns" import { useAdminDeletePublishableApiKey, useAdminPublishableApiKeys, @@ -71,7 +82,7 @@ export const ApiKeyManagementListTable = () => { } return ( - +
{t("apiKeyManagement.domain")} @@ -89,7 +100,7 @@ export const ApiKeyManagementListTable = () => { return ( {headerGroup.headers.map((header) => { return ( @@ -247,7 +258,46 @@ const useColumns = () => { }), columnHelper.accessor("id", { header: "Key", - cell: ({ getValue }) => getValue(), + cell: ({ getValue }) => { + const token = getValue() + + return ( +
e.stopPropagation()} + > + + {token} + + +
+ ) + }, + }), + columnHelper.accessor("revoked_at", { + header: t("fields.status"), + cell: ({ getValue }) => { + const revokedAt = getValue() + + return ( + + {revokedAt ? t("general.revoked") : t("general.active")} + + ) + }, + }), + columnHelper.accessor("created_at", { + header: t("fields.created"), + cell: ({ getValue }) => { + const date = getValue() + + return format(new Date(date), "dd MMM, yyyy") + }, }), columnHelper.display({ id: "actions", diff --git a/packages/admin-next/dashboard/src/routes/locations/location-detail/components/location-sales-channel-section/location-sales-channel-section.tsx b/packages/admin-next/dashboard/src/routes/locations/location-detail/components/location-sales-channel-section/location-sales-channel-section.tsx index cf0e17529974b..deeafce0efbdb 100644 --- a/packages/admin-next/dashboard/src/routes/locations/location-detail/components/location-sales-channel-section/location-sales-channel-section.tsx +++ b/packages/admin-next/dashboard/src/routes/locations/location-detail/components/location-sales-channel-section/location-sales-channel-section.tsx @@ -29,7 +29,7 @@ type LocationSalesChannelSectionProps = { location: StockLocationExpandedDTO } -const PAGE_SIZE = 20 +const PAGE_SIZE = 10 export const LocationSalesChannelSection = ({ location, diff --git a/packages/admin-next/dashboard/src/routes/sales-channels/sales-channel-detail/index.ts b/packages/admin-next/dashboard/src/routes/sales-channels/sales-channel-detail/index.ts index 29e5a16bccb01..8d5a59045f7b8 100644 --- a/packages/admin-next/dashboard/src/routes/sales-channels/sales-channel-detail/index.ts +++ b/packages/admin-next/dashboard/src/routes/sales-channels/sales-channel-detail/index.ts @@ -1 +1,2 @@ +export { salesChannelLoader as loader } from "./loader" export { SalesChannelDetail as Component } from "./sales-channel-detail" diff --git a/packages/admin-next/dashboard/src/routes/sales-channels/sales-channel-detail/loader.ts b/packages/admin-next/dashboard/src/routes/sales-channels/sales-channel-detail/loader.ts new file mode 100644 index 0000000000000..5208c2e065737 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/sales-channels/sales-channel-detail/loader.ts @@ -0,0 +1,21 @@ +import { AdminSalesChannelsRes } from "@medusajs/medusa" +import { Response } from "@medusajs/medusa-js" +import { adminProductKeys } from "medusa-react" +import { LoaderFunctionArgs } from "react-router-dom" + +import { medusa, queryClient } from "../../../lib/medusa" + +const salesChannelDetailQuery = (id: string) => ({ + queryKey: adminProductKeys.detail(id), + queryFn: async () => medusa.admin.salesChannels.retrieve(id), +}) + +export const salesChannelLoader = async ({ params }: LoaderFunctionArgs) => { + const id = params.id + const query = salesChannelDetailQuery(id!) + + return ( + queryClient.getQueryData>(query.queryKey) ?? + (await queryClient.fetchQuery(query)) + ) +} diff --git a/packages/admin-next/dashboard/src/routes/sales-channels/sales-channel-detail/sales-channel-detail.tsx b/packages/admin-next/dashboard/src/routes/sales-channels/sales-channel-detail/sales-channel-detail.tsx index a8250eb963ce9..dd74f831360b5 100644 --- a/packages/admin-next/dashboard/src/routes/sales-channels/sales-channel-detail/sales-channel-detail.tsx +++ b/packages/admin-next/dashboard/src/routes/sales-channels/sales-channel-detail/sales-channel-detail.tsx @@ -1,13 +1,20 @@ import { useAdminSalesChannel } from "medusa-react" -import { Outlet, useParams } from "react-router-dom" +import { Outlet, useLoaderData, useParams } from "react-router-dom" import { JsonViewSection } from "../../../components/common/json-view-section" import { SalesChannelGeneralSection } from "./components/sales-channel-general-section" import { SalesChannelProductSection } from "./components/sales-channel-product-section" +import { salesChannelLoader } from "./loader" export const SalesChannelDetail = () => { + const initialData = useLoaderData() as Awaited< + ReturnType + > + const { id } = useParams() - const { sales_channel, isLoading } = useAdminSalesChannel(id!) + const { sales_channel, isLoading } = useAdminSalesChannel(id!, { + initialData, + }) if (isLoading || !sales_channel) { return
Loading...
diff --git a/packages/design-system/ui/src/components/copy/copy.tsx b/packages/design-system/ui/src/components/copy/copy.tsx index 9eceab6771821..0fc55300049d0 100644 --- a/packages/design-system/ui/src/components/copy/copy.tsx +++ b/packages/design-system/ui/src/components/copy/copy.tsx @@ -2,77 +2,115 @@ import { Tooltip } from "@/components/tooltip" import { clx } from "@/utils/clx" -import { CheckCircleSolid, SquareTwoStack } from "@medusajs/icons" +import { + CheckCircleMiniSolid, + CheckCircleSolid, + SquareTwoStack, + SquareTwoStackMini, +} from "@medusajs/icons" import { Slot } from "@radix-ui/react-slot" import copy from "copy-to-clipboard" import React, { useState } from "react" type CopyProps = React.HTMLAttributes & { content: string + variant?: "mini" | "default" | null asChild?: boolean } /** * This component is based on the `button` element and supports all of its props */ -const Copy = React.forwardRef< - HTMLButtonElement, - CopyProps ->(({ - children, - className, - /** - * The content to copy. - */ - content, - /** - * Whether to remove the wrapper `button` element and use the - * passed child element instead. - */ - asChild = false, - ...props - }: CopyProps, ref) => { - const [done, setDone] = useState(false) - const [open, setOpen] = useState(false) - const [text, setText] = useState("Copy") +const Copy = React.forwardRef( + ( + { + children, + className, + /** + * The content to copy. + */ + content, + /** + * The variant of the copy button. + */ + variant = "default", + /** + * Whether to remove the wrapper `button` element and use the + * passed child element instead. + */ + asChild = false, + ...props + }: CopyProps, + ref + ) => { + const [done, setDone] = useState(false) + const [open, setOpen] = useState(false) + const [text, setText] = useState("Copy") - const copyToClipboard = () => { - setDone(true) - copy(content) + const copyToClipboard = ( + e: + | React.MouseEvent + | React.MouseEvent + ) => { + e.stopPropagation() - setTimeout(() => { - setDone(false) - }, 2000) - } + setDone(true) + copy(content) + + setTimeout(() => { + setDone(false) + }, 2000) + } - React.useEffect(() => { - if (done) { - setText("Copied") - return + React.useEffect(() => { + if (done) { + setText("Copied") + return + } + + setTimeout(() => { + setText("Copy") + }, 500) + }, [done]) + + const isDefaultVariant = ( + variant?: string | null + ): variant is "default" => { + return variant === "default" } - setTimeout(() => { - setText("Copy") - }, 500) - }, [done]) + const isDefault = isDefaultVariant(variant) - const Component = asChild ? Slot : "button" + const Component = asChild ? Slot : "button" - return ( - - - {children ? children : done ? : } - - - ) -}) + return ( + + + {children ? ( + children + ) : done ? ( + isDefault ? ( + + ) : ( + + ) + ) : isDefault ? ( + + ) : ( + + )} + + + ) + } +) Copy.displayName = "Copy" export { Copy } From 5e0db37ddb87d7ca7494e761887decbc11f8a23c Mon Sep 17 00:00:00 2001 From: Kasper Date: Mon, 22 Jan 2024 15:45:22 +0100 Subject: [PATCH 2/9] add changeset --- .changeset/gentle-pots-enjoy.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/gentle-pots-enjoy.md diff --git a/.changeset/gentle-pots-enjoy.md b/.changeset/gentle-pots-enjoy.md new file mode 100644 index 0000000000000..9629706edaefe --- /dev/null +++ b/.changeset/gentle-pots-enjoy.md @@ -0,0 +1,5 @@ +--- +"@medusajs/ui": patch +--- + +feature(ui): Add mini variant to Copy component, and prevent clicks from propigating to parent elements" From fa5aab19a95f93519e01098b853ff4e9956533a7 Mon Sep 17 00:00:00 2001 From: Kasper Date: Tue, 23 Jan 2024 10:36:09 +0100 Subject: [PATCH 3/9] fix: navigate to new api key on create --- .../create-publishable-api-key-form.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-create/components/create-publishable-api-key-form/create-publishable-api-key-form.tsx b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-create/components/create-publishable-api-key-form/create-publishable-api-key-form.tsx index dd5c196b8325d..d7766fed21718 100644 --- a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-create/components/create-publishable-api-key-form/create-publishable-api-key-form.tsx +++ b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-create/components/create-publishable-api-key-form/create-publishable-api-key-form.tsx @@ -6,6 +6,7 @@ import { useTranslation } from "react-i18next" import * as zod from "zod" import { useEffect } from "react" +import { useNavigate } from "react-router-dom" import { Form } from "../../../../../components/common/form" type CreatePublishableApiKeyFormProps = { @@ -37,9 +38,16 @@ export const CreatePublishableApiKeyForm = ({ }, [isDirty]) const { t } = useTranslation() + const navigate = useNavigate() const handleSubmit = form.handleSubmit(async (values) => { - await mutateAsync(values) + await mutateAsync(values, { + onSuccess: ({ publishable_api_key }) => { + navigate(`/settings/api-key-management/${publishable_api_key.id}`, { + replace: true, + }) + }, + }) }) return ( From 93e6b5f53604859804f4365708edb4622d89f591 Mon Sep 17 00:00:00 2001 From: Kasper Date: Tue, 23 Jan 2024 14:55:10 +0100 Subject: [PATCH 4/9] address feedback and small adjustments do UI --- .changeset/gentle-pots-enjoy.md | 2 +- .../public/locales/en/translation.json | 1 + .../src/components/common/user-link/index.ts | 1 + .../components/common/user-link/user-link.tsx | 34 +++++++++ .../api-key-general-section.tsx | 70 ++++++++----------- .../api-key-sales-channel-section.tsx | 10 ++- .../api-key-management-list-table.tsx | 7 +- .../ui/src/components/avatar/avatar.tsx | 22 ++++-- .../components/status-badge/status-badge.tsx | 70 ++++++++++++------- yarn.lock | 29 ++++++++ 10 files changed, 161 insertions(+), 85 deletions(-) create mode 100644 packages/admin-next/dashboard/src/components/common/user-link/index.ts create mode 100644 packages/admin-next/dashboard/src/components/common/user-link/user-link.tsx diff --git a/.changeset/gentle-pots-enjoy.md b/.changeset/gentle-pots-enjoy.md index 9629706edaefe..4d498c9594427 100644 --- a/.changeset/gentle-pots-enjoy.md +++ b/.changeset/gentle-pots-enjoy.md @@ -2,4 +2,4 @@ "@medusajs/ui": patch --- -feature(ui): Add mini variant to Copy component, and prevent clicks from propigating to parent elements" +feature(ui): Adds a `size` variant to `` component, and prevent clicks from propigating to parent elements". Also adds a new `size` prop to `` component, and additional sizes to the `` component. diff --git a/packages/admin-next/dashboard/public/locales/en/translation.json b/packages/admin-next/dashboard/public/locales/en/translation.json index 4ce5ae032c383..8d0c34af3155d 100644 --- a/packages/admin-next/dashboard/public/locales/en/translation.json +++ b/packages/admin-next/dashboard/public/locales/en/translation.json @@ -190,6 +190,7 @@ "publishableApiKeyHint": "Publishable API keys are used to limit the scope of requests to specific sales channels.", "deleteKeyWarning": "You are about to delete the API key {{title}}. This action cannot be undone.", "revokeKeyWarning": "You are about to revoke the API key {{title}}. This action cannot be undone, and the key cannot be used in future requests.", + "removeSalesChannelWarning": "You are about to remove the sales channel {{name}} from the API key. This action cannot be undone.", "removeSalesChannelsWarning_one": "You are about to remove {{count}} sales channel from the API key. This action cannot be undone.", "removeSalesChannelsWarning_other": "You are about to remove {{count}} sales channels from the API key. This action cannot be undone.", "createdBy": "Created by", diff --git a/packages/admin-next/dashboard/src/components/common/user-link/index.ts b/packages/admin-next/dashboard/src/components/common/user-link/index.ts new file mode 100644 index 0000000000000..951235f82422f --- /dev/null +++ b/packages/admin-next/dashboard/src/components/common/user-link/index.ts @@ -0,0 +1 @@ +export * from "./user-link" diff --git a/packages/admin-next/dashboard/src/components/common/user-link/user-link.tsx b/packages/admin-next/dashboard/src/components/common/user-link/user-link.tsx new file mode 100644 index 0000000000000..1a23adcf617ab --- /dev/null +++ b/packages/admin-next/dashboard/src/components/common/user-link/user-link.tsx @@ -0,0 +1,34 @@ +import { Avatar, Text } from "@medusajs/ui" +import { Link } from "react-router-dom" + +type UserLinkProps = { + id: string + first_name?: string | null + last_name?: string | null + email: string + type?: "customer" | "user" +} + +export const UserLink = ({ + id, + first_name, + last_name, + email, + type = "user", +}: UserLinkProps) => { + const name = [first_name, last_name].filter(Boolean).join(" ") + const fallback = name ? name.slice(0, 1) : email.slice(0, 1) + const link = type === "user" ? `/settings/users/${id}` : `/customers/${id}` + + return ( + + + + {name || email} + + + ) +} diff --git a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/components/api-key-general-section/api-key-general-section.tsx b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/components/api-key-general-section/api-key-general-section.tsx index c19f63f454775..c03cfc689f8d8 100644 --- a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/components/api-key-general-section/api-key-general-section.tsx +++ b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/components/api-key-general-section/api-key-general-section.tsx @@ -1,9 +1,4 @@ -import { - ArrowUpRightOnBox, - PencilSquare, - Trash, - XCircle, -} from "@medusajs/icons" +import { PencilSquare, Trash, XCircle } from "@medusajs/icons" import { PublishableApiKey } from "@medusajs/medusa" import { Container, @@ -19,9 +14,9 @@ import { useAdminUser, } from "medusa-react" import { useTranslation } from "react-i18next" -import { Link } from "react-router-dom" import { ActionMenu } from "../../../../../components/common/action-menu" import { Skeleton } from "../../../../../components/common/skeleton" +import { UserLink } from "../../../../../components/common/user-link" type ApiKeyGeneralSectionProps = { apiKey: PublishableApiKey @@ -72,6 +67,22 @@ export const ApiKeyGeneralSection = ({ apiKey }: ApiKeyGeneralSectionProps) => { await revokeAsync() } + const dangerousActions = [ + { + icon: , + label: t("general.delete"), + onClick: handleDelete, + }, + ] + + if (!apiKey.revoked_at) { + dangerousActions.unshift({ + icon: , + label: t("apiKeyManagement.revoke"), + onClick: handleRevoke, + }) + } + return (
@@ -92,24 +103,13 @@ export const ApiKeyGeneralSection = ({ apiKey }: ApiKeyGeneralSectionProps) => { ], }, { - actions: [ - { - icon: , - label: t("apiKeyManagement.revoke"), - onClick: handleRevoke, - }, - { - icon: , - label: t("general.delete"), - onClick: handleDelete, - }, - ], + actions: dangerousActions, }, ]} />
-
+
{t("fields.key")} @@ -117,25 +117,20 @@ export const ApiKeyGeneralSection = ({ apiKey }: ApiKeyGeneralSectionProps) => { className="bg-ui-tag-neutral-bg border border-ui-tag-neutral-border text-ui-tag-neutral-text flex items-center gap-x-0.5 w-fit rounded-full pl-2 pr-1 py-px cursor-default overflow-hidden" onClick={(e) => e.stopPropagation()} > - + {apiKey.id}
-
+
{t("apiKeyManagement.createdBy")}
{apiKey.revoked_at && ( -
+
{t("apiKeyManagement.revokedBy")} @@ -164,7 +159,12 @@ const ActionBy = ({ userId }: { userId: string | null }) => { } if (isLoading) { - return + return ( +
+ + +
+ ) } if (!user) { @@ -175,15 +175,5 @@ const ActionBy = ({ userId }: { userId: string | null }) => { ) } - const name = [user.first_name, user.last_name].filter(Boolean).join(" ") - - return ( - - {name || user.email} - - - ) + return } diff --git a/packages/admin-next/dashboard/src/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/routes/api-key-management/api-key-management-detail/components/api-key-sales-channel-section/api-key-sales-channel-section.tsx index d2385fa94644d..3071e1e3f58ff 100644 --- a/packages/admin-next/dashboard/src/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/routes/api-key-management/api-key-management-detail/components/api-key-sales-channel-section/api-key-sales-channel-section.tsx @@ -139,11 +139,9 @@ export const ApiKeySalesChannelSection = ({
{t("salesChannels.domain")} - - - +
{!noRecords && (
@@ -256,7 +254,7 @@ const SalesChannelActions = ({ const handleDelete = async () => { const res = await prompt({ title: t("general.areYouSure"), - description: t("locations.removeSalesChannelsWarning", { count: 1 }), + description: t("apiKeyManagement.removeSalesChannelWarning"), confirmText: t("general.delete"), cancelText: t("general.cancel"), }) diff --git a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-list/components/api-key-management-list-table/api-key-management-list-table.tsx b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-list/components/api-key-management-list-table/api-key-management-list-table.tsx index 5b6a0c2ea1499..21c0b3915d7b1 100644 --- a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-list/components/api-key-management-list-table/api-key-management-list-table.tsx +++ b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-list/components/api-key-management-list-table/api-key-management-list-table.tsx @@ -266,12 +266,7 @@ const useColumns = () => { className="bg-ui-tag-neutral-bg border border-ui-tag-neutral-border text-ui-tag-neutral-text flex items-center gap-x-0.5 w-fit max-w-[200px] rounded-full pl-2 pr-1 py-px cursor-default overflow-hidden" onClick={(e) => e.stopPropagation()} > - + {token} diff --git a/packages/design-system/ui/src/components/avatar/avatar.tsx b/packages/design-system/ui/src/components/avatar/avatar.tsx index 9828742510521..9a9df41d1db61 100644 --- a/packages/design-system/ui/src/components/avatar/avatar.tsx +++ b/packages/design-system/ui/src/components/avatar/avatar.tsx @@ -14,18 +14,24 @@ const avatarVariants = cva({ rounded: "rounded-full", }, size: { - xsmall: "h-5 w-5", - small: "h-6 w-6", + "2xsmall": "h-5 w-5", + xsmall: "h-6 w-6", + small: "h-7 w-7", base: "h-8 w-8", large: "h-10 w-10", xlarge: "h-12 w-12", }, }, compoundVariants: [ + { + variant: "squared", + size: "2xsmall", + className: "rounded-md", + }, { variant: "squared", size: "xsmall", - className: "rounded-[4px]", + className: "rounded-md", }, { variant: "squared", @@ -62,14 +68,20 @@ const innerVariants = cva({ rounded: "rounded-full", }, size: { - xsmall: "txt-compact-xsmall-plus h-4 w-4", - small: "txt-compact-xsmall-plus h-5 w-5", + "2xsmall": "txt-compact-xsmall-plus h-4 w-4", + xsmall: "txt-compact-xsmall-plus h-5 w-5", + small: "txt-compact-small-plus h-6 w-6", base: "txt-compact-small-plus h-7 w-7", large: "txt-compact-medium-plus h-9 w-9", xlarge: "txt-compact-large-plus h-11 w-11", }, }, compoundVariants: [ + { + variant: "squared", + size: "2xsmall", + className: "rounded-sm", + }, { variant: "squared", size: "xsmall", diff --git a/packages/design-system/ui/src/components/status-badge/status-badge.tsx b/packages/design-system/ui/src/components/status-badge/status-badge.tsx index 6f7af3c591292..083efcd8b4198 100644 --- a/packages/design-system/ui/src/components/status-badge/status-badge.tsx +++ b/packages/design-system/ui/src/components/status-badge/status-badge.tsx @@ -1,11 +1,43 @@ import * as React from "react" import { clx } from "@/utils/clx" +import { VariantProps, cva } from "cva" + +const statusBadgeVariants = cva({ + base: "txt-compact-xsmall-plus leading-none bg-ui-bg-subtle text-ui-fg-subtle border border-ui-border-base flex items-center select-none w-fit box-border overflow-hidden", + variants: { + size: { + small: "pl-0.5 pr-2 py-[3px] rounded-md gap-x-0.5", + xsmall: "pl-0 pr-1.5 py-px rounded-md", + "2xsmall": "pl-0 pr-1 rounded-md", + }, + }, + defaultVariants: { + size: "2xsmall", + }, +}) + +const dotVariants = cva({ + base: "flex items-center justify-center w-5 h-[18px] [&_div]:w-2 [&_div]:h-2 [&_div]:rounded-sm", + variants: { + color: { + green: "[&_div]:bg-ui-tag-green-icon", + red: "[&_div]:bg-ui-tag-red-icon", + orange: "[&_div]:bg-ui-tag-orange-icon", + blue: "[&_div]:bg-ui-tag-blue-icon", + purple: "[&_div]:bg-ui-tag-purple-icon", + grey: "[&_div]:bg-ui-tag-neutral-icon", + }, + }, + defaultVariants: { + color: "grey", + }, +}) interface StatusBadgeProps - extends Omit, "color"> { - color?: "green" | "red" | "blue" | "orange" | "grey" | "purple" -} + extends Omit, "color">, + VariantProps, + VariantProps {} /** * This component is based on the span element and supports all of its props @@ -19,6 +51,10 @@ const StatusBadge = React.forwardRef( * The status's color. */ color = "grey", + /** + * The size of the badge. + */ + size = "2xsmall", ...props }: StatusBadgeProps, ref @@ -26,32 +62,12 @@ const StatusBadge = React.forwardRef( return ( - - - - - +
+
+
{children} ) diff --git a/yarn.lock b/yarn.lock index ee39d59dc07a7..79f758f4fbd30 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7997,6 +7997,7 @@ __metadata: "@medusajs/ui-preset": "workspace:^" "@medusajs/vite-plugin-extension": "workspace:^" "@radix-ui/react-collapsible": 1.0.3 + "@radix-ui/react-hover-card": ^1.0.7 "@tanstack/react-query": 4.22.0 "@tanstack/react-table": 8.10.7 "@types/react": 18.2.43 @@ -10288,6 +10289,34 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-hover-card@npm:^1.0.7": + version: 1.0.7 + resolution: "@radix-ui/react-hover-card@npm:1.0.7" + dependencies: + "@babel/runtime": ^7.13.10 + "@radix-ui/primitive": 1.0.1 + "@radix-ui/react-compose-refs": 1.0.1 + "@radix-ui/react-context": 1.0.1 + "@radix-ui/react-dismissable-layer": 1.0.5 + "@radix-ui/react-popper": 1.1.3 + "@radix-ui/react-portal": 1.0.4 + "@radix-ui/react-presence": 1.0.1 + "@radix-ui/react-primitive": 1.0.3 + "@radix-ui/react-use-controllable-state": 1.0.1 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: f29f3da5bd9a967b5a35e91ac2d1b223191c7a074550d9d9cc10a0c0baf62ba0705b32912a7d2ef1ea5c27dd5e130a9fda9cbe6c2a7f3c2037ed5dfed89aa8cc + languageName: node + linkType: hard + "@radix-ui/react-id@npm:1.0.0": version: 1.0.0 resolution: "@radix-ui/react-id@npm:1.0.0" From 7be10337ca17cc69b26a8e3d8551f656dbd9fe04 Mon Sep 17 00:00:00 2001 From: Kasper Date: Tue, 23 Jan 2024 15:00:14 +0100 Subject: [PATCH 5/9] fix: font weight --- .../api-key-general-section/api-key-general-section.tsx | 2 +- .../api-key-management-list-table.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/components/api-key-general-section/api-key-general-section.tsx b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/components/api-key-general-section/api-key-general-section.tsx index c03cfc689f8d8..202175953add9 100644 --- a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/components/api-key-general-section/api-key-general-section.tsx +++ b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/components/api-key-general-section/api-key-general-section.tsx @@ -117,7 +117,7 @@ export const ApiKeyGeneralSection = ({ apiKey }: ApiKeyGeneralSectionProps) => { className="bg-ui-tag-neutral-bg border border-ui-tag-neutral-border text-ui-tag-neutral-text flex items-center gap-x-0.5 w-fit rounded-full pl-2 pr-1 py-px cursor-default overflow-hidden" onClick={(e) => e.stopPropagation()} > - + {apiKey.id} diff --git a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-list/components/api-key-management-list-table/api-key-management-list-table.tsx b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-list/components/api-key-management-list-table/api-key-management-list-table.tsx index 21c0b3915d7b1..900e16570fa9a 100644 --- a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-list/components/api-key-management-list-table/api-key-management-list-table.tsx +++ b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-list/components/api-key-management-list-table/api-key-management-list-table.tsx @@ -263,10 +263,10 @@ const useColumns = () => { return (
e.stopPropagation()} > - + {token} From 6e8f6b34dba6f0d611af1681207b4a4ce95d2bc5 Mon Sep 17 00:00:00 2001 From: Kasper Date: Tue, 23 Jan 2024 15:13:32 +0100 Subject: [PATCH 6/9] fix: copy text styles --- .../api-key-general-section.tsx | 11 ++--- .../api-key-management-list-table.tsx | 8 +++- .../store-general-section.tsx | 45 ++++++++++++------- 3 files changed, 42 insertions(+), 22 deletions(-) diff --git a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/components/api-key-general-section/api-key-general-section.tsx b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/components/api-key-general-section/api-key-general-section.tsx index 202175953add9..89ebed001d2bb 100644 --- a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/components/api-key-general-section/api-key-general-section.tsx +++ b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-detail/components/api-key-general-section/api-key-general-section.tsx @@ -113,14 +113,15 @@ export const ApiKeyGeneralSection = ({ apiKey }: ApiKeyGeneralSectionProps) => { {t("fields.key")} -
e.stopPropagation()} - > +
{apiKey.id} - +
diff --git a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-list/components/api-key-management-list-table/api-key-management-list-table.tsx b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-list/components/api-key-management-list-table/api-key-management-list-table.tsx index 900e16570fa9a..4effd24a8c415 100644 --- a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-list/components/api-key-management-list-table/api-key-management-list-table.tsx +++ b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-list/components/api-key-management-list-table/api-key-management-list-table.tsx @@ -263,13 +263,17 @@ const useColumns = () => { return (
e.stopPropagation()} > {token} - +
) }, diff --git a/packages/admin-next/dashboard/src/routes/store/store-detail/components/store-general-section/store-general-section.tsx b/packages/admin-next/dashboard/src/routes/store/store-detail/components/store-general-section/store-general-section.tsx index 73c147547f753..362c98141a936 100644 --- a/packages/admin-next/dashboard/src/routes/store/store-detail/components/store-general-section/store-general-section.tsx +++ b/packages/admin-next/dashboard/src/routes/store/store-detail/components/store-general-section/store-general-section.tsx @@ -51,11 +51,16 @@ export const StoreGeneralSection = ({ store }: StoreGeneralSectionProps) => { {t("store.swapLinkTemplate")} {store.swap_link_template ? ( - - - {store.swap_link_template} - - +
+ + {store.swap_link_template} + + +
) : ( - @@ -67,11 +72,16 @@ export const StoreGeneralSection = ({ store }: StoreGeneralSectionProps) => { {t("store.paymentLinkTemplate")} {store.payment_link_template ? ( - - - {store.payment_link_template} - - +
+ + {store.payment_link_template} + + +
) : ( - @@ -83,11 +93,16 @@ export const StoreGeneralSection = ({ store }: StoreGeneralSectionProps) => { {t("store.inviteLinkTemplate")} {store.invite_link_template ? ( - - - {store.invite_link_template} - - +
+ + {store.invite_link_template} + + +
) : ( - From a7267c758f2f2698a8ca4e3745cbc5527a83a343 Mon Sep 17 00:00:00 2001 From: Kasper Date: Tue, 23 Jan 2024 17:44:41 +0100 Subject: [PATCH 7/9] update lock --- yarn.lock | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/yarn.lock b/yarn.lock index 79f758f4fbd30..ee39d59dc07a7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7997,7 +7997,6 @@ __metadata: "@medusajs/ui-preset": "workspace:^" "@medusajs/vite-plugin-extension": "workspace:^" "@radix-ui/react-collapsible": 1.0.3 - "@radix-ui/react-hover-card": ^1.0.7 "@tanstack/react-query": 4.22.0 "@tanstack/react-table": 8.10.7 "@types/react": 18.2.43 @@ -10289,34 +10288,6 @@ __metadata: languageName: node linkType: hard -"@radix-ui/react-hover-card@npm:^1.0.7": - version: 1.0.7 - resolution: "@radix-ui/react-hover-card@npm:1.0.7" - dependencies: - "@babel/runtime": ^7.13.10 - "@radix-ui/primitive": 1.0.1 - "@radix-ui/react-compose-refs": 1.0.1 - "@radix-ui/react-context": 1.0.1 - "@radix-ui/react-dismissable-layer": 1.0.5 - "@radix-ui/react-popper": 1.1.3 - "@radix-ui/react-portal": 1.0.4 - "@radix-ui/react-presence": 1.0.1 - "@radix-ui/react-primitive": 1.0.3 - "@radix-ui/react-use-controllable-state": 1.0.1 - peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - "@types/react": - optional: true - "@types/react-dom": - optional: true - checksum: f29f3da5bd9a967b5a35e91ac2d1b223191c7a074550d9d9cc10a0c0baf62ba0705b32912a7d2ef1ea5c27dd5e130a9fda9cbe6c2a7f3c2037ed5dfed89aa8cc - languageName: node - linkType: hard - "@radix-ui/react-id@npm:1.0.0": version: 1.0.0 resolution: "@radix-ui/react-id@npm:1.0.0" From 727a2d83e1e6d426b129e1b0cd0adac1d47a573e Mon Sep 17 00:00:00 2001 From: Kasper Date: Tue, 23 Jan 2024 17:52:39 +0100 Subject: [PATCH 8/9] rm size variant from StatusBadge --- .../components/status-badge/status-badge.tsx | 28 ++++--------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/packages/design-system/ui/src/components/status-badge/status-badge.tsx b/packages/design-system/ui/src/components/status-badge/status-badge.tsx index 083efcd8b4198..80a955db82f32 100644 --- a/packages/design-system/ui/src/components/status-badge/status-badge.tsx +++ b/packages/design-system/ui/src/components/status-badge/status-badge.tsx @@ -4,20 +4,6 @@ import { clx } from "@/utils/clx" import { VariantProps, cva } from "cva" const statusBadgeVariants = cva({ - base: "txt-compact-xsmall-plus leading-none bg-ui-bg-subtle text-ui-fg-subtle border border-ui-border-base flex items-center select-none w-fit box-border overflow-hidden", - variants: { - size: { - small: "pl-0.5 pr-2 py-[3px] rounded-md gap-x-0.5", - xsmall: "pl-0 pr-1.5 py-px rounded-md", - "2xsmall": "pl-0 pr-1 rounded-md", - }, - }, - defaultVariants: { - size: "2xsmall", - }, -}) - -const dotVariants = cva({ base: "flex items-center justify-center w-5 h-[18px] [&_div]:w-2 [&_div]:h-2 [&_div]:rounded-sm", variants: { color: { @@ -36,8 +22,7 @@ const dotVariants = cva({ interface StatusBadgeProps extends Omit, "color">, - VariantProps, - VariantProps {} + VariantProps {} /** * This component is based on the span element and supports all of its props @@ -51,10 +36,6 @@ const StatusBadge = React.forwardRef( * The status's color. */ color = "grey", - /** - * The size of the badge. - */ - size = "2xsmall", ...props }: StatusBadgeProps, ref @@ -62,10 +43,13 @@ const StatusBadge = React.forwardRef( return ( -
+
{children} From 692df6c1ac595550e8f85d50e7342a772b3d2f98 Mon Sep 17 00:00:00 2001 From: Kasper Date: Tue, 23 Jan 2024 17:53:54 +0100 Subject: [PATCH 9/9] update changeset --- .changeset/gentle-pots-enjoy.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/gentle-pots-enjoy.md b/.changeset/gentle-pots-enjoy.md index 4d498c9594427..c6e75da20ab6f 100644 --- a/.changeset/gentle-pots-enjoy.md +++ b/.changeset/gentle-pots-enjoy.md @@ -2,4 +2,4 @@ "@medusajs/ui": patch --- -feature(ui): Adds a `size` variant to `` component, and prevent clicks from propigating to parent elements". Also adds a new `size` prop to `` component, and additional sizes to the `` component. +feature(ui): Adds a `size` variant to `` component, and prevent clicks from propigating to parent elements". Also adds additional sizes to the `` component.