feat(admin-ui): Multi-language support (#4962)
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import clsx from "clsx"
|
||||
import React, { useEffect, useMemo, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { sum } from "lodash"
|
||||
import Tooltip from "../../../../components/atoms/tooltip"
|
||||
@@ -57,6 +58,7 @@ function Input(props: InputProps) {
|
||||
disabled,
|
||||
} = props
|
||||
const selectedCount = Object.keys(selected).length
|
||||
const { t } = useTranslation()
|
||||
|
||||
const selectedOption = useMemo(() => {
|
||||
const ret: string[] = []
|
||||
@@ -100,7 +102,9 @@ function Input(props: InputProps) {
|
||||
)}
|
||||
{selectedCount === 0 ? (
|
||||
<span className="text-grey-50">
|
||||
{placeholder ? placeholder : "Choose categories"}
|
||||
{placeholder
|
||||
? placeholder
|
||||
: t("multiselect-choose-categories", "Choose categories")}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -157,6 +161,7 @@ function PopupItem(props: PopupItemProps) {
|
||||
selectedSubcategoriesCount,
|
||||
} = props
|
||||
|
||||
const { t } = useTranslation()
|
||||
const hasChildren = !!option.children?.length
|
||||
|
||||
const onClick = (e) => {
|
||||
@@ -190,7 +195,11 @@ function PopupItem(props: PopupItemProps) {
|
||||
<div className="flex items-center gap-2">
|
||||
{!!selectedSubcategoriesCount && (
|
||||
<span className="text-small text-gray-400">
|
||||
{selectedSubcategoriesCount} selected
|
||||
{t(
|
||||
"domain-categories-multiselect-selected-with-counts",
|
||||
"{{count}}",
|
||||
{ count: selectedSubcategoriesCount }
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
<ChevronRightIcon size={16} />
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
} from "medusa-react"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useNavigate, useParams } from "react-router-dom"
|
||||
|
||||
import BackButton from "../../../components/atoms/back-button"
|
||||
import Spacer from "../../../components/atoms/spacer"
|
||||
import Spinner from "../../../components/atoms/spinner"
|
||||
@@ -25,11 +26,13 @@ import { useWidgets } from "../../../providers/widget-provider"
|
||||
import Medusa from "../../../services/api"
|
||||
import { getErrorMessage } from "../../../utils/error-messages"
|
||||
import { getErrorStatus } from "../../../utils/get-error-status"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
const CollectionDetails = () => {
|
||||
const { id } = useParams()
|
||||
|
||||
const { collection, isLoading, error, refetch } = useAdminCollection(id!)
|
||||
const { t } = useTranslation()
|
||||
const deleteCollection = useAdminDeleteCollection(id!)
|
||||
const updateCollection = useAdminUpdateCollection(id!)
|
||||
const [showEdit, setShowEdit] = useState(false)
|
||||
@@ -97,10 +100,17 @@ const CollectionDetails = () => {
|
||||
}
|
||||
|
||||
setShowAddProducts(false)
|
||||
notification("Success", "Updated products in collection", "success")
|
||||
notification(
|
||||
t("details-success", "Success"),
|
||||
t(
|
||||
"details-updated-products-in-collection",
|
||||
"Updated products in collection"
|
||||
),
|
||||
"success"
|
||||
)
|
||||
refetch()
|
||||
} catch (error) {
|
||||
notification("Error", getErrorMessage(error), "error")
|
||||
notification(t("details-error", "Error"), getErrorMessage(error), "error")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,7 +152,7 @@ const CollectionDetails = () => {
|
||||
<BackButton
|
||||
className="mb-xsmall"
|
||||
path="/a/products?view=collections"
|
||||
label="Back to Collections"
|
||||
label={t("details-back-to-collections", "Back to Collections")}
|
||||
/>
|
||||
<div className="gap-y-xsmall flex flex-col">
|
||||
{getWidgets("product_collection.details.before").map((w, i) => {
|
||||
@@ -167,12 +177,12 @@ const CollectionDetails = () => {
|
||||
forceDropdown
|
||||
actions={[
|
||||
{
|
||||
label: "Edit Collection",
|
||||
label: t("details-edit-collection", "Edit Collection"),
|
||||
onClick: () => setShowEdit(true),
|
||||
icon: <EditIcon size="20" />,
|
||||
},
|
||||
{
|
||||
label: "Delete",
|
||||
label: t("details-delete", "Delete"),
|
||||
onClick: () => setShowDelete(!showDelete),
|
||||
variant: "danger",
|
||||
icon: <TrashIcon size="20" />,
|
||||
@@ -186,7 +196,9 @@ const CollectionDetails = () => {
|
||||
</div>
|
||||
{collection.metadata && (
|
||||
<div className="mt-large gap-y-base flex flex-col">
|
||||
<h3 className="inter-base-semibold">Metadata</h3>
|
||||
<h3 className="inter-base-semibold">
|
||||
{t("details-metadata", "Metadata")}
|
||||
</h3>
|
||||
<div>
|
||||
<JSONView data={collection.metadata} />
|
||||
</div>
|
||||
@@ -199,14 +211,17 @@ const CollectionDetails = () => {
|
||||
title="Products"
|
||||
actions={[
|
||||
{
|
||||
label: "Edit Products",
|
||||
label: t("details-edit-products", "Edit Products"),
|
||||
icon: <EditIcon size="20" />,
|
||||
onClick: () => setShowAddProducts(!showAddProducts),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<p className="text-grey-50 inter-base-regular mt-xsmall mb-base">
|
||||
Products in this collection
|
||||
{t(
|
||||
"details-products-in-this-collection",
|
||||
"Products in this collection"
|
||||
)}
|
||||
</p>
|
||||
{collection && (
|
||||
<ViewProductsTable
|
||||
@@ -228,7 +243,10 @@ const CollectionDetails = () => {
|
||||
)
|
||||
})}
|
||||
|
||||
<RawJSON data={collection} title="Raw collection" />
|
||||
<RawJSON
|
||||
data={collection}
|
||||
title={t("details-raw-collection", "Raw collection")}
|
||||
/>
|
||||
</div>
|
||||
<Spacer />
|
||||
</div>
|
||||
@@ -243,10 +261,13 @@ const CollectionDetails = () => {
|
||||
{showDelete && (
|
||||
<DeletePrompt
|
||||
handleClose={() => setShowDelete(!showDelete)}
|
||||
heading="Delete collection"
|
||||
successText="Successfully deleted collection"
|
||||
heading={t("details-delete-collection", "Delete collection")}
|
||||
successText={t(
|
||||
"details-successfully-deleted-collection",
|
||||
"Successfully deleted collection"
|
||||
)}
|
||||
onDelete={async () => handleDelete()}
|
||||
confirmText="Yes, delete"
|
||||
confirmText={t("details-yes-delete", "Yes, delete")}
|
||||
/>
|
||||
)}
|
||||
{showAddProducts && (
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Customer } from "@medusajs/medusa"
|
||||
import { useAdminUpdateCustomer } from "medusa-react"
|
||||
import { useEffect } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import MetadataForm, {
|
||||
getMetadataFormValues,
|
||||
getSubmittableMetadata,
|
||||
@@ -33,6 +34,8 @@ const EditCustomerModal = ({
|
||||
handleClose,
|
||||
customer,
|
||||
}: EditCustomerModalProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const form = useForm<EditCustomerFormType>({
|
||||
defaultValues: getDefaultValues(customer),
|
||||
})
|
||||
@@ -61,11 +64,22 @@ const EditCustomerModal = ({
|
||||
{
|
||||
onSuccess: () => {
|
||||
handleClose()
|
||||
notification("Success", "Successfully updated customer", "success")
|
||||
notification(
|
||||
t("details-success", "Success"),
|
||||
t(
|
||||
"details-successfully-updated-customer",
|
||||
"Successfully updated customer"
|
||||
),
|
||||
"success"
|
||||
)
|
||||
},
|
||||
onError: (err) => {
|
||||
handleClose()
|
||||
notification("Error", getErrorMessage(err), "error")
|
||||
notification(
|
||||
t("details-error", "Error"),
|
||||
getErrorMessage(err),
|
||||
"error"
|
||||
)
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -79,22 +93,26 @@ const EditCustomerModal = ({
|
||||
<Modal handleClose={handleClose}>
|
||||
<Modal.Body>
|
||||
<Modal.Header handleClose={handleClose}>
|
||||
<span className="inter-xlarge-semibold">Customer Details</span>
|
||||
<span className="inter-xlarge-semibold">
|
||||
{t("details-customer-details", "Customer Details")}
|
||||
</span>
|
||||
</Modal.Header>
|
||||
<Modal.Content>
|
||||
<div className="gap-y-xlarge flex flex-col">
|
||||
<div>
|
||||
<h2 className="inter-base-semibold text-grey-90 mb-4">General</h2>
|
||||
<h2 className="inter-base-semibold text-grey-90 mb-4">
|
||||
{t("details-general", "General")}
|
||||
</h2>
|
||||
<div className="flex w-full space-x-2">
|
||||
<InputField
|
||||
label="First Name"
|
||||
label={t("details-first-name", "First Name")}
|
||||
{...register("first_name")}
|
||||
placeholder="Lebron"
|
||||
placeholder={t("details-lebron", "Lebron")}
|
||||
/>
|
||||
<InputField
|
||||
label="Last Name"
|
||||
label={t("details-last-name", "Last Name")}
|
||||
{...register("last_name")}
|
||||
placeholder="James"
|
||||
placeholder={t("details-james", "James")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -102,7 +120,7 @@ const EditCustomerModal = ({
|
||||
<h2 className="inter-base-semibold text-grey-90 mb-4">Contact</h2>
|
||||
<div className="flex space-x-2">
|
||||
<InputField
|
||||
label="Email"
|
||||
label={t("details-email", "Email")}
|
||||
{...register("email", {
|
||||
validate: (value) => !!validateEmail(value),
|
||||
disabled: customer.has_account,
|
||||
@@ -115,14 +133,16 @@ const EditCustomerModal = ({
|
||||
disabled={customer.has_account}
|
||||
/>
|
||||
<InputField
|
||||
label="Phone number"
|
||||
label={t("details-phone-number", "Phone number")}
|
||||
{...register("phone")}
|
||||
placeholder="+45 42 42 42 42"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="inter-base-semibold mb-base">Metadata</h2>
|
||||
<h2 className="inter-base-semibold mb-base">
|
||||
{t("details-metadata", "Metadata")}
|
||||
</h2>
|
||||
<MetadataForm form={nestedForm(form, "metadata")} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -136,7 +156,7 @@ const EditCustomerModal = ({
|
||||
className="mr-2"
|
||||
type="button"
|
||||
>
|
||||
Cancel
|
||||
{t("details-cancel", "Cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
loading={updateCustomer.isLoading}
|
||||
@@ -145,7 +165,7 @@ const EditCustomerModal = ({
|
||||
size="small"
|
||||
onClick={onSubmit}
|
||||
>
|
||||
Save and close
|
||||
{t("details-save-and-close", "Save and close")}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Footer>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useAdminCustomer } from "medusa-react"
|
||||
import moment from "moment"
|
||||
import { useState } from "react"
|
||||
import { useNavigate, useParams } from "react-router-dom"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import Avatar from "../../../components/atoms/avatar"
|
||||
import BackButton from "../../../components/atoms/back-button"
|
||||
import Spinner from "../../../components/atoms/spinner"
|
||||
@@ -24,6 +25,7 @@ const CustomerDetail = () => {
|
||||
const navigate = useNavigate()
|
||||
|
||||
const { customer, isLoading, error } = useAdminCustomer(id!)
|
||||
const { t } = useTranslation()
|
||||
const [showEdit, setShowEdit] = useState(false)
|
||||
|
||||
const customerName = () => {
|
||||
@@ -36,7 +38,7 @@ const CustomerDetail = () => {
|
||||
|
||||
const actions: ActionType[] = [
|
||||
{
|
||||
label: "Edit",
|
||||
label: t("details-edit", "Edit"),
|
||||
onClick: () => setShowEdit(true),
|
||||
icon: <EditIcon size={20} />,
|
||||
},
|
||||
@@ -70,7 +72,7 @@ const CustomerDetail = () => {
|
||||
return (
|
||||
<div>
|
||||
<BackButton
|
||||
label="Back to Customers"
|
||||
label={t("details-back-to-customers", "Back to Customers")}
|
||||
path="/a/customers"
|
||||
className="mb-xsmall"
|
||||
/>
|
||||
@@ -110,13 +112,13 @@ const CustomerDetail = () => {
|
||||
<div className="mt-6 flex space-x-6 divide-x">
|
||||
<div className="flex flex-col">
|
||||
<div className="inter-smaller-regular text-grey-50 mb-1">
|
||||
First seen
|
||||
{t("details-first-seen", "First seen")}
|
||||
</div>
|
||||
<div>{moment(customer.created_at).format("DD MMM YYYY")}</div>
|
||||
</div>
|
||||
<div className="flex flex-col pl-6">
|
||||
<div className="inter-smaller-regular text-grey-50 mb-1">
|
||||
Phone
|
||||
{t("details-phone", "Phone")}
|
||||
</div>
|
||||
<div className="max-w-[200px] truncate">
|
||||
{customer.phone || "N/A"}
|
||||
@@ -124,13 +126,13 @@ const CustomerDetail = () => {
|
||||
</div>
|
||||
<div className="flex flex-col pl-6">
|
||||
<div className="inter-smaller-regular text-grey-50 mb-1">
|
||||
Orders
|
||||
{t("details-orders", "Orders")}
|
||||
</div>
|
||||
<div>{customer.orders.length}</div>
|
||||
</div>
|
||||
<div className="h-100 flex flex-col pl-6">
|
||||
<div className="inter-smaller-regular text-grey-50 mb-1">
|
||||
User
|
||||
{t("details-user", "User")}
|
||||
</div>
|
||||
<div className="h-50 flex items-center justify-center">
|
||||
<StatusDot
|
||||
@@ -142,8 +144,13 @@ const CustomerDetail = () => {
|
||||
</div>
|
||||
</Section>
|
||||
<BodyCard
|
||||
title={`Orders (${customer.orders.length})`}
|
||||
subtitle="An overview of Customer Orders"
|
||||
title={t("details-orders", "Orders {{count}}", {
|
||||
count: customer.orders.length,
|
||||
})}
|
||||
subtitle={t(
|
||||
"details-an-overview-of-customer-orders",
|
||||
"An overview of Customer Orders"
|
||||
)}
|
||||
>
|
||||
<div className="flex grow flex-col">
|
||||
<CustomerOrdersTable id={customer.id} />
|
||||
@@ -161,7 +168,10 @@ const CustomerDetail = () => {
|
||||
)
|
||||
})}
|
||||
|
||||
<RawJSON data={customer} title="Raw customer" />
|
||||
<RawJSON
|
||||
data={customer}
|
||||
title={t("details-raw-customer", "Raw customer")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{showEdit && customer && (
|
||||
|
||||
@@ -20,6 +20,7 @@ import Modal from "../../../components/molecules/modal"
|
||||
import useNotification from "../../../hooks/use-notification"
|
||||
import { getErrorMessage } from "../../../utils/error-messages"
|
||||
import { nestedForm } from "../../../utils/nested-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
type CustomerGroupModalProps = {
|
||||
open: boolean
|
||||
@@ -40,6 +41,7 @@ function CustomerGroupModal({
|
||||
onClose,
|
||||
open,
|
||||
}: CustomerGroupModalProps) {
|
||||
const { t } = useTranslation()
|
||||
const form = useForm<CustomerGroupModalFormType>({
|
||||
defaultValues: getDefaultValues(customerGroup),
|
||||
})
|
||||
@@ -64,10 +66,18 @@ function CustomerGroupModal({
|
||||
const { general, metadata } = data
|
||||
|
||||
const onSuccess = () => {
|
||||
const title = customerGroup ? "Group Updated" : "Group Created"
|
||||
const title = customerGroup
|
||||
? t("groups-group-updated", "Group Updated")
|
||||
: t("groups-group-created", "Group Created")
|
||||
const msg = customerGroup
|
||||
? "The customer group has been updated"
|
||||
: "The customer group has been created"
|
||||
? t(
|
||||
"groups-the-customer-group-has-been-updated",
|
||||
"The customer group has been updated"
|
||||
)
|
||||
: t(
|
||||
"groups-the-customer-group-has-been-created",
|
||||
"The customer group has been created"
|
||||
)
|
||||
|
||||
notification(title, msg, "success")
|
||||
|
||||
@@ -110,7 +120,12 @@ function CustomerGroupModal({
|
||||
<Modal.Body>
|
||||
<Modal.Header handleClose={onClose}>
|
||||
<span className="inter-xlarge-semibold">
|
||||
{customerGroup ? "Edit" : "Create a New"} Customer Group
|
||||
{customerGroup
|
||||
? t("groups-edit-customer-group", "Edit Customer Group")
|
||||
: t(
|
||||
"groups-create-a-new-customer-group",
|
||||
"Create a New Customer Group"
|
||||
)}
|
||||
</span>
|
||||
</Modal.Header>
|
||||
|
||||
@@ -118,11 +133,15 @@ function CustomerGroupModal({
|
||||
<Modal.Content>
|
||||
<div className="gap-y-xlarge flex flex-col">
|
||||
<div>
|
||||
<h2 className="inter-base-semibold mb-base">Details</h2>
|
||||
<h2 className="inter-base-semibold mb-base">
|
||||
{t("groups-details", "Details")}
|
||||
</h2>
|
||||
<CustomerGroupGeneralForm form={nestedForm(form, "general")} />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="inter-base-semibold mb-base">Metadata</h2>
|
||||
<h2 className="inter-base-semibold mb-base">
|
||||
{t("groups-metadata", "Metadata")}
|
||||
</h2>
|
||||
<MetadataForm form={nestedForm(form, "metadata")} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -137,10 +156,14 @@ function CustomerGroupModal({
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
>
|
||||
Cancel
|
||||
{t("groups-cancel", "Cancel")}
|
||||
</Button>
|
||||
<Button size="small" variant="primary" type="submit">
|
||||
<span>{customerGroup ? "Edit" : "Publish"} Group</span>
|
||||
<span>
|
||||
{customerGroup
|
||||
? t("groups-edit-group", "Edit Group")
|
||||
: t("groups-publish-group", "Publish Group")}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Footer>
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
useAdminRemoveCustomersFromCustomerGroup,
|
||||
} from "medusa-react"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { useNavigate, useParams } from "react-router-dom"
|
||||
import BackButton from "../../../components/atoms/back-button"
|
||||
@@ -40,10 +41,15 @@ const defaultQueryProps = {
|
||||
* Placeholder for the customer groups list.
|
||||
*/
|
||||
function CustomersListPlaceholder() {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className="center flex h-full min-h-[756px] items-center justify-center">
|
||||
<span className="text-xs text-gray-400">
|
||||
No customers in this group yet
|
||||
{t(
|
||||
"groups-no-customers-in-this-group-yet",
|
||||
"No customers in this group yet"
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
@@ -129,7 +135,7 @@ function CustomerGroupCustomersList(props: CustomerGroupCustomersListProps) {
|
||||
|
||||
return (
|
||||
<BodyCard
|
||||
title="Customers"
|
||||
title={t("groups-customers", "Customers")}
|
||||
actionables={actions}
|
||||
className="min-h-[756px] w-full"
|
||||
>
|
||||
@@ -170,6 +176,7 @@ type CustomerGroupDetailsHeaderProps = {
|
||||
function CustomerGroupDetailsHeader(props: CustomerGroupDetailsHeaderProps) {
|
||||
const [showDeleteConfirmation, setShowDeleteConfirmation] = useState(false)
|
||||
|
||||
const { t } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const { mutate: deleteGroup } = useAdminDeleteCustomerGroup(
|
||||
@@ -180,12 +187,12 @@ function CustomerGroupDetailsHeader(props: CustomerGroupDetailsHeaderProps) {
|
||||
|
||||
const actions: ActionType[] = [
|
||||
{
|
||||
label: "Edit",
|
||||
label: t("groups-edit", "Edit"),
|
||||
onClick: open,
|
||||
icon: <EditIcon size={20} />,
|
||||
},
|
||||
{
|
||||
label: "Delete",
|
||||
label: t("groups-delete", "Delete"),
|
||||
onClick: () => {
|
||||
setShowDeleteConfirmation(true)
|
||||
},
|
||||
@@ -213,10 +220,13 @@ function CustomerGroupDetailsHeader(props: CustomerGroupDetailsHeaderProps) {
|
||||
<DeletePrompt
|
||||
onDelete={onDeleteConfirmed}
|
||||
handleClose={handleConfirmDialogClose}
|
||||
confirmText="Yes, delete"
|
||||
heading="Delete the group"
|
||||
successText="Group deleted"
|
||||
text="Are you sure you want to delete this customer group?"
|
||||
confirmText={t("groups-yes-delete", "Yes, delete")}
|
||||
heading={t("groups-delete-the-group", "Delete the group")}
|
||||
successText={t("groups-group-deleted", "Group deleted")}
|
||||
text={t(
|
||||
"groups-confirm-delete-customer-group",
|
||||
"Are you sure you want to delete this customer group?"
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<CustomerGroupModal
|
||||
@@ -265,7 +275,7 @@ function CustomerGroupDetails() {
|
||||
<div className="-mt-4 pb-4">
|
||||
<BackButton
|
||||
path="/a/customers/groups"
|
||||
label="Back to customer groups"
|
||||
label={t("groups-back-to-customer-groups", "Back to customer groups")}
|
||||
className="mb-4"
|
||||
/>
|
||||
<div className="gap-y-xsmall flex flex-col">
|
||||
|
||||
@@ -10,6 +10,7 @@ import { useWidgets } from "../../../providers/widget-provider"
|
||||
import CustomersPageTableHeader from "../header"
|
||||
import CustomerGroupModal from "./customer-group-modal"
|
||||
import Details from "./details"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
/*
|
||||
* Customer groups index page
|
||||
@@ -17,10 +18,11 @@ import Details from "./details"
|
||||
function Index() {
|
||||
const { state, open, close } = useToggleState()
|
||||
const { getWidgets } = useWidgets()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const actions = [
|
||||
{
|
||||
label: "New group",
|
||||
label: t("groups-new-group", "New group"),
|
||||
onClick: open,
|
||||
icon: (
|
||||
<span className="text-grey-90">
|
||||
|
||||
+18
-3
@@ -7,6 +7,8 @@ import {
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import useNotification from "../../../../../hooks/use-notification"
|
||||
import { getErrorMessage } from "../../../../../utils/error-messages"
|
||||
import {
|
||||
@@ -68,6 +70,7 @@ export const ConditionsProvider = ({
|
||||
discount,
|
||||
children,
|
||||
}: ConditionsProviderProps) => {
|
||||
const { t } = useTranslation()
|
||||
const [conditions, setConditions] = useState<ConditionMap>(defaultConditions)
|
||||
const { mutate } = useAdminUpdateDiscount(discount.id)
|
||||
|
||||
@@ -130,8 +133,14 @@ export const ConditionsProvider = ({
|
||||
{
|
||||
onSuccess: () => {
|
||||
notification(
|
||||
"Conditions were successfully added",
|
||||
"Discount conditions updated",
|
||||
t(
|
||||
"add-condition-conditions-were-successfully-added",
|
||||
"Conditions were successfully added"
|
||||
),
|
||||
t(
|
||||
"add-condition-discount-conditions-updated",
|
||||
"Discount conditions updated"
|
||||
),
|
||||
"success"
|
||||
)
|
||||
},
|
||||
@@ -175,9 +184,15 @@ export const ConditionsProvider = ({
|
||||
}
|
||||
|
||||
export const useConditions = () => {
|
||||
const { t } = useTranslation()
|
||||
const context = useContext(ConditionsContext)
|
||||
if (context === null) {
|
||||
throw new Error("useConditions must be used within a ConditionsProvider")
|
||||
throw new Error(
|
||||
t(
|
||||
"add-condition-use-conditions-must-be-used-within-a-conditions-provider",
|
||||
"useConditions must be used within a ConditionsProvider"
|
||||
)
|
||||
)
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
+7
-5
@@ -1,11 +1,12 @@
|
||||
import { useAdminCollections } from "medusa-react"
|
||||
import { useContext, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import Button from "../../../../../../../components/fundamentals/button"
|
||||
import Modal from "../../../../../../../components/molecules/modal"
|
||||
import { LayeredModalContext } from "../../../../../../../components/molecules/modal/layered-modal"
|
||||
import { SelectableTable } from "../../../../../../../components/templates/selectable-table"
|
||||
import useQueryFilters from "../../../../../../../hooks/use-query-filters"
|
||||
import { defaultQueryProps } from "../../../../..//new/discount-form/condition-tables/shared/common"
|
||||
import { defaultQueryProps } from "../../../../../new/discount-form/condition-tables/shared/common"
|
||||
import {
|
||||
CollectionRow,
|
||||
CollectionsHeader,
|
||||
@@ -14,6 +15,7 @@ import {
|
||||
import { useEditConditionContext } from "../../edit-condition-provider"
|
||||
|
||||
const AddCollectionConditionsScreen = () => {
|
||||
const { t } = useTranslation()
|
||||
const params = useQueryFilters(defaultQueryProps)
|
||||
|
||||
const { pop } = useContext(LayeredModalContext)
|
||||
@@ -38,7 +40,7 @@ const AddCollectionConditionsScreen = () => {
|
||||
options={{
|
||||
enableSearch: true,
|
||||
immediateSearchFocus: true,
|
||||
searchPlaceholder: "Search...",
|
||||
searchPlaceholder: t("collections-search", "Search..."),
|
||||
}}
|
||||
resourceName="Collections"
|
||||
totalCount={count ?? 0}
|
||||
@@ -55,21 +57,21 @@ const AddCollectionConditionsScreen = () => {
|
||||
<Modal.Footer>
|
||||
<div className="space-x-xsmall flex w-full justify-end">
|
||||
<Button variant="secondary" size="small" onClick={pop}>
|
||||
Cancel
|
||||
{t("collections-cancel", "Cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="small"
|
||||
onClick={() => saveAndGoBack(selectedResources, () => refetch())}
|
||||
>
|
||||
Save and go back
|
||||
{t("collections-save-and-go-back", "Save and go back")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="small"
|
||||
onClick={() => saveAndClose(selectedResources)}
|
||||
>
|
||||
Save and close
|
||||
{t("collections-save-and-close", "Save and close")}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Footer>
|
||||
|
||||
+7
-4
@@ -1,5 +1,7 @@
|
||||
import { useAdminCustomerGroups } from "medusa-react"
|
||||
import { useContext, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import Modal from "../../../../../../../components/molecules/modal"
|
||||
import { SelectableTable } from "../../../../../../../components/templates/selectable-table"
|
||||
import useQueryFilters from "../../../../../../../hooks/use-query-filters"
|
||||
@@ -14,6 +16,7 @@ import Button from "../../../../../../../components/fundamentals/button"
|
||||
import { LayeredModalContext } from "../../../../../../../components/molecules/modal/layered-modal"
|
||||
|
||||
const AddCustomerGroupsConditionsScreen = () => {
|
||||
const { t } = useTranslation()
|
||||
const params = useQueryFilters(defaultQueryProps)
|
||||
|
||||
const { pop } = useContext(LayeredModalContext)
|
||||
@@ -38,7 +41,7 @@ const AddCustomerGroupsConditionsScreen = () => {
|
||||
options={{
|
||||
enableSearch: true,
|
||||
immediateSearchFocus: true,
|
||||
searchPlaceholder: "Search...",
|
||||
searchPlaceholder: t("customer-groups-search", "Search..."),
|
||||
}}
|
||||
resourceName="Groups"
|
||||
totalCount={count ?? 0}
|
||||
@@ -55,21 +58,21 @@ const AddCustomerGroupsConditionsScreen = () => {
|
||||
<Modal.Footer>
|
||||
<div className="space-x-xsmall flex w-full justify-end">
|
||||
<Button variant="secondary" size="small" onClick={pop}>
|
||||
Cancel
|
||||
{t("customer-groups-cancel", "Cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="small"
|
||||
onClick={() => saveAndGoBack(selectedResources, () => refetch())}
|
||||
>
|
||||
Save and go back
|
||||
{t("customer-groups-save-and-go-back", "Save and go back")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="small"
|
||||
onClick={() => saveAndClose(selectedResources)}
|
||||
>
|
||||
Save and close
|
||||
{t("customer-groups-save-and-close", "Save and close")}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Footer>
|
||||
|
||||
+6
-4
@@ -1,5 +1,6 @@
|
||||
import { useAdminProductTypes } from "medusa-react"
|
||||
import { useContext, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import Button from "../../../../../../../components/fundamentals/button"
|
||||
import Modal from "../../../../../../../components/molecules/modal"
|
||||
import { LayeredModalContext } from "../../../../../../../components/molecules/modal/layered-modal"
|
||||
@@ -14,6 +15,7 @@ import {
|
||||
import { useEditConditionContext } from "../../edit-condition-provider"
|
||||
|
||||
const AddTypesConditionsScreen = () => {
|
||||
const { t } = useTranslation()
|
||||
const params = useQueryFilters(defaultQueryProps)
|
||||
|
||||
const { pop } = useContext(LayeredModalContext)
|
||||
@@ -40,7 +42,7 @@ const AddTypesConditionsScreen = () => {
|
||||
options={{
|
||||
enableSearch: true,
|
||||
immediateSearchFocus: true,
|
||||
searchPlaceholder: "Search...",
|
||||
searchPlaceholder: t("product-types-search", "Search..."),
|
||||
}}
|
||||
resourceName="Types"
|
||||
totalCount={count ?? 0}
|
||||
@@ -57,21 +59,21 @@ const AddTypesConditionsScreen = () => {
|
||||
<Modal.Footer>
|
||||
<div className="space-x-xsmall flex w-full justify-end">
|
||||
<Button variant="secondary" size="small" onClick={pop}>
|
||||
Cancel
|
||||
{t("product-types-cancel", "Cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="small"
|
||||
onClick={() => saveAndGoBack(selectedResources, () => refetch())}
|
||||
>
|
||||
Save and go back
|
||||
{t("product-types-save-and-go-back", "Save and go back")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="small"
|
||||
onClick={() => saveAndClose(selectedResources)}
|
||||
>
|
||||
Save and close
|
||||
{t("product-types-save-and-close", "Save and close")}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Footer>
|
||||
|
||||
+7
-5
@@ -1,11 +1,12 @@
|
||||
import { useAdminProducts } from "medusa-react"
|
||||
import { useContext, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import Button from "../../../../../../../components/fundamentals/button"
|
||||
import Modal from "../../../../../../../components/molecules/modal"
|
||||
import { LayeredModalContext } from "../../../../../../../components/molecules/modal/layered-modal"
|
||||
import { SelectableTable } from "../../../../../../../components/templates/selectable-table"
|
||||
import useQueryFilters from "../../../../../../../hooks/use-query-filters"
|
||||
import { defaultQueryProps } from "../../../../..//new/discount-form/condition-tables/shared/common"
|
||||
import { defaultQueryProps } from "../../../../../new/discount-form/condition-tables/shared/common"
|
||||
import {
|
||||
ProductRow,
|
||||
ProductsHeader,
|
||||
@@ -14,6 +15,7 @@ import {
|
||||
import { useEditConditionContext } from "../../edit-condition-provider"
|
||||
|
||||
const AddProductConditionsScreen = () => {
|
||||
const { t } = useTranslation()
|
||||
const params = useQueryFilters(defaultQueryProps)
|
||||
|
||||
const { pop } = useContext(LayeredModalContext)
|
||||
@@ -35,7 +37,7 @@ const AddProductConditionsScreen = () => {
|
||||
options={{
|
||||
enableSearch: true,
|
||||
immediateSearchFocus: true,
|
||||
searchPlaceholder: "Search...",
|
||||
searchPlaceholder: t("products-search", "Search..."),
|
||||
}}
|
||||
resourceName="Products"
|
||||
totalCount={count ?? 0}
|
||||
@@ -52,21 +54,21 @@ const AddProductConditionsScreen = () => {
|
||||
<Modal.Footer>
|
||||
<div className="space-x-xsmall flex w-full justify-end">
|
||||
<Button variant="secondary" size="small" onClick={pop}>
|
||||
Cancel
|
||||
{t("products-cancel", "Cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="small"
|
||||
onClick={() => saveAndGoBack(selectedResources)}
|
||||
>
|
||||
Save and go back
|
||||
{t("products-save-and-go-back", "Save and go back")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="small"
|
||||
onClick={() => saveAndClose(selectedResources)}
|
||||
>
|
||||
Save and close
|
||||
{t("products-save-and-close", "Save and close")}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Footer>
|
||||
|
||||
+6
-4
@@ -1,5 +1,6 @@
|
||||
import { useAdminProductTags } from "medusa-react"
|
||||
import { useContext, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import Button from "../../../../../../../components/fundamentals/button"
|
||||
import Modal from "../../../../../../../components/molecules/modal"
|
||||
import { LayeredModalContext } from "../../../../../../../components/molecules/modal/layered-modal"
|
||||
@@ -14,6 +15,7 @@ import {
|
||||
import { useEditConditionContext } from "../../edit-condition-provider"
|
||||
|
||||
const AddTagsConditionsScreen = () => {
|
||||
const { t } = useTranslation()
|
||||
const params = useQueryFilters(defaultQueryProps)
|
||||
|
||||
const { pop } = useContext(LayeredModalContext)
|
||||
@@ -36,7 +38,7 @@ const AddTagsConditionsScreen = () => {
|
||||
options={{
|
||||
enableSearch: true,
|
||||
immediateSearchFocus: true,
|
||||
searchPlaceholder: "Search...",
|
||||
searchPlaceholder: t("tags-search", "Search..."),
|
||||
}}
|
||||
resourceName="Tags"
|
||||
totalCount={count ?? 0}
|
||||
@@ -53,21 +55,21 @@ const AddTagsConditionsScreen = () => {
|
||||
<Modal.Footer>
|
||||
<div className="space-x-xsmall flex w-full justify-end">
|
||||
<Button variant="secondary" size="small" onClick={pop}>
|
||||
Cancel
|
||||
{t("tags-cancel", "Cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="small"
|
||||
onClick={() => saveAndGoBack(selectedResources)}
|
||||
>
|
||||
Save and go back
|
||||
{t("tags-save-and-go-back", "Save and go back")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="small"
|
||||
onClick={() => saveAndClose(selectedResources)}
|
||||
>
|
||||
Save and close
|
||||
{t("tags-save-and-close", "Save and close")}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Footer>
|
||||
|
||||
+3
-1
@@ -6,8 +6,10 @@ import AddCustomerGroupsConditionsScreen from "./add-condition-resources/custome
|
||||
import AddTypesConditionsScreen from "./add-condition-resources/product-types/add-types"
|
||||
import AddProductConditionsScreen from "./add-condition-resources/products/add-products"
|
||||
import AddTagsConditionsScreen from "./add-condition-resources/tags/add-tags"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
export const useAddConditionsModalScreen = (condition: DiscountCondition) => {
|
||||
const { t } = useTranslation()
|
||||
const { pop } = React.useContext(LayeredModalContext)
|
||||
|
||||
const renderModalScreen = () => {
|
||||
@@ -26,7 +28,7 @@ export const useAddConditionsModalScreen = (condition: DiscountCondition) => {
|
||||
}
|
||||
|
||||
return {
|
||||
title: `Add conditions`,
|
||||
title: t("edit-condition-add-conditions", "Add conditions"),
|
||||
onBack: pop,
|
||||
view: renderModalScreen(),
|
||||
}
|
||||
|
||||
+8
-4
@@ -1,5 +1,6 @@
|
||||
import clsx from "clsx"
|
||||
import { useContext } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import Button from "../../../../../components/fundamentals/button"
|
||||
import PlusIcon from "../../../../../components/fundamentals/icons/plus-icon"
|
||||
import { LayeredModalContext } from "../../../../../components/molecules/modal/layered-modal"
|
||||
@@ -17,6 +18,7 @@ const ExistingConditionTableActions = ({
|
||||
onDeselect,
|
||||
onRemove,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const { condition } = useEditConditionContext()
|
||||
|
||||
const addConditionsModalScreen = useAddConditionsModalScreen(condition)
|
||||
@@ -35,7 +37,9 @@ const ExistingConditionTableActions = ({
|
||||
<div className={clsx("transition-all duration-200", classes)}>
|
||||
<div className="mb-2 flex h-[34px] items-center divide-x">
|
||||
<span className="inter-small-regular text-grey-50 mr-3">
|
||||
{numberOfSelectedRows} selected
|
||||
{t("edit-condition-selected-with-count", "{{count}}", {
|
||||
count: numberOfSelectedRows,
|
||||
})}
|
||||
</span>
|
||||
<div className="space-x-xsmall flex pl-3">
|
||||
<Button
|
||||
@@ -44,7 +48,7 @@ const ExistingConditionTableActions = ({
|
||||
variant="ghost"
|
||||
className="border-grey-20 border"
|
||||
>
|
||||
Deselect
|
||||
{t("edit-condition-deselect", "Deselect")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={onRemove}
|
||||
@@ -52,7 +56,7 @@ const ExistingConditionTableActions = ({
|
||||
variant="ghost"
|
||||
className="border-grey-20 border text-rose-50"
|
||||
>
|
||||
Remove
|
||||
{t("edit-condition-remove", "Remove")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -63,7 +67,7 @@ const ExistingConditionTableActions = ({
|
||||
className="border-grey-20 border"
|
||||
onClick={() => push(addConditionsModalScreen)}
|
||||
>
|
||||
<PlusIcon size={20} /> Add
|
||||
<PlusIcon size={20} /> {t("edit-condition-add", "Add")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+10
-2
@@ -13,6 +13,7 @@ import CustomerGroupsConditionsTable from "./add-condition-resources/customer-gr
|
||||
import { Discount, DiscountCondition } from "@medusajs/medusa"
|
||||
import { capitalize } from "lodash"
|
||||
import { getTitle } from "../../../utils"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
type Props = {
|
||||
open: boolean
|
||||
@@ -22,6 +23,7 @@ type Props = {
|
||||
}
|
||||
|
||||
const EditConditionsModal = ({ open, condition, discount, onClose }: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const context = useContext(LayeredModalContext)
|
||||
|
||||
const renderModalContext = () => {
|
||||
@@ -49,7 +51,13 @@ const EditConditionsModal = ({ open, condition, discount, onClose }: Props) => {
|
||||
<Modal.Body>
|
||||
<Modal.Header handleClose={onClose}>
|
||||
<h1 className="inter-xlarge-semibold">
|
||||
Edit {capitalize(getTitle(condition?.type))} in Discount Condition
|
||||
{t(
|
||||
"edit-condition-title",
|
||||
"Edit {{type}} in Discount Condition",
|
||||
{
|
||||
type: capitalize(getTitle(condition?.type, t)),
|
||||
}
|
||||
)}
|
||||
</h1>
|
||||
</Modal.Header>
|
||||
{renderModalContext()}
|
||||
@@ -61,7 +69,7 @@ const EditConditionsModal = ({ open, condition, discount, onClose }: Props) => {
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
>
|
||||
Close
|
||||
{t("edit-condition-close", "Close")}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Footer>
|
||||
|
||||
+32
-7
@@ -4,6 +4,7 @@ import {
|
||||
useAdminDeleteDiscountConditionResourceBatch,
|
||||
} from "medusa-react"
|
||||
import { createContext, ReactNode, useContext } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { LayeredModalContext } from "../../../../../components/molecules/modal/layered-modal"
|
||||
import useNotification from "../../../../../hooks/use-notification"
|
||||
|
||||
@@ -33,6 +34,7 @@ export const EditConditionProvider = ({
|
||||
onClose,
|
||||
children,
|
||||
}: ConditionsProviderProps) => {
|
||||
const { t } = useTranslation()
|
||||
const notification = useNotification()
|
||||
|
||||
const { pop, reset } = useContext(LayeredModalContext)
|
||||
@@ -54,14 +56,24 @@ export const EditConditionProvider = ({
|
||||
{
|
||||
onSuccess: () => {
|
||||
notification(
|
||||
"Success",
|
||||
"The resources were successfully added",
|
||||
t("edit-condition-success", "Success"),
|
||||
t(
|
||||
"edit-condition-the-resources-were-successfully-added",
|
||||
"The resources were successfully added"
|
||||
),
|
||||
"success"
|
||||
)
|
||||
onSuccessCallback?.()
|
||||
},
|
||||
onError: () =>
|
||||
notification("Error", "Failed to add resources", "error"),
|
||||
notification(
|
||||
t("edit-condition-error", "Error"),
|
||||
t(
|
||||
"edit-condition-failed-to-add-resources",
|
||||
"Failed to add resources"
|
||||
),
|
||||
"error"
|
||||
),
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -72,13 +84,23 @@ export const EditConditionProvider = ({
|
||||
{
|
||||
onSuccess: () => {
|
||||
notification(
|
||||
"Success",
|
||||
"The resources were successfully removed",
|
||||
t("edit-condition-success", "Success"),
|
||||
t(
|
||||
"edit-condition-the-resources-were-successfully-removed",
|
||||
"The resources were successfully removed"
|
||||
),
|
||||
"success"
|
||||
)
|
||||
},
|
||||
onError: () =>
|
||||
notification("Error", "Failed to remove resources", "error"),
|
||||
notification(
|
||||
t("edit-condition-error", "Error"),
|
||||
t(
|
||||
"edit-condition-failed-to-remove-resources",
|
||||
"Failed to remove resources"
|
||||
),
|
||||
"error"
|
||||
),
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -115,7 +137,10 @@ export const useEditConditionContext = () => {
|
||||
const context = useContext(EditConditionContext)
|
||||
if (context === null) {
|
||||
throw new Error(
|
||||
"useEditConditionContext must be used within an EditConditionProvider"
|
||||
t(
|
||||
"edit-condition-use-edit-condition-context-must-be-used-within-an-edit-condition-provider",
|
||||
"useEditConditionContext must be used within an EditConditionProvider"
|
||||
)
|
||||
)
|
||||
}
|
||||
return context
|
||||
|
||||
@@ -7,6 +7,7 @@ import AddCondition from "./add-condition"
|
||||
import { ConditionsProvider } from "./add-condition/conditions-provider"
|
||||
import EditConditionsModal from "./edit-condition/edit-condition-modal"
|
||||
import { useDiscountConditions } from "./use-discount-conditions"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
type DiscountDetailsConditionsProps = {
|
||||
discount: Discount
|
||||
@@ -15,6 +16,7 @@ type DiscountDetailsConditionsProps = {
|
||||
const DiscountDetailsConditions: React.FC<DiscountDetailsConditionsProps> = ({
|
||||
discount,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [show, setShow] = useState(false)
|
||||
|
||||
const { conditions, selectedCondition, deSelectCondition } =
|
||||
@@ -24,12 +26,12 @@ const DiscountDetailsConditions: React.FC<DiscountDetailsConditionsProps> = ({
|
||||
<ConditionsProvider discount={discount}>
|
||||
<div>
|
||||
<BodyCard
|
||||
title="Conditions"
|
||||
title={t("conditions-conditions", "Conditions")}
|
||||
className="min-h-[200px]"
|
||||
forceDropdown
|
||||
actionables={[
|
||||
{
|
||||
label: "Add condition",
|
||||
label: t("conditions-add-condition-label", "Add condition"),
|
||||
icon: <PlusIcon size={16} />,
|
||||
onClick: () => setShow(true),
|
||||
},
|
||||
@@ -57,7 +59,10 @@ const DiscountDetailsConditions: React.FC<DiscountDetailsConditionsProps> = ({
|
||||
) : (
|
||||
<div className="gap-y-small flex flex-1 flex-col items-center justify-center">
|
||||
<span className="inter-base-regular text-grey-50">
|
||||
This discount has no conditions
|
||||
{t(
|
||||
"conditions-this-discount-has-no-conditions",
|
||||
"This discount has no conditions"
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
+37
-11
@@ -5,14 +5,17 @@ import {
|
||||
useAdminGetDiscountCondition,
|
||||
} from "medusa-react"
|
||||
import { useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import EditIcon from "../../../../components/fundamentals/icons/edit-icon"
|
||||
import TrashIcon from "../../../../components/fundamentals/icons/trash-icon"
|
||||
import { ActionType } from "../../../../components/molecules/actionables"
|
||||
import useNotification from "../../../../hooks/use-notification"
|
||||
import { getErrorMessage } from "../../../../utils/error-messages"
|
||||
import { DiscountConditionType } from "../../types"
|
||||
import { TFunction } from "i18next"
|
||||
|
||||
export const useDiscountConditions = (discount: Discount) => {
|
||||
const { t } = useTranslation()
|
||||
const [selectedCondition, setSelectedCondition] = useState<string | null>(
|
||||
null
|
||||
)
|
||||
@@ -34,11 +37,19 @@ export const useDiscountConditions = (discount: Discount) => {
|
||||
const removeCondition = (conditionId: string) => {
|
||||
mutate(conditionId, {
|
||||
onSuccess: () => {
|
||||
notification("Success", "Condition removed", "success")
|
||||
notification(
|
||||
t("conditions-success", "Success"),
|
||||
t("conditions-condition-removed", "Condition removed"),
|
||||
"success"
|
||||
)
|
||||
refetch()
|
||||
},
|
||||
onError: (error) => {
|
||||
notification("Error", getErrorMessage(error), "error")
|
||||
notification(
|
||||
t("conditions-error", "Error"),
|
||||
getErrorMessage(error),
|
||||
"error"
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -46,16 +57,16 @@ export const useDiscountConditions = (discount: Discount) => {
|
||||
const itemized = discount.rule.conditions.map((condition) => ({
|
||||
type: condition.type,
|
||||
title: getTitle(condition.type),
|
||||
description: getDescription(condition.type),
|
||||
description: getDescription(condition.type, t),
|
||||
actions: [
|
||||
{
|
||||
label: "Edit condition",
|
||||
label: t("conditions-edit-condition", "Edit condition"),
|
||||
icon: <EditIcon size={16} />,
|
||||
variant: "ghost",
|
||||
onClick: () => setSelectedCondition(condition.id),
|
||||
},
|
||||
{
|
||||
label: "Delete condition",
|
||||
label: t("conditions-delete-condition", "Delete condition"),
|
||||
icon: <TrashIcon size={16} />,
|
||||
variant: "danger",
|
||||
onClick: () => removeCondition(condition.id),
|
||||
@@ -89,17 +100,32 @@ const getTitle = (type: DiscountConditionType) => {
|
||||
}
|
||||
}
|
||||
|
||||
const getDescription = (type: DiscountConditionType) => {
|
||||
const getDescription = (type: DiscountConditionType, t: TFunction) => {
|
||||
switch (type) {
|
||||
case DiscountConditionType.PRODUCTS:
|
||||
return "Discount is applicable to specific products"
|
||||
return t(
|
||||
"conditions-discount-is-applicable-to-specific-products",
|
||||
"Discount is applicable to specific products"
|
||||
)
|
||||
case DiscountConditionType.PRODUCT_COLLECTIONS:
|
||||
return "Discount is applicable to specific collections"
|
||||
return t(
|
||||
"conditions-discount-is-applicable-to-specific-collections",
|
||||
"Discount is applicable to specific collections"
|
||||
)
|
||||
case DiscountConditionType.PRODUCT_TAGS:
|
||||
return "Discount is applicable to specific product tags"
|
||||
return t(
|
||||
"conditions-discount-is-applicable-to-specific-product-tags",
|
||||
"Discount is applicable to specific product tags"
|
||||
)
|
||||
case DiscountConditionType.PRODUCT_TYPES:
|
||||
return "Discount is applicable to specific product types"
|
||||
return t(
|
||||
"conditions-discount-is-applicable-to-specific-product-types",
|
||||
"Discount is applicable to specific product types"
|
||||
)
|
||||
case DiscountConditionType.CUSTOMER_GROUPS:
|
||||
return "Discount is applicable to specific customer groups"
|
||||
return t(
|
||||
"conditions-discount-is-applicable-to-specific-customer-groups",
|
||||
"Discount is applicable to specific customer groups"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+21
-5
@@ -2,6 +2,7 @@ import { Discount } from "@medusajs/medusa"
|
||||
import { useAdminUpdateDiscount } from "medusa-react"
|
||||
import React, { useEffect } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import DiscountConfigurationForm, {
|
||||
DiscountConfigurationFormType,
|
||||
} from "../../../../components/forms/discount/discount-configuration-form"
|
||||
@@ -26,6 +27,8 @@ const EditConfigurations: React.FC<EditConfigurationsProps> = ({
|
||||
onClose,
|
||||
open,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { mutate, isLoading } = useAdminUpdateDiscount(discount.id)
|
||||
const notification = useNotification()
|
||||
|
||||
@@ -48,12 +51,23 @@ const EditConfigurations: React.FC<EditConfigurationsProps> = ({
|
||||
},
|
||||
{
|
||||
onSuccess: ({ discount }) => {
|
||||
notification("Success", "Discount updated successfully", "success")
|
||||
notification(
|
||||
t("configurations-success", "Success"),
|
||||
t(
|
||||
"configurations-discount-updated-successfully",
|
||||
"Discount updated successfully"
|
||||
),
|
||||
"success"
|
||||
)
|
||||
reset(getDefaultValues(discount))
|
||||
onClose()
|
||||
},
|
||||
onError: (error) => {
|
||||
notification("Error", getErrorMessage(error), "error")
|
||||
notification(
|
||||
t("configurations-error", "Error"),
|
||||
getErrorMessage(error),
|
||||
"error"
|
||||
)
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -69,7 +83,9 @@ const EditConfigurations: React.FC<EditConfigurationsProps> = ({
|
||||
<Modal open={open} handleClose={onClose} isLargeModal>
|
||||
<Modal.Body>
|
||||
<Modal.Header handleClose={onClose}>
|
||||
<h1 className="inter-xlarge-semibold">Edit configurations</h1>
|
||||
<h1 className="inter-xlarge-semibold">
|
||||
{t("configurations-edit-configurations", "Edit configurations")}
|
||||
</h1>
|
||||
</Modal.Header>
|
||||
<form onSubmit={onSubmit}>
|
||||
<Modal.Content>
|
||||
@@ -84,7 +100,7 @@ const EditConfigurations: React.FC<EditConfigurationsProps> = ({
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
>
|
||||
Cancel
|
||||
{t("configurations-cancel", "Cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
@@ -94,7 +110,7 @@ const EditConfigurations: React.FC<EditConfigurationsProps> = ({
|
||||
loading={isLoading}
|
||||
disabled={isLoading}
|
||||
>
|
||||
Save
|
||||
{t("configurations-save", "Save")}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Footer>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Discount } from "@medusajs/medusa"
|
||||
import React from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import EditIcon from "../../../../components/fundamentals/icons/edit-icon"
|
||||
import NumberedItem from "../../../../components/molecules/numbered-item"
|
||||
import BodyCard from "../../../../components/organisms/body-card"
|
||||
@@ -12,17 +13,21 @@ type ConfigurationsProps = {
|
||||
}
|
||||
|
||||
const Configurations: React.FC<ConfigurationsProps> = ({ discount }) => {
|
||||
const { t } = useTranslation()
|
||||
const configurations = useDiscountConfigurations(discount)
|
||||
const { state, open, close } = useToggleState()
|
||||
|
||||
return (
|
||||
<>
|
||||
<BodyCard
|
||||
title={"Configurations"}
|
||||
title={t("configurations-configurations", "Configurations")}
|
||||
className="min-h-[200px]"
|
||||
actionables={[
|
||||
{
|
||||
label: "Edit configurations",
|
||||
label: t(
|
||||
"configurations-edit-configurations",
|
||||
"Edit configurations"
|
||||
),
|
||||
onClick: open,
|
||||
icon: <EditIcon size={20} />,
|
||||
},
|
||||
|
||||
+47
-14
@@ -1,4 +1,5 @@
|
||||
import { ReactNode } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { ActionType } from "../../../../components/molecules/actionables"
|
||||
import ClockIcon from "../../../../components/fundamentals/icons/clock-icon"
|
||||
@@ -32,23 +33,27 @@ const CommonDescription = ({ text }) => (
|
||||
)
|
||||
|
||||
const useDiscountConfigurations = (discount: Discount) => {
|
||||
const { t } = useTranslation()
|
||||
const updateDiscount = useAdminUpdateDiscount(discount.id)
|
||||
const notification = useNotification()
|
||||
|
||||
const conditions: displaySetting[] = []
|
||||
|
||||
conditions.push({
|
||||
title: "Start date",
|
||||
title: t("configurations-start-date", "Start date"),
|
||||
description: <DisplaySettingsDateDescription date={discount.starts_at} />,
|
||||
})
|
||||
|
||||
if (discount.ends_at) {
|
||||
conditions.push({
|
||||
title: "End date",
|
||||
title: t("configurations-end-date", "End date"),
|
||||
description: <DisplaySettingsDateDescription date={discount.ends_at} />,
|
||||
actions: [
|
||||
{
|
||||
label: "Delete configuration",
|
||||
label: t(
|
||||
"configurations-delete-configuration",
|
||||
"Delete configuration"
|
||||
),
|
||||
icon: <TrashIcon size={20} />,
|
||||
variant: "danger",
|
||||
onClick: async () =>
|
||||
@@ -57,13 +62,20 @@ const useDiscountConfigurations = (discount: Discount) => {
|
||||
{
|
||||
onSuccess: () => {
|
||||
notification(
|
||||
"Success",
|
||||
"Discount end date removed",
|
||||
t("configurations-success", "Success"),
|
||||
t(
|
||||
"configurations-discount-end-date-removed",
|
||||
"Discount end date removed"
|
||||
),
|
||||
"success"
|
||||
)
|
||||
},
|
||||
onError: (error) => {
|
||||
notification("Error", getErrorMessage(error), "error")
|
||||
notification(
|
||||
t("configurations-error", "Error"),
|
||||
getErrorMessage(error),
|
||||
"error"
|
||||
)
|
||||
},
|
||||
}
|
||||
),
|
||||
@@ -73,13 +85,16 @@ const useDiscountConfigurations = (discount: Discount) => {
|
||||
}
|
||||
if (discount.usage_limit) {
|
||||
conditions.push({
|
||||
title: "Number of redemptions",
|
||||
title: t("configurations-number-of-redemptions", "Number of redemptions"),
|
||||
description: (
|
||||
<CommonDescription text={discount.usage_limit.toLocaleString("en")} />
|
||||
),
|
||||
actions: [
|
||||
{
|
||||
label: "Delete configuration",
|
||||
label: t(
|
||||
"configurations-delete-configuration",
|
||||
"Delete configuration"
|
||||
),
|
||||
icon: <TrashIcon size={20} />,
|
||||
variant: "danger",
|
||||
onClick: async () =>
|
||||
@@ -87,10 +102,21 @@ const useDiscountConfigurations = (discount: Discount) => {
|
||||
{ usage_limit: null },
|
||||
{
|
||||
onSuccess: () => {
|
||||
notification("Success", "Redemption limit removed", "success")
|
||||
notification(
|
||||
t("configurations-success", "Success"),
|
||||
t(
|
||||
"configurations-redemption-limit-removed",
|
||||
"Redemption limit removed"
|
||||
),
|
||||
"success"
|
||||
)
|
||||
},
|
||||
onError: (error) => {
|
||||
notification("Error", getErrorMessage(error), "error")
|
||||
notification(
|
||||
t("configurations-error", "Error"),
|
||||
getErrorMessage(error),
|
||||
"error"
|
||||
)
|
||||
},
|
||||
}
|
||||
),
|
||||
@@ -110,7 +136,7 @@ const useDiscountConfigurations = (discount: Discount) => {
|
||||
),
|
||||
actions: [
|
||||
{
|
||||
label: "Delete setting",
|
||||
label: t("configurations-delete-setting", "Delete setting"),
|
||||
icon: <TrashIcon size={20} />,
|
||||
variant: "danger",
|
||||
onClick: async () =>
|
||||
@@ -119,13 +145,20 @@ const useDiscountConfigurations = (discount: Discount) => {
|
||||
{
|
||||
onSuccess: () => {
|
||||
notification(
|
||||
"Success",
|
||||
"Discount duration removed",
|
||||
t("configurations-success", "Success"),
|
||||
t(
|
||||
"configurations-discount-duration-removed",
|
||||
"Discount duration removed"
|
||||
),
|
||||
"success"
|
||||
)
|
||||
},
|
||||
onError: (error) => {
|
||||
notification("Error", getErrorMessage(error), "error")
|
||||
notification(
|
||||
t("configurations-error", "Error"),
|
||||
getErrorMessage(error),
|
||||
"error"
|
||||
)
|
||||
},
|
||||
}
|
||||
),
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Discount } from "@medusajs/medusa"
|
||||
import { useAdminUpdateDiscount } from "medusa-react"
|
||||
import React, { useEffect } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import DiscountGeneralForm, {
|
||||
DiscountGeneralFormType,
|
||||
} from "../../../../components/forms/discount/discount-general-form"
|
||||
@@ -32,6 +33,7 @@ const EditGeneral: React.FC<EditGeneralProps> = ({
|
||||
open,
|
||||
onClose,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { mutate, isLoading } = useAdminUpdateDiscount(discount.id)
|
||||
const notification = useNotification()
|
||||
|
||||
@@ -56,11 +58,22 @@ const EditGeneral: React.FC<EditGeneralProps> = ({
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
notification("Success", "Discount updated successfully", "success")
|
||||
notification(
|
||||
t("general-success", "Success"),
|
||||
t(
|
||||
"general-discount-updated-successfully",
|
||||
"Discount updated successfully"
|
||||
),
|
||||
"success"
|
||||
)
|
||||
onClose()
|
||||
},
|
||||
onError: (error) => {
|
||||
notification("Error", getErrorMessage(error), "error")
|
||||
notification(
|
||||
t("general-error", "Error"),
|
||||
getErrorMessage(error),
|
||||
"error"
|
||||
)
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -76,13 +89,17 @@ const EditGeneral: React.FC<EditGeneralProps> = ({
|
||||
<Modal open={open} handleClose={onClose}>
|
||||
<Modal.Body>
|
||||
<Modal.Header handleClose={onClose}>
|
||||
<h1 className="inter-xlarge-semibold">Edit general information</h1>
|
||||
<h1 className="inter-xlarge-semibold">
|
||||
{t("general-edit-general-information", "Edit general information")}
|
||||
</h1>
|
||||
</Modal.Header>
|
||||
<form onSubmit={onSubmit}>
|
||||
<Modal.Content>
|
||||
<div className="gap-y-xlarge flex flex-col">
|
||||
<div>
|
||||
<h2 className="inter-base-semibold mb-base">Details</h2>
|
||||
<h2 className="inter-base-semibold mb-base">
|
||||
{t("general-details", "Details")}
|
||||
</h2>
|
||||
<DiscountGeneralForm
|
||||
form={nestedForm(form, "general")}
|
||||
type={discount.rule.type}
|
||||
@@ -90,7 +107,9 @@ const EditGeneral: React.FC<EditGeneralProps> = ({
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="inter-base-semibold mb-base">Metadata</h2>
|
||||
<h2 className="inter-base-semibold mb-base">
|
||||
{t("general-metadata", "Metadata")}
|
||||
</h2>
|
||||
<MetadataForm form={nestedForm(form, "metadata")} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -103,7 +122,7 @@ const EditGeneral: React.FC<EditGeneralProps> = ({
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
>
|
||||
Cancel
|
||||
{t("general-cancel", "Cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
@@ -112,7 +131,7 @@ const EditGeneral: React.FC<EditGeneralProps> = ({
|
||||
disabled={isLoading}
|
||||
loading={isLoading}
|
||||
>
|
||||
Save and close
|
||||
{t("general-save-and-close", "Save and close")}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Footer>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Discount } from "@medusajs/medusa"
|
||||
import { useAdminDeleteDiscount, useAdminUpdateDiscount } from "medusa-react"
|
||||
import React from "react"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import Badge from "../../../../components/fundamentals/badge"
|
||||
import EditIcon from "../../../../components/fundamentals/icons/edit-icon"
|
||||
import TrashIcon from "../../../../components/fundamentals/icons/trash-icon"
|
||||
@@ -14,12 +15,14 @@ import useToggleState from "../../../../hooks/use-toggle-state"
|
||||
import { getErrorMessage } from "../../../../utils/error-messages"
|
||||
import { formatAmountWithSymbol } from "../../../../utils/prices"
|
||||
import EditGeneral from "./edit-general"
|
||||
import { TFunction } from "i18next"
|
||||
|
||||
type GeneralProps = {
|
||||
discount: Discount
|
||||
}
|
||||
|
||||
const General: React.FC<GeneralProps> = ({ discount }) => {
|
||||
const { t } = useTranslation()
|
||||
const dialog = useImperativeDialog()
|
||||
const navigate = useNavigate()
|
||||
const notification = useNotification()
|
||||
@@ -28,17 +31,31 @@ const General: React.FC<GeneralProps> = ({ discount }) => {
|
||||
|
||||
const onDelete = async () => {
|
||||
const shouldDelete = await dialog({
|
||||
heading: "Delete Promotion",
|
||||
text: "Are you sure you want to delete this promotion?",
|
||||
heading: t("general-delete-promotion", "Delete Promotion"),
|
||||
text: t(
|
||||
"general-confirm-delete-promotion",
|
||||
"Are you sure you want to delete this promotion?"
|
||||
),
|
||||
})
|
||||
if (shouldDelete) {
|
||||
deletediscount.mutate(undefined, {
|
||||
onSuccess: () => {
|
||||
notification("Success", "Promotion deleted successfully", "success")
|
||||
notification(
|
||||
t("general-success", "Success"),
|
||||
t(
|
||||
"general-promotion-deleted-successfully",
|
||||
"Promotion deleted successfully"
|
||||
),
|
||||
"success"
|
||||
)
|
||||
navigate("/a/discounts/")
|
||||
},
|
||||
onError: (err) => {
|
||||
notification("Error", getErrorMessage(err), "error")
|
||||
notification(
|
||||
t("general-error", "Error"),
|
||||
getErrorMessage(err),
|
||||
"error"
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -51,15 +68,26 @@ const General: React.FC<GeneralProps> = ({ discount }) => {
|
||||
},
|
||||
{
|
||||
onSuccess: ({ discount: { is_disabled } }) => {
|
||||
const pastTense = !is_disabled ? "published" : "drafted"
|
||||
notification(
|
||||
"Success",
|
||||
`Discount ${pastTense} successfully`,
|
||||
t("general-success", "Success"),
|
||||
!is_disabled
|
||||
? t(
|
||||
"general-discount-published-successfully",
|
||||
"Discount published successfully"
|
||||
)
|
||||
: t(
|
||||
"general-discount-drafted-successfully",
|
||||
"Discount drafted successfully"
|
||||
),
|
||||
"success"
|
||||
)
|
||||
},
|
||||
onError: (err) => {
|
||||
notification("Error", getErrorMessage(err), "error")
|
||||
notification(
|
||||
t("general-error", "Error"),
|
||||
getErrorMessage(err),
|
||||
"error"
|
||||
)
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -69,12 +97,12 @@ const General: React.FC<GeneralProps> = ({ discount }) => {
|
||||
|
||||
const actionables: ActionType[] = [
|
||||
{
|
||||
label: "Edit general information",
|
||||
label: t("general-edit-general-information", "Edit general information"),
|
||||
onClick: open,
|
||||
icon: <EditIcon size={20} />,
|
||||
},
|
||||
{
|
||||
label: "Delete discount",
|
||||
label: t("general-delete-discount", "Delete discount"),
|
||||
onClick: onDelete,
|
||||
variant: "danger",
|
||||
icon: <TrashIcon size={20} />,
|
||||
@@ -95,15 +123,15 @@ const General: React.FC<GeneralProps> = ({ discount }) => {
|
||||
<span>
|
||||
<Badge variant="default">
|
||||
<span className="text-grey-90 inter-small-regular">
|
||||
{"Template discount"}
|
||||
{t("general-template-discount", "Template discount")}
|
||||
</span>
|
||||
</Badge>
|
||||
</span>
|
||||
)}
|
||||
<StatusSelector
|
||||
isDraft={discount?.is_disabled}
|
||||
activeState="Published"
|
||||
draftState="Draft"
|
||||
activeState={t("general-published", "Published")}
|
||||
draftState={t("general-draft", "Draft")}
|
||||
onChange={onStatusChange}
|
||||
/>
|
||||
</div>
|
||||
@@ -111,9 +139,9 @@ const General: React.FC<GeneralProps> = ({ discount }) => {
|
||||
>
|
||||
<div className="flex">
|
||||
<div className="border-grey-20 border-l pl-6">
|
||||
{getPromotionDescription(discount)}
|
||||
{getPromotionDescription(discount, t)}
|
||||
<span className="inter-small-regular text-grey-50">
|
||||
Discount Amount
|
||||
{t("general-discount-amount", "Discount Amount")}
|
||||
</span>
|
||||
</div>
|
||||
<div className="border-grey-20 ml-12 border-l pl-6">
|
||||
@@ -121,7 +149,7 @@ const General: React.FC<GeneralProps> = ({ discount }) => {
|
||||
{discount.regions.length.toLocaleString("en-US")}
|
||||
</h2>
|
||||
<span className="inter-small-regular text-grey-50">
|
||||
Valid Regions
|
||||
{t("general-valid-regions", "Valid Regions")}
|
||||
</span>
|
||||
</div>
|
||||
<div className="border-grey-20 ml-12 border-l pl-6">
|
||||
@@ -129,7 +157,7 @@ const General: React.FC<GeneralProps> = ({ discount }) => {
|
||||
{discount.usage_count.toLocaleString("en-US")}
|
||||
</h2>
|
||||
<span className="inter-small-regular text-grey-50">
|
||||
Total Redemptions
|
||||
{t("general-total-redemptions", "Total Redemptions")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -140,7 +168,7 @@ const General: React.FC<GeneralProps> = ({ discount }) => {
|
||||
)
|
||||
}
|
||||
|
||||
const getPromotionDescription = (discount: Discount) => {
|
||||
const getPromotionDescription = (discount: Discount, t: TFunction) => {
|
||||
switch (discount.rule.type) {
|
||||
case "fixed":
|
||||
return (
|
||||
@@ -167,10 +195,12 @@ const getPromotionDescription = (discount: Discount) => {
|
||||
)
|
||||
case "free_shipping":
|
||||
return (
|
||||
<h2 className="inter-xlarge-regular text-grey-90">{`FREE SHIPPING`}</h2>
|
||||
<h2 className="inter-xlarge-regular text-grey-90">
|
||||
{t("general-free-shipping", "FREE SHIPPING")}
|
||||
</h2>
|
||||
)
|
||||
default:
|
||||
return "Unknown discount type"
|
||||
return t("general-unknown-discount-type", "Unknown discount type")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useAdminDeleteDiscount, useAdminDiscount } from "medusa-react"
|
||||
import { useState } from "react"
|
||||
import { useNavigate, useParams } from "react-router-dom"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import BackButton from "../../../components/atoms/back-button"
|
||||
import Spinner from "../../../components/atoms/spinner"
|
||||
import WidgetContainer from "../../../components/extensions/widget-container"
|
||||
@@ -16,6 +17,7 @@ import Configurations from "./configurations"
|
||||
import General from "./general"
|
||||
|
||||
const Edit = () => {
|
||||
const { t } = useTranslation()
|
||||
const { id } = useParams()
|
||||
const navigate = useNavigate()
|
||||
|
||||
@@ -35,10 +37,18 @@ const Edit = () => {
|
||||
const handleDelete = () => {
|
||||
deleteDiscount.mutate(undefined, {
|
||||
onSuccess: () => {
|
||||
notification("Success", "Discount deleted", "success")
|
||||
notification(
|
||||
t("details-success", "Success"),
|
||||
t("details-discount-deleted", "Discount deleted"),
|
||||
"success"
|
||||
)
|
||||
},
|
||||
onError: (error) => {
|
||||
notification("Error", getErrorMessage(error), "error")
|
||||
notification(
|
||||
t("details-error", "Error"),
|
||||
getErrorMessage(error),
|
||||
"error"
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -72,15 +82,18 @@ const Edit = () => {
|
||||
<DeletePrompt
|
||||
handleClose={() => setShowDelete(!showDelete)}
|
||||
onDelete={async () => handleDelete()}
|
||||
successText="Discount deleted"
|
||||
confirmText="Yes, delete"
|
||||
text="Are you sure you want to delete this discount?"
|
||||
heading="Delete discount"
|
||||
successText={t("details-discount-deleted", "Discount deleted")}
|
||||
confirmText={t("details-yes-delete", "Yes, delete")}
|
||||
text={t(
|
||||
"details-confirm-delete-discount",
|
||||
"Are you sure you want to delete this discount?"
|
||||
)}
|
||||
heading={t("details-delete-discount", "Delete discount")}
|
||||
/>
|
||||
)}
|
||||
|
||||
<BackButton
|
||||
label="Back to Discounts"
|
||||
label={t("details-back-to-discounts", "Back to Discounts")}
|
||||
path="/a/discounts"
|
||||
className="mb-xsmall"
|
||||
/>
|
||||
@@ -109,7 +122,10 @@ const Edit = () => {
|
||||
/>
|
||||
)
|
||||
})}
|
||||
<RawJSON data={discount} title="Raw discount" />
|
||||
<RawJSON
|
||||
data={discount}
|
||||
title={t("details-raw-discount", "Raw discount")}
|
||||
/>
|
||||
</DiscountFormProvider>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState } from "react"
|
||||
import { Route, Routes } from "react-router-dom"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import Fade from "../../components/atoms/fade-wrapper"
|
||||
import Spacer from "../../components/atoms/spacer"
|
||||
import RouteContainer from "../../components/extensions/route-container"
|
||||
@@ -16,11 +17,12 @@ import DiscountForm from "./new/discount-form"
|
||||
import { DiscountFormProvider } from "./new/discount-form/form/discount-form-context"
|
||||
|
||||
const DiscountIndex = () => {
|
||||
const { t } = useTranslation()
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
|
||||
const actionables = [
|
||||
{
|
||||
label: "Add Discount",
|
||||
label: t("discounts-add-discount", "Add Discount"),
|
||||
onClick: () => setIsOpen(true),
|
||||
icon: <PlusIcon size={20} />,
|
||||
},
|
||||
|
||||
+21
-6
@@ -10,6 +10,7 @@ import { AddConditionSelectorProps, ConditionMap } from "../../types"
|
||||
import useConditionModalItems, {
|
||||
ConditionItem,
|
||||
} from "./use-condition-modal-items"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
type AddConditionsModalProps = AddConditionSelectorProps & {
|
||||
isDetails?: boolean
|
||||
@@ -23,6 +24,7 @@ const AddConditionsModal = ({
|
||||
save,
|
||||
isDetails = false,
|
||||
}: AddConditionsModalProps) => {
|
||||
const { t } = useTranslation()
|
||||
const layeredModalContext = useContext(LayeredModalContext)
|
||||
|
||||
const [items, setItems] = useState<ConditionItem[]>(
|
||||
@@ -49,10 +51,20 @@ const AddConditionsModal = ({
|
||||
<LayeredModal context={layeredModalContext} handleClose={onClose}>
|
||||
<Modal.Body className="flex h-[calc(100vh-134px)] flex-col">
|
||||
<Modal.Header handleClose={onClose}>
|
||||
<span className="inter-xlarge-semibold">Add Conditions</span>
|
||||
<span className="inter-xlarge-semibold">
|
||||
{t("discount-form-add-conditions", "Add Conditions")}
|
||||
</span>
|
||||
<span className="text-grey-90 mt-6 flex items-center gap-1 font-semibold">
|
||||
Choose a condition type{" "}
|
||||
<IconTooltip content="You can only add one of each type of condition" />
|
||||
{t(
|
||||
"discount-form-choose-a-condition-type",
|
||||
"Choose a condition type"
|
||||
)}{" "}
|
||||
<IconTooltip
|
||||
content={t(
|
||||
"discount-form-you-can-only-add-one-of-each-type-of-condition",
|
||||
"You can only add one of each type of condition"
|
||||
)}
|
||||
/>
|
||||
</span>
|
||||
</Modal.Header>
|
||||
|
||||
@@ -62,7 +74,10 @@ const AddConditionsModal = ({
|
||||
) : (
|
||||
<div className="flex h-full flex-1 flex-col items-center justify-center">
|
||||
<span className="inter-base-regular text-grey-40">
|
||||
You cannot add any more conditions
|
||||
{t(
|
||||
"discount-form-you-cannot-add-any-more-conditions",
|
||||
"You cannot add any more conditions"
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -76,7 +91,7 @@ const AddConditionsModal = ({
|
||||
size="small"
|
||||
onClick={onClose}
|
||||
>
|
||||
Cancel
|
||||
{t("discount-form-cancel", "Cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
@@ -89,7 +104,7 @@ const AddConditionsModal = ({
|
||||
className="text-small w-32 justify-center"
|
||||
variant="primary"
|
||||
>
|
||||
Save
|
||||
{t("discount-form-save", "Save")}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Footer>
|
||||
|
||||
+5
-3
@@ -3,6 +3,7 @@ import Button from "../../../../../../components/fundamentals/button"
|
||||
import { LayeredModalContext } from "../../../../../../components/molecules/modal/layered-modal"
|
||||
import { DiscountConditionOperator } from "../../../../types"
|
||||
import { useDiscountForm } from "../../form/discount-form-context"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
type AddConditionFooterProps = {
|
||||
type:
|
||||
@@ -22,6 +23,7 @@ const AddConditionFooter: React.FC<AddConditionFooterProps> = ({
|
||||
operator,
|
||||
onClose,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { pop, reset } = useContext(LayeredModalContext)
|
||||
const { updateCondition } = useDiscountForm()
|
||||
|
||||
@@ -35,7 +37,7 @@ const AddConditionFooter: React.FC<AddConditionFooterProps> = ({
|
||||
reset()
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
{t("add-condition-tables-cancel", "Cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
@@ -49,7 +51,7 @@ const AddConditionFooter: React.FC<AddConditionFooterProps> = ({
|
||||
pop()
|
||||
}}
|
||||
>
|
||||
Save and add more
|
||||
{t("add-condition-tables-save-and-add-more", "Save and add more")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
@@ -60,7 +62,7 @@ const AddConditionFooter: React.FC<AddConditionFooterProps> = ({
|
||||
reset()
|
||||
}}
|
||||
>
|
||||
Save and close
|
||||
{t("add-condition-tables-save-and-close", "Save and close")}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
|
||||
+7
-1
@@ -1,5 +1,7 @@
|
||||
import { useAdminCollections } from "medusa-react"
|
||||
import { useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import Modal from "../../../../../../components/molecules/modal"
|
||||
import { SelectableTable } from "../../../../../../components/templates/selectable-table"
|
||||
import useQueryFilters from "../../../../../../hooks/use-query-filters"
|
||||
@@ -20,6 +22,7 @@ import AddConditionFooter from "./add-condition-footer"
|
||||
const AddCollectionConditionSelector = ({
|
||||
onClose,
|
||||
}: AddConditionSelectorProps) => {
|
||||
const { t } = useTranslation()
|
||||
const params = useQueryFilters(defaultQueryProps)
|
||||
|
||||
const { conditions } = useDiscountForm()
|
||||
@@ -62,7 +65,10 @@ const AddCollectionConditionSelector = ({
|
||||
options={{
|
||||
enableSearch: true,
|
||||
immediateSearchFocus: true,
|
||||
searchPlaceholder: "Search by title...",
|
||||
searchPlaceholder: t(
|
||||
"add-condition-tables-search-by-title",
|
||||
"Search by title..."
|
||||
),
|
||||
}}
|
||||
resourceName="Collections"
|
||||
totalCount={count || 0}
|
||||
|
||||
+6
-1
@@ -1,5 +1,6 @@
|
||||
import { useAdminCustomerGroups } from "medusa-react"
|
||||
import { useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import Modal from "../../../../../../components/molecules/modal"
|
||||
import { SelectableTable } from "../../../../../../components/templates/selectable-table"
|
||||
import useQueryFilters from "../../../../../../hooks/use-query-filters"
|
||||
@@ -20,6 +21,7 @@ import AddConditionFooter from "./add-condition-footer"
|
||||
const AddCustomerGroupConditionSelector = ({
|
||||
onClose,
|
||||
}: AddConditionSelectorProps) => {
|
||||
const { t } = useTranslation()
|
||||
const params = useQueryFilters(defaultQueryProps)
|
||||
|
||||
const { conditions } = useDiscountForm()
|
||||
@@ -59,7 +61,10 @@ const AddCustomerGroupConditionSelector = ({
|
||||
options={{
|
||||
enableSearch: true,
|
||||
immediateSearchFocus: true,
|
||||
searchPlaceholder: "Search groups...",
|
||||
searchPlaceholder: t(
|
||||
"add-condition-tables-search-groups",
|
||||
"Search groups..."
|
||||
),
|
||||
}}
|
||||
resourceName="Customer groups"
|
||||
totalCount={count || 0}
|
||||
|
||||
+6
-1
@@ -1,5 +1,6 @@
|
||||
import { useAdminProducts } from "medusa-react"
|
||||
import { useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import Modal from "../../../../../../components/molecules/modal"
|
||||
import { SelectableTable } from "../../../../../../components/templates/selectable-table"
|
||||
import useQueryFilters from "../../../../../../hooks/use-query-filters"
|
||||
@@ -20,6 +21,7 @@ import AddConditionFooter from "./add-condition-footer"
|
||||
const AddProductConditionSelector = ({
|
||||
onClose,
|
||||
}: AddConditionSelectorProps) => {
|
||||
const { t } = useTranslation()
|
||||
const params = useQueryFilters(defaultQueryProps)
|
||||
|
||||
const { conditions } = useDiscountForm()
|
||||
@@ -55,7 +57,10 @@ const AddProductConditionSelector = ({
|
||||
options={{
|
||||
enableSearch: true,
|
||||
immediateSearchFocus: true,
|
||||
searchPlaceholder: "Search products...",
|
||||
searchPlaceholder: t(
|
||||
"add-condition-tables-search-products",
|
||||
"Search products..."
|
||||
),
|
||||
}}
|
||||
resourceName="Products"
|
||||
totalCount={count || 0}
|
||||
|
||||
+6
-1
@@ -1,5 +1,6 @@
|
||||
import { useAdminProductTags } from "medusa-react"
|
||||
import { useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import Modal from "../../../../../../components/molecules/modal"
|
||||
import { SelectableTable } from "../../../../../../components/templates/selectable-table"
|
||||
import useQueryFilters from "../../../../../../hooks/use-query-filters"
|
||||
@@ -14,6 +15,7 @@ import { TagColumns, TagHeader, TagRow } from "../shared/tags"
|
||||
import AddConditionFooter from "./add-condition-footer"
|
||||
|
||||
const AddTagConditionSelector = ({ onClose }: AddConditionSelectorProps) => {
|
||||
const { t } = useTranslation()
|
||||
const params = useQueryFilters(defaultQueryProps)
|
||||
|
||||
const { conditions } = useDiscountForm()
|
||||
@@ -45,7 +47,10 @@ const AddTagConditionSelector = ({ onClose }: AddConditionSelectorProps) => {
|
||||
options={{
|
||||
enableSearch: true,
|
||||
immediateSearchFocus: true,
|
||||
searchPlaceholder: "Search by tag...",
|
||||
searchPlaceholder: t(
|
||||
"add-condition-tables-search-by-tag",
|
||||
"Search by tag..."
|
||||
),
|
||||
}}
|
||||
resourceName="Tags"
|
||||
totalCount={count || 0}
|
||||
|
||||
+6
-1
@@ -1,5 +1,6 @@
|
||||
import { useAdminProductTypes } from "medusa-react"
|
||||
import { useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import Modal from "../../../../../../components/molecules/modal"
|
||||
import { SelectableTable } from "../../../../../../components/templates/selectable-table"
|
||||
import useQueryFilters from "../../../../../../hooks/use-query-filters"
|
||||
@@ -14,6 +15,7 @@ import { TypeRow, TypesHeader, useTypesColumns } from "../shared/types"
|
||||
import AddConditionFooter from "./add-condition-footer"
|
||||
|
||||
const AddTypeConditionSelector = ({ onClose }: AddConditionSelectorProps) => {
|
||||
const { t } = useTranslation()
|
||||
const params = useQueryFilters(defaultQueryProps)
|
||||
|
||||
const { conditions } = useDiscountForm()
|
||||
@@ -48,7 +50,10 @@ const AddTypeConditionSelector = ({ onClose }: AddConditionSelectorProps) => {
|
||||
options={{
|
||||
enableSearch: true,
|
||||
immediateSearchFocus: true,
|
||||
searchPlaceholder: "Search by type...",
|
||||
searchPlaceholder: t(
|
||||
"add-condition-tables-search-by-type",
|
||||
"Search by type..."
|
||||
),
|
||||
}}
|
||||
resourceName="Types"
|
||||
totalCount={count || 0}
|
||||
|
||||
+6
-1
@@ -1,5 +1,6 @@
|
||||
import { useAdminCollections } from "medusa-react"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import Modal from "../../../../../../components/molecules/modal"
|
||||
import { SelectableTable } from "../../../../../../components/templates/selectable-table"
|
||||
import useQueryFilters from "../../../../../../hooks/use-query-filters"
|
||||
@@ -20,6 +21,7 @@ import DetailsConditionFooter from "./details-condition-footer"
|
||||
const DetailsCollectionConditionSelector = ({
|
||||
onClose,
|
||||
}: AddConditionSelectorProps) => {
|
||||
const { t } = useTranslation()
|
||||
const params = useQueryFilters(defaultQueryProps)
|
||||
|
||||
const { conditions } = useConditions()
|
||||
@@ -65,7 +67,10 @@ const DetailsCollectionConditionSelector = ({
|
||||
options={{
|
||||
enableSearch: true,
|
||||
immediateSearchFocus: true,
|
||||
searchPlaceholder: "Search by title...",
|
||||
searchPlaceholder: t(
|
||||
"details-condition-tables-search-by-title",
|
||||
"Search by title..."
|
||||
),
|
||||
}}
|
||||
resourceName="Collections"
|
||||
totalCount={count || 0}
|
||||
|
||||
+6
-1
@@ -1,5 +1,6 @@
|
||||
import { useAdminCustomerGroups } from "medusa-react"
|
||||
import { useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import Modal from "../../../../../../components/molecules/modal"
|
||||
import { SelectableTable } from "../../../../../../components/templates/selectable-table"
|
||||
import useQueryFilters from "../../../../../../hooks/use-query-filters"
|
||||
@@ -20,6 +21,7 @@ import DetailsConditionFooter from "./details-condition-footer"
|
||||
const DetailsCustomerGroupConditionSelector = ({
|
||||
onClose,
|
||||
}: AddConditionSelectorProps) => {
|
||||
const { t } = useTranslation()
|
||||
const params = useQueryFilters(defaultQueryProps)
|
||||
|
||||
const { conditions } = useConditions()
|
||||
@@ -59,7 +61,10 @@ const DetailsCustomerGroupConditionSelector = ({
|
||||
options={{
|
||||
enableSearch: true,
|
||||
immediateSearchFocus: true,
|
||||
searchPlaceholder: "Search groups...",
|
||||
searchPlaceholder: t(
|
||||
"details-condition-tables-search-groups",
|
||||
"Search groups..."
|
||||
),
|
||||
}}
|
||||
resourceName="Customer groups"
|
||||
totalCount={count || 0}
|
||||
|
||||
+5
-3
@@ -1,4 +1,5 @@
|
||||
import React, { useContext } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import Button from "../../../../../../components/fundamentals/button"
|
||||
import { LayeredModalContext } from "../../../../../../components/molecules/modal/layered-modal"
|
||||
import { useConditions } from "../../../../details/conditions/add-condition/conditions-provider"
|
||||
@@ -22,13 +23,14 @@ const DetailsConditionFooter: React.FC<AddConditionFooterProps> = ({
|
||||
operator,
|
||||
onClose,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { pop, reset } = useContext(LayeredModalContext)
|
||||
const { updateCondition, updateAndSave } = useConditions()
|
||||
|
||||
return (
|
||||
<div className="gap-x-xsmall flex w-full justify-end">
|
||||
<Button variant="ghost" size="small" onClick={onClose}>
|
||||
Cancel
|
||||
{t("details-condition-tables-cancel", "Cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
@@ -42,7 +44,7 @@ const DetailsConditionFooter: React.FC<AddConditionFooterProps> = ({
|
||||
pop()
|
||||
}}
|
||||
>
|
||||
Save and add more
|
||||
{t("details-condition-tables-save-and-add-more", "Save and add more")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
@@ -53,7 +55,7 @@ const DetailsConditionFooter: React.FC<AddConditionFooterProps> = ({
|
||||
reset()
|
||||
}}
|
||||
>
|
||||
Save and close
|
||||
{t("details-condition-tables-save-and-close", "Save and close")}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
|
||||
+6
-1
@@ -1,5 +1,6 @@
|
||||
import { useAdminProducts } from "medusa-react"
|
||||
import { useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import Modal from "../../../../../../components/molecules/modal"
|
||||
import { SelectableTable } from "../../../../../../components/templates/selectable-table"
|
||||
import useQueryFilters from "../../../../../../hooks/use-query-filters"
|
||||
@@ -20,6 +21,7 @@ import DetailsConditionFooter from "./details-condition-footer"
|
||||
const DetailsProductConditionSelector = ({
|
||||
onClose,
|
||||
}: AddConditionSelectorProps) => {
|
||||
const { t } = useTranslation()
|
||||
const params = useQueryFilters(defaultQueryProps)
|
||||
|
||||
const { conditions } = useConditions()
|
||||
@@ -55,7 +57,10 @@ const DetailsProductConditionSelector = ({
|
||||
options={{
|
||||
enableSearch: true,
|
||||
immediateSearchFocus: true,
|
||||
searchPlaceholder: "Search products...",
|
||||
searchPlaceholder: t(
|
||||
"details-condition-tables-search-products",
|
||||
"Search products..."
|
||||
),
|
||||
}}
|
||||
resourceName="Products"
|
||||
totalCount={count || 0}
|
||||
|
||||
+6
-1
@@ -1,5 +1,6 @@
|
||||
import { useAdminProductTags } from "medusa-react"
|
||||
import { useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import Modal from "../../../../../../components/molecules/modal"
|
||||
import { SelectableTable } from "../../../../../../components/templates/selectable-table"
|
||||
import useQueryFilters from "../../../../../../hooks/use-query-filters"
|
||||
@@ -16,6 +17,7 @@ import DetailsConditionFooter from "./details-condition-footer"
|
||||
const DetailsTagConditionSelector = ({
|
||||
onClose,
|
||||
}: AddConditionSelectorProps) => {
|
||||
const { t } = useTranslation()
|
||||
const params = useQueryFilters(defaultQueryProps)
|
||||
|
||||
const { conditions } = useConditions()
|
||||
@@ -47,7 +49,10 @@ const DetailsTagConditionSelector = ({
|
||||
options={{
|
||||
enableSearch: true,
|
||||
immediateSearchFocus: true,
|
||||
searchPlaceholder: "Search by tag...",
|
||||
searchPlaceholder: t(
|
||||
"details-condition-tables-search-by-tag",
|
||||
"Search by tag..."
|
||||
),
|
||||
}}
|
||||
resourceName="Tags"
|
||||
totalCount={count || 0}
|
||||
|
||||
+6
-1
@@ -1,5 +1,6 @@
|
||||
import { useAdminProductTypes } from "medusa-react"
|
||||
import { useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import Modal from "../../../../../../components/molecules/modal"
|
||||
import { SelectableTable } from "../../../../../../components/templates/selectable-table"
|
||||
import useQueryFilters from "../../../../../../hooks/use-query-filters"
|
||||
@@ -16,6 +17,7 @@ import DetailsConditionFooter from "./details-condition-footer"
|
||||
const DetailsTypeConditionSelector = ({
|
||||
onClose,
|
||||
}: AddConditionSelectorProps) => {
|
||||
const { t } = useTranslation()
|
||||
const params = useQueryFilters(defaultQueryProps)
|
||||
|
||||
const { conditions } = useConditions()
|
||||
@@ -50,7 +52,10 @@ const DetailsTypeConditionSelector = ({
|
||||
options={{
|
||||
enableSearch: true,
|
||||
immediateSearchFocus: true,
|
||||
searchPlaceholder: "Search by type...",
|
||||
searchPlaceholder: t(
|
||||
"details-condition-tables-search-by-type",
|
||||
"Search by type..."
|
||||
),
|
||||
}}
|
||||
resourceName="Types"
|
||||
totalCount={count || 0}
|
||||
|
||||
+12
-2
@@ -1,5 +1,6 @@
|
||||
import { useAdminCollections } from "medusa-react"
|
||||
import { useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import Modal from "../../../../../../components/molecules/modal"
|
||||
import { SelectableTable } from "../../../../../../components/templates/selectable-table"
|
||||
import useQueryFilters from "../../../../../../hooks/use-query-filters"
|
||||
@@ -15,6 +16,7 @@ import ConditionOperator from "../shared/condition-operator"
|
||||
import EditConditionFooter from "./edit-condition-footer"
|
||||
|
||||
const EditCollectionConditionSelector = ({ onClose }) => {
|
||||
const { t } = useTranslation()
|
||||
const params = useQueryFilters(defaultQueryProps)
|
||||
const { conditions } = useDiscountForm()
|
||||
const [items, setItems] = useState(
|
||||
@@ -55,8 +57,16 @@ const EditCollectionConditionSelector = ({ onClose }) => {
|
||||
options={{
|
||||
enableSearch: true,
|
||||
immediateSearchFocus: true,
|
||||
searchPlaceholder: "Search by title...",
|
||||
filters: [{ title: "Title", name: "title" }],
|
||||
searchPlaceholder: t(
|
||||
"edit-condition-tables-search-by-title",
|
||||
"Search by title..."
|
||||
),
|
||||
filters: [
|
||||
{
|
||||
title: t("edit-condition-tables-title", "Title"),
|
||||
name: "title",
|
||||
},
|
||||
],
|
||||
}}
|
||||
resourceName="Collections"
|
||||
totalCount={count || 0}
|
||||
|
||||
+6
-1
@@ -1,5 +1,6 @@
|
||||
import { useAdminCustomerGroups } from "medusa-react"
|
||||
import { useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import Modal from "../../../../../../components/molecules/modal"
|
||||
import { SelectableTable } from "../../../../../../components/templates/selectable-table"
|
||||
import useQueryFilters from "../../../../../../hooks/use-query-filters"
|
||||
@@ -15,6 +16,7 @@ import {
|
||||
import EditConditionFooter from "./edit-condition-footer"
|
||||
|
||||
const EditCustomerGroupConditionSelector = ({ onClose }) => {
|
||||
const { t } = useTranslation()
|
||||
const params = useQueryFilters(defaultQueryProps)
|
||||
const { conditions } = useDiscountForm()
|
||||
const [items, setItems] = useState(conditions.customer_groups?.items || [])
|
||||
@@ -52,7 +54,10 @@ const EditCustomerGroupConditionSelector = ({ onClose }) => {
|
||||
options={{
|
||||
enableSearch: true,
|
||||
immediateSearchFocus: true,
|
||||
searchPlaceholder: "Search groups...",
|
||||
searchPlaceholder: t(
|
||||
"edit-condition-tables-search-groups",
|
||||
"Search groups..."
|
||||
),
|
||||
}}
|
||||
resourceName="Customer groups"
|
||||
totalCount={count || 0}
|
||||
|
||||
+5
-3
@@ -1,4 +1,5 @@
|
||||
import React from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import Button from "../../../../../../components/fundamentals/button"
|
||||
import { DiscountConditionOperator } from "../../../../types"
|
||||
import { useDiscountForm } from "../../form/discount-form-context"
|
||||
@@ -21,11 +22,12 @@ const EditConditionFooter: React.FC<EditConditionFooterProps> = ({
|
||||
operator,
|
||||
onClose,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { updateCondition } = useDiscountForm()
|
||||
return (
|
||||
<div className="gap-x-xsmall flex w-full items-center justify-end">
|
||||
<Button variant="secondary" size="small" onClick={onClose}>
|
||||
Cancel
|
||||
{t("edit-condition-tables-cancel", "Cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="danger"
|
||||
@@ -39,7 +41,7 @@ const EditConditionFooter: React.FC<EditConditionFooterProps> = ({
|
||||
onClose()
|
||||
}}
|
||||
>
|
||||
Delete condition
|
||||
{t("edit-condition-tables-delete-condition", "Delete condition")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
@@ -54,7 +56,7 @@ const EditConditionFooter: React.FC<EditConditionFooterProps> = ({
|
||||
}}
|
||||
className="min-w-[128px]"
|
||||
>
|
||||
Save
|
||||
{t("edit-condition-tables-save", "Save")}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
|
||||
+6
-1
@@ -1,6 +1,7 @@
|
||||
import { Product } from "@medusajs/medusa"
|
||||
import { useAdminProducts } from "medusa-react"
|
||||
import { useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import Modal from "../../../../../../components/molecules/modal"
|
||||
import { SelectableTable } from "../../../../../../components/templates/selectable-table"
|
||||
import useQueryFilters from "../../../../../../hooks/use-query-filters"
|
||||
@@ -16,6 +17,7 @@ import {
|
||||
import EditConditionFooter from "./edit-condition-footer"
|
||||
|
||||
const EditProductConditionSelector = ({ onClose }) => {
|
||||
const { t } = useTranslation()
|
||||
const params = useQueryFilters(defaultQueryProps)
|
||||
const { conditions } = useDiscountForm()
|
||||
const [items, setItems] = useState(conditions.products?.items || [])
|
||||
@@ -49,7 +51,10 @@ const EditProductConditionSelector = ({ onClose }) => {
|
||||
options={{
|
||||
enableSearch: true,
|
||||
immediateSearchFocus: true,
|
||||
searchPlaceholder: "Search products...",
|
||||
searchPlaceholder: t(
|
||||
"edit-condition-tables-search-products",
|
||||
"Search products..."
|
||||
),
|
||||
}}
|
||||
resourceName="Products"
|
||||
totalCount={count || 0}
|
||||
|
||||
+6
-1
@@ -1,5 +1,6 @@
|
||||
import { useAdminProductTags } from "medusa-react"
|
||||
import { useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import Spinner from "../../../../../../components/atoms/spinner"
|
||||
import Modal from "../../../../../../components/molecules/modal"
|
||||
import { SelectableTable } from "../../../../../../components/templates/selectable-table"
|
||||
@@ -12,6 +13,7 @@ import { TagColumns, TagHeader, TagRow } from "../shared/tags"
|
||||
import EditConditionFooter from "./edit-condition-footer"
|
||||
|
||||
const EditTagConditionSelector = ({ onClose }) => {
|
||||
const { t } = useTranslation()
|
||||
const params = useQueryFilters(defaultQueryProps)
|
||||
const { conditions } = useDiscountForm()
|
||||
|
||||
@@ -46,7 +48,10 @@ const EditTagConditionSelector = ({ onClose }) => {
|
||||
options={{
|
||||
enableSearch: true,
|
||||
immediateSearchFocus: true,
|
||||
searchPlaceholder: "Search by tag...",
|
||||
searchPlaceholder: t(
|
||||
"edit-condition-tables-search-by-tag",
|
||||
"Search by tag..."
|
||||
),
|
||||
}}
|
||||
resourceName="Tags"
|
||||
totalCount={count || 0}
|
||||
|
||||
+6
-1
@@ -1,5 +1,6 @@
|
||||
import { useAdminProductTypes } from "medusa-react"
|
||||
import { useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import Modal from "../../../../../../components/molecules/modal"
|
||||
import { SelectableTable } from "../../../../../../components/templates/selectable-table"
|
||||
import useQueryFilters from "../../../../../../hooks/use-query-filters"
|
||||
@@ -11,6 +12,7 @@ import { TypeRow, TypesHeader, useTypesColumns } from "../shared/types"
|
||||
import EditConditionFooter from "./edit-condition-footer"
|
||||
|
||||
const EditTypeConditionSelector = ({ onClose }) => {
|
||||
const { t } = useTranslation()
|
||||
const params = useQueryFilters(defaultQueryProps)
|
||||
const { conditions } = useDiscountForm()
|
||||
const [items, setItems] = useState(conditions.product_types?.items || [])
|
||||
@@ -43,7 +45,10 @@ const EditTypeConditionSelector = ({ onClose }) => {
|
||||
options={{
|
||||
enableSearch: true,
|
||||
immediateSearchFocus: true,
|
||||
searchPlaceholder: "Search by type...",
|
||||
searchPlaceholder: t(
|
||||
"edit-condition-tables-search-by-type",
|
||||
"Search by type..."
|
||||
),
|
||||
}}
|
||||
resourceName="Types"
|
||||
totalCount={count || 0}
|
||||
|
||||
+4
-2
@@ -1,6 +1,7 @@
|
||||
import { ProductCollection } from "@medusajs/medusa"
|
||||
import { useMemo } from "react"
|
||||
import { Column, HeaderGroup, Row } from "react-table"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import SortingIcon from "../../../../../../components/fundamentals/icons/sorting-icon"
|
||||
import Table from "../../../../../../components/molecules/table"
|
||||
|
||||
@@ -38,12 +39,13 @@ export const CollectionsHeader = ({
|
||||
}
|
||||
|
||||
export const useCollectionColumns = () => {
|
||||
const { t } = useTranslation()
|
||||
const columns = useMemo<Column<ProductCollection>[]>(() => {
|
||||
return [
|
||||
{
|
||||
Header: () => (
|
||||
<div className="flex min-w-[546px] items-center gap-1">
|
||||
Title <SortingIcon size={16} />
|
||||
{t("shared-title", "Title")} <SortingIcon size={16} />
|
||||
</div>
|
||||
),
|
||||
accessor: "title",
|
||||
@@ -54,7 +56,7 @@ export const useCollectionColumns = () => {
|
||||
{
|
||||
Header: () => (
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
Products <SortingIcon size={16} />
|
||||
{t("shared-products", "Products")} <SortingIcon size={16} />
|
||||
</div>
|
||||
),
|
||||
id: "products",
|
||||
|
||||
+10
-2
@@ -1,4 +1,5 @@
|
||||
import React from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import RadioGroup from "../../../../../../components/organisms/radio-group"
|
||||
import { DiscountConditionOperator } from "../../../../types"
|
||||
|
||||
@@ -11,6 +12,7 @@ const ConditionOperator: React.FC<ConditionOperatorProps> = ({
|
||||
value,
|
||||
onChange,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<RadioGroup.Root
|
||||
value={value}
|
||||
@@ -21,13 +23,19 @@ const ConditionOperator: React.FC<ConditionOperatorProps> = ({
|
||||
className="w-full"
|
||||
label="In"
|
||||
value={DiscountConditionOperator.IN}
|
||||
description="Applies to the selected items."
|
||||
description={t(
|
||||
"shared-applies-to-the-selected-items",
|
||||
"Applies to the selected items."
|
||||
)}
|
||||
/>
|
||||
<RadioGroup.Item
|
||||
className="w-full"
|
||||
label="Not in"
|
||||
value={DiscountConditionOperator.NOT_IN}
|
||||
description="Applies to all items except the selected items."
|
||||
description={t(
|
||||
"shared-applies-to-all-items-except-the-selected-items",
|
||||
"Applies to all items except the selected items."
|
||||
)}
|
||||
/>
|
||||
</RadioGroup.Root>
|
||||
)
|
||||
|
||||
+4
-2
@@ -1,6 +1,7 @@
|
||||
import { CustomerGroup } from "@medusajs/medusa"
|
||||
import { useMemo } from "react"
|
||||
import { Column, HeaderGroup, Row } from "react-table"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import SortingIcon from "../../../../../../components/fundamentals/icons/sorting-icon"
|
||||
import Table from "../../../../../../components/molecules/table"
|
||||
|
||||
@@ -38,12 +39,13 @@ export const CustomerGroupsRow = ({ row }: { row: Row<CustomerGroup> }) => {
|
||||
}
|
||||
|
||||
export const useGroupColumns = () => {
|
||||
const { t } = useTranslation()
|
||||
const columns = useMemo<Column<CustomerGroup>[]>(() => {
|
||||
return [
|
||||
{
|
||||
Header: () => (
|
||||
<div className="flex min-w-[540px] items-center gap-1">
|
||||
Title <SortingIcon size={16} />
|
||||
{t("shared-title", "Title")} <SortingIcon size={16} />
|
||||
</div>
|
||||
),
|
||||
accessor: "name",
|
||||
@@ -51,7 +53,7 @@ export const useGroupColumns = () => {
|
||||
{
|
||||
Header: () => (
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
Members <SortingIcon size={16} />
|
||||
{t("shared-members", "Members")} <SortingIcon size={16} />
|
||||
</div>
|
||||
),
|
||||
id: "members",
|
||||
|
||||
+5
-3
@@ -1,6 +1,7 @@
|
||||
import { Product } from "@medusajs/medusa"
|
||||
import { useMemo } from "react"
|
||||
import { Column, HeaderGroup, Row } from "react-table"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import SortingIcon from "../../../../../../components/fundamentals/icons/sorting-icon"
|
||||
import ImagePlaceholder from "../../../../../../components/fundamentals/image-placeholder"
|
||||
import StatusIndicator from "../../../../../../components/fundamentals/status-indicator"
|
||||
@@ -54,12 +55,13 @@ export const ProductsHeader = ({
|
||||
}
|
||||
|
||||
export const useProductColumns = () => {
|
||||
const { t } = useTranslation()
|
||||
const columns = useMemo<Column<Product>[]>(() => {
|
||||
return [
|
||||
{
|
||||
Header: () => (
|
||||
<div className="flex min-w-[443px] items-center gap-1">
|
||||
Title <SortingIcon size={16} />
|
||||
{t("shared-title", "Title")} <SortingIcon size={16} />
|
||||
</div>
|
||||
),
|
||||
accessor: "title",
|
||||
@@ -86,7 +88,7 @@ export const useProductColumns = () => {
|
||||
{
|
||||
Header: () => (
|
||||
<div className="flex items-center gap-1">
|
||||
Status <SortingIcon size={16} />
|
||||
{t("shared-status", "Status")} <SortingIcon size={16} />
|
||||
</div>
|
||||
),
|
||||
accessor: "status",
|
||||
@@ -102,7 +104,7 @@ export const useProductColumns = () => {
|
||||
{
|
||||
Header: () => (
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
Variants <SortingIcon size={16} />
|
||||
{t("shared-variants", "Variants")} <SortingIcon size={16} />
|
||||
</div>
|
||||
),
|
||||
id: "variants",
|
||||
|
||||
+8
-1
@@ -1,5 +1,6 @@
|
||||
import { ProductTag } from "@medusajs/medusa"
|
||||
import { Column, HeaderGroup, Row } from "react-table"
|
||||
import { Translation } from "react-i18next"
|
||||
import SortingIcon from "../../../../../../components/fundamentals/icons/sorting-icon"
|
||||
import Table from "../../../../../../components/molecules/table"
|
||||
|
||||
@@ -7,7 +8,13 @@ export const TagColumns: Column<ProductTag>[] = [
|
||||
{
|
||||
Header: () => (
|
||||
<div className="flex items-center gap-1">
|
||||
Tag <SortingIcon size={16} />
|
||||
<Translation>
|
||||
{(t) => (
|
||||
<>
|
||||
{t("shared-tag", "Tag")} <SortingIcon size={16} />
|
||||
</>
|
||||
)}
|
||||
</Translation>
|
||||
</div>
|
||||
),
|
||||
accessor: "value",
|
||||
|
||||
+8
-3
@@ -1,6 +1,7 @@
|
||||
import { ProductType } from "@medusajs/medusa"
|
||||
import { useMemo } from "react"
|
||||
import { Column, HeaderGroup, Row } from "react-table"
|
||||
import { Translation } from "react-i18next"
|
||||
import SortingIcon from "../../../../../../components/fundamentals/icons/sorting-icon"
|
||||
import Table from "../../../../../../components/molecules/table"
|
||||
|
||||
@@ -42,9 +43,13 @@ export const useTypesColumns = () => {
|
||||
return [
|
||||
{
|
||||
Header: () => (
|
||||
<div className="flex min-w-[626px] items-center gap-1">
|
||||
Type <SortingIcon size={16} />
|
||||
</div>
|
||||
<Translation>
|
||||
{(t) => (
|
||||
<div className="flex min-w-[626px] items-center gap-1">
|
||||
{t("shared-type", "Type")} <SortingIcon size={16} />
|
||||
</div>
|
||||
)}
|
||||
</Translation>
|
||||
),
|
||||
accessor: "value",
|
||||
Cell: ({ row: { original } }) => {
|
||||
|
||||
+7
-1
@@ -1,4 +1,5 @@
|
||||
import React from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import Modal from "../../../../components/molecules/modal"
|
||||
import { DiscountConditionType } from "../../types"
|
||||
import { getTitle } from "../../utils"
|
||||
@@ -17,10 +18,15 @@ const EditConditionsModal: React.FC<EditConditionsModalProps> = ({
|
||||
onClose,
|
||||
view,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<Modal open handleClose={onClose}>
|
||||
<Modal.Header handleClose={onClose}>
|
||||
<h1 className="inter-xlarge-semibold">Edit {getTitle(view)}</h1>
|
||||
<h1 className="inter-xlarge-semibold">
|
||||
{t("edit-conditions-modal-title", "Edit {{title}}", {
|
||||
title: getTitle(view, t),
|
||||
})}
|
||||
</h1>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<Content view={view} onClose={onClose} />
|
||||
|
||||
+10
-1
@@ -5,6 +5,7 @@ import {
|
||||
useFormContext,
|
||||
UseFormReturn,
|
||||
} from "react-hook-form"
|
||||
|
||||
import {
|
||||
AllocationType,
|
||||
ConditionMap,
|
||||
@@ -14,6 +15,7 @@ import {
|
||||
UpdateConditionProps,
|
||||
} from "../../../types"
|
||||
import { DiscountFormValues } from "./mappers"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
type DiscountFormProviderProps = {
|
||||
children?: React.ReactNode
|
||||
@@ -208,8 +210,15 @@ const DiscountFormContext = React.createContext<{
|
||||
export const useDiscountForm = () => {
|
||||
const context = React.useContext(DiscountFormContext)
|
||||
const form = useFormContext<DiscountFormValues>()
|
||||
const { t } = useTranslation()
|
||||
|
||||
if (!context) {
|
||||
throw new Error("useDiscountForm must be a child of DiscountFormContext")
|
||||
throw new Error(
|
||||
t(
|
||||
"form-use-discount-form-must-be-a-child-of-discount-form-context",
|
||||
"useDiscountForm must be a child of DiscountFormContext"
|
||||
)
|
||||
)
|
||||
}
|
||||
return { ...form, ...context }
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useWatch } from "react-hook-form"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import MetadataForm from "../../../../components/forms/general/metadata-form"
|
||||
import Button from "../../../../components/fundamentals/button"
|
||||
import CrossIcon from "../../../../components/fundamentals/icons/cross-icon"
|
||||
@@ -26,6 +27,7 @@ const DiscountForm = ({ closeForm }: DiscountFormProps) => {
|
||||
const navigate = useNavigate()
|
||||
const notification = useNotification()
|
||||
const { handleSubmit, handleReset, control, form } = useDiscountForm()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { onSaveAsActive, onSaveAsInactive } = useFormActions()
|
||||
|
||||
@@ -45,7 +47,11 @@ const DiscountForm = ({ closeForm }: DiscountFormProps) => {
|
||||
handleReset()
|
||||
})
|
||||
.catch((error) => {
|
||||
notification("Error", getErrorMessage(error), "error")
|
||||
notification(
|
||||
t("discount-form-error", "Error"),
|
||||
getErrorMessage(error),
|
||||
"error"
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -55,7 +61,11 @@ const DiscountForm = ({ closeForm }: DiscountFormProps) => {
|
||||
closeFormModal()
|
||||
handleReset()
|
||||
} catch (error) {
|
||||
notification("Error", getErrorMessage(error), "error")
|
||||
notification(
|
||||
t("discount-form-error", "Error"),
|
||||
getErrorMessage(error),
|
||||
"error"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +93,7 @@ const DiscountForm = ({ closeForm }: DiscountFormProps) => {
|
||||
variant="ghost"
|
||||
className="rounded-rounded border"
|
||||
>
|
||||
Save as draft
|
||||
{t("discount-form-save-as-draft", "Save as draft")}
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
@@ -91,7 +101,7 @@ const DiscountForm = ({ closeForm }: DiscountFormProps) => {
|
||||
onClick={handleSubmit(submitCTA)}
|
||||
className="rounded-rounded"
|
||||
>
|
||||
Publish discount
|
||||
{t("discount-form-publish-discount", "Publish discount")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -99,7 +109,9 @@ const DiscountForm = ({ closeForm }: DiscountFormProps) => {
|
||||
<FocusModal.Main>
|
||||
<div className="mb-[25%] flex justify-center">
|
||||
<div className="w-full max-w-[700px] pt-16">
|
||||
<h1 className="inter-xlarge-semibold">Create new discount</h1>
|
||||
<h1 className="inter-xlarge-semibold">
|
||||
{t("discount-form-create-new-discount", "Create new discount")}
|
||||
</h1>
|
||||
<Accordion
|
||||
className="text-grey-90 pt-7"
|
||||
defaultValue={["promotion-type"]}
|
||||
@@ -107,23 +119,27 @@ const DiscountForm = ({ closeForm }: DiscountFormProps) => {
|
||||
>
|
||||
<Accordion.Item
|
||||
forceMountContent
|
||||
title="Discount type"
|
||||
title={t("discount-form-discount-type", "Discount type")}
|
||||
required
|
||||
tooltip="Select a discount type"
|
||||
tooltip={t(
|
||||
"discount-form-select-a-discount-type",
|
||||
"Select a discount type"
|
||||
)}
|
||||
value="promotion-type"
|
||||
>
|
||||
<DiscountType />
|
||||
{discountType === DiscountRuleType.FIXED && (
|
||||
<div className="mt-xlarge">
|
||||
<h3 className="inter-base-semibold">
|
||||
Allocation<span className="text-rose-50">*</span>
|
||||
{t("discount-form-allocation", "Allocation")}
|
||||
<span className="text-rose-50">*</span>
|
||||
</h3>
|
||||
<DiscountAllocation />
|
||||
</div>
|
||||
)}
|
||||
</Accordion.Item>
|
||||
<Accordion.Item
|
||||
title="General"
|
||||
title={t("discount-form-general", "General")}
|
||||
required
|
||||
value="general"
|
||||
forceMountContent
|
||||
@@ -132,24 +148,36 @@ const DiscountForm = ({ closeForm }: DiscountFormProps) => {
|
||||
</Accordion.Item>
|
||||
<Accordion.Item
|
||||
forceMountContent
|
||||
title="Configuration"
|
||||
title={t("discount-form-configuration", "Configuration")}
|
||||
value="configuration"
|
||||
description="Discount code applies from you hit the publish button and forever if left untouched."
|
||||
description={t(
|
||||
"discount-form-discount-code-application-disclaimer",
|
||||
"Discount code applies from when you hit the publish button and forever if left untouched."
|
||||
)}
|
||||
>
|
||||
<Configuration />
|
||||
</Accordion.Item>
|
||||
<Accordion.Item
|
||||
forceMountContent
|
||||
title="Conditions"
|
||||
description="Discount code apply to all products if left untouched."
|
||||
title={t("discount-form-conditions", "Conditions")}
|
||||
description={t(
|
||||
"discount-form-discount-code-apply-to-all-products-if-left-untouched",
|
||||
"Discount code apply to all products if left untouched."
|
||||
)}
|
||||
value="conditions"
|
||||
tooltip="Add conditions to your Discount"
|
||||
tooltip={t(
|
||||
"discount-form-add-conditions-to-your-discount",
|
||||
"Add conditions to your Discount"
|
||||
)}
|
||||
>
|
||||
<DiscountNewConditions />
|
||||
</Accordion.Item>
|
||||
<Accordion.Item
|
||||
title="Metadata"
|
||||
subtitle="Metadata allows you to add additional information to your discount."
|
||||
title={t("discount-form-metadata", "Metadata")}
|
||||
subtitle={t(
|
||||
"discount-form-metadata-usage-description",
|
||||
"Metadata allows you to add additional information to your discount."
|
||||
)}
|
||||
value="metadata"
|
||||
forceMountContent
|
||||
>
|
||||
|
||||
+17
-10
@@ -11,6 +11,8 @@ import {
|
||||
} from "../../../../types"
|
||||
import EditConditionsModal from "../../edit-conditions-modal"
|
||||
import { useDiscountForm } from "../../form/discount-form-context"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { TFunction } from "i18next"
|
||||
|
||||
type ConditionItemProps<Type extends DiscountConditionType> = {
|
||||
index: number
|
||||
@@ -29,6 +31,7 @@ const ConditionItem = <Type extends DiscountConditionType>({
|
||||
setCondition,
|
||||
items,
|
||||
}: ConditionItemProps<Type>) => {
|
||||
const { t } = useTranslation()
|
||||
const queryParams = useMemo(() => {
|
||||
switch (type) {
|
||||
case DiscountConditionType.PRODUCTS:
|
||||
@@ -173,7 +176,7 @@ const ConditionItem = <Type extends DiscountConditionType>({
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="flex w-full flex-1 flex-col justify-center truncate">
|
||||
<div className="inter-small-semibold">{getTitle(type)}</div>
|
||||
<div className="inter-small-semibold">{getTitle(type, t)}</div>
|
||||
<div className="inter-small-regular gap-x-xsmall flex w-full flex-1 items-center">
|
||||
<div className="gap-x-2xsmall text-grey-50 inter-small-regular flex w-full flex-1 items-center">
|
||||
{visibleItems.map((item, i) => {
|
||||
@@ -186,7 +189,11 @@ const ConditionItem = <Type extends DiscountConditionType>({
|
||||
)
|
||||
})}
|
||||
{remainder > 0 && (
|
||||
<span className="text-grey-40 ml-2">+{remainder} more</span>
|
||||
<span className="text-grey-40 ml-2">
|
||||
{t("condition-item-remainder-more", "+{{remainder}} more", {
|
||||
remainder,
|
||||
})}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -197,12 +204,12 @@ const ConditionItem = <Type extends DiscountConditionType>({
|
||||
forceDropdown
|
||||
actions={[
|
||||
{
|
||||
label: "Edit",
|
||||
label: t("conditions-edit", "Edit"),
|
||||
onClick: () => setShowEdit(true),
|
||||
icon: <EditIcon size={16} />,
|
||||
},
|
||||
{
|
||||
label: "Delete condition",
|
||||
label: t("conditions-delete-condition", "Delete condition"),
|
||||
onClick: () =>
|
||||
updateCondition({
|
||||
type,
|
||||
@@ -223,18 +230,18 @@ const ConditionItem = <Type extends DiscountConditionType>({
|
||||
)
|
||||
}
|
||||
|
||||
const getTitle = (type: DiscountConditionType) => {
|
||||
const getTitle = (type: DiscountConditionType, t: TFunction) => {
|
||||
switch (type) {
|
||||
case DiscountConditionType.PRODUCTS:
|
||||
return "Product"
|
||||
return t("conditions-product", "Product")
|
||||
case DiscountConditionType.PRODUCT_COLLECTIONS:
|
||||
return "Collection"
|
||||
return t("conditions-collection", "Collection")
|
||||
case DiscountConditionType.PRODUCT_TAGS:
|
||||
return "Tag"
|
||||
return t("conditions-tag", "Tag")
|
||||
case DiscountConditionType.CUSTOMER_GROUPS:
|
||||
return "Customer group"
|
||||
return t("conditions-customer-group", "Customer group")
|
||||
case DiscountConditionType.PRODUCT_TYPES:
|
||||
return "Type"
|
||||
return t("conditions-type", "Type")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -1,5 +1,6 @@
|
||||
import { Discount } from "@medusajs/medusa"
|
||||
import React, { useEffect, useMemo, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import Button from "../../../../../../components/fundamentals/button"
|
||||
import PlusIcon from "../../../../../../components/fundamentals/icons/plus-icon"
|
||||
import AddConditionsModal from "../../add-conditions-modal"
|
||||
@@ -13,6 +14,7 @@ type DiscountNewConditionsProps = {
|
||||
const DiscountNewConditions: React.FC<DiscountNewConditionsProps> = ({
|
||||
discount,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { setConditions, conditions } = useDiscountForm()
|
||||
const [showConditionsModal, setShowConditionsModal] = useState(false)
|
||||
|
||||
@@ -68,7 +70,7 @@ const DiscountNewConditions: React.FC<DiscountNewConditionsProps> = ({
|
||||
className="rounded-rounded mt-4 w-full border p-2"
|
||||
>
|
||||
<PlusIcon size={18} />
|
||||
<span>Add Condition</span>
|
||||
<span>{t("conditions-add-condition", "Add Condition")}</span>
|
||||
</Button>
|
||||
)}
|
||||
{showConditionsModal && (
|
||||
|
||||
+55
-17
@@ -2,6 +2,7 @@ import { Discount } from "@medusajs/medusa"
|
||||
import clsx from "clsx"
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { Controller } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import DatePicker from "../../../../../components/atoms/date-picker/date-picker"
|
||||
import TimePicker from "../../../../../components/atoms/date-picker/time-picker"
|
||||
import Switch from "../../../../../components/atoms/switch"
|
||||
@@ -45,6 +46,7 @@ const Settings: React.FC<SettingsProps> = ({ promotion, isEdit = false }) => {
|
||||
hasExpiryDate,
|
||||
handleConfigurationChanged,
|
||||
} = useDiscountForm()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [openItems, setOpenItems] = React.useState<string[]>(
|
||||
isEdit && promotion
|
||||
@@ -78,9 +80,15 @@ const Settings: React.FC<SettingsProps> = ({ promotion, isEdit = false }) => {
|
||||
headingSize="medium"
|
||||
forceMountContent
|
||||
className="border-b-0"
|
||||
title="Start date"
|
||||
subtitle="Schedule the discount to activate in the future."
|
||||
tooltip="If you want to schedule the discount to activate in the future, you can set a start date here, otherwise the discount will be active immediately."
|
||||
title={t("sections-start-date", "Start date")}
|
||||
subtitle={t(
|
||||
"sections-schedule-the-discount-to-activate-in-the-future",
|
||||
"Schedule the discount to activate in the future."
|
||||
)}
|
||||
tooltip={t(
|
||||
"sections-select-discount-start-date",
|
||||
"If you want to schedule the discount to activate in the future, you can set a start date here, otherwise the discount will be active immediately."
|
||||
)}
|
||||
value="starts_at"
|
||||
customTrigger={
|
||||
<Switch checked={openItems.indexOf("starts_at") > -1} />
|
||||
@@ -104,11 +112,11 @@ const Settings: React.FC<SettingsProps> = ({ promotion, isEdit = false }) => {
|
||||
<>
|
||||
<DatePicker
|
||||
date={date}
|
||||
label="Start date"
|
||||
label={t("sections-start-date", "Start date")}
|
||||
onSubmitDate={onChange}
|
||||
/>
|
||||
<TimePicker
|
||||
label="Start time"
|
||||
label={t("sections-start-time", "Start time")}
|
||||
date={date}
|
||||
onSubmitDate={onChange}
|
||||
/>
|
||||
@@ -122,9 +130,18 @@ const Settings: React.FC<SettingsProps> = ({ promotion, isEdit = false }) => {
|
||||
headingSize="medium"
|
||||
forceMountContent
|
||||
className="border-b-0"
|
||||
title="Discount has an expiry date?"
|
||||
subtitle="Schedule the discount to deactivate in the future."
|
||||
tooltip="If you want to schedule the discount to deactivate in the future, you can set an expiry date here."
|
||||
title={t(
|
||||
"sections-discount-has-an-expiry-date",
|
||||
"Discount has an expiry date?"
|
||||
)}
|
||||
subtitle={t(
|
||||
"sections-schedule-the-discount-to-deactivate-in-the-future",
|
||||
"Schedule the discount to deactivate in the future."
|
||||
)}
|
||||
tooltip={t(
|
||||
"sections-select-discount-end-date",
|
||||
"If you want to schedule the discount to deactivate in the future, you can set an expiry date here."
|
||||
)}
|
||||
value="ends_at"
|
||||
customTrigger={
|
||||
<Switch checked={openItems.indexOf("ends_at") > -1} />
|
||||
@@ -150,11 +167,11 @@ const Settings: React.FC<SettingsProps> = ({ promotion, isEdit = false }) => {
|
||||
<>
|
||||
<DatePicker
|
||||
date={date}
|
||||
label="Expiry date"
|
||||
label={t("sections-expiry-date", "Expiry date")}
|
||||
onSubmitDate={onChange}
|
||||
/>
|
||||
<TimePicker
|
||||
label="Expiry time"
|
||||
label={t("sections-expiry-time", "Expiry time")}
|
||||
date={date}
|
||||
onSubmitDate={onChange}
|
||||
/>
|
||||
@@ -168,9 +185,18 @@ const Settings: React.FC<SettingsProps> = ({ promotion, isEdit = false }) => {
|
||||
headingSize="medium"
|
||||
forceMountContent
|
||||
className="border-b-0"
|
||||
title="Limit the number of redemptions?"
|
||||
subtitle="Limit applies across all customers, not per customer."
|
||||
tooltip="If you wish to limit the amount of times a customer can redeem this discount, you can set a limit here."
|
||||
title={t(
|
||||
"sections-limit-the-number-of-redemptions",
|
||||
"Limit the number of redemptions?"
|
||||
)}
|
||||
subtitle={t(
|
||||
"sections-limit-applies-across-all-customers-not-per-customer",
|
||||
"Limit applies across all customers, not per customer."
|
||||
)}
|
||||
tooltip={t(
|
||||
"sections-limit-discount-number-of-uses",
|
||||
"If you wish to limit the amount of times a customer can redeem this discount, you can set a limit here."
|
||||
)}
|
||||
value="usage_limit"
|
||||
customTrigger={
|
||||
<Switch checked={openItems.indexOf("usage_limit") > -1} />
|
||||
@@ -183,7 +209,10 @@ const Settings: React.FC<SettingsProps> = ({ promotion, isEdit = false }) => {
|
||||
>
|
||||
<InputField
|
||||
{...register("usage_limit", { valueAsNumber: true })}
|
||||
label="Number of redemptions"
|
||||
label={t(
|
||||
"sections-number-of-redemptions",
|
||||
"Number of redemptions"
|
||||
)}
|
||||
type="number"
|
||||
placeholder="5"
|
||||
min={1}
|
||||
@@ -196,10 +225,19 @@ const Settings: React.FC<SettingsProps> = ({ promotion, isEdit = false }) => {
|
||||
disabled={!isDynamic}
|
||||
headingSize="medium"
|
||||
forceMountContent
|
||||
title="Availability duration?"
|
||||
title={t(
|
||||
"sections-availability-duration",
|
||||
"Availability duration?"
|
||||
)}
|
||||
className="border-b-0"
|
||||
subtitle="Set the duration of the discount."
|
||||
tooltip="Select a discount type"
|
||||
subtitle={t(
|
||||
"sections-set-the-duration-of-the-discount",
|
||||
"Set the duration of the discount."
|
||||
)}
|
||||
tooltip={t(
|
||||
"sections-select-a-discount-type",
|
||||
"Select a discount type"
|
||||
)}
|
||||
value="valid_duration"
|
||||
customTrigger={
|
||||
<Switch checked={openItems.indexOf("valid_duration") > -1} />
|
||||
|
||||
+12
-4
@@ -1,11 +1,13 @@
|
||||
import clsx from "clsx"
|
||||
import { Controller } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import RadioGroup from "../../../../../components/organisms/radio-group"
|
||||
import { AllocationType } from "../../../types"
|
||||
import { useDiscountForm } from "../form/discount-form-context"
|
||||
|
||||
const DiscountAllocation = () => {
|
||||
const { control } = useDiscountForm()
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Controller
|
||||
@@ -22,14 +24,20 @@ const DiscountAllocation = () => {
|
||||
<RadioGroup.Item
|
||||
value={AllocationType.TOTAL}
|
||||
className="flex-1"
|
||||
label="Total amount"
|
||||
description="Apply to the total amount"
|
||||
label={t("sections-total-amount", "Total amount")}
|
||||
description={t(
|
||||
"sections-apply-to-the-total-amount",
|
||||
"Apply to the total amount"
|
||||
)}
|
||||
/>
|
||||
<RadioGroup.Item
|
||||
value={AllocationType.ITEM}
|
||||
className="flex-1"
|
||||
label="Item specific"
|
||||
description="Apply to every allowed item"
|
||||
label={t("sections-item-specific", "Item specific")}
|
||||
description={t(
|
||||
"sections-apply-to-every-allowed-item",
|
||||
"Apply to every allowed item"
|
||||
)}
|
||||
/>
|
||||
</RadioGroup.Root>
|
||||
)
|
||||
|
||||
+17
-6
@@ -1,11 +1,13 @@
|
||||
import clsx from "clsx"
|
||||
import { Controller, useWatch } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import RadioGroup from "../../../../../components/organisms/radio-group"
|
||||
import { DiscountRuleType } from "../../../types"
|
||||
import { useDiscountForm } from "../form/discount-form-context"
|
||||
|
||||
const DiscountType = () => {
|
||||
const { control } = useDiscountForm()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const regions = useWatch({
|
||||
control,
|
||||
@@ -27,22 +29,31 @@ const DiscountType = () => {
|
||||
<RadioGroup.Item
|
||||
value={DiscountRuleType.PERCENTAGE}
|
||||
className="flex-1"
|
||||
label="Percentage"
|
||||
label={t("sections-percentage", "Percentage")}
|
||||
description={"Discount applied in %"}
|
||||
/>
|
||||
<RadioGroup.Item
|
||||
value={DiscountRuleType.FIXED}
|
||||
className="flex-1"
|
||||
label="Fixed amount"
|
||||
description={"Discount in whole numbers"}
|
||||
label={t("sections-fixed-amount", "Fixed amount")}
|
||||
description={t(
|
||||
"sections-discount-in-whole-numbers",
|
||||
"Discount in whole numbers"
|
||||
)}
|
||||
disabled={Array.isArray(regions) && regions.length > 1}
|
||||
disabledTooltip="You can only select one valid region if you want to use the fixed amount type"
|
||||
disabledTooltip={t(
|
||||
"sections-you-can-only-select-one-valid-region-if-you-want-to-use-the-fixed-amount-type",
|
||||
"You can only select one valid region if you want to use the fixed amount type"
|
||||
)}
|
||||
/>
|
||||
<RadioGroup.Item
|
||||
value={DiscountRuleType.FREE_SHIPPING}
|
||||
className="flex-1"
|
||||
label="Free shipping"
|
||||
description={"Override delivery amount"}
|
||||
label={t("sections-free-shipping", "Free shipping")}
|
||||
description={t(
|
||||
"sections-override-delivery-amount",
|
||||
"Override delivery amount"
|
||||
)}
|
||||
/>
|
||||
</RadioGroup.Root>
|
||||
)
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Discount } from "@medusajs/medusa"
|
||||
import { useAdminRegions } from "medusa-react"
|
||||
import React, { useEffect, useMemo, useState } from "react"
|
||||
import { Controller, useWatch } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import Checkbox from "../../../../../components/atoms/checkbox"
|
||||
import IconTooltip from "../../../../../components/molecules/icon-tooltip"
|
||||
import InputField from "../../../../../components/molecules/input"
|
||||
@@ -17,6 +18,7 @@ type GeneralProps = {
|
||||
const General: React.FC<GeneralProps> = ({ discount }) => {
|
||||
const initialCurrency = discount?.regions?.[0].currency_code || undefined
|
||||
|
||||
const { t } = useTranslation()
|
||||
const [fixedRegionCurrency, setFixedRegionCurrency] = useState<
|
||||
string | undefined
|
||||
>(initialCurrency)
|
||||
@@ -59,7 +61,10 @@ const General: React.FC<GeneralProps> = ({ discount }) => {
|
||||
name="regions"
|
||||
control={control}
|
||||
rules={{
|
||||
required: "At least one region is required",
|
||||
required: t(
|
||||
"sections-at-least-one-region-is-required",
|
||||
"At least one region is required"
|
||||
),
|
||||
validate: (value) =>
|
||||
Array.isArray(value) ? value.length > 0 : !!value,
|
||||
}}
|
||||
@@ -70,7 +75,10 @@ const General: React.FC<GeneralProps> = ({ discount }) => {
|
||||
onChange={(value) => {
|
||||
onChange(type === "fixed" ? [value] : value)
|
||||
}}
|
||||
label="Choose valid regions"
|
||||
label={t(
|
||||
"sections-choose-valid-regions",
|
||||
"Choose valid regions"
|
||||
)}
|
||||
isMulti={type !== "fixed"}
|
||||
selectAll={type !== "fixed"}
|
||||
isSearchable
|
||||
@@ -82,11 +90,13 @@ const General: React.FC<GeneralProps> = ({ discount }) => {
|
||||
/>
|
||||
<div className="gap-x-base gap-y-base my-base flex">
|
||||
<InputField
|
||||
label="Code"
|
||||
label={t("sections-code", "Code")}
|
||||
className="flex-1"
|
||||
placeholder="SUMMERSALE10"
|
||||
placeholder={t("sections-summersale-10", "SUMMERSALE10")}
|
||||
required
|
||||
{...register("code", { required: "Code is required" })}
|
||||
{...register("code", {
|
||||
required: t("sections-code-is-required", "Code is required"),
|
||||
})}
|
||||
/>
|
||||
|
||||
{type !== "free_shipping" && (
|
||||
@@ -103,13 +113,16 @@ const General: React.FC<GeneralProps> = ({ discount }) => {
|
||||
name="rule.value"
|
||||
control={control}
|
||||
rules={{
|
||||
required: "Amount is required",
|
||||
required: t(
|
||||
"sections-amount-is-required",
|
||||
"Amount is required"
|
||||
),
|
||||
min: 1,
|
||||
}}
|
||||
render={({ field: { value, onChange } }) => {
|
||||
return (
|
||||
<CurrencyInput.Amount
|
||||
label={"Amount"}
|
||||
label={t("sections-amount", "Amount")}
|
||||
required
|
||||
amount={value}
|
||||
onChange={onChange}
|
||||
@@ -122,7 +135,7 @@ const General: React.FC<GeneralProps> = ({ discount }) => {
|
||||
) : (
|
||||
<div className="flex-1">
|
||||
<InputField
|
||||
label="Percentage"
|
||||
label={t("sections-percentage", "Percentage")}
|
||||
min={0}
|
||||
required
|
||||
type="number"
|
||||
@@ -141,15 +154,22 @@ const General: React.FC<GeneralProps> = ({ discount }) => {
|
||||
|
||||
<div className="text-grey-50 inter-small-regular mb-6 flex flex-col">
|
||||
<span>
|
||||
The code your customers will enter during checkout. This will
|
||||
appear on your customer’s invoice.
|
||||
{t(
|
||||
"sections-customer-invoice-code",
|
||||
"The code your customers will enter during checkout. This will appear on your customer\u2019s invoice."
|
||||
)}
|
||||
</span>
|
||||
<span>
|
||||
{t(
|
||||
"sections-uppercase-letters-and-numbers-only",
|
||||
"Uppercase letters and numbers only."
|
||||
)}
|
||||
</span>
|
||||
<span>Uppercase letters and numbers only.</span>
|
||||
</div>
|
||||
<TextArea
|
||||
label="Description"
|
||||
label={t("sections-description", "Description")}
|
||||
required
|
||||
placeholder="Summer Sale 2022"
|
||||
placeholder={t("sections-summer-sale-2022", "Summer Sale 2022")}
|
||||
rows={1}
|
||||
{...register("rule.description", {
|
||||
required: true,
|
||||
@@ -162,7 +182,10 @@ const General: React.FC<GeneralProps> = ({ discount }) => {
|
||||
render={({ field: { onChange, value } }) => {
|
||||
return (
|
||||
<Checkbox
|
||||
label="This is a template discount"
|
||||
label={t(
|
||||
"sections-this-is-a-template-discount",
|
||||
"This is a template discount"
|
||||
)}
|
||||
name="is_dynamic"
|
||||
id="is_dynamic"
|
||||
checked={value}
|
||||
@@ -172,9 +195,10 @@ const General: React.FC<GeneralProps> = ({ discount }) => {
|
||||
}}
|
||||
/>
|
||||
<IconTooltip
|
||||
content={
|
||||
content={t(
|
||||
"sections-template-discounts-description",
|
||||
"Template discounts allow you to define a set of rules that can be used across a group of discounts. This is useful in campaigns that should generate unique codes for each user, but where the rules for all unique codes should be the same."
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
||||
+31
-14
@@ -1,4 +1,5 @@
|
||||
import { useContext, useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { LayeredModalContext } from "../../../../components/molecules/modal/layered-modal"
|
||||
import { DiscountConditionType } from "../../types"
|
||||
import AddCollectionConditionSelector from "./condition-tables/add-condition-tables/collections"
|
||||
@@ -29,16 +30,20 @@ const useConditionModalItems = ({
|
||||
onClose,
|
||||
}: UseConditionModalItemsProps) => {
|
||||
const layeredModalContext = useContext(LayeredModalContext)
|
||||
const { t } = useTranslation()
|
||||
|
||||
const items: ConditionItem[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
label: "Product",
|
||||
label: t("discount-form-product", "Product"),
|
||||
value: DiscountConditionType.PRODUCTS,
|
||||
description: "Only for specific products",
|
||||
description: t(
|
||||
"discount-form-only-for-specific-products",
|
||||
"Only for specific products"
|
||||
),
|
||||
onClick: () =>
|
||||
layeredModalContext.push({
|
||||
title: "Choose products",
|
||||
title: t("discount-form-choose-products", "Choose products"),
|
||||
onBack: () => layeredModalContext.pop(),
|
||||
view: isDetails ? (
|
||||
<DetailsProductConditionSelector onClose={onClose} />
|
||||
@@ -48,12 +53,15 @@ const useConditionModalItems = ({
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: "Customer group",
|
||||
label: t("discount-form-customer-group", "Customer group"),
|
||||
value: DiscountConditionType.CUSTOMER_GROUPS,
|
||||
description: "Only for specific customer groups",
|
||||
description: t(
|
||||
"discount-form-only-for-specific-customer-groups",
|
||||
"Only for specific customer groups"
|
||||
),
|
||||
onClick: () => {
|
||||
layeredModalContext.push({
|
||||
title: "Choose groups",
|
||||
title: t("discount-form-choose-groups", "Choose groups"),
|
||||
onBack: () => layeredModalContext.pop(),
|
||||
view: isDetails ? (
|
||||
<DetailsCustomerGroupConditionSelector onClose={onClose} />
|
||||
@@ -64,9 +72,12 @@ const useConditionModalItems = ({
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Tag",
|
||||
label: t("discount-form-tag", "Tag"),
|
||||
value: DiscountConditionType.PRODUCT_TAGS,
|
||||
description: "Only for specific tags",
|
||||
description: t(
|
||||
"discount-form-only-for-specific-tags",
|
||||
"Only for specific tags"
|
||||
),
|
||||
onClick: () =>
|
||||
layeredModalContext.push({
|
||||
title: "Choose tags",
|
||||
@@ -79,12 +90,15 @@ const useConditionModalItems = ({
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: "Collection",
|
||||
label: t("discount-form-collection", "Collection"),
|
||||
value: DiscountConditionType.PRODUCT_COLLECTIONS,
|
||||
description: "Only for specific product collections",
|
||||
description: t(
|
||||
"discount-form-only-for-specific-product-collections",
|
||||
"Only for specific product collections"
|
||||
),
|
||||
onClick: () =>
|
||||
layeredModalContext.push({
|
||||
title: "Choose collections",
|
||||
title: t("discount-form-choose-collections", "Choose collections"),
|
||||
onBack: () => layeredModalContext.pop(),
|
||||
view: isDetails ? (
|
||||
<DetailsCollectionConditionSelector onClose={onClose} />
|
||||
@@ -94,12 +108,15 @@ const useConditionModalItems = ({
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: "Type",
|
||||
label: t("discount-form-type", "Type"),
|
||||
value: DiscountConditionType.PRODUCT_TYPES,
|
||||
description: "Only for specific product types",
|
||||
description: t(
|
||||
"discount-form-only-for-specific-product-types",
|
||||
"Only for specific product types"
|
||||
),
|
||||
onClick: () =>
|
||||
layeredModalContext.push({
|
||||
title: "Choose types",
|
||||
title: t("discount-form-choose-types", "Choose types"),
|
||||
onBack: () => layeredModalContext.pop(),
|
||||
view: isDetails ? (
|
||||
<DetailsTypeConditionSelector onClose={onClose} />
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { TFunction } from "i18next"
|
||||
|
||||
enum DiscountConditionType {
|
||||
PRODUCTS = "products",
|
||||
PRODUCT_TYPES = "product_types",
|
||||
@@ -6,17 +8,17 @@ enum DiscountConditionType {
|
||||
CUSTOMER_GROUPS = "customer_groups",
|
||||
}
|
||||
|
||||
export const getTitle = (view: DiscountConditionType) => {
|
||||
export const getTitle = (view: DiscountConditionType, t: TFunction) => {
|
||||
switch (view) {
|
||||
case DiscountConditionType.PRODUCTS:
|
||||
return "products"
|
||||
return t("utils-products", "products")
|
||||
case DiscountConditionType.CUSTOMER_GROUPS:
|
||||
return "groups"
|
||||
return t("utils-groups", "groups")
|
||||
case DiscountConditionType.PRODUCT_TAGS:
|
||||
return "tags"
|
||||
return t("utils-tags", "tags")
|
||||
case DiscountConditionType.PRODUCT_COLLECTIONS:
|
||||
return "collections"
|
||||
return t("utils-collections", "collections")
|
||||
case DiscountConditionType.PRODUCT_TYPES:
|
||||
return "types"
|
||||
return t("utils-types", "types")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useAdminCreateGiftCard } from "medusa-react"
|
||||
import React, { useEffect } from "react"
|
||||
import { useForm, useWatch } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import GiftCardBalanceForm, {
|
||||
GiftCardBalanceFormType,
|
||||
} from "../../components/forms/gift-card/gift-card-balance-form"
|
||||
@@ -32,6 +33,7 @@ type CustomGiftCardFormType = {
|
||||
}
|
||||
|
||||
const CustomGiftcard: React.FC<CustomGiftcardProps> = ({ onClose, open }) => {
|
||||
const { t } = useTranslation()
|
||||
const form = useForm<CustomGiftCardFormType>()
|
||||
const {
|
||||
handleSubmit,
|
||||
@@ -70,14 +72,21 @@ const CustomGiftcard: React.FC<CustomGiftcardProps> = ({ onClose, open }) => {
|
||||
{
|
||||
onSuccess: () => {
|
||||
notification(
|
||||
"Created gift card",
|
||||
"Custom gift card was created successfully",
|
||||
t("gift-cards-created-gift-card", "Created gift card"),
|
||||
t(
|
||||
"gift-cards-custom-gift-card-was-created-successfully",
|
||||
"Custom gift card was created successfully"
|
||||
),
|
||||
"success"
|
||||
)
|
||||
onClose()
|
||||
},
|
||||
onError: (error) => {
|
||||
notification("Error", getErrorMessage(error), "error")
|
||||
notification(
|
||||
t("gift-cards-error", "Error"),
|
||||
getErrorMessage(error),
|
||||
"error"
|
||||
)
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -87,13 +96,17 @@ const CustomGiftcard: React.FC<CustomGiftcardProps> = ({ onClose, open }) => {
|
||||
<Modal open={open} handleClose={onClose}>
|
||||
<Modal.Body>
|
||||
<Modal.Header handleClose={onClose}>
|
||||
<h2 className="inter-xlarge-semibold">Custom Gift Card</h2>
|
||||
<h2 className="inter-xlarge-semibold">
|
||||
{t("gift-cards-custom-gift-card", "Custom Gift Card")}
|
||||
</h2>
|
||||
</Modal.Header>
|
||||
<form onSubmit={onSubmit}>
|
||||
<Modal.Content>
|
||||
<div className="gap-y-xlarge flex flex-col">
|
||||
<div>
|
||||
<h2 className="inter-base-semibold mb-base">Details</h2>
|
||||
<h2 className="inter-base-semibold mb-base">
|
||||
{t("gift-cards-details", "Details")}
|
||||
</h2>
|
||||
<div className="gap-x-xsmall grid grid-cols-2">
|
||||
<GiftCardRegionForm form={nestedForm(form, "region")} />
|
||||
<GiftCardBalanceForm
|
||||
@@ -104,7 +117,9 @@ const CustomGiftcard: React.FC<CustomGiftcardProps> = ({ onClose, open }) => {
|
||||
</div>
|
||||
<GiftCardEndsAtForm form={nestedForm(form, "ends_at")} />
|
||||
<div>
|
||||
<h2 className="inter-base-semibold mb-base">Receiver</h2>
|
||||
<h2 className="inter-base-semibold mb-base">
|
||||
{t("gift-cards-receiver", "Receiver")}
|
||||
</h2>
|
||||
<GiftCardReceiverForm form={nestedForm(form, "receiver")} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -117,7 +132,7 @@ const CustomGiftcard: React.FC<CustomGiftcardProps> = ({ onClose, open }) => {
|
||||
size="small"
|
||||
type="button"
|
||||
>
|
||||
Cancel
|
||||
{t("gift-cards-cancel", "Cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
@@ -126,7 +141,7 @@ const CustomGiftcard: React.FC<CustomGiftcardProps> = ({ onClose, open }) => {
|
||||
disabled={isSubmitting || !isDirty}
|
||||
loading={isSubmitting}
|
||||
>
|
||||
Create and send
|
||||
{t("gift-cards-create-and-send", "Create and send")}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Footer>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { GiftCard } from "@medusajs/medusa"
|
||||
import { useAdminUpdateGiftCard } from "medusa-react"
|
||||
import { useEffect } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import GiftCardEndsAtForm, {
|
||||
GiftCardEndsAtFormType,
|
||||
} from "../../../components/forms/gift-card/gift-card-ends-at-form"
|
||||
@@ -30,6 +31,7 @@ const EditGiftCardModal = ({
|
||||
onClose,
|
||||
giftCard,
|
||||
}: EditGiftCardModalProps) => {
|
||||
const { t } = useTranslation()
|
||||
const form = useForm<EditGiftCardFormType>({
|
||||
defaultValues: getDefaultValues(giftCard),
|
||||
})
|
||||
@@ -52,8 +54,11 @@ const EditGiftCardModal = ({
|
||||
{
|
||||
onSuccess: () => {
|
||||
notification(
|
||||
"Updated Gift card",
|
||||
"Gift card was successfully updated",
|
||||
t("details-updated-gift-card", "Updated Gift card"),
|
||||
t(
|
||||
"details-gift-card-was-successfully-updated",
|
||||
"Gift card was successfully updated"
|
||||
),
|
||||
"success"
|
||||
)
|
||||
|
||||
@@ -61,7 +66,10 @@ const EditGiftCardModal = ({
|
||||
},
|
||||
onError: (err) => {
|
||||
notification(
|
||||
"Failed to update Gift card",
|
||||
t(
|
||||
"details-failed-to-update-gift-card",
|
||||
"Failed to update Gift card"
|
||||
),
|
||||
getErrorMessage(err),
|
||||
"error"
|
||||
)
|
||||
@@ -80,13 +88,17 @@ const EditGiftCardModal = ({
|
||||
<Modal open={open} handleClose={onClose}>
|
||||
<Modal.Body>
|
||||
<Modal.Header handleClose={onClose}>
|
||||
<h1 className="inter-xlarge-semibold">Edit Gift Card</h1>
|
||||
<h1 className="inter-xlarge-semibold">
|
||||
{t("details-edit-gift-card", "Edit Gift Card")}
|
||||
</h1>
|
||||
</Modal.Header>
|
||||
<form onSubmit={onSubmit}>
|
||||
<Modal.Content>
|
||||
<div className="gap-y-xlarge flex flex-col">
|
||||
<div>
|
||||
<h2 className="inter-base-semibold mb-base">Details</h2>
|
||||
<h2 className="inter-base-semibold mb-base">
|
||||
{t("details-details", "Details")}
|
||||
</h2>
|
||||
<GiftCardRegionForm form={nestedForm(form, "region")} />
|
||||
</div>
|
||||
<GiftCardEndsAtForm form={nestedForm(form, "ends_at")} />
|
||||
@@ -100,7 +112,7 @@ const EditGiftCardModal = ({
|
||||
onClick={onClose}
|
||||
type="button"
|
||||
>
|
||||
Cancel
|
||||
{t("details-cancel", "Cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
@@ -109,7 +121,7 @@ const EditGiftCardModal = ({
|
||||
disabled={isLoading || !isDirty}
|
||||
loading={isLoading}
|
||||
>
|
||||
Save and close
|
||||
{t("details-save-and-close", "Save and close")}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Footer>
|
||||
|
||||
@@ -16,9 +16,11 @@ import { getErrorMessage } from "../../../utils/error-messages"
|
||||
import { formatAmountWithSymbol } from "../../../utils/prices"
|
||||
import EditGiftCardModal from "./edit-gift-card-modal"
|
||||
import UpdateBalanceModal from "./update-balance-modal"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
const GiftCardDetails = () => {
|
||||
const { id } = useParams()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { gift_card: giftCard, isLoading } = useAdminGiftCard(id!, {
|
||||
enabled: !!id,
|
||||
@@ -44,12 +46,12 @@ const GiftCardDetails = () => {
|
||||
|
||||
const actions = [
|
||||
{
|
||||
label: "Edit details",
|
||||
label: t("details-edit-details", "Edit details"),
|
||||
onClick: openEdit,
|
||||
icon: <EditIcon size={20} />,
|
||||
},
|
||||
{
|
||||
label: "Update balance",
|
||||
label: t("details-update-balance-label", "Update balance"),
|
||||
onClick: openBalance,
|
||||
icon: <DollarSignIcon size={20} />,
|
||||
},
|
||||
@@ -61,12 +63,20 @@ const GiftCardDetails = () => {
|
||||
{
|
||||
onSuccess: () => {
|
||||
notification(
|
||||
"Updated status",
|
||||
"Successfully updated the status of the Gift Card",
|
||||
t("details-updated-status", "Updated status"),
|
||||
t(
|
||||
"details-successfully-updated-the-status-of-the-gift-card",
|
||||
"Successfully updated the status of the Gift Card"
|
||||
),
|
||||
"success"
|
||||
)
|
||||
},
|
||||
onError: (err) => notification("Error", getErrorMessage(err), "error"),
|
||||
onError: (err) =>
|
||||
notification(
|
||||
t("details-error", "Error"),
|
||||
getErrorMessage(err),
|
||||
"error"
|
||||
),
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -74,7 +84,7 @@ const GiftCardDetails = () => {
|
||||
return (
|
||||
<div>
|
||||
<BackButton
|
||||
label="Back to Gift Cards"
|
||||
label={t("details-back-to-gift-cards", "Back to Gift Cards")}
|
||||
path="/a/gift-cards"
|
||||
className="mb-xsmall"
|
||||
/>
|
||||
@@ -115,7 +125,7 @@ const GiftCardDetails = () => {
|
||||
<div className="flex space-x-6 divide-x">
|
||||
<div className="flex flex-col">
|
||||
<div className="inter-smaller-regular text-grey-50 mb-1">
|
||||
Original amount
|
||||
{t("details-original-amount", "Original amount")}
|
||||
</div>
|
||||
<div>
|
||||
{formatAmountWithSymbol({
|
||||
@@ -126,7 +136,7 @@ const GiftCardDetails = () => {
|
||||
</div>
|
||||
<div className="flex flex-col pl-6">
|
||||
<div className="inter-smaller-regular text-grey-50 mb-1">
|
||||
Balance
|
||||
{t("details-balance", "Balance")}
|
||||
</div>
|
||||
<div>
|
||||
{formatAmountWithSymbol({
|
||||
@@ -137,14 +147,14 @@ const GiftCardDetails = () => {
|
||||
</div>
|
||||
<div className="flex flex-col pl-6">
|
||||
<div className="inter-smaller-regular text-grey-50 mb-1">
|
||||
Region
|
||||
{t("details-region", "Region")}
|
||||
</div>
|
||||
<div>{giftCard.region.name}</div>
|
||||
</div>
|
||||
{giftCard.ends_at && (
|
||||
<div className="flex flex-col pl-6">
|
||||
<div className="inter-smaller-regular text-grey-50 mb-1">
|
||||
Expires on
|
||||
{t("details-expires-on", "Expires on")}
|
||||
</div>
|
||||
<div>
|
||||
{moment(giftCard.ends_at).format("DD MMM YYYY")}
|
||||
@@ -153,7 +163,7 @@ const GiftCardDetails = () => {
|
||||
)}
|
||||
<div className="flex flex-col pl-6">
|
||||
<div className="inter-smaller-regular text-grey-50 mb-1">
|
||||
Created
|
||||
{t("details-created", "Created")}
|
||||
</div>
|
||||
<div>
|
||||
{moment(giftCard.created_at).format("DD MMM YYYY")}
|
||||
@@ -174,7 +184,10 @@ const GiftCardDetails = () => {
|
||||
)
|
||||
})}
|
||||
|
||||
<RawJSON data={giftCard} title="Raw gift card" />
|
||||
<RawJSON
|
||||
data={giftCard}
|
||||
title={t("details-raw-gift-card", "Raw gift card")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<UpdateBalanceModal
|
||||
|
||||
@@ -2,6 +2,7 @@ import { GiftCard } from "@medusajs/medusa"
|
||||
import { useAdminUpdateGiftCard } from "medusa-react"
|
||||
import { useEffect } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import GiftCardBalanceForm, {
|
||||
GiftCardBalanceFormType,
|
||||
} from "../../../components/forms/gift-card/gift-card-balance-form"
|
||||
@@ -26,6 +27,7 @@ const UpdateBalanceModal = ({
|
||||
onClose,
|
||||
giftCard,
|
||||
}: UpdateBalanceModalProps) => {
|
||||
const { t } = useTranslation()
|
||||
const form = useForm<UpdateBalanceModalFormData>({
|
||||
defaultValues: getDefaultValues(giftCard),
|
||||
})
|
||||
@@ -48,8 +50,11 @@ const UpdateBalanceModal = ({
|
||||
{
|
||||
onSuccess: () => {
|
||||
notification(
|
||||
"Balance updated",
|
||||
"Gift card balance was updated",
|
||||
t("details-balance-updated", "Balance updated"),
|
||||
t(
|
||||
"details-gift-card-balance-was-updated",
|
||||
"Gift card balance was updated"
|
||||
),
|
||||
"success"
|
||||
)
|
||||
|
||||
@@ -57,7 +62,7 @@ const UpdateBalanceModal = ({
|
||||
},
|
||||
onError: (err) => {
|
||||
notification(
|
||||
"Failed to update balance",
|
||||
t("details-failed-to-update-balance", "Failed to update balance"),
|
||||
getErrorMessage(err),
|
||||
"error"
|
||||
)
|
||||
@@ -76,7 +81,9 @@ const UpdateBalanceModal = ({
|
||||
<Modal open={open} handleClose={onClose}>
|
||||
<Modal.Body>
|
||||
<Modal.Header handleClose={onClose}>
|
||||
<h1 className="inter-xlarge-semibold">Update Balance</h1>
|
||||
<h1 className="inter-xlarge-semibold">
|
||||
{t("details-update-balance", "Update Balance")}
|
||||
</h1>
|
||||
</Modal.Header>
|
||||
<form onSubmit={onSubmit}>
|
||||
<Modal.Content>
|
||||
@@ -94,7 +101,7 @@ const UpdateBalanceModal = ({
|
||||
onClick={onClose}
|
||||
type="button"
|
||||
>
|
||||
Cancel
|
||||
{t("details-cancel", "Cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
@@ -103,7 +110,7 @@ const UpdateBalanceModal = ({
|
||||
loading={isLoading}
|
||||
disabled={isLoading || !isDirty}
|
||||
>
|
||||
Save and close
|
||||
{t("details-save-and-close", "Save and close")}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Footer>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Product } from "@medusajs/medusa"
|
||||
import { useAdminProducts } from "medusa-react"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import BackButton from "../../../components/atoms/back-button"
|
||||
import Spinner from "../../../components/atoms/spinner"
|
||||
import WidgetContainer from "../../../components/extensions/widget-container"
|
||||
@@ -15,6 +16,7 @@ import { getErrorStatus } from "../../../utils/get-error-status"
|
||||
|
||||
const Manage = () => {
|
||||
const navigate = useNavigate()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { products, error } = useAdminProducts(
|
||||
{
|
||||
@@ -56,7 +58,7 @@ const Manage = () => {
|
||||
<div className="pb-5xlarge">
|
||||
<BackButton
|
||||
path="/a/gift-cards"
|
||||
label="Back to Gift Cards"
|
||||
label={t("manage-back-to-gift-cards", "Back to Gift Cards")}
|
||||
className="mb-xsmall"
|
||||
/>
|
||||
<div className="gap-x-base grid grid-cols-12">
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
} from "medusa-react"
|
||||
import { Controller, useFieldArray, useForm, useWatch } from "react-hook-form"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import FileUploadField from "../../components/atoms/file-upload-field"
|
||||
import Button from "../../components/fundamentals/button"
|
||||
import PlusIcon from "../../components/fundamentals/icons/plus-icon"
|
||||
@@ -43,6 +44,7 @@ const NewGiftCard = ({ onClose }: NewGiftCardProps) => {
|
||||
const { mutate, isLoading } = useAdminCreateProduct()
|
||||
const navigate = useNavigate()
|
||||
const notification = useNotification()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { register, setValue, handleSubmit, control } =
|
||||
useForm<NewGiftCardFormData>({
|
||||
@@ -75,13 +77,27 @@ const NewGiftCard = ({ onClose }: NewGiftCardProps) => {
|
||||
const trimmedName = data.title.trim()
|
||||
|
||||
if (!trimmedName) {
|
||||
notification("Error", "Please enter a name for the Gift Card", "error")
|
||||
notification(
|
||||
t("gift-cards-error", "Error"),
|
||||
t(
|
||||
"gift-cards-please-enter-a-name-for-the-gift-card",
|
||||
"Please enter a name for the Gift Card"
|
||||
),
|
||||
"error"
|
||||
)
|
||||
focusByName("name")
|
||||
return
|
||||
}
|
||||
|
||||
if (!data.denominations?.length) {
|
||||
notification("Error", "Please add at least one denomination", "error")
|
||||
notification(
|
||||
t("gift-cards-error", "Error"),
|
||||
t(
|
||||
"gift-cards-please-add-at-least-one-denomination",
|
||||
"Please add at least one denomination"
|
||||
),
|
||||
"error"
|
||||
)
|
||||
focusByName("add-denomination")
|
||||
return
|
||||
}
|
||||
@@ -105,7 +121,7 @@ const NewGiftCard = ({ onClose }: NewGiftCardProps) => {
|
||||
title: data.title,
|
||||
description: data.description,
|
||||
discountable: false,
|
||||
options: [{ title: "Denominations" }],
|
||||
options: [{ title: t("gift-cards-denominations", "Denominations") }],
|
||||
variants: data.denominations.map((d, i) => ({
|
||||
title: `${i + 1}`,
|
||||
inventory_quantity: 0,
|
||||
@@ -121,12 +137,23 @@ const NewGiftCard = ({ onClose }: NewGiftCardProps) => {
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
notification("Success", "Successfully created Gift Card", "success")
|
||||
notification(
|
||||
t("gift-cards-success", "Success"),
|
||||
t(
|
||||
"gift-cards-successfully-created-gift-card",
|
||||
"Successfully created Gift Card"
|
||||
),
|
||||
"success"
|
||||
)
|
||||
refetch()
|
||||
navigate("/a/gift-cards/manage")
|
||||
},
|
||||
onError: (err) => {
|
||||
notification("Error", getErrorMessage(err), "error")
|
||||
notification(
|
||||
t("gift-cards-error", "Error"),
|
||||
getErrorMessage(err),
|
||||
"error"
|
||||
)
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -138,28 +165,40 @@ const NewGiftCard = ({ onClose }: NewGiftCardProps) => {
|
||||
<Modal.Body>
|
||||
<Modal.Header handleClose={onClose}>
|
||||
<div>
|
||||
<h1 className="inter-xlarge-semibold">Create Gift Card</h1>
|
||||
<h1 className="inter-xlarge-semibold">
|
||||
{t("gift-cards-create-gift-card", "Create Gift Card")}
|
||||
</h1>
|
||||
</div>
|
||||
</Modal.Header>
|
||||
<Modal.Content>
|
||||
<div className="mb-base">
|
||||
<h3 className="inter-base-semibold">Gift Card Details</h3>
|
||||
<h3 className="inter-base-semibold">
|
||||
{t("gift-cards-gift-card-details", "Gift Card Details")}
|
||||
</h3>
|
||||
</div>
|
||||
<div className="gap-y-base flex flex-col">
|
||||
<InputField
|
||||
label={"Name"}
|
||||
label={t("gift-cards-name", "Name")}
|
||||
required
|
||||
placeholder="The best Gift Card"
|
||||
placeholder={t(
|
||||
"gift-cards-the-best-gift-card",
|
||||
"The best Gift Card"
|
||||
)}
|
||||
{...register("title", { required: true })}
|
||||
/>
|
||||
<TextArea
|
||||
label="Description"
|
||||
placeholder="The best Gift Card of all time"
|
||||
label={t("gift-cards-description", "Description")}
|
||||
placeholder={t(
|
||||
"gift-cards-the-best-gift-card-of-all-time",
|
||||
"The best Gift Card of all time"
|
||||
)}
|
||||
{...register("description")}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-xlarge">
|
||||
<h3 className="inter-base-semibold">Thumbnail</h3>
|
||||
<h3 className="inter-base-semibold">
|
||||
{t("gift-cards-thumbnail", "Thumbnail")}
|
||||
</h3>
|
||||
<div className="mt-base h-[80px]">
|
||||
{thumbnail ? (
|
||||
<div className="flex items-center gap-x-6">
|
||||
@@ -178,7 +217,7 @@ const NewGiftCard = ({ onClose }: NewGiftCardProps) => {
|
||||
type="button"
|
||||
onClick={() => setValue("thumbnail", null)}
|
||||
>
|
||||
Delete
|
||||
{t("gift-cards-delete", "Delete")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -192,14 +231,18 @@ const NewGiftCard = ({ onClose }: NewGiftCardProps) => {
|
||||
"image/webp",
|
||||
]}
|
||||
onFileChosen={handleFileUpload}
|
||||
placeholder="1200 x 1600 (3:4) recommended, up to 10MB each"
|
||||
placeholder={t(
|
||||
"gift-cards-size-recommended",
|
||||
"1200 x 1600 (3:4) recommended, up to 10MB each"
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-xlarge">
|
||||
<h3 className="inter-base-semibold mb-base">
|
||||
Denominations<span className="text-rose-50">*</span>
|
||||
{t("gift-cards-denominations", "Denominations")}
|
||||
<span className="text-rose-50">*</span>
|
||||
</h3>
|
||||
<div className="gap-y-xsmall flex flex-col">
|
||||
{fields.map((denomination, index) => {
|
||||
@@ -223,7 +266,7 @@ const NewGiftCard = ({ onClose }: NewGiftCardProps) => {
|
||||
render={({ field: { value, onChange, ref } }) => {
|
||||
return (
|
||||
<CurrencyInput.Amount
|
||||
label="Amount"
|
||||
label={t("gift-cards-amount", "Amount")}
|
||||
amount={value || undefined}
|
||||
onChange={onChange}
|
||||
ref={ref}
|
||||
@@ -257,7 +300,7 @@ const NewGiftCard = ({ onClose }: NewGiftCardProps) => {
|
||||
type="button"
|
||||
>
|
||||
<PlusIcon size={20} />
|
||||
Add Denomination
|
||||
{t("gift-cards-add-denomination", "Add Denomination")}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
@@ -270,7 +313,7 @@ const NewGiftCard = ({ onClose }: NewGiftCardProps) => {
|
||||
className="w-eventButton"
|
||||
onClick={onClose}
|
||||
>
|
||||
Cancel
|
||||
{t("gift-cards-cancel", "Cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
@@ -280,7 +323,7 @@ const NewGiftCard = ({ onClose }: NewGiftCardProps) => {
|
||||
loading={isLoading}
|
||||
disabled={isLoading}
|
||||
>
|
||||
Create & Publish
|
||||
{t("gift-cards-create-publish", "Create & Publish")}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Footer>
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from "medusa-react"
|
||||
import { useMemo, useState } from "react"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import PageDescription from "../../components/atoms/page-description"
|
||||
import Spacer from "../../components/atoms/spacer"
|
||||
import Spinner from "../../components/atoms/spinner"
|
||||
@@ -26,6 +27,7 @@ import CustomGiftcard from "./custom-giftcard"
|
||||
import NewGiftCard from "./new"
|
||||
|
||||
const Overview = () => {
|
||||
const { t } = useTranslation()
|
||||
const { products, isLoading } = useAdminProducts({
|
||||
is_giftcard: true,
|
||||
})
|
||||
@@ -63,8 +65,20 @@ const Overview = () => {
|
||||
{ status },
|
||||
{
|
||||
onSuccess: () =>
|
||||
notification("Success", "Successfully updated Gift Card", "success"),
|
||||
onError: (err) => notification("Error", getErrorMessage(err), "error"),
|
||||
notification(
|
||||
t("gift-cards-success", "Success"),
|
||||
t(
|
||||
"gift-cards-successfully-updated-gift-card",
|
||||
"Successfully updated Gift Card"
|
||||
),
|
||||
"success"
|
||||
),
|
||||
onError: (err) =>
|
||||
notification(
|
||||
t("gift-cards-error", "Error"),
|
||||
getErrorMessage(err),
|
||||
"error"
|
||||
),
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -79,7 +93,7 @@ const Overview = () => {
|
||||
|
||||
const actionables = [
|
||||
{
|
||||
label: "Custom Gift Card",
|
||||
label: t("gift-cards-custom-gift-card", "Custom Gift Card"),
|
||||
onClick: openCustom,
|
||||
icon: <PlusIcon size={20} />,
|
||||
},
|
||||
@@ -102,8 +116,11 @@ const Overview = () => {
|
||||
<>
|
||||
<div className="flex flex-col">
|
||||
<PageDescription
|
||||
title="Gift Cards"
|
||||
subtitle="Manage the Gift Cards of your Medusa store"
|
||||
title={t("gift-cards-gift-cards", "Gift Cards")}
|
||||
subtitle={t(
|
||||
"gift-cards-manage",
|
||||
"Manage the Gift Cards of your Medusa store"
|
||||
)}
|
||||
/>
|
||||
{!isLoading ? (
|
||||
<div className="gap-y-xsmall flex flex-col">
|
||||
@@ -125,21 +142,32 @@ const Overview = () => {
|
||||
onUnpublish={onUpdate}
|
||||
/>
|
||||
) : (
|
||||
<BannerCard title="Are you ready to sell your first Gift Card?">
|
||||
<BannerCard
|
||||
title={t(
|
||||
"gift-cards-are-you-ready-to-sell-your-first-gift-card",
|
||||
"Are you ready to sell your first Gift Card?"
|
||||
)}
|
||||
>
|
||||
<BannerCard.Description
|
||||
cta={{
|
||||
label: "Create Gift Card",
|
||||
label: t("gift-cards-create-gift-card", "Create Gift Card"),
|
||||
onClick: () => setShowCreate(true),
|
||||
}}
|
||||
>
|
||||
No Gift Card has been added yet.
|
||||
{t(
|
||||
"gift-cards-no-gift-card-has-been-added-yet",
|
||||
"No Gift Card has been added yet."
|
||||
)}
|
||||
</BannerCard.Description>
|
||||
</BannerCard>
|
||||
)}
|
||||
|
||||
<BodyCard
|
||||
title="History"
|
||||
subtitle="See the history of purchased Gift Cards"
|
||||
title={t("gift-cards-history", "History")}
|
||||
subtitle={t(
|
||||
"gift-cards-see-the-history-of-purchased-gift-cards",
|
||||
"See the history of purchased Gift Cards"
|
||||
)}
|
||||
actionables={actionables}
|
||||
>
|
||||
<GiftCardTable />
|
||||
@@ -171,9 +199,12 @@ const Overview = () => {
|
||||
<DeletePrompt
|
||||
handleClose={closeDelete}
|
||||
onDelete={async () => onDelete()}
|
||||
successText="Successfully deleted Gift Card"
|
||||
confirmText="Yes, delete"
|
||||
heading="Delete Gift Card"
|
||||
successText={t(
|
||||
"gift-cards-successfully-deleted-gift-card",
|
||||
"Successfully deleted Gift Card"
|
||||
)}
|
||||
confirmText={t("gift-cards-yes-delete", "Yes, delete")}
|
||||
heading={t("gift-cards-delete-gift-card", "Delete Gift Card")}
|
||||
/>
|
||||
)}
|
||||
<Spacer />
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import clsx from "clsx"
|
||||
import { useMemo, useEffect, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import PlusIcon from "../../components/fundamentals/icons/plus-icon"
|
||||
import FilterDropdownContainer from "../../components/molecules/filter-dropdown/container"
|
||||
import TabFilter from "../../components/molecules/filter-tab"
|
||||
@@ -14,6 +15,7 @@ const ProductsFilter = ({
|
||||
onRemoveTab,
|
||||
onSaveTab,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [tempState, setTempState] = useState(filters)
|
||||
const [name, setName] = useState("")
|
||||
|
||||
@@ -71,7 +73,7 @@ const ProductsFilter = ({
|
||||
)}
|
||||
>
|
||||
<div className="rounded-rounded bg-grey-5 border-grey-20 inter-small-semibold flex h-6 items-center border px-2">
|
||||
Filters
|
||||
{t("inventory-filters", "Filters")}
|
||||
<div className="text-grey-40 ml-1 flex items-center rounded">
|
||||
<span className="text-violet-60 inter-small-semibold">
|
||||
{numberOfFilters ? numberOfFilters : "0"}
|
||||
|
||||
+24
-14
@@ -3,6 +3,7 @@ import { StockLocationAddressDTO } from "@medusajs/types"
|
||||
import { useAdminRegions } from "medusa-react"
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import { Controller, useWatch } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import InputField from "../../../../../components/molecules/input"
|
||||
import { NextSelect } from "../../../../../components/molecules/select/next-select"
|
||||
import FormValidator from "../../../../../utils/form-validator"
|
||||
@@ -20,6 +21,7 @@ const AddressForm = ({
|
||||
control,
|
||||
} = form
|
||||
|
||||
const { t } = useTranslation()
|
||||
const { regions } = useAdminRegions()
|
||||
|
||||
const watchedAddressForm = useWatch({
|
||||
@@ -59,12 +61,14 @@ const AddressForm = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<span className="inter-base-semibold">Address</span>
|
||||
<span className="inter-base-semibold">
|
||||
{t("address-form-address", "Address")}
|
||||
</span>
|
||||
<div className="gap-y-large gap-x-large grid grid-cols-1">
|
||||
<div className="gap-x-large grid grid-cols-2">
|
||||
<InputField
|
||||
label="Company"
|
||||
placeholder="Company"
|
||||
label={t("address-form-company", "Company")}
|
||||
placeholder={t("address-form-company", "Company")}
|
||||
errors={errors}
|
||||
{...register(path("company"), {
|
||||
pattern: FormValidator.whiteSpaceRule("Company"),
|
||||
@@ -73,20 +77,23 @@ const AddressForm = ({
|
||||
</div>
|
||||
<div className="gap-x-large grid grid-cols-2">
|
||||
<InputField
|
||||
label="Address 1"
|
||||
placeholder="Address 1"
|
||||
label={t("address-form-address-1", "Address 1")}
|
||||
placeholder={t("address-form-address-1", "Address 1")}
|
||||
errors={errors}
|
||||
required={addressFieldsRequired}
|
||||
{...register(path("address_1"), {
|
||||
pattern: FormValidator.whiteSpaceRule("Address 1"),
|
||||
required: addressFieldsRequired
|
||||
? "This field is required"
|
||||
? t(
|
||||
"address-form-this-field-is-required",
|
||||
"This field is required"
|
||||
)
|
||||
: undefined,
|
||||
})}
|
||||
/>
|
||||
<InputField
|
||||
label="Address 2"
|
||||
placeholder="Address 2"
|
||||
label={t("address-form-address-2", "Address 2")}
|
||||
placeholder={t("address-form-address-2", "Address 2")}
|
||||
errors={errors}
|
||||
{...register(path("address_2"), {
|
||||
pattern: FormValidator.whiteSpaceRule("Address 2"),
|
||||
@@ -95,16 +102,16 @@ const AddressForm = ({
|
||||
</div>
|
||||
<div className="gap-x-large grid grid-cols-2">
|
||||
<InputField
|
||||
label="Postal code"
|
||||
placeholder="Postal code"
|
||||
label={t("address-form-postal-code", "Postal code")}
|
||||
placeholder={t("address-form-postal-code", "Postal code")}
|
||||
errors={errors}
|
||||
{...register(path("postal_code"), {
|
||||
pattern: FormValidator.whiteSpaceRule("Postal code"),
|
||||
})}
|
||||
/>
|
||||
<InputField
|
||||
label="City"
|
||||
placeholder="City"
|
||||
label={t("address-form-city", "City")}
|
||||
placeholder={t("address-form-city", "City")}
|
||||
errors={errors}
|
||||
{...register(path("city"), {
|
||||
pattern: FormValidator.whiteSpaceRule("City"),
|
||||
@@ -117,7 +124,10 @@ const AddressForm = ({
|
||||
name={path("country_code")}
|
||||
rules={{
|
||||
required: addressFieldsRequired
|
||||
? "This field is required"
|
||||
? t(
|
||||
"address-form-this-field-is-required",
|
||||
"This field is required"
|
||||
)
|
||||
: undefined,
|
||||
}}
|
||||
render={({ field: { value, onChange } }) => {
|
||||
@@ -134,7 +144,7 @@ const AddressForm = ({
|
||||
|
||||
return (
|
||||
<NextSelect
|
||||
label="Country"
|
||||
label={t("address-form-country", "Country")}
|
||||
required={addressFieldsRequired}
|
||||
value={fieldValue}
|
||||
options={countries}
|
||||
|
||||
+6
-2
@@ -2,8 +2,9 @@ import { SalesChannel } from "@medusajs/medusa"
|
||||
import { StockLocationExpandedDTO } from "@medusajs/types"
|
||||
import {
|
||||
useAdminAddLocationToSalesChannel,
|
||||
useAdminRemoveLocationFromSalesChannel
|
||||
useAdminRemoveLocationFromSalesChannel,
|
||||
} from "medusa-react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import SalesChannelsModal from "../../../../../components/forms/product/sales-channels-modal"
|
||||
import Button from "../../../../../components/fundamentals/button"
|
||||
import useToggleState from "../../../../../hooks/use-toggle-state"
|
||||
@@ -18,6 +19,7 @@ const EditSalesChannels = ({
|
||||
close: closeSalesChannelsModal,
|
||||
open: openSalesChannelsModal,
|
||||
} = useToggleState()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { mutateAsync: addLocationToSalesChannel } =
|
||||
useAdminAddLocationToSalesChannel()
|
||||
@@ -62,7 +64,9 @@ const EditSalesChannels = ({
|
||||
type="button"
|
||||
onClick={openSalesChannelsModal}
|
||||
>
|
||||
{location.sales_channels?.length ? "Edit channels" : "Add channels"}
|
||||
{location.sales_channels?.length
|
||||
? t("edit-sales-channels-edit-channels", "Edit channels")
|
||||
: t("edit-sales-channels-add-channels", "Add channels")}
|
||||
</Button>
|
||||
<SalesChannelsModal
|
||||
open={showSalesChannelsModal}
|
||||
|
||||
+8
-3
@@ -1,3 +1,4 @@
|
||||
import { useTranslation } from "react-i18next"
|
||||
import FormValidator from "../../../../../utils/form-validator"
|
||||
import InputField from "../../../../../components/molecules/input"
|
||||
import { NestedForm } from "../../../../../utils/nested-form"
|
||||
@@ -11,6 +12,7 @@ type Props = {
|
||||
}
|
||||
|
||||
const GeneralForm = ({ form }: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const {
|
||||
register,
|
||||
path,
|
||||
@@ -20,11 +22,14 @@ const GeneralForm = ({ form }: Props) => {
|
||||
<div>
|
||||
<div className="gap-x-large mb-small grid grid-cols-2">
|
||||
<InputField
|
||||
label="Location name"
|
||||
placeholder="Flagship store, warehouse"
|
||||
label={t("general-form-location-name", "Location name")}
|
||||
placeholder={t(
|
||||
"general-form-flagship-store-warehouse",
|
||||
"Flagship store, warehouse"
|
||||
)}
|
||||
required
|
||||
{...register(path("name"), {
|
||||
required: "Name is required",
|
||||
required: t("general-form-name-is-required", "Name is required"),
|
||||
pattern: FormValidator.whiteSpaceRule("Location name"),
|
||||
})}
|
||||
errors={errors}
|
||||
|
||||
+27
-8
@@ -1,12 +1,13 @@
|
||||
import { StockLocationDTO } from "@medusajs/types"
|
||||
import { useAdminDeleteStockLocation } from "medusa-react"
|
||||
import React from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import IconBadge from "../../../../../components/fundamentals/icon-badge"
|
||||
import BuildingsIcon from "../../../../../components/fundamentals/icons/buildings-icon"
|
||||
import EditIcon from "../../../../../components/fundamentals/icons/edit-icon"
|
||||
import TrashIcon from "../../../../../components/fundamentals/icons/trash-icon"
|
||||
import Actionables, {
|
||||
ActionType
|
||||
ActionType,
|
||||
} from "../../../../../components/molecules/actionables"
|
||||
import useImperativeDialog from "../../../../../hooks/use-imperative-dialog"
|
||||
import useNotification from "../../../../../hooks/use-notification"
|
||||
@@ -22,6 +23,7 @@ type Props = {
|
||||
}
|
||||
|
||||
const LocationCard: React.FC<Props> = ({ location }) => {
|
||||
const { t } = useTranslation()
|
||||
const { mutate: deleteLocation } = useAdminDeleteStockLocation(location.id)
|
||||
|
||||
const dialog = useImperativeDialog()
|
||||
@@ -36,18 +38,32 @@ const LocationCard: React.FC<Props> = ({ location }) => {
|
||||
|
||||
const onDelete = async () => {
|
||||
const shouldDelete = await dialog({
|
||||
heading: "Delete Location",
|
||||
text: "Are you sure you want to delete this location. This will also delete all inventory levels and reservations associated with this location.",
|
||||
heading: t("location-card-delete-location", "Delete Location"),
|
||||
text: t(
|
||||
"location-card-confirm-delete",
|
||||
"Are you sure you want to delete this location. This will also delete all inventory levels and reservations associated with this location."
|
||||
),
|
||||
extraConfirmation: true,
|
||||
entityName: location.name,
|
||||
})
|
||||
if (shouldDelete) {
|
||||
deleteLocation(undefined, {
|
||||
onSuccess: () => {
|
||||
notification("Success", "Location deleted successfully", "success")
|
||||
notification(
|
||||
t("location-card-success", "Success"),
|
||||
t(
|
||||
"location-card-location-deleted-successfully",
|
||||
"Location deleted successfully"
|
||||
),
|
||||
"success"
|
||||
)
|
||||
},
|
||||
onError: (err) => {
|
||||
notification("Error", getErrorMessage(err), "error")
|
||||
notification(
|
||||
t("location-card-error", "Error"),
|
||||
getErrorMessage(err),
|
||||
"error"
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -55,13 +71,13 @@ const LocationCard: React.FC<Props> = ({ location }) => {
|
||||
|
||||
const DropdownActions: ActionType[] = [
|
||||
{
|
||||
label: "Edit details",
|
||||
label: t("location-card-edit-details", "Edit details"),
|
||||
onClick: openLocationEdit,
|
||||
variant: "normal",
|
||||
icon: <EditIcon size="20px" />,
|
||||
},
|
||||
{
|
||||
label: "Delete",
|
||||
label: t("location-card-delete", "Delete"),
|
||||
onClick: onDelete,
|
||||
variant: "danger",
|
||||
icon: <TrashIcon size="20px" />,
|
||||
@@ -90,7 +106,10 @@ const LocationCard: React.FC<Props> = ({ location }) => {
|
||||
{isFeatureEnabled("sales_channels") && (
|
||||
<div className="py-base border-grey-20 border-t border-solid px-6">
|
||||
<h2 className="inter-small-semibold text-gray-500">
|
||||
Connected sales channels
|
||||
{t(
|
||||
"location-card-connected-sales-channels",
|
||||
"Connected sales channels"
|
||||
)}
|
||||
</h2>
|
||||
<SalesChannelsSection location={location} />
|
||||
</div>
|
||||
|
||||
+4
-2
@@ -1,5 +1,6 @@
|
||||
import { SalesChannel, StockLocationExpandedDTO } from "@medusajs/medusa"
|
||||
import { useFieldArray } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import SalesChannelsModal from "../../../../../components/forms/product/sales-channels-modal"
|
||||
import Button from "../../../../../components/fundamentals/button"
|
||||
import SalesChannelsList from "../../../../../components/molecules/sales-channels-list"
|
||||
@@ -14,6 +15,7 @@ const SalesChannelsForm = ({
|
||||
location: StockLocationExpandedDTO | null
|
||||
form: NestedForm<AddSalesChannelsFormType>
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const {
|
||||
state: showSalesChannelsModal,
|
||||
close: closeSalesChannelsModal,
|
||||
@@ -44,7 +46,7 @@ const SalesChannelsForm = ({
|
||||
onClick={openSalesChannelsModal}
|
||||
type="button"
|
||||
>
|
||||
Add sales channels
|
||||
{t("sales-channels-form-add-sales-channels", "Add sales channels")}
|
||||
</Button>
|
||||
) : (
|
||||
<div className="flex w-full items-center justify-between">
|
||||
@@ -58,7 +60,7 @@ const SalesChannelsForm = ({
|
||||
type="button"
|
||||
onClick={openSalesChannelsModal}
|
||||
>
|
||||
Edit channels
|
||||
{t("sales-channels-form-edit-channels", "Edit channels")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
+6
-1
@@ -1,4 +1,5 @@
|
||||
import { StockLocationExpandedDTO } from "@medusajs/types"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import SalesChannelsList from "../../../../../components/molecules/sales-channels-list"
|
||||
import EditSalesChannels from "../edit-sales-channels"
|
||||
|
||||
@@ -7,12 +8,16 @@ const SalesChannelsSection = ({
|
||||
}: {
|
||||
location: StockLocationExpandedDTO
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<div className="py-base">
|
||||
<div className="flex w-full items-center justify-between">
|
||||
{!location.sales_channels?.length ? (
|
||||
<span className="inter-base-regular text-grey-40">
|
||||
Not connected to any sales channels yet
|
||||
{t(
|
||||
"sales-channels-section-not-connected-to-any-sales-channels-yet",
|
||||
"Not connected to any sales channels yet"
|
||||
)}
|
||||
</span>
|
||||
) : (
|
||||
<SalesChannelsList salesChannels={location.sales_channels} />
|
||||
|
||||
@@ -4,6 +4,8 @@ import {
|
||||
StockLocationAddressInput,
|
||||
StockLocationDTO,
|
||||
} from "@medusajs/medusa"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import GeneralForm, { GeneralFormType } from "../components/general-form"
|
||||
import MetadataForm, {
|
||||
MetadataFormType,
|
||||
@@ -32,6 +34,7 @@ export type LocationEditModalProps = {
|
||||
}
|
||||
|
||||
const LocationEditModal = ({ onClose, location }: LocationEditModalProps) => {
|
||||
const { t } = useTranslation()
|
||||
const form = useForm<EditLocationForm>({
|
||||
defaultValues: {
|
||||
general: {
|
||||
@@ -56,10 +59,17 @@ const LocationEditModal = ({ onClose, location }: LocationEditModalProps) => {
|
||||
mutate(payload, {
|
||||
onSuccess: () => {
|
||||
onClose()
|
||||
notification("Success", "Location edited successfully", "success")
|
||||
notification(
|
||||
t("edit-success", "Success"),
|
||||
t(
|
||||
"edit-location-edited-successfully",
|
||||
"Location edited successfully"
|
||||
),
|
||||
"success"
|
||||
)
|
||||
},
|
||||
onError: (err) => {
|
||||
notification("Error", getErrorMessage(err), "error")
|
||||
notification(t("edit-error", "Error"), getErrorMessage(err), "error")
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -68,7 +78,9 @@ const LocationEditModal = ({ onClose, location }: LocationEditModalProps) => {
|
||||
<Modal handleClose={onClose}>
|
||||
<Modal.Body className="top-20">
|
||||
<Modal.Header handleClose={onClose}>
|
||||
<h1 className="text-xl font-semibold">Edit Location Details</h1>
|
||||
<h1 className="text-xl font-semibold">
|
||||
{t("edit-edit-location-details", "Edit Location Details")}
|
||||
</h1>
|
||||
</Modal.Header>
|
||||
<Modal.Content>
|
||||
<form className="w-full">
|
||||
@@ -76,7 +88,9 @@ const LocationEditModal = ({ onClose, location }: LocationEditModalProps) => {
|
||||
<GeneralForm form={nestedForm(form, "general")} />
|
||||
<AddressForm form={nestedForm(form, "address")} />
|
||||
<div>
|
||||
<h2 className="inter-base-semibold mb-base">Metadata</h2>
|
||||
<h2 className="inter-base-semibold mb-base">
|
||||
{t("edit-metadata", "Metadata")}
|
||||
</h2>
|
||||
<MetadataForm form={nestedForm(form, "metadata")} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -91,7 +105,7 @@ const LocationEditModal = ({ onClose, location }: LocationEditModalProps) => {
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
>
|
||||
Cancel
|
||||
{t("edit-cancel", "Cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
@@ -100,7 +114,7 @@ const LocationEditModal = ({ onClose, location }: LocationEditModalProps) => {
|
||||
disabled={!isDirty || isLoading}
|
||||
onClick={onSubmit}
|
||||
>
|
||||
Save and close
|
||||
{t("edit-save-and-close", "Save and close")}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Footer>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { AdminPostStockLocationsReq, SalesChannel } from "@medusajs/medusa"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import GeneralForm, { GeneralFormType } from "../components/general-form"
|
||||
import MetadataForm, {
|
||||
MetadataFormType,
|
||||
@@ -38,6 +39,7 @@ type NewLocationForm = {
|
||||
}
|
||||
|
||||
const NewLocation = ({ onClose }: { onClose: () => void }) => {
|
||||
const { t } = useTranslation()
|
||||
const [accordionValue, setAccordionValue] = React.useState("general")
|
||||
const form = useForm<NewLocationForm>({
|
||||
defaultValues: {
|
||||
@@ -111,12 +113,19 @@ const NewLocation = ({ onClose }: { onClose: () => void }) => {
|
||||
)
|
||||
)
|
||||
.then(() => {
|
||||
notification("Success", "Location added successfully", "success")
|
||||
notification(
|
||||
t("new-success", "Success"),
|
||||
t("new-location-added-successfully", "Location added successfully"),
|
||||
"success"
|
||||
)
|
||||
})
|
||||
.catch(() => {
|
||||
notification(
|
||||
"Error",
|
||||
"Location was created successfully, but there was an error associating sales channels",
|
||||
t("new-error", "Error"),
|
||||
t(
|
||||
"new-location-created",
|
||||
"Location was created successfully, but there was an error associating sales channels"
|
||||
),
|
||||
"error"
|
||||
)
|
||||
})
|
||||
@@ -124,7 +133,7 @@ const NewLocation = ({ onClose }: { onClose: () => void }) => {
|
||||
onClose()
|
||||
})
|
||||
} catch (err) {
|
||||
notification("Error", getErrorMessage(err), "error")
|
||||
notification(t("new-error", "Error"), getErrorMessage(err), "error")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,9 +152,15 @@ const NewLocation = ({ onClose }: { onClose: () => void }) => {
|
||||
</Button>
|
||||
{isShowingClosePrompt && (
|
||||
<DeletePrompt
|
||||
heading="Are you sure you want to cancel with unsaved changes"
|
||||
confirmText="Yes, cancel"
|
||||
cancelText="No, continue creating"
|
||||
heading={t(
|
||||
"new-cancel-location-changes",
|
||||
"Are you sure you want to cancel with unsaved changes"
|
||||
)}
|
||||
confirmText={t("new-yes-cancel", "Yes, cancel")}
|
||||
cancelText={t(
|
||||
"new-no-continue-creating",
|
||||
"No, continue creating"
|
||||
)}
|
||||
successText={false}
|
||||
handleClose={closeClosePrompt}
|
||||
onDelete={async () => onClose()}
|
||||
@@ -158,7 +173,7 @@ const NewLocation = ({ onClose }: { onClose: () => void }) => {
|
||||
type="submit"
|
||||
disabled={!isDirty}
|
||||
>
|
||||
Add location
|
||||
{t("new-add-location", "Add location")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -166,7 +181,7 @@ const NewLocation = ({ onClose }: { onClose: () => void }) => {
|
||||
<FocusModal.Main className="no-scrollbar flex w-full justify-center">
|
||||
<div className="medium:w-7/12 large:w-6/12 small:w-4/5 my-16 max-w-[700px]">
|
||||
<h1 className="mb-base text-grey-90 text-xlarge px-1 font-semibold">
|
||||
Add new location
|
||||
{t("new-add-new-location", "Add new location")}
|
||||
</h1>
|
||||
<Accordion
|
||||
value={accordionValue}
|
||||
@@ -175,17 +190,22 @@ const NewLocation = ({ onClose }: { onClose: () => void }) => {
|
||||
>
|
||||
<Accordion.Item
|
||||
value={"general"}
|
||||
title={"General Information"}
|
||||
title={t("new-general-information", "General Information")}
|
||||
required
|
||||
>
|
||||
<p className="inter-base-regular text-grey-50">
|
||||
Specify the details about this location
|
||||
{t(
|
||||
"new-location-details",
|
||||
"Specify the details about this location"
|
||||
)}
|
||||
</p>
|
||||
<div className="mt-xlarge gap-y-xlarge flex flex-col pb-0.5">
|
||||
<GeneralForm form={nestedForm(form, "general")} />
|
||||
<AddressForm form={nestedForm(form, "address")} />
|
||||
<div>
|
||||
<h2 className="inter-base-semibold mb-base">Metadata</h2>
|
||||
<h2 className="inter-base-semibold mb-base">
|
||||
{t("new-metadata", "Metadata")}
|
||||
</h2>
|
||||
<MetadataForm form={nestedForm(form, "metadata")} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -196,8 +216,10 @@ const NewLocation = ({ onClose }: { onClose: () => void }) => {
|
||||
title={"Sales Channels"}
|
||||
>
|
||||
<p className="inter-base-regular text-grey-50">
|
||||
Specify which Sales Channels this location's items can be
|
||||
purchased through.
|
||||
{t(
|
||||
"new-select-location-channel",
|
||||
"Specify which Sales Channels this location's items can be purchased through."
|
||||
)}
|
||||
</p>
|
||||
<div className="mt-xlarge flex">
|
||||
<SalesChannelsForm
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { useParams } from "react-router-dom"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import Button from "../../components/fundamentals/button"
|
||||
import Medusa from "../../services/api"
|
||||
|
||||
const Oauth = () => {
|
||||
const { t } = useTranslation()
|
||||
const { app_name, code, state } = useParams()
|
||||
return (
|
||||
<>
|
||||
@@ -18,7 +20,7 @@ const Oauth = () => {
|
||||
})
|
||||
}
|
||||
>
|
||||
Complete Installation
|
||||
{t("oauth-complete-installation", "Complete Installation")}
|
||||
</Button>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Controller } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import RadioGroup from "../../../../components/organisms/radio-group"
|
||||
import { NestedForm } from "../../../../utils/nested-form"
|
||||
|
||||
@@ -11,6 +12,7 @@ type Props = {
|
||||
}
|
||||
|
||||
const ClaimTypeForm = ({ form }: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const { control, path } = form
|
||||
|
||||
return (
|
||||
@@ -26,12 +28,12 @@ const ClaimTypeForm = ({ form }: Props) => {
|
||||
className="flex items-center"
|
||||
>
|
||||
<RadioGroup.SimpleItem
|
||||
label="Refund"
|
||||
label={t("claim-type-form-refund", "Refund")}
|
||||
value="refund"
|
||||
onChange={onChange}
|
||||
/>
|
||||
<RadioGroup.SimpleItem
|
||||
label="Replace"
|
||||
label={t("claim-type-form-replace", "Replace")}
|
||||
value="replace"
|
||||
onChange={onChange}
|
||||
/>
|
||||
|
||||
+5
-1
@@ -1,6 +1,7 @@
|
||||
import { Order } from "@medusajs/medusa"
|
||||
import { getCoreRowModel, useReactTable } from "@tanstack/react-table"
|
||||
import { FieldArrayWithId, useFieldArray } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import InputError from "../../../../components/atoms/input-error"
|
||||
import { NestedForm } from "../../../../utils/nested-form"
|
||||
import { ItemsToReceiveTable } from "./items-to-receive-table"
|
||||
@@ -43,6 +44,7 @@ export const ItemsToReceiveForm = ({ form, order }: Props) => {
|
||||
formState: { errors },
|
||||
} = form
|
||||
|
||||
const { t } = useTranslation()
|
||||
const { fields } = useFieldArray({
|
||||
control,
|
||||
name: path("items"),
|
||||
@@ -62,7 +64,9 @@ export const ItemsToReceiveForm = ({ form, order }: Props) => {
|
||||
|
||||
return (
|
||||
<div className="gap-y-xsmall flex flex-col">
|
||||
<h2 className="inter-base-semibold">Items to receive</h2>
|
||||
<h2 className="inter-base-semibold">
|
||||
{t("items-to-receive-form-items-to-receive", "Items to receive")}
|
||||
</h2>
|
||||
|
||||
<ItemsToReceiveTable instance={tableInstance} />
|
||||
<InputError errors={errors} name={path("items")} />
|
||||
|
||||
+13
-3
@@ -1,6 +1,7 @@
|
||||
import { createColumnHelper } from "@tanstack/react-table"
|
||||
import React, { ChangeEvent, useCallback, useMemo } from "react"
|
||||
import { Controller, useWatch } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import CopyToClipboard from "../../../../components/atoms/copy-to-clipboard"
|
||||
import Thumbnail from "../../../../components/atoms/thumbnail"
|
||||
import IndeterminateCheckbox from "../../../../components/molecules/indeterminate-checkbox"
|
||||
@@ -20,6 +21,7 @@ type Props = {
|
||||
}
|
||||
|
||||
export const useItemsToReceiveColumns = ({ form, orderCurrency }: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const { control, setValue, getValues, path } = form
|
||||
|
||||
const updateQuantity = useCallback(
|
||||
@@ -90,7 +92,7 @@ export const useItemsToReceiveColumns = ({ form, orderCurrency }: Props) => {
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor("variant_title", {
|
||||
header: "Product",
|
||||
header: t("items-to-receive-form-product", "Product"),
|
||||
cell: ({ getValue, row: { original } }) => {
|
||||
const value = getValue()
|
||||
|
||||
@@ -120,7 +122,11 @@ export const useItemsToReceiveColumns = ({ form, orderCurrency }: Props) => {
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: "quantity",
|
||||
header: () => <p className="text-right">Quantity</p>,
|
||||
header: () => (
|
||||
<p className="text-right">
|
||||
{t("items-to-receive-form-quantity", "Quantity")}
|
||||
</p>
|
||||
),
|
||||
maxSize: 50,
|
||||
cell: ({
|
||||
row: {
|
||||
@@ -147,7 +153,11 @@ export const useItemsToReceiveColumns = ({ form, orderCurrency }: Props) => {
|
||||
}),
|
||||
columnHelper.accessor("refundable", {
|
||||
maxSize: 80,
|
||||
header: () => <p className="text-right">Refundable</p>,
|
||||
header: () => (
|
||||
<p className="text-right">
|
||||
{t("items-to-receive-form-refundable", "Refundable")}
|
||||
</p>
|
||||
),
|
||||
cell: ({ getValue }) => {
|
||||
return (
|
||||
<p className="text-right">
|
||||
|
||||
+19
-8
@@ -1,6 +1,7 @@
|
||||
import { useAdminReturnReasons } from "medusa-react"
|
||||
import { useMemo } from "react"
|
||||
import { Controller, useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { ReturnReasonDetails } from ".."
|
||||
import Button from "../../../../../components/fundamentals/button"
|
||||
import Modal from "../../../../../components/molecules/modal"
|
||||
@@ -40,6 +41,7 @@ const AddReasonScreen = ({
|
||||
isClaim = false,
|
||||
addReasonDetails,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const { return_reasons } = useAdminReturnReasons()
|
||||
const returnReasonOptions = useMemo(() => {
|
||||
if (isClaim) {
|
||||
@@ -74,15 +76,20 @@ const AddReasonScreen = ({
|
||||
<>
|
||||
<Modal.Content>
|
||||
<div className="gap-y-base flex flex-col">
|
||||
<h2 className="inter-base-semibold">Reason for Return</h2>
|
||||
<h2 className="inter-base-semibold">
|
||||
{t("add-return-reason-reason-for-return", "Reason for Return")}
|
||||
</h2>
|
||||
<Controller
|
||||
control={control}
|
||||
name="reason"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<NextSelect
|
||||
label="Reason"
|
||||
placeholder="Choose a return reason"
|
||||
label={t("add-return-reason-reason", "Reason")}
|
||||
placeholder={t(
|
||||
"add-return-reason-choose-a-return-reason",
|
||||
"Choose a return reason"
|
||||
)}
|
||||
{...field}
|
||||
options={returnReasonOptions}
|
||||
isClearable
|
||||
@@ -91,8 +98,11 @@ const AddReasonScreen = ({
|
||||
}}
|
||||
/>
|
||||
<TextArea
|
||||
label="Note"
|
||||
placeholder="Product was damaged during shipping"
|
||||
label={t("add-return-reason-note", "Note")}
|
||||
placeholder={t(
|
||||
"add-return-reason-product-was-damaged-during-shipping",
|
||||
"Product was damaged during shipping"
|
||||
)}
|
||||
{...register("note")}
|
||||
/>
|
||||
</div>
|
||||
@@ -100,7 +110,7 @@ const AddReasonScreen = ({
|
||||
<Modal.Footer>
|
||||
<div className="gap-x-xsmall flex w-full items-center justify-end">
|
||||
<Button size="small" variant="secondary" onClick={pop} type="button">
|
||||
Cancel
|
||||
{t("add-return-reason-cancel", "Cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
@@ -109,7 +119,7 @@ const AddReasonScreen = ({
|
||||
disabled={!isDirty}
|
||||
type="button"
|
||||
>
|
||||
Save and go back
|
||||
{t("add-return-reason-save-and-go-back", "Save and go back")}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Footer>
|
||||
@@ -119,10 +129,11 @@ const AddReasonScreen = ({
|
||||
|
||||
export const useAddReasonScreen = () => {
|
||||
const { pop, push } = useLayeredModal()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const pushScreen = (props: Props) => {
|
||||
push({
|
||||
title: "Select Reason",
|
||||
title: t("add-return-reason-select-reason-title", "Select Reason"),
|
||||
onBack: () => pop(),
|
||||
view: <AddReasonScreen {...props} />,
|
||||
})
|
||||
|
||||
+5
-1
@@ -1,5 +1,6 @@
|
||||
import { Row } from "@tanstack/react-table"
|
||||
import { Controller, useWatch } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import {
|
||||
ItemsToReturnFormType,
|
||||
ReturnItemObject,
|
||||
@@ -25,6 +26,7 @@ const AddReturnReason = ({ row, form, isClaim = false }: Props) => {
|
||||
formState: { errors },
|
||||
} = form
|
||||
|
||||
const { t } = useTranslation()
|
||||
const { pushScreen } = useAddReasonScreen()
|
||||
|
||||
const reasonDetails = useWatch({
|
||||
@@ -69,7 +71,9 @@ const AddReturnReason = ({ row, form, isClaim = false }: Props) => {
|
||||
}
|
||||
>
|
||||
<span>
|
||||
{reasonDetails?.reason ? "Edit" : "Select"} reason
|
||||
{reasonDetails?.reason
|
||||
? t("add-return-reason-edit-reason", "Edit reason")
|
||||
: t("add-return-reason-select-reason", "Select reason")}
|
||||
</span>
|
||||
</Button>
|
||||
)
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table"
|
||||
import { FieldArrayWithId, useFieldArray } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import InputError from "../../../../components/atoms/input-error"
|
||||
import { FormImage, Option } from "../../../../types/shared"
|
||||
import { NestedForm } from "../../../../utils/nested-form"
|
||||
@@ -50,6 +51,7 @@ type Props = {
|
||||
}
|
||||
|
||||
const ItemsToReturnForm = ({ form, order, isClaim = false }: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const {
|
||||
control,
|
||||
path,
|
||||
@@ -78,7 +80,9 @@ const ItemsToReturnForm = ({ form, order, isClaim = false }: Props) => {
|
||||
return (
|
||||
<div className="gap-y-base flex flex-col">
|
||||
<h2 className="inter-base-semibold">
|
||||
Items to {isClaim ? "claim" : "return"}
|
||||
{isClaim
|
||||
? t("items-to-return-form-items-to-claim", "Items to claim")
|
||||
: t("items-to-return-form-items-to-return", "Items to return")}
|
||||
</h2>
|
||||
<ItemsToReturnTable form={form} instance={table} isClaim={isClaim} />
|
||||
<InputError errors={errors} name={path("items")} />
|
||||
|
||||
+13
-3
@@ -1,6 +1,7 @@
|
||||
import { createColumnHelper } from "@tanstack/react-table"
|
||||
import React, { useCallback, useMemo } from "react"
|
||||
import { Controller, useWatch } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { ItemsToReturnFormType, ReturnItemObject } from "."
|
||||
import CopyToClipboard from "../../../../components/atoms/copy-to-clipboard"
|
||||
import { Thumbnail } from "../../../../components/atoms/thumbnail/thumbnail"
|
||||
@@ -17,6 +18,7 @@ type Props = {
|
||||
}
|
||||
|
||||
export const useItemsToReturnColumns = ({ form, orderCurrency }: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const { control, setValue, getValues, path } = form
|
||||
|
||||
const updateQuantity = useCallback(
|
||||
@@ -97,7 +99,7 @@ export const useItemsToReturnColumns = ({ form, orderCurrency }: Props) => {
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor("variant_title", {
|
||||
header: "Product",
|
||||
header: t("items-to-return-form-product", "Product"),
|
||||
cell: ({ getValue, row: { original } }) => {
|
||||
const value = getValue()
|
||||
|
||||
@@ -127,7 +129,11 @@ export const useItemsToReturnColumns = ({ form, orderCurrency }: Props) => {
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: "quantity",
|
||||
header: () => <p className="text-right">Quantity</p>,
|
||||
header: () => (
|
||||
<p className="text-right">
|
||||
{t("items-to-return-form-quantity", "Quantity")}
|
||||
</p>
|
||||
),
|
||||
maxSize: 50,
|
||||
cell: ({
|
||||
row: {
|
||||
@@ -154,7 +160,11 @@ export const useItemsToReturnColumns = ({ form, orderCurrency }: Props) => {
|
||||
}),
|
||||
columnHelper.accessor("refundable", {
|
||||
maxSize: 80,
|
||||
header: () => <p className="text-right">Refundable</p>,
|
||||
header: () => (
|
||||
<p className="text-right">
|
||||
{t("items-to-return-form-refundable", "Refundable")}
|
||||
</p>
|
||||
),
|
||||
cell: ({ getValue }) => {
|
||||
return (
|
||||
<p className="text-right">
|
||||
|
||||
+9
-3
@@ -11,6 +11,7 @@ import {
|
||||
} from "@tanstack/react-table"
|
||||
import { useAdminVariants } from "medusa-react"
|
||||
import { useMemo, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import Button from "../../../../../components/fundamentals/button"
|
||||
import Modal from "../../../../../components/molecules/modal"
|
||||
import { useLayeredModal } from "../../../../../components/molecules/modal/layered-modal"
|
||||
@@ -36,6 +37,7 @@ const AddAdditionalItemsScreen = ({
|
||||
order,
|
||||
}: Props) => {
|
||||
const { pop } = useLayeredModal()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [{ pageIndex, pageSize }, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
@@ -162,7 +164,7 @@ const AddAdditionalItemsScreen = ({
|
||||
<Modal.Footer>
|
||||
<div className="gap-x-xsmall flex w-full items-center justify-end">
|
||||
<Button variant="secondary" size="small" onClick={pop}>
|
||||
Go back
|
||||
{t("add-additional-items-screen-go-back", "Go back")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
@@ -170,7 +172,7 @@ const AddAdditionalItemsScreen = ({
|
||||
type="button"
|
||||
onClick={onSubmit}
|
||||
>
|
||||
Add products
|
||||
{t("add-additional-items-screen-add-products", "Add products")}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Footer>
|
||||
@@ -180,10 +182,14 @@ const AddAdditionalItemsScreen = ({
|
||||
|
||||
export const useAddAdditionalItemsScreen = () => {
|
||||
const { push, pop } = useLayeredModal()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const pushScreen = (props: Props) => {
|
||||
push({
|
||||
title: "Add Product Variants",
|
||||
title: t(
|
||||
"add-additional-items-screen-add-product-variants",
|
||||
"Add Product Variants"
|
||||
),
|
||||
onBack: () => pop(),
|
||||
view: <AddAdditionalItemsScreen {...props} />,
|
||||
})
|
||||
|
||||
+6
-1
@@ -1,6 +1,7 @@
|
||||
import { PricedVariant } from "@medusajs/medusa/dist/types/pricing"
|
||||
import { flexRender, Table as Instance } from "@tanstack/react-table"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import LoadingContainer from "../../../../../components/atoms/loading-container"
|
||||
import Table from "../../../../../components/molecules/table"
|
||||
|
||||
@@ -17,6 +18,7 @@ export const AddAdditionalItemsTable = ({
|
||||
}: Props) => {
|
||||
const [query, setQuery] = useState<string | undefined>(undefined)
|
||||
const { getHeaderGroups, getRowModel } = instance
|
||||
const { t } = useTranslation()
|
||||
|
||||
useEffect(() => {
|
||||
setSearchTerm(query)
|
||||
@@ -26,7 +28,10 @@ export const AddAdditionalItemsTable = ({
|
||||
<LoadingContainer isLoading={isLoadingData}>
|
||||
<Table
|
||||
enableSearch
|
||||
searchPlaceholder="Search products"
|
||||
searchPlaceholder={t(
|
||||
"add-additional-items-screen-search-products",
|
||||
"Search products"
|
||||
)}
|
||||
searchValue={query}
|
||||
handleSearch={setQuery}
|
||||
>
|
||||
|
||||
+19
-5
@@ -3,6 +3,7 @@ import { PricedVariant } from "@medusajs/medusa/dist/types/pricing"
|
||||
import { createColumnHelper } from "@tanstack/react-table"
|
||||
import clsx from "clsx"
|
||||
import { useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import Thumbnail from "../../../../../components/atoms/thumbnail"
|
||||
import Tooltip from "../../../../../components/atoms/tooltip"
|
||||
import SortingIcon from "../../../../../components/fundamentals/icons/sorting-icon"
|
||||
@@ -12,6 +13,7 @@ import { formatAmountWithSymbol } from "../../../../../utils/prices"
|
||||
const columnHelper = createColumnHelper<PricedVariant>()
|
||||
|
||||
export const useAddAdditionalItemsColumns = (order: Order) => {
|
||||
const { t } = useTranslation()
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
columnHelper.display({
|
||||
@@ -49,9 +51,10 @@ export const useAddAdditionalItemsColumns = (order: Order) => {
|
||||
/>
|
||||
) : (
|
||||
<Tooltip
|
||||
content={
|
||||
content={t(
|
||||
"add-additional-items-screen-variant-price-missing",
|
||||
"This variant does not have a price for the region/currency of this order, and cannot be selected."
|
||||
}
|
||||
)}
|
||||
>
|
||||
<IndeterminateCheckbox
|
||||
checked={getIsSelected()}
|
||||
@@ -124,7 +127,11 @@ export const useAddAdditionalItemsColumns = (order: Order) => {
|
||||
}),
|
||||
columnHelper.accessor("inventory_quantity", {
|
||||
maxSize: 20,
|
||||
header: () => <p className="select-none text-right">Stock</p>,
|
||||
header: () => (
|
||||
<p className="select-none text-right">
|
||||
{t("add-additional-items-screen-stock", "Stock")}
|
||||
</p>
|
||||
),
|
||||
cell: ({ cell: { getValue }, row: { getCanSelect } }) => {
|
||||
const isSelectable = getCanSelect()
|
||||
|
||||
@@ -141,7 +148,11 @@ export const useAddAdditionalItemsColumns = (order: Order) => {
|
||||
}),
|
||||
columnHelper.accessor("calculated_price_incl_tax", {
|
||||
maxSize: 80,
|
||||
header: () => <p className="text-right">Price</p>,
|
||||
header: () => (
|
||||
<p className="text-right">
|
||||
{t("add-additional-items-screen-price", "Price")}
|
||||
</p>
|
||||
),
|
||||
cell: ({
|
||||
getValue,
|
||||
row: {
|
||||
@@ -154,7 +165,10 @@ export const useAddAdditionalItemsColumns = (order: Order) => {
|
||||
<div className="text-right">
|
||||
{original_price_incl_tax !== price && (
|
||||
<Tooltip
|
||||
content="The price has been overridden in a price list, that is applicable to this order."
|
||||
content={t(
|
||||
"add-additional-items-screen-price-overridden-in-price-list-applicable-to-this-order",
|
||||
"The price has been overridden in a price list, that is applicable to this order."
|
||||
)}
|
||||
side="top"
|
||||
>
|
||||
<p className="text-grey-40 cursor-default line-through">
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Order } from "@medusajs/medusa"
|
||||
import { getCoreRowModel, useReactTable } from "@tanstack/react-table"
|
||||
import clsx from "clsx"
|
||||
import { FieldArrayWithId, useFieldArray } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import Button from "../../../../components/fundamentals/button"
|
||||
import { NestedForm } from "../../../../utils/nested-form"
|
||||
import { useAddAdditionalItemsScreen } from "./add-additional-items-screen"
|
||||
@@ -38,6 +39,7 @@ type Props = {
|
||||
}
|
||||
|
||||
const ItemsToSendForm = ({ form, order }: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const { control, path } = form
|
||||
|
||||
const { fields, append, remove } = useFieldArray({
|
||||
@@ -68,7 +70,9 @@ const ItemsToSendForm = ({ form, order }: Props) => {
|
||||
})}
|
||||
>
|
||||
<div className="flex w-full items-center">
|
||||
<h2 className="inter-base-semibold">Items to send</h2>
|
||||
<h2 className="inter-base-semibold">
|
||||
{t("items-to-send-form-items-to-send", "Items to send")}
|
||||
</h2>
|
||||
</div>
|
||||
{fields.length > 0 && <AdditionalItemsTable instance={tableInstance} />}
|
||||
|
||||
@@ -86,7 +90,7 @@ const ItemsToSendForm = ({ form, order }: Props) => {
|
||||
})
|
||||
}
|
||||
>
|
||||
Add products
|
||||
{t("items-to-send-form-add-products", "Add products")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+15
-4
@@ -1,5 +1,6 @@
|
||||
import { createColumnHelper } from "@tanstack/react-table"
|
||||
import { useCallback, useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import CopyToClipboard from "../../../../components/atoms/copy-to-clipboard"
|
||||
import Thumbnail from "../../../../components/atoms/thumbnail"
|
||||
import Tooltip from "../../../../components/atoms/tooltip"
|
||||
@@ -23,6 +24,7 @@ export const useAdditionalItemsColumns = ({
|
||||
orderCurrency,
|
||||
removeItem,
|
||||
}: AdditionalItemsColumnProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { control, setValue, getValues, path } = form
|
||||
|
||||
const updateQuantity = useCallback(
|
||||
@@ -39,7 +41,7 @@ export const useAdditionalItemsColumns = ({
|
||||
return [
|
||||
columnHelper.display({
|
||||
id: "product_display",
|
||||
header: "Product",
|
||||
header: t("items-to-send-form-product", "Product"),
|
||||
cell: ({
|
||||
row: {
|
||||
original: { thumbnail, product_title, variant_title, sku },
|
||||
@@ -71,7 +73,11 @@ export const useAdditionalItemsColumns = ({
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: "quantity",
|
||||
header: () => <p className="text-right">Quantity</p>,
|
||||
header: () => (
|
||||
<p className="text-right">
|
||||
{t("items-to-send-form-quantity", "Quantity")}
|
||||
</p>
|
||||
),
|
||||
maxSize: 50,
|
||||
cell: ({
|
||||
row: {
|
||||
@@ -96,7 +102,9 @@ export const useAdditionalItemsColumns = ({
|
||||
}),
|
||||
columnHelper.accessor("price", {
|
||||
maxSize: 50,
|
||||
header: () => <p className="text-right">Price</p>,
|
||||
header: () => (
|
||||
<p className="text-right">{t("items-to-send-form-price", "Price")}</p>
|
||||
),
|
||||
cell: ({
|
||||
getValue,
|
||||
row: {
|
||||
@@ -109,7 +117,10 @@ export const useAdditionalItemsColumns = ({
|
||||
<div className="text-right">
|
||||
{original_price !== price && (
|
||||
<Tooltip
|
||||
content="The price has been overridden in a price list, that is applicable to this order."
|
||||
content={t(
|
||||
"items-to-send-form-price-overridden-in-price-list-applicable-to-this-order",
|
||||
"The price has been overridden in a price list, that is applicable to this order."
|
||||
)}
|
||||
side="top"
|
||||
>
|
||||
<p className="text-grey-40 cursor-default line-through">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Order } from "@medusajs/medusa"
|
||||
import { Controller, useWatch } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import Button from "../../../../components/fundamentals/button"
|
||||
import CrossIcon from "../../../../components/fundamentals/icons/cross-icon"
|
||||
import EditIcon from "../../../../components/fundamentals/icons/edit-icon"
|
||||
@@ -18,6 +19,7 @@ export type RefundAmountFormType = {
|
||||
}
|
||||
|
||||
const RefundAmountForm = ({ form, initialValue = 0, order }: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const {
|
||||
control,
|
||||
path,
|
||||
@@ -49,7 +51,10 @@ const RefundAmountForm = ({ form, initialValue = 0, order }: Props) => {
|
||||
size="small"
|
||||
type="button"
|
||||
className="h-10 w-10"
|
||||
aria-label="Cancel editing refund amount"
|
||||
aria-label={t(
|
||||
"refund-amount-form-cancel-editing-refund-amount",
|
||||
"Cancel editing refund amount"
|
||||
)}
|
||||
onClick={disableEdit}
|
||||
>
|
||||
<CrossIcon size={16} className="text-grey-40" />
|
||||
@@ -60,7 +65,10 @@ const RefundAmountForm = ({ form, initialValue = 0, order }: Props) => {
|
||||
size="small"
|
||||
type="button"
|
||||
onClick={enableEdit}
|
||||
aria-label="Edit refund amount"
|
||||
aria-label={t(
|
||||
"refund-amount-form-edit-refund-amount",
|
||||
"Edit refund amount"
|
||||
)}
|
||||
className="h-10 w-10"
|
||||
>
|
||||
<EditIcon size={16} className="text-grey-40" />
|
||||
@@ -75,12 +83,18 @@ const RefundAmountForm = ({ form, initialValue = 0, order }: Props) => {
|
||||
rules={{
|
||||
min: {
|
||||
value: 0,
|
||||
message: "Refund amount cannot be negative",
|
||||
message: t(
|
||||
"refund-amount-form-refund-amount-cannot-be-negative",
|
||||
"Refund amount cannot be negative"
|
||||
),
|
||||
},
|
||||
required: true,
|
||||
validate: (value) => {
|
||||
if (value === undefined || !(value >= 0)) {
|
||||
return "The refund amount must be at least 0"
|
||||
return t(
|
||||
"refund-amount-form-the-refund-amount-must-be-at-least-0",
|
||||
"The refund amount must be at least 0"
|
||||
)
|
||||
}
|
||||
},
|
||||
}}
|
||||
|
||||
+23
-4
@@ -10,6 +10,7 @@ import { ReservationItemDTO } from "@medusajs/types"
|
||||
import Tooltip from "../../../../components/atoms/tooltip"
|
||||
import WarningCircleIcon from "../../../../components/fundamentals/icons/warning-circle"
|
||||
import { sum } from "lodash"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
const ReservationIndicator = ({
|
||||
reservations,
|
||||
@@ -18,6 +19,7 @@ const ReservationIndicator = ({
|
||||
reservations?: ReservationItemDTO[]
|
||||
lineItem: LineItem
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { variant, isLoading, isFetching } = useAdminVariantsInventory(
|
||||
lineItem.variant_id!,
|
||||
{
|
||||
@@ -58,7 +60,13 @@ const ReservationIndicator = ({
|
||||
<div className="gap-y-base grid grid-cols-1 divide-y">
|
||||
{!!awaitingReservation && (
|
||||
<span className="flex w-full items-center">
|
||||
{awaitingReservation} items not reserved
|
||||
{t(
|
||||
"reservation-indicator-awaiting-reservation-count",
|
||||
"{{awaitingReservation}} items not reserved",
|
||||
{
|
||||
awaitingReservation,
|
||||
}
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
{reservations?.map((reservation) => (
|
||||
@@ -74,7 +82,10 @@ const ReservationIndicator = ({
|
||||
</div>
|
||||
) : (
|
||||
<span className="flex w-full items-center">
|
||||
This item has been fulfilled.
|
||||
{t(
|
||||
"reservation-indicator-this-item-has-been-fulfilled",
|
||||
"This item has been fulfilled."
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -115,16 +126,24 @@ const EditReservationButton = ({
|
||||
lineItem: LineItem
|
||||
onClick: () => void
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<div className="pt-base first:pt-0">
|
||||
{`${reservation.quantity} item: ${locationName}`}
|
||||
{t(
|
||||
"edit-reservation-button-quantity-item-location-name",
|
||||
"{{quantity}} item: ${{locationName}}",
|
||||
{
|
||||
quantity: reservation.quantity,
|
||||
locationName,
|
||||
}
|
||||
)}
|
||||
<Button
|
||||
onClick={onClick}
|
||||
variant="ghost"
|
||||
size="small"
|
||||
className="mt-2 w-full border"
|
||||
>
|
||||
Edit reservation
|
||||
{t("reservation-indicator-edit-reservation", "Edit reservation")}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Order } from "@medusajs/medusa"
|
||||
import { useMemo } from "react"
|
||||
import { UseFormReturn, useWatch } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import IconTooltip from "../../../../components/molecules/icon-tooltip"
|
||||
import { nestedForm } from "../../../../utils/nested-form"
|
||||
import { formatAmountWithSymbol } from "../../../../utils/prices"
|
||||
@@ -15,6 +16,7 @@ type Props = {
|
||||
}
|
||||
|
||||
export const ClaimSummary = ({ form, order }: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const { control } = form
|
||||
|
||||
const claimItems = useWatch({
|
||||
@@ -77,7 +79,9 @@ export const ClaimSummary = ({ form, order }: Props) => {
|
||||
<div className="gap-y-base border-grey-20 py-large flex flex-col border-y">
|
||||
{selectedClaimItems.length > 0 && (
|
||||
<div>
|
||||
<p className="inter-base-semibold mb-small">Claimed items</p>
|
||||
<p className="inter-base-semibold mb-small">
|
||||
{t("rma-summaries-claimed-items", "Claimed items")}
|
||||
</p>
|
||||
<div className="gap-y-xsmall flex flex-col">
|
||||
{selectedClaimItems.map((item, index) => {
|
||||
return (
|
||||
@@ -110,12 +114,15 @@ export const ClaimSummary = ({ form, order }: Props) => {
|
||||
{claimType !== "refund" && replacementItems.length > 0 && (
|
||||
<div>
|
||||
<div className="mb-small gap-x-2xsmall flex items-center">
|
||||
<p className="inter-base-semibold">Replacement items</p>
|
||||
<p className="inter-base-semibold">
|
||||
{t("rma-summaries-replacement-items", "Replacement items")}
|
||||
</p>
|
||||
<IconTooltip
|
||||
type="warning"
|
||||
content={
|
||||
content={t(
|
||||
"rma-summaries-customer-refund-description",
|
||||
"The customer will receive a full refund for the claimed items, as the cost of replacement items and shipping will not be deducted. Alternatively, you can choose to set a custom refund amount when you receive the returned items or create an exchange instead."
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="gap-y-xsmall flex flex-col">
|
||||
@@ -153,13 +160,21 @@ export const ClaimSummary = ({ form, order }: Props) => {
|
||||
data-testid="refund-amount-container"
|
||||
>
|
||||
<div className="gap-x-2xsmall flex items-center">
|
||||
<p className="inter-base-semibold">Refund amount</p>
|
||||
<p className="inter-base-semibold">
|
||||
{t("rma-summaries-refund-amount", "Refund amount")}
|
||||
</p>
|
||||
<IconTooltip
|
||||
type="info"
|
||||
content={
|
||||
claimType === "replace" && claimItemShipping.option
|
||||
? "The customer will be refunded once the returned items are received"
|
||||
: "The customer will be refunded immediately"
|
||||
? t(
|
||||
"rma-summaries-the-customer-will-be-refunded-once-the-returned-items-are-received",
|
||||
"The customer will be refunded once the returned items are received"
|
||||
)
|
||||
: t(
|
||||
"rma-summaries-the-customer-will-be-refunded-immediately",
|
||||
"The customer will be refunded immediately"
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
+8
-2
@@ -1,6 +1,7 @@
|
||||
import { Order, Return } from "@medusajs/medusa"
|
||||
import { useMemo } from "react"
|
||||
import { UseFormReturn, useWatch } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { nestedForm } from "../../../../utils/nested-form"
|
||||
import { ReceiveReturnFormType } from "../../details/receive-return"
|
||||
import RefundAmountForm from "../refund-amount-form"
|
||||
@@ -15,6 +16,7 @@ type Props = {
|
||||
|
||||
export const ReceiveReturnSummary = ({ form, order, returnRequest }: Props) => {
|
||||
const { control } = form
|
||||
const { t } = useTranslation()
|
||||
|
||||
const items = useWatch({
|
||||
control,
|
||||
@@ -74,7 +76,9 @@ export const ReceiveReturnSummary = ({ form, order, returnRequest }: Props) => {
|
||||
<div className="gap-y-base border-grey-20 py-large flex flex-col border-y">
|
||||
{itemToReceive.length > 0 && (
|
||||
<div>
|
||||
<p className="inter-base-semibold mb-small">Receiving</p>
|
||||
<p className="inter-base-semibold mb-small">
|
||||
{t("rma-summaries-receiving", "Receiving")}
|
||||
</p>
|
||||
<div className="gap-y-xsmall flex flex-col">
|
||||
{itemToReceive.map((item, index) => {
|
||||
return (
|
||||
@@ -107,7 +111,9 @@ export const ReceiveReturnSummary = ({ form, order, returnRequest }: Props) => {
|
||||
className="inter-large-semibold flex items-center justify-between"
|
||||
data-testid="refund-amount-container"
|
||||
>
|
||||
<p className="inter-base-semibold">Refund amount</p>
|
||||
<p className="inter-base-semibold">
|
||||
{t("rma-summaries-refund-amount", "Refund amount")}
|
||||
</p>
|
||||
<RefundAmountForm
|
||||
form={nestedForm(form, "refund_amount")}
|
||||
order={order}
|
||||
|
||||
+3
-1
@@ -1,3 +1,4 @@
|
||||
import { useTranslation } from "react-i18next"
|
||||
import CornerDownRightIcon from "../../../../components/fundamentals/icons/corner-down-right-icon"
|
||||
import { formatAmountWithSymbol } from "../../../../utils/prices"
|
||||
|
||||
@@ -14,6 +15,7 @@ export const SummaryShippingLine = ({
|
||||
price,
|
||||
currencyCode,
|
||||
}: ShippingLineProps) => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="gap-x-base flex items-center">
|
||||
@@ -34,7 +36,7 @@ export const SummaryShippingLine = ({
|
||||
amount: price,
|
||||
currency: currencyCode,
|
||||
})
|
||||
: "Free"}
|
||||
: t("rma-summaries-free", "Free")}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Controller } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import IconTooltip from "../../../../components/molecules/icon-tooltip"
|
||||
import IndeterminateCheckbox from "../../../../components/molecules/indeterminate-checkbox"
|
||||
import { NestedForm } from "../../../../utils/nested-form"
|
||||
@@ -13,12 +14,13 @@ type Props = {
|
||||
}
|
||||
|
||||
const SendNotificationForm = ({ form, type }: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const { control, path } = form
|
||||
|
||||
const subject = {
|
||||
return: "return",
|
||||
swap: "exchange",
|
||||
claim: "claim",
|
||||
return: t("send-notification-form-return", "return"),
|
||||
swap: t("send-notification-form-exchange", "exchange"),
|
||||
claim: t("send-notification-form-claim", "claim"),
|
||||
}[type]
|
||||
|
||||
return (
|
||||
@@ -31,10 +33,19 @@ const SendNotificationForm = ({ form, type }: Props) => {
|
||||
<div className="mr-xsmall">
|
||||
<IndeterminateCheckbox checked={value} onChange={onChange} />
|
||||
</div>
|
||||
<p className="inter-small-semibold mr-1.5">Send notifications</p>
|
||||
<p className="inter-small-semibold mr-1.5">
|
||||
{t(
|
||||
"send-notification-form-send-notifications",
|
||||
"Send notifications"
|
||||
)}
|
||||
</p>
|
||||
<IconTooltip
|
||||
type="info"
|
||||
content={`If unchecked the customer will not receive communication about this ${subject}.`}
|
||||
content={t(
|
||||
"send-notification-form-if-unchecked-the-customer-will-not-receive-communication",
|
||||
"If unchecked the customer will not receive communication about this {{subject}}.",
|
||||
{ subject }
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Order } from "@medusajs/medusa"
|
||||
import { useWatch } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import Button from "../../../../components/fundamentals/button"
|
||||
import { AddressPayload } from "../../../../components/templates/address-form"
|
||||
import { NestedForm } from "../../../../utils/nested-form"
|
||||
@@ -13,6 +14,7 @@ type Props = {
|
||||
const ShippingAddressForm = ({ form, order }: Props) => {
|
||||
const { control, path } = form
|
||||
const { pushScreen } = useShippingAddressFormScreen()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const {
|
||||
address_1,
|
||||
@@ -29,7 +31,9 @@ const ShippingAddressForm = ({ form, order }: Props) => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 className="inter-base-semibold">Shipping address</h2>
|
||||
<h2 className="inter-base-semibold">
|
||||
{t("shipping-address-form-shipping-address", "Shipping address")}
|
||||
</h2>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="inter-small-regular text-grey-50">
|
||||
<p>{`${address_1}${address_2 ? `, ${address_2}` : ""}`}</p>
|
||||
@@ -49,7 +53,10 @@ const ShippingAddressForm = ({ form, order }: Props) => {
|
||||
type="button"
|
||||
onClick={() => pushScreen({ form, order })}
|
||||
>
|
||||
Ship to a different address
|
||||
{t(
|
||||
"shipping-address-form-ship-to-a-different-address",
|
||||
"Ship to a different address"
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+9
-3
@@ -2,6 +2,7 @@ import { Order } from "@medusajs/medusa"
|
||||
import { useAdminRegion } from "medusa-react"
|
||||
import { useMemo } from "react"
|
||||
import { useWatch } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import Button from "../../../../components/fundamentals/button"
|
||||
import Modal from "../../../../components/molecules/modal"
|
||||
import { useLayeredModal } from "../../../../components/molecules/modal/layered-modal"
|
||||
@@ -17,6 +18,7 @@ type Props = {
|
||||
}
|
||||
|
||||
const ShippingAddressFormScreen = ({ form, order }: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const {
|
||||
resetField,
|
||||
path,
|
||||
@@ -82,7 +84,7 @@ const ShippingAddressFormScreen = ({ form, order }: Props) => {
|
||||
type="button"
|
||||
onClick={cancelAndPop}
|
||||
>
|
||||
Cancel
|
||||
{t("shipping-address-form-cancel", "Cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
@@ -91,7 +93,7 @@ const ShippingAddressFormScreen = ({ form, order }: Props) => {
|
||||
type="button"
|
||||
onClick={pop}
|
||||
>
|
||||
Save and go back
|
||||
{t("shipping-address-form-save-and-go-back", "Save and go back")}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Footer>
|
||||
@@ -101,10 +103,14 @@ const ShippingAddressFormScreen = ({ form, order }: Props) => {
|
||||
|
||||
export const useShippingAddressFormScreen = () => {
|
||||
const { pop, push } = useLayeredModal()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const pushScreen = (props: Props) => {
|
||||
push({
|
||||
title: "Shipping Information",
|
||||
title: t(
|
||||
"shipping-address-form-shipping-information",
|
||||
"Shipping Information"
|
||||
),
|
||||
onBack: () => pop(),
|
||||
view: <ShippingAddressFormScreen {...props} />,
|
||||
})
|
||||
|
||||
@@ -4,6 +4,7 @@ import clsx from "clsx"
|
||||
import { useAdminShippingOptions } from "medusa-react"
|
||||
import { useMemo } from "react"
|
||||
import { Controller, useWatch } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import PriceFormInput from "../../../../components/forms/general/prices-form/price-form-input"
|
||||
import Button from "../../../../components/fundamentals/button"
|
||||
import TrashIcon from "../../../../components/fundamentals/icons/trash-icon"
|
||||
@@ -37,6 +38,7 @@ const ShippingForm = ({
|
||||
isClaim = false,
|
||||
required = false,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const {
|
||||
control,
|
||||
path,
|
||||
@@ -111,20 +113,38 @@ const ShippingForm = ({
|
||||
<div className="gap-y-base flex flex-col">
|
||||
<div className="flex flex-col">
|
||||
<h2 className="inter-base-semibold">
|
||||
Shipping for {isReturn ? "return" : "replacement"} items
|
||||
{isReturn
|
||||
? t(
|
||||
"shipping-form-shipping-for-return-items",
|
||||
"Shipping for return items"
|
||||
)
|
||||
: t(
|
||||
"shipping-form-shipping-for-replacement-items",
|
||||
"Shipping for replacement items"
|
||||
)}
|
||||
</h2>
|
||||
<ShippingFormHelpText isClaim={isClaim} isReturn={isReturn} />
|
||||
</div>
|
||||
<Controller
|
||||
control={control}
|
||||
name={path("option")}
|
||||
rules={{ required: required ? `Shipping method is required` : false }}
|
||||
rules={{
|
||||
required: required
|
||||
? t(
|
||||
"shipping-form-shipping-method-is-required",
|
||||
"Shipping method is required"
|
||||
)
|
||||
: false,
|
||||
}}
|
||||
render={({ field: { value, onChange, onBlur, ref, name } }) => {
|
||||
return (
|
||||
<NextSelect
|
||||
ref={ref}
|
||||
placeholder="Choose shipping method"
|
||||
label="Shipping method"
|
||||
placeholder={t(
|
||||
"shipping-form-choose-shipping-method",
|
||||
"Choose shipping method"
|
||||
)}
|
||||
label={t("shipping-form-shipping-method", "Shipping method")}
|
||||
name={name}
|
||||
options={returnShippingOptions}
|
||||
value={value}
|
||||
@@ -175,7 +195,7 @@ const ShippingForm = ({
|
||||
className="h-10"
|
||||
onClick={setCustomPrice}
|
||||
>
|
||||
Add custom price
|
||||
{t("shipping-form-add-custom-price", "Add custom price")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@@ -188,13 +208,21 @@ const ShippingFormHelpText = ({
|
||||
isClaim = false,
|
||||
isReturn = false,
|
||||
}: Pick<Props, "isClaim" | "isReturn">) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const text = useMemo(() => {
|
||||
if (isClaim && isReturn) {
|
||||
return "Return shipping for items claimed by the customer is complimentary."
|
||||
return t(
|
||||
"shipping-form-return-shipping-for-items-claimed-by-the-customer-is-complimentary",
|
||||
"Return shipping for items claimed by the customer is complimentary."
|
||||
)
|
||||
}
|
||||
|
||||
if (!isReturn) {
|
||||
return "Shipping for replacement items is complimentary."
|
||||
return t(
|
||||
"shipping-form-shipping-for-replacement-items-is-complimentary",
|
||||
"Shipping for replacement items is complimentary."
|
||||
)
|
||||
}
|
||||
|
||||
return undefined
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user