refactor(dashboard): refresh domains (#7087)

This commit is contained in:
Frane Polić
2024-04-24 10:38:52 +02:00
committed by GitHub
parent 40686ba980
commit fe68b5c0f2
49 changed files with 624 additions and 228 deletions
@@ -280,7 +280,12 @@
"manageInventoryLabel": "Manage inventory",
"manageInventoryHint": "When enabled the inventory level will be regulated when orders and returns are created.",
"allowBackordersLabel": "Allow backorders",
"allowBackordersHint": "When enabled the variant can be sold even if the inventory level is below zero."
"allowBackordersHint": "When enabled the variant can be sold even if the inventory level is below zero.",
"toast": {
"levelsBatch": "Inventory levels updated.",
"update": "Inventory item updated successfully",
"updateLevel": "Inventory level updated successfully"
}
}
},
"options": {
@@ -1018,6 +1023,9 @@
"languageHint": "The language you want to use in the admin dashboard. This will not change the language of your store.",
"languagePlaceholder": "Select language",
"usageInsightsHint": "Share usage insights and help us improve Medusa. You can read more about what we collect and how we use it in our <0>documentation</0>."
},
"toast": {
"edit": "Profiles changes saved"
}
},
"users": {
@@ -1051,6 +1059,7 @@
"manageYourStoresDetails": "Manage your store's details",
"editStore": "Edit store",
"defaultCurrency": "Default currency",
"defaultRegion": "Default region",
"swapLinkTemplate": "Swap link template",
"paymentLinkTemplate": "Payment link template",
"inviteLinkTemplate": "Invite link template",
@@ -1061,6 +1070,11 @@
"currencyAlreadyAdded": "The currency has already been added to your store.",
"edit": {
"header": "Edit Store"
},
"toast": {
"update": "Store successfully updated",
"currenciesUpdated": "Currencies updated successfully",
"currenciesRemoved": "Removed currencies from the store successfully"
}
},
"regions": {
@@ -1171,7 +1185,12 @@
"selectLocations": "Select locations that stock the item.",
"deleteLocationWarning": "You are about to delete the location {{name}}. This action cannot be undone.",
"removeSalesChannelsWarning_one": "You are about to remove {{count}} sales channel from the location.",
"removeSalesChannelsWarning_other": "You are about to remove {{count}} sales channels from the location."
"removeSalesChannelsWarning_other": "You are about to remove {{count}} sales channels from the location.",
"toast": {
"create": "Location created sucessfully",
"update": "Location updated sucessfully",
"removeChannel": "Sales channel removed sucessfully"
}
},
"reservations": {
"domain": "Reservations",
@@ -1187,7 +1206,12 @@
"addProducts": "Add Products",
"editSalesChannel": "Edit sales channel",
"productAlreadyAdded": "The product has already been added to the sales channel.",
"deleteSalesChannelWarning": "You are about to delete the sales channel {{name}}. This action cannot be undone."
"deleteSalesChannelWarning": "You are about to delete the sales channel {{name}}. This action cannot be undone.",
"toast": {
"create": "Sales channel created successfully",
"update": "Sales channel updated successfully",
"delete": "Sales channel deleted successfully"
}
},
"apiKeyManagement": {
"domain": {
@@ -1279,7 +1303,10 @@
"successAction": "Sign in to start using Medusa",
"invalidTokenTitle": "Your invite token is invalid",
"invalidTokenHint": "Try requesting a new invite link.",
"passwordMismatch": "Passwords do not match"
"passwordMismatch": "Passwords do not match",
"toast": {
"accepted": "Invite successfully accepted"
}
},
"resetPassword": {
"title": "Reset password",
@@ -167,6 +167,7 @@ export type InventoryItemRes = {
stocked_quantity: number
reserved_quantity: number
location_levels?: InventoryNext.InventoryLevelDTO[]
variant?: ProductVariantDTO | ProductVariantDTO[]
}
}
@@ -13,7 +13,7 @@ export const AdjustInventoryDrawer = () => {
const {
inventory_item: inventoryItem,
isLoading,
isPending: isLoading,
isError,
error,
} = useInventoryItem(id!)
@@ -1,6 +1,6 @@
import * as zod from "zod"
import { Button, Input, Text } from "@medusajs/ui"
import { Button, Input, Text, toast } from "@medusajs/ui"
import { InventoryLevelDTO, StockLocationDTO } from "@medusajs/types"
import {
RouteDrawer,
@@ -61,7 +61,7 @@ export const AdjustInventoryForm = ({
const stockedQuantityUpdate = form.watch("stocked_quantity")
const { mutateAsync } = useUpdateInventoryItemLevel(
const { mutateAsync, isPending: isLoading } = useUpdateInventoryItemLevel(
item.id,
level.location_id
)
@@ -71,9 +71,21 @@ export const AdjustInventoryForm = ({
return handleSuccess()
}
await mutateAsync({
stocked_quantity: value.stocked_quantity,
})
try {
await mutateAsync({
stocked_quantity: value.stocked_quantity,
})
toast.success(t("general.success"), {
description: t("inventory.toast.updateLevel"),
dismissLabel: t("actions.close"),
})
} catch (e) {
toast.error(t("general.error"), {
description: e.message,
dismissLabel: t("actions.close"),
})
}
return handleSuccess()
})
@@ -141,7 +153,7 @@ export const AdjustInventoryForm = ({
{t("actions.cancel")}
</Button>
</RouteDrawer.Close>
<Button type="submit" size="small" isLoading={false}>
<Button type="submit" size="small" isLoading={isLoading}>
{t("actions.save")}
</Button>
</div>
@@ -1,6 +1,6 @@
import * as zod from "zod"
import { Button, Input } from "@medusajs/ui"
import { Button, Input, toast } from "@medusajs/ui"
import {
RouteDrawer,
useRouteModal,
@@ -52,13 +52,22 @@ export const EditInventoryItemAttributesForm = ({
resolver: zodResolver(EditInventoryItemAttributesSchema),
})
const { mutateAsync } = useUpdateInventoryItem(item.id)
const { mutateAsync, isPending: isLoading } = useUpdateInventoryItem(item.id)
const handleSubmit = form.handleSubmit(async (values) => {
mutateAsync(values, {
await mutateAsync(values, {
onSuccess: () => {
toast.success(t("general.success"), {
description: t("inventory.toast.update"),
dismissLabel: t("actions.close"),
})
handleSuccess()
},
onError: (error) =>
toast.error(t("general.error"), {
description: error.message,
dismissLabel: t("actions.close"),
}),
})
})
@@ -241,7 +250,7 @@ export const EditInventoryItemAttributesForm = ({
{t("actions.cancel")}
</Button>
</RouteDrawer.Close>
<Button type="submit" size="small" isLoading={false}>
<Button type="submit" size="small" isLoading={isLoading}>
{t("actions.save")}
</Button>
</div>
@@ -11,7 +11,7 @@ export const InventoryItemAttributesEdit = () => {
const {
inventory_item: inventoryItem,
isLoading,
isPending: isLoading,
isError,
error,
} = useInventoryItem(id!)
@@ -1,6 +1,6 @@
import * as zod from "zod"
import { Button, Input } from "@medusajs/ui"
import { Button, Input, toast } from "@medusajs/ui"
import {
RouteDrawer,
useRouteModal,
@@ -39,13 +39,22 @@ export const EditInventoryItemForm = ({ item }: EditInventoryItemFormProps) => {
resolver: zodResolver(EditInventoryItemSchema),
})
const { mutateAsync } = useUpdateInventoryItem(item.id)
const { mutateAsync, isPending: isLoading } = useUpdateInventoryItem(item.id)
const handleSubmit = form.handleSubmit(async (values) => {
mutateAsync(values as any, {
onSuccess: () => {
toast.success(t("general.success"), {
description: t("inventory.toast.update"),
dismissLabel: t("actions.close"),
})
handleSuccess()
},
onError: (e) =>
toast.error(t("general.error"), {
description: e.message,
dismissLabel: t("actions.close"),
}),
})
})
@@ -94,7 +103,7 @@ export const EditInventoryItemForm = ({ item }: EditInventoryItemFormProps) => {
{t("actions.cancel")}
</Button>
</RouteDrawer.Close>
<Button type="submit" size="small" isLoading={false}>
<Button type="submit" size="small" isLoading={isLoading}>
{t("actions.save")}
</Button>
</div>
@@ -11,7 +11,7 @@ export const InventoryItemEdit = () => {
const {
inventory_item: inventoryItem,
isLoading,
isPending: isLoading,
isError,
error,
} = useInventoryItem(id!)
@@ -1,5 +1,5 @@
import { Container, Heading, Text } from "@medusajs/ui"
import { InventoryNext, ProductVariantDTO } from "@medusajs/types"
import { Container, Heading } from "@medusajs/ui"
import { ProductVariantDTO } from "@medusajs/types"
import { ActionMenu } from "../../../../components/common/action-menu"
import { InventoryItemRes } from "../../../../types/api-responses"
@@ -52,7 +52,7 @@ export const InventoryItemGeneralSection = ({
title={t("fields.inStock")}
value={getQuantityFormat(
inventoryItem.stocked_quantity,
inventoryItem.location_levels.length
inventoryItem.location_levels?.length
)}
/>
@@ -60,14 +60,14 @@ export const InventoryItemGeneralSection = ({
title={t("inventory.reserved")}
value={getQuantityFormat(
inventoryItem.reserved_quantity,
inventoryItem.location_levels.length
inventoryItem.location_levels?.length
)}
/>
<SectionRow
title={t("inventory.available")}
value={getQuantityFormat(
inventoryItem.stocked_quantity - inventoryItem.reserved_quantity,
inventoryItem.location_levels.length
inventoryItem.location_levels?.length
)}
/>
</Container>
@@ -3,7 +3,6 @@ import { Button, Container, Heading } from "@medusajs/ui"
import { InventoryItemRes } from "../../../../types/api-responses"
import { ItemLocationListTable } from "./location-levels-table/location-list-table"
import { Link } from "react-router-dom"
import { ReservationItemTable } from "./reservations-table/reservation-list-table"
import { useTranslation } from "react-i18next"
type InventoryItemLocationLevelsSectionProps = {
@@ -15,11 +15,16 @@ export const ItemLocationListTable = ({
pageSize: PAGE_SIZE,
})
const { inventory_levels, count, isLoading, isError, error } =
useInventoryItemLevels(inventory_item_id, {
...searchParams,
fields: "*stock_locations",
})
const {
inventory_levels,
count,
isPending: isLoading,
isError,
error,
} = useInventoryItemLevels(inventory_item_id, {
...searchParams,
fields: "*stock_locations",
})
const columns = useLocationListTableColumns()
@@ -15,7 +15,7 @@ export const LocationItem = ({
}: LocationItemProps) => {
return (
<div
className={clx("flex w-full rounded-lg border px-2 py-2 gap-x-2", {
className={clx("flex w-full gap-x-2 rounded-lg border px-2 py-2", {
"border-ui-border-interactive ": selected,
})}
onClick={() => onSelect(!selected)}
@@ -1,14 +1,11 @@
import * as zod from "zod"
import { Button, Table, Text } from "@medusajs/ui"
import { Button, Text, toast } from "@medusajs/ui"
import {
RouteDrawer,
useRouteModal,
} from "../../../../../../components/route-modal"
import {
useBatchInventoryItemLevels,
useUpdateInventoryItem,
} from "../../../../../../hooks/api/inventory"
import { useBatchInventoryItemLevels } from "../../../../../../hooks/api/inventory"
import { useFieldArray, useForm } from "react-hook-form"
import { InventoryItemRes } from "../../../../../../types/api-responses"
@@ -35,13 +32,13 @@ const EditInventoryItemAttributesSchema = z.object({
const getDefaultValues = (
allLocations: StockLocationDTO[],
existinLevels: Set<string>
existingLevels: Set<string>
) => {
return {
locations: allLocations.map((location) => ({
...location,
location_id: location.id,
selected: existinLevels.has(location.id),
selected: existingLevels.has(location.id),
})),
}
}
@@ -97,14 +94,26 @@ export const ManageLocationsForm = ({
return handleSuccess()
}
await mutateAsync({
creates: selectedLocations.map((location_id) => ({
location_id,
})),
deletes: unselectedLocations,
})
try {
await mutateAsync({
creates: selectedLocations.map((location_id) => ({
location_id,
})),
deletes: unselectedLocations,
})
return handleSuccess()
handleSuccess()
toast.success(t("general.success"), {
description: t("inventory.toast.update"),
dismissLabel: t("actions.close"),
})
} catch (e) {
toast.error(t("general.error"), {
description: e.message,
dismissLabel: t("actions.close"),
})
}
})
return (
@@ -114,7 +123,7 @@ export const ManageLocationsForm = ({
className="flex flex-1 flex-col overflow-hidden"
>
<RouteDrawer.Body className="flex flex-1 flex-col gap-y-4 overflow-auto">
<div className="grid grid-rows-2 divide-y rounded-lg border text-ui-fg-subtle shadow-elevation-card-rest">
<div className="text-ui-fg-subtle shadow-elevation-card-rest grid grid-rows-2 divide-y rounded-lg border">
<div className="grid grid-cols-2 divide-x">
<Text className="px-2 py-1.5" size="small" leading="compact">
{t("fields.title")}
@@ -136,7 +145,7 @@ export const ManageLocationsForm = ({
<Text size="small" weight="plus" leading="compact">
{t("locations.domain")}
</Text>
<div className="flex w-full justify-between text-ui-fg-subtle">
<div className="text-ui-fg-subtle flex w-full justify-between">
<Text size="small" leading="compact">
{t("locations.selectLocations")}
</Text>
@@ -12,7 +12,7 @@ export const ManageLocationsDrawer = () => {
const {
inventory_item: inventoryItem,
isLoading,
isPending: isLoading,
isError,
error,
} = useInventoryItem(id!)
@@ -16,11 +16,16 @@ export const ReservationItemTable = ({
pageSize: PAGE_SIZE,
})
const { reservations, count, isLoading, isError, error } =
useReservationItems({
...searchParams,
inventory_item_id: [inventoryItem.id],
})
const {
reservations,
count,
isPending: isLoading,
isError,
error,
} = useReservationItems({
...searchParams,
inventory_item_id: [inventoryItem.id],
})
const columns = useInventoryTableColumns({ sku: inventoryItem.sku! })
@@ -1,2 +1,2 @@
export { InventoryDeatil as Component } from "./inventory-detail"
export { InventoryDetail as Component } from "./inventory-detail"
export { inventoryItemLoader as loader } from "./loader"
@@ -5,19 +5,26 @@ import { InventoryItemGeneralSection } from "./components/inventory-item-general
import { InventoryItemLocationLevelsSection } from "./components/inventory-item-location-levels"
import { InventoryItemReservationsSection } from "./components/inventory-item-reservations"
import { JsonViewSection } from "../../../components/common/json-view-section"
import { inventoryItemLoader } from "./loader"
import { useInventoryItem } from "../../../hooks/api/inventory"
import { inventoryItemLoader } from "./loader"
export const InventoryDeatil = () => {
export const InventoryDetail = () => {
const { id } = useParams()
const initialData = useLoaderData() as Awaited<
ReturnType<typeof inventoryItemLoader>
>
const { inventory_item, isLoading, isError, error } = useInventoryItem(
const {
inventory_item,
isPending: isLoading,
isError,
error,
} = useInventoryItem(
id!,
{},
{
fields: "*variants",
},
{
initialData,
}
@@ -38,7 +45,7 @@ export const InventoryDeatil = () => {
return (
<div className="flex flex-col gap-y-2">
<div className="flex flex-col gap-x-4 lg:flex-row lg:items-start">
<div className="w-full flex flex-col gap-y-2">
<div className="flex w-full flex-col gap-y-2">
<InventoryItemGeneralSection inventoryItem={inventory_item} />
<InventoryItemLocationLevelsSection inventoryItem={inventory_item} />
<InventoryItemReservationsSection inventoryItem={inventory_item} />
@@ -47,7 +54,7 @@ export const InventoryDeatil = () => {
</div>
<Outlet />
</div>
<div className="w-full lg:max-w-[400px] max-w-[100%] mt-2 lg:mt-0 flex flex-col gap-y-2">
<div className="mt-2 flex w-full max-w-[100%] flex-col gap-y-2 lg:mt-0 lg:max-w-[400px]">
<InventoryItemAttributeSection inventoryItem={inventory_item} />
<div className="lg:hidden">
<JsonViewSection data={inventory_item} />
@@ -1,7 +1,7 @@
import { Container, Heading } from "@medusajs/ui"
import { InventoryNext } from "@medusajs/types"
import { DataTable } from "../../../../components/table/data-table"
import { InventoryNext } from "@medusajs/types"
import { useDataTable } from "../../../../hooks/use-data-table"
import { useInventoryItems } from "../../../../hooks/api/inventory"
import { useInventoryTableColumns } from "./use-inventory-table-columns"
@@ -17,10 +17,16 @@ export const InventoryListTable = () => {
const { searchParams, raw } = useInventoryTableQuery({
pageSize: PAGE_SIZE,
})
const { inventory_items, count, isLoading, isError, error } =
useInventoryItems({
...searchParams,
})
const {
inventory_items,
count,
isPending: isLoading,
isError,
error,
} = useInventoryItems({
...searchParams,
})
const filters = useInventoryTableFilters()
const columns = useInventoryTableColumns()
@@ -1,6 +1,6 @@
import { zodResolver } from "@hookform/resolvers/zod"
import { UserRoles } from "@medusajs/medusa"
import { Alert, Button, Heading, Input, Text } from "@medusajs/ui"
import { Alert, Button, Heading, Input, Text, toast } from "@medusajs/ui"
import { AnimatePresence, motion } from "framer-motion"
import { Trans, useTranslation } from "react-i18next"
import { Link, useSearchParams } from "react-router-dom"
@@ -49,7 +49,7 @@ export const Invite = () => {
const isValidInvite = invite && validateDecodedInvite(invite)
return (
<div className="min-h-dvh w-dvw bg-ui-bg-base relative flex items-center justify-center p-4">
<div className="bg-ui-bg-base relative flex min-h-dvh w-dvw items-center justify-center p-4">
<div className="flex w-full max-w-[300px] flex-col items-center">
<LogoBox
className="mb-4"
@@ -204,10 +204,10 @@ const CreateView = ({
},
})
const { mutateAsync: createAuthUser, isLoading: isCreatingAuthUser } =
const { mutateAsync: createAuthUser, isPending: isCreatingAuthUser } =
useV2CreateAuthUser()
const { mutateAsync: acceptInvite, isLoading: isAcceptingInvite } =
const { mutateAsync: acceptInvite, isPending: isAcceptingInvite } =
useV2AcceptInvite(token)
const handleSubmit = form.handleSubmit(async (data) => {
@@ -228,6 +228,10 @@ const CreateView = ({
token: authToken,
})
onSuccess()
toast.success(t("general.success"), {
description: t("invite.toast.accepted"),
dismissLabel: t("actions.close"),
})
} catch (error) {
if (isAxiosError(error) && error.response?.status === 400) {
form.setError("root", {
@@ -1,5 +1,5 @@
import { zodResolver } from "@hookform/resolvers/zod"
import { Button, Heading, Input, Text } from "@medusajs/ui"
import { Button, Heading, Input, Text, toast } from "@medusajs/ui"
import { useForm } from "react-hook-form"
import { useTranslation } from "react-i18next"
import * as zod from "zod"
@@ -49,17 +49,24 @@ export const CreateLocationForm = () => {
const { mutateAsync, isPending } = useCreateStockLocation()
const handleSubmit = form.handleSubmit(async (values) => {
mutateAsync(
{
try {
await mutateAsync({
name: values.name,
address: values.address,
},
{
onSuccess: () => {
handleSuccess("/settings/locations")
},
}
)
})
handleSuccess("/settings/locations")
toast.success(t("general.success"), {
description: t("locations.toast.create"),
dismissLabel: t("actions.close"),
})
} catch (e) {
toast.error(t("general.error"), {
description: e.message,
dismissLabel: t("actions.close"),
})
}
})
return (
@@ -1,6 +1,6 @@
import { PencilSquare, Trash } from "@medusajs/icons"
import { SalesChannelDTO } from "@medusajs/types"
import { Button, Container, Heading, usePrompt } from "@medusajs/ui"
import { Button, Container, Heading, toast, usePrompt } from "@medusajs/ui"
import { createColumnHelper } from "@tanstack/react-table"
import { useAdminRemoveLocationFromSalesChannel } from "medusa-react"
import { useMemo } from "react"
@@ -82,10 +82,22 @@ const SalesChannelActions = ({
return
}
await mutateAsync({
location_id: locationId,
sales_channel_id: salesChannel.id,
})
try {
await mutateAsync({
location_id: locationId,
sales_channel_id: salesChannel.id,
})
toast.success(t("general.success"), {
description: t("locations.toast.removeChannel"),
dismissLabel: t("actions.close"),
})
} catch (e) {
toast.error(t("general.error"), {
description: e.message,
dismissLabel: t("actions.close"),
})
}
}
return (
@@ -7,7 +7,12 @@ import { LocationSalesChannelSection } from "./components/location-sales-channel
export const LocationDetail = () => {
const { id } = useParams()
const { stock_location, isLoading, isError, error } = useStockLocation(id!, {
const {
stock_location,
isPending: isLoading,
isError,
error,
} = useStockLocation(id!, {
fields: "*address,*sales_channels",
})
@@ -1,5 +1,5 @@
import { zodResolver } from "@hookform/resolvers/zod"
import { Button, Input } from "@medusajs/ui"
import { Button, Input, toast } from "@medusajs/ui"
import { useForm } from "react-hook-form"
import { useTranslation } from "react-i18next"
import * as zod from "zod"
@@ -55,17 +55,23 @@ export const EditLocationForm = ({ location }: EditLocationFormProps) => {
const { mutateAsync, isPending } = useUpdateStockLocation(location.id)
const handleSubmit = form.handleSubmit(async (values) => {
mutateAsync(
{
try {
await mutateAsync({
name: values.name,
address: values.address,
},
{
onSuccess: () => {
handleSuccess()
},
}
)
})
handleSuccess()
toast.success(t("general.success"), {
description: t("locations.toast.update"),
dismissLabel: t("actions.close"),
})
} catch (e) {
toast.error(t("general.error"), {
description: e.message,
dismissLabel: t("actions.close"),
})
}
})
return (
@@ -2,14 +2,18 @@ import { Heading } from "@medusajs/ui"
import { useTranslation } from "react-i18next"
import { useParams } from "react-router-dom"
import { RouteDrawer } from "../../../components/route-modal"
import { useStockLocations } from "../../../hooks/api/stock-locations"
import { useStockLocation } from "../../../hooks/api/stock-locations"
import { EditLocationForm } from "./components/edit-location-form"
export const LocationEdit = () => {
const { id } = useParams()
const { stock_locations, isLoading, isError, error } = useStockLocations({
id,
const {
stock_location,
isPending: isLoading,
isError,
error,
} = useStockLocation(id, {
fields: "*address",
})
@@ -19,8 +23,6 @@ export const LocationEdit = () => {
throw error
}
const stock_location = stock_locations?.[0]
return (
<RouteDrawer>
<RouteDrawer.Header>
@@ -19,16 +19,21 @@ export const LocationsListTable = () => {
* Note: The endpoint is bugged and does not return count, causing the table to not render
* any rows.
*/
const { stock_locations, count, isLoading, isError, error } =
useStockLocations({
...searchParams,
fields: "*address",
})
const {
stock_locations = [],
count,
isLoading,
isError,
error,
} = useStockLocations({
...searchParams,
fields: "*address",
})
const columns = useLocationTableColumns()
const { table } = useDataTable({
data: stock_locations ?? [],
data: stock_locations,
columns,
count,
enablePagination: true,
@@ -56,7 +61,8 @@ export const LocationsListTable = () => {
count={count || 1}
columns={columns}
navigateTo={(row) => row.id}
isLoading={isLoading}
// TODO: revisit loader - on query change this will cause unmounting of the table, rendering loader briefly and again rendering table which will make search input unfocused
// isLoading={isLoading}
queryObject={raw}
pagination
search
@@ -7,11 +7,12 @@ export const useLocationTableQuery = ({
pageSize?: number
prefix?: string
}) => {
const raw = useQueryParams(["offset"], prefix)
const raw = useQueryParams(["q", "offset"], prefix)
const searchParams = {
limit: pageSize,
offset: raw.offset,
q: raw.q,
}
return {
@@ -1,5 +1,5 @@
import { zodResolver } from "@hookform/resolvers/zod"
import { Button, Heading, Input, Text } from "@medusajs/ui"
import { Alert, Button, Heading, Input, Text } from "@medusajs/ui"
import { useForm } from "react-hook-form"
import { Trans, useTranslation } from "react-i18next"
import { Link, useLocation, useNavigate } from "react-router-dom"
@@ -34,40 +34,38 @@ export const Login = () => {
const { mutateAsync, isPending } = useEmailPassLogin()
const handleSubmit = form.handleSubmit(async ({ email, password }) => {
await mutateAsync(
{
try {
await mutateAsync({
email,
password,
},
{
onSuccess: () => {
navigate(from, { replace: true })
},
onError: (error) => {
if (isAxiosError(error)) {
if (error.response?.status === 401) {
form.setError("email", {
type: "manual",
})
})
form.setError("password", {
type: "manual",
message: t("errors.invalidCredentials"),
})
return
}
}
form.setError("root.serverError", {
navigate(from, { replace: true })
} catch (error) {
if (isAxiosError(error)) {
if (error.response?.status === 401) {
form.setError("email", {
type: "manual",
message: t("errors.serverError"),
})
},
form.setError("password", {
type: "manual",
message: t("errors.invalidCredentials"),
})
return
}
}
)
form.setError("root.serverError", {
type: "manual",
message: t("errors.serverError"),
})
}
})
const serverError = form.formState.errors?.root?.serverError?.message
return (
<div className="bg-ui-bg-base flex min-h-dvh w-dvw items-center justify-center">
<div className="m-4 flex w-full max-w-[300px] flex-col items-center">
@@ -123,6 +121,11 @@ export const Login = () => {
{t("actions.continue")}
</Button>
</form>
{serverError && (
<Alert className="mt-4" dismissible variant="error">
{serverError}
</Alert>
)}
</Form>
<div className="my-6 h-px w-full border-b border-dotted" />
<span className="text-ui-fg-subtle txt-small">
@@ -3,7 +3,7 @@ import { useMe } from "../../../hooks/api/users"
import { ProfileGeneralSection } from "./components/profile-general-section"
export const ProfileDetail = () => {
const { user, isLoading, isError, error } = useMe()
const { user, isPending: isLoading, isError, error } = useMe()
if (isLoading) {
return <div>Loading...</div>
@@ -14,7 +14,7 @@ export const ProfileDetail = () => {
throw error
}
throw json("An unknown error has occured", 500)
throw json("An unknown error has occurred", 500)
}
return (
@@ -1,5 +1,5 @@
import { zodResolver } from "@hookform/resolvers/zod"
import { Button, Input, Select, Switch } from "@medusajs/ui"
import { Button, Input, Select, Switch, toast } from "@medusajs/ui"
import { useForm } from "react-hook-form"
import { Trans, useTranslation } from "react-i18next"
import * as zod from "zod"
@@ -39,8 +39,8 @@ export const EditProfileForm = ({ user, usageInsights }: EditProfileProps) => {
resolver: zodResolver(EditProfileSchema),
})
const changeLanguage = (code: string) => {
i18n.changeLanguage(code)
const changeLanguage = async (code: string) => {
await i18n.changeLanguage(code)
}
const sortedLanguages = languages.sort((a, b) =>
@@ -50,21 +50,26 @@ export const EditProfileForm = ({ user, usageInsights }: EditProfileProps) => {
const { mutateAsync, isPending } = useUpdateUser(user.id!)
const handleSubmit = form.handleSubmit(async (values) => {
await mutateAsync(
{
try {
await mutateAsync({
first_name: values.first_name,
last_name: values.last_name,
},
{
onError: () => {
return
},
}
)
})
changeLanguage(values.language)
await changeLanguage(values.language)
handleSuccess()
handleSuccess()
toast.success(t("general.success"), {
description: t("profile.toast.edit"),
dismissLabel: t("actions.close"),
})
} catch (e) {
toast.error(t("general.error"), {
description: e.message,
dismissLabel: t("actions.close"),
})
}
})
return (
@@ -5,7 +5,7 @@ import { useMe } from "../../../hooks/api/users"
import { EditProfileForm } from "./components/edit-profile-form/edit-profile-form"
export const ProfileEdit = () => {
const { user, isLoading, isError, error } = useMe()
const { user, isPending: isLoading, isError, error } = useMe()
const { t } = useTranslation()
@@ -1,6 +1,6 @@
import { zodResolver } from "@hookform/resolvers/zod"
import { SalesChannelDTO } from "@medusajs/types"
import { Button, Checkbox, Hint, Tooltip } from "@medusajs/ui"
import { Button, Checkbox, Hint, toast, Tooltip } from "@medusajs/ui"
import { keepPreviousData } from "@tanstack/react-query"
import {
OnChangeFn,
@@ -67,7 +67,13 @@ export const AddProductsToSalesChannelForm = ({
}
const { searchParams, raw } = useProductTableQuery({ pageSize: PAGE_SIZE })
const { products, count, isLoading, isError, error } = useProducts(
const {
products,
count,
isPending: isLoading,
isError,
error,
} = useProducts(
{
expand: "variants,sales_channels",
...searchParams,
@@ -108,8 +114,17 @@ export const AddProductsToSalesChannelForm = ({
},
{
onSuccess: () => {
toast.success(t("general.success"), {
description: t("salesChannels.toast.update"),
dismissLabel: t("actions.close"),
})
handleSuccess()
},
onError: (error) =>
toast.error(t("general.error"), {
description: error.message,
dismissLabel: t("actions.close"),
}),
}
)
})
@@ -5,7 +5,12 @@ import { AddProductsToSalesChannelForm } from "./components"
export const SalesChannelAddProducts = () => {
const { id } = useParams()
const { sales_channel, isLoading, isError, error } = useSalesChannel(id!)
const {
sales_channel,
isPending: isLoading,
isError,
error,
} = useSalesChannel(id!)
if (isError) {
throw error
@@ -1,5 +1,13 @@
import { zodResolver } from "@hookform/resolvers/zod"
import { Button, Heading, Input, Switch, Text, Textarea } from "@medusajs/ui"
import {
Button,
Heading,
Input,
Switch,
Text,
Textarea,
toast,
} from "@medusajs/ui"
import { useForm } from "react-hook-form"
import { useTranslation } from "react-i18next"
import * as zod from "zod"
@@ -40,8 +48,17 @@ export const CreateSalesChannelForm = () => {
},
{
onSuccess: ({ sales_channel }) => {
toast.success(t("general.success"), {
description: t("salesChannels.toast.create"),
dismissLabel: t("actions.close"),
})
handleSuccess(`../${sales_channel.id}`)
},
onError: (error) =>
toast.success(t("general.success"), {
description: error.message,
dismissLabel: t("actions.close"),
}),
}
)
})
@@ -1,5 +1,12 @@
import { PencilSquare, Trash } from "@medusajs/icons"
import { Container, Heading, StatusBadge, Text, usePrompt } from "@medusajs/ui"
import {
Container,
Heading,
StatusBadge,
Text,
toast,
usePrompt,
} from "@medusajs/ui"
import { useTranslation } from "react-i18next"
import { SalesChannelDTO } from "@medusajs/types"
@@ -36,11 +43,21 @@ export const SalesChannelGeneralSection = ({
return
}
await mutateAsync(undefined, {
onSuccess: () => {
navigate("/settings/sales-channels", { replace: true })
},
})
try {
await mutateAsync()
navigate("/settings/sales-channels", { replace: true })
toast.success(t("general.success"), {
description: t("salesChannels.toast.delete"),
dismissLabel: t("actions.close"),
})
} catch (e) {
toast.error(t("general.error"), {
description: e.message,
dismissLabel: t("actions.close"),
})
}
}
return (
@@ -1,5 +1,12 @@
import { PencilSquare, Trash } from "@medusajs/icons"
import { Button, Checkbox, Container, Heading, usePrompt } from "@medusajs/ui"
import {
Button,
Checkbox,
Container,
Heading,
toast,
usePrompt,
} from "@medusajs/ui"
import { RowSelectionState, createColumnHelper } from "@tanstack/react-table"
import { useMemo, useState } from "react"
import { useTranslation } from "react-i18next"
@@ -29,7 +36,13 @@ export const SalesChannelProductSection = ({
const [rowSelection, setRowSelection] = useState<RowSelectionState>({})
const { searchParams, raw } = useProductTableQuery({ pageSize: PAGE_SIZE })
const { products, count, isLoading, isError, error } = useProducts(
const {
products,
count,
isPending: isLoading,
isError,
error,
} = useProducts(
{
...searchParams,
sales_channel_id: [salesChannel.id],
@@ -87,8 +100,18 @@ export const SalesChannelProductSection = ({
},
{
onSuccess: () => {
toast.success(t("general.success"), {
description: t("salesChannels.toast.update"),
dismissLabel: t("actions.close"),
})
setRowSelection({})
},
onError: (error) => {
toast.error(t("general.error"), {
description: error.message,
dismissLabel: t("actions.close"),
})
},
}
)
}
@@ -199,9 +222,20 @@ const ProductListCellActions = ({
const { mutateAsync } = useSalesChannelRemoveProducts(salesChannelId)
const onRemove = async () => {
await mutateAsync({
product_ids: [productId],
})
try {
await mutateAsync({
product_ids: [productId],
})
toast.success(t("general.success"), {
description: t("salesChannels.toast.update"),
dismissLabel: t("actions.close"),
})
} catch (e) {
toast.error(t("general.error"), {
description: e.message,
dismissLabel: t("actions.close"),
})
}
}
return (
@@ -12,7 +12,7 @@ export const SalesChannelDetail = () => {
>
const { id } = useParams()
const { sales_channel, isLoading } = useSalesChannel(id!, {
const { sales_channel, isPending: isLoading } = useSalesChannel(id!, {
initialData,
})
@@ -1,5 +1,5 @@
import { zodResolver } from "@hookform/resolvers/zod"
import { Button, Input, Switch, Textarea } from "@medusajs/ui"
import { Button, Input, Switch, Textarea, toast } from "@medusajs/ui"
import { useForm } from "react-hook-form"
import { useTranslation } from "react-i18next"
import * as zod from "zod"
@@ -48,8 +48,18 @@ export const EditSalesChannelForm = ({
},
{
onSuccess: () => {
toast.success(t("general.success"), {
description: t("salesChannels.toast.update"),
dismissLabel: t("actions.close"),
})
handleSuccess()
},
onError: (error) => {
toast.error(t("general.error"), {
description: error.message,
dismissLabel: t("actions.close"),
})
},
}
)
})
@@ -10,7 +10,12 @@ export const SalesChannelEdit = () => {
const { id } = useParams()
const { t } = useTranslation()
const { sales_channel, isLoading, isError, error } = useSalesChannel(id!)
const {
sales_channel,
isPending: isLoading,
isError,
error,
} = useSalesChannel(id!)
if (isError) {
throw error
@@ -1,6 +1,6 @@
import { PencilSquare, Trash } from "@medusajs/icons"
import { SalesChannelDTO } from "@medusajs/types"
import { Button, Container, Heading, usePrompt } from "@medusajs/ui"
import { Button, Container, Heading, toast, usePrompt } from "@medusajs/ui"
import { keepPreviousData } from "@tanstack/react-query"
import { createColumnHelper } from "@tanstack/react-table"
import { useMemo } from "react"
@@ -24,12 +24,15 @@ export const SalesChannelListTable = () => {
const { raw, searchParams } = useSalesChannelTableQuery({
pageSize: PAGE_SIZE,
})
const { sales_channels, count, isLoading, isError, error } = useSalesChannels(
searchParams,
{
placeholderData: keepPreviousData,
}
)
const {
sales_channels,
count,
isPending: isLoading,
isError,
error,
} = useSalesChannels(searchParams, {
placeholderData: keepPreviousData,
})
const columns = useColumns()
@@ -97,7 +100,18 @@ const SalesChannelActions = ({
return
}
await mutateAsync()
try {
await mutateAsync()
toast.success(t("general.success"), {
description: t("salesChannels.toast.delete"),
dismissLabel: t("actions.close"),
})
} catch (e) {
toast.error(t("general.error"), {
description: e.message,
dismissLabel: t("actions.close"),
})
}
}
return (
@@ -1,5 +1,5 @@
import { Currency } from "@medusajs/medusa"
import { Button, Checkbox, Hint, Tooltip } from "@medusajs/ui"
import { Button, Checkbox, Hint, toast, Tooltip } from "@medusajs/ui"
import {
OnChangeFn,
RowSelectionState,
@@ -68,12 +68,15 @@ export const AddCurrenciesForm = ({ store }: AddCurrenciesFormProps) => {
prefix: PREFIX,
})
const { currencies, count, isLoading, isError, error } = useCurrencies(
searchParams,
{
placeholderData: keepPreviousData,
}
)
const {
currencies,
count,
isPending: isLoading,
isError,
error,
} = useCurrencies(searchParams, {
placeholderData: keepPreviousData,
})
const preSelectedRows = store.supported_currency_codes.map((c) => c)
@@ -101,16 +104,21 @@ export const AddCurrenciesForm = ({ store }: AddCurrenciesFormProps) => {
new Set([...data.currencies, ...preSelectedRows])
) as string[]
await mutateAsync(
{
try {
await mutateAsync({
supported_currency_codes: currencies,
},
{
onSuccess: () => {
handleSuccess()
},
}
)
})
toast.success(t("general.success"), {
description: t("store.toast.currenciesUpdated"),
dismissLabel: t("actions.close"),
})
handleSuccess()
} catch (e) {
toast.error(t("general.error"), {
description: e.message,
dismissLabel: t("actions.close"),
})
}
})
if (isError) {
@@ -3,7 +3,7 @@ import { useStore } from "../../../hooks/api/store"
import { AddCurrenciesForm } from "./components/add-currencies-form/add-currencies-form"
export const StoreAddCurrencies = () => {
const { store, isLoading, isError, error } = useStore()
const { store, isPending: isLoading, isError, error } = useStore()
if (isError) {
throw error
@@ -5,6 +5,7 @@ import {
CommandBar,
Container,
Heading,
toast,
usePrompt,
} from "@medusajs/ui"
import { keepPreviousData } from "@tanstack/react-query"
@@ -31,7 +32,13 @@ export const StoreCurrencySection = ({ store }: StoreCurrencySectionProps) => {
const { searchParams, raw } = useCurrenciesTableQuery({ pageSize: PAGE_SIZE })
const { currencies, count, isLoading, isError, error } = useCurrencies(
const {
currencies,
count,
isPending: isLoading,
isError,
error,
} = useCurrencies(
{
code: store.supported_currency_codes,
...searchParams,
@@ -56,8 +63,9 @@ export const StoreCurrencySection = ({ store }: StoreCurrencySectionProps) => {
enableRowSelection: true,
pageSize: PAGE_SIZE,
meta: {
currencyCodes: store.supported_currency_codes,
storeId: store.id,
currencyCodes: store.supported_currency_codes,
defaultCurrencyCode: store.default_currency_code,
},
})
@@ -81,18 +89,24 @@ export const StoreCurrencySection = ({ store }: StoreCurrencySectionProps) => {
return
}
await mutateAsync(
{
try {
await mutateAsync({
supported_currency_codes: store.supported_currency_codes.filter(
(c) => !ids.includes(c)
),
},
{
onSuccess: () => {
setRowSelection({})
},
}
)
})
setRowSelection({})
toast.success(t("general.success"), {
description: t("store.toast.currenciesRemoved"),
dismissLabel: t("actions.close"),
})
} catch (e) {
toast.error(t("general.error"), {
description: e.message,
dismissLabel: t("actions.close"),
})
}
}
if (isError) {
@@ -151,10 +165,12 @@ const CurrencyActions = ({
storeId,
currency,
currencyCodes,
defaultCurrencyCode,
}: {
storeId: string
currency: CurrencyDTO
currencyCodes: string[]
defaultCurrencyCode: string
}) => {
const { mutateAsync } = useUpdateStore(storeId)
@@ -177,11 +193,23 @@ const CurrencyActions = ({
return
}
await mutateAsync({
supported_currency_codes: currencyCodes.filter(
(c) => c !== currency.code
),
})
try {
await mutateAsync({
supported_currency_codes: currencyCodes.filter(
(c) => c !== currency.code
),
})
toast.success(t("general.success"), {
description: t("store.toast.currenciesRemoved"),
dismissLabel: t("actions.close"),
})
} catch (e) {
toast.error(t("general.error"), {
description: e.message,
dismissLabel: t("actions.close"),
})
}
}
return (
@@ -193,6 +221,7 @@ const CurrencyActions = ({
icon: <Trash />,
label: t("actions.remove"),
onClick: handleRemove,
disabled: currency.code === defaultCurrencyCode,
},
],
},
@@ -240,9 +269,11 @@ const useColumns = () => {
columnHelper.display({
id: "actions",
cell: ({ row, table }) => {
const { currencyCodes, storeId } = table.options.meta as {
const { currencyCodes, storeId, defaultCurrencyCode } = table.options
.meta as {
currencyCodes: string[]
storeId: string
defaultCurrencyCode: string
}
return (
@@ -250,6 +281,7 @@ const useColumns = () => {
storeId={storeId}
currency={row.original}
currencyCodes={currencyCodes}
defaultCurrencyCode={defaultCurrencyCode}
/>
)
},
@@ -3,6 +3,7 @@ import { Badge, Container, Heading, Text } from "@medusajs/ui"
import { useTranslation } from "react-i18next"
import { ActionMenu } from "../../../../../components/common/action-menu"
import { ExtendedStoreDTO } from "../../../../../types/api-responses"
import { useRegion } from "../../../../../hooks/api/regions"
type StoreGeneralSectionProps = {
store: ExtendedStoreDTO
@@ -11,6 +12,10 @@ type StoreGeneralSectionProps = {
export const StoreGeneralSection = ({ store }: StoreGeneralSectionProps) => {
const { t } = useTranslation()
const { region } = useRegion(store.default_region_id, undefined, {
enabled: !!store.default_region_id,
})
return (
<Container className="divide-y p-0">
<div className="flex items-center justify-between px-6 py-4">
@@ -57,6 +62,19 @@ export const StoreGeneralSection = ({ store }: StoreGeneralSectionProps) => {
</div>
</div>
)}
{region && (
<div className="grid grid-cols-2 px-6 py-4">
<Text size="small" leading="compact" weight="plus">
{t("store.defaultRegion")}
</Text>
<div className="flex items-center gap-x-2">
<Text size="small" leading="compact">
{region.name}
</Text>
</div>
</div>
)}
</Container>
)
}
@@ -9,7 +9,12 @@ import { storeLoader } from "./loader"
export const StoreDetail = () => {
const initialData = useLoaderData() as Awaited<ReturnType<typeof storeLoader>>
const { store, isLoading, isError, error } = useStore({
const {
store,
isPending: isLoading,
isError,
error,
} = useStore({
initialData,
})
@@ -1,5 +1,5 @@
import { zodResolver } from "@hookform/resolvers/zod"
import { Button, Input } from "@medusajs/ui"
import { Button, Input, Select, toast } from "@medusajs/ui"
import { useForm } from "react-hook-form"
import { useTranslation } from "react-i18next"
import { z } from "zod"
@@ -11,6 +11,7 @@ import {
} from "../../../../../components/route-modal"
import { useUpdateStore } from "../../../../../hooks/api/store"
import { ExtendedStoreDTO } from "../../../../../types/api-responses"
import { useRegions } from "../../../../../hooks/api/regions"
type EditStoreFormProps = {
store: ExtendedStoreDTO
@@ -18,8 +19,8 @@ type EditStoreFormProps = {
const EditStoreSchema = z.object({
name: z.string().min(1),
// default_currency_code: z.string().optional(),
// default_region_id: z.string().optional(),
default_currency_code: z.string().optional(),
default_region_id: z.string().optional(),
// default_location_id: z.string().optional(),
})
@@ -30,18 +31,32 @@ export const EditStoreForm = ({ store }: EditStoreFormProps) => {
const form = useForm<z.infer<typeof EditStoreSchema>>({
defaultValues: {
name: store.name,
default_region_id: store.default_region_id || undefined,
default_currency_code: store.default_currency_code || undefined,
},
resolver: zodResolver(EditStoreSchema),
})
const { mutateAsync, isPending } = useUpdateStore(store.id)
const { regions, isPending: isRegionsLoading } = useRegions({ limit: 999 })
const handleSubmit = form.handleSubmit(async (values) => {
mutateAsync(values, {
onSuccess: () => {
handleSuccess()
},
})
try {
await mutateAsync(values)
handleSuccess()
toast.success(t("general.success"), {
description: t("store.toast.update"),
dismissLabel: t("actions.close"),
})
} catch (e) {
toast.error(t("general.error"), {
description: e.message,
dismissLabel: t("actions.close"),
})
}
})
return (
@@ -62,7 +77,61 @@ export const EditStoreForm = ({ store }: EditStoreFormProps) => {
</Form.Item>
)}
/>
{/* TODO: Add comboboxes for default region, location, and currency. `q` is currently missing on all v2 endpoints */}
{/* TODO: Add comboboxes for default sales channel and location */}
<Form.Field
control={form.control}
name="default_currency_code"
render={({ field: { onChange, ...field } }) => {
return (
<Form.Item>
<Form.Label>{t("store.defaultCurrency")}</Form.Label>
<Form.Control>
<Select {...field} onValueChange={onChange}>
<Select.Trigger ref={field.ref}>
<Select.Value />
</Select.Trigger>
<Select.Content>
{store.supported_currency_codes.map((code) => (
<Select.Item key={code} value={code}>
{code.toUpperCase()}
</Select.Item>
))}
</Select.Content>
</Select>
</Form.Control>
</Form.Item>
)
}}
/>
<Form.Field
control={form.control}
name="default_region_id"
render={({ field: { onChange, ...field } }) => {
return (
<Form.Item>
<Form.Label>{t("store.defaultRegion")}</Form.Label>
<Form.Control>
<Select
{...field}
onValueChange={onChange}
disabled={isRegionsLoading}
>
<Select.Trigger ref={field.ref}>
<Select.Value />
</Select.Trigger>
<Select.Content>
{(regions || []).map((region) => (
<Select.Item key={region.id} value={region.id}>
{region.name}
</Select.Item>
))}
</Select.Content>
</Select>
</Form.Control>
</Form.Item>
)
}}
/>
</div>
</RouteDrawer.Body>
<RouteDrawer.Footer>
@@ -6,7 +6,7 @@ import { EditStoreForm } from "./components/edit-store-form/edit-store-form"
export const StoreEdit = () => {
const { t } = useTranslation()
const { store, isLoading, isError, error } = useStore()
const { store, isPending: isLoading, isError, error } = useStore()
if (isError) {
throw error
@@ -12,12 +12,12 @@ export const deleteStockLocationsStep = createStep(
return new StepResponse(void 0, input)
},
async (deletedLocaitonIds, { container }) => {
if (!deletedLocaitonIds?.length) {
async (deletedLocationIds, { container }) => {
if (!deletedLocationIds?.length) {
return
}
const service = container.resolve(ModuleRegistrationName.STOCK_LOCATION)
await service.restore(deletedLocaitonIds)
await service.restore(deletedLocationIds)
}
)
+7 -1
View File
@@ -1,9 +1,14 @@
import { BigNumberRawValue } from "@medusajs/types"
import { BigNumber, MikroOrmBigNumberProperty } from "@medusajs/utils"
import {
BigNumber,
MikroOrmBigNumberProperty,
Searchable,
} from "@medusajs/utils"
import { Entity, PrimaryKey, Property } from "@mikro-orm/core"
@Entity({ tableName: "currency" })
class Currency {
@Searchable()
@PrimaryKey({ columnType: "text" })
code!: string
@@ -13,6 +18,7 @@ class Currency {
@Property({ columnType: "text" })
symbol_native: string
@Searchable()
@Property({ columnType: "text" })
name: string
@@ -11,6 +11,7 @@ export const AdminGetCurrenciesParams = createFindParams({
limit: 50,
}).merge(
z.object({
q: z.string().optional(),
code: z.union([z.string(), z.array(z.string())]).optional(),
$and: z.lazy(() => AdminGetCurrenciesParams.array()).optional(),
$or: z.lazy(() => AdminGetCurrenciesParams.array()).optional(),