feat(admin-ui): Multi-language support (#4962)
This commit is contained in:
1 parent
107aaa371c
commit
afd4e72cdf
347 files changed
+9601
-2231
No files matched your search
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/admin-ui": minor
|
||||||
|
"@medusajs/admin": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(admin-ui): Multi-language support
|
||||||
@@ -28,7 +28,8 @@
|
|||||||
"dev": "yarn create:dev:entry && webpack serve --mode=development --config ./webpack.config.dev.ts --progress profile",
|
"dev": "yarn create:dev:entry && webpack serve --mode=development --config ./webpack.config.dev.ts --progress profile",
|
||||||
"analyze:bundle": "ANALYZE_BUNDLE=true webpack --config ./webpack.config.dev.ts",
|
"analyze:bundle": "ANALYZE_BUNDLE=true webpack --config ./webpack.config.dev.ts",
|
||||||
"analyze:deps": "ANALYZE_DEPS=true webpack serve --config ./webpack.config.dev.ts --progress profile",
|
"analyze:deps": "ANALYZE_DEPS=true webpack serve --config ./webpack.config.dev.ts --progress profile",
|
||||||
"build": "tsup"
|
"build": "tsup",
|
||||||
|
"sync:i18n": "i18next 'ui/src/**/*.{ts,tsx,js,jsx}' -c 'ui/i18next-parser.config.js'"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "7.22.5",
|
"@babel/parser": "7.22.5",
|
||||||
@@ -62,6 +63,9 @@
|
|||||||
"emoji-picker-react": "^4.4.3",
|
"emoji-picker-react": "^4.4.3",
|
||||||
"framer-motion": "^9.1.6",
|
"framer-motion": "^9.1.6",
|
||||||
"html-webpack-plugin": "^5.5.1",
|
"html-webpack-plugin": "^5.5.1",
|
||||||
|
"i18next": "^22.5.0",
|
||||||
|
"i18next-browser-languagedetector": "^7.0.1",
|
||||||
|
"i18next-http-backend": "^2.2.1",
|
||||||
"md5": "^2.3.0",
|
"md5": "^2.3.0",
|
||||||
"medusa-react": "*",
|
"medusa-react": "*",
|
||||||
"mini-css-extract-plugin": "^2.7.6",
|
"mini-css-extract-plugin": "^2.7.6",
|
||||||
@@ -88,6 +92,7 @@
|
|||||||
"react-hook-form": "7.38.0",
|
"react-hook-form": "7.38.0",
|
||||||
"react-hot-toast": "2.4.0",
|
"react-hot-toast": "2.4.0",
|
||||||
"react-hotkeys-hook": "^3.4.7",
|
"react-hotkeys-hook": "^3.4.7",
|
||||||
|
"react-i18next": "^12.3.1",
|
||||||
"react-json-tree": "^0.17.0",
|
"react-json-tree": "^0.17.0",
|
||||||
"react-jwt": "^1.1.4",
|
"react-jwt": "^1.1.4",
|
||||||
"react-nestable": "^2.0.0",
|
"react-nestable": "^2.0.0",
|
||||||
@@ -117,6 +122,7 @@
|
|||||||
"@types/react-dom": "^18.0.10",
|
"@types/react-dom": "^18.0.10",
|
||||||
"@types/react-table": "^7.7.9",
|
"@types/react-table": "^7.7.9",
|
||||||
"duplicate-dependencies-webpack-plugin": "^1.0.2",
|
"duplicate-dependencies-webpack-plugin": "^1.0.2",
|
||||||
|
"i18next-parser": "^8.0.0",
|
||||||
"jest": "25.5.4",
|
"jest": "25.5.4",
|
||||||
"ts-jest": "25.5.1",
|
"ts-jest": "25.5.1",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
module.exports = {
|
||||||
|
contextSeparator: "_",
|
||||||
|
// Key separator used in your translation keys
|
||||||
|
|
||||||
|
createOldCatalogs: false,
|
||||||
|
// Save the \_old files
|
||||||
|
|
||||||
|
defaultNamespace: "translation",
|
||||||
|
// Default namespace used in your i18next config
|
||||||
|
|
||||||
|
defaultValue: "",
|
||||||
|
// Default value to give to keys with no value
|
||||||
|
// You may also specify a function accepting the locale, namespace, key, and value as arguments
|
||||||
|
|
||||||
|
indentation: 2,
|
||||||
|
// Indentation of the catalog files
|
||||||
|
|
||||||
|
keepRemoved: false,
|
||||||
|
// Keep keys from the catalog that are no longer in code
|
||||||
|
|
||||||
|
keySeparator: false,
|
||||||
|
// Key separator used in your translation keys
|
||||||
|
// If you want to use plain english keys, separators such as `.` and `:` will conflict. You might want to set `keySeparator: false` and `namespaceSeparator: false`. That way, `t('Status: Loading...')` will not think that there are a namespace and three separator dots for instance.
|
||||||
|
|
||||||
|
// see below for more details
|
||||||
|
lexers: {
|
||||||
|
hbs: ["HandlebarsLexer"],
|
||||||
|
handlebars: ["HandlebarsLexer"],
|
||||||
|
|
||||||
|
htm: ["HTMLLexer"],
|
||||||
|
html: ["HTMLLexer"],
|
||||||
|
|
||||||
|
mjs: ["JavascriptLexer"],
|
||||||
|
js: ["JavascriptLexer"], // if you're writing jsx inside .js files, change this to JsxLexer
|
||||||
|
ts: ["JavascriptLexer"],
|
||||||
|
jsx: ["JsxLexer"],
|
||||||
|
tsx: ["JsxLexer"],
|
||||||
|
|
||||||
|
default: ["JavascriptLexer"],
|
||||||
|
},
|
||||||
|
|
||||||
|
lineEnding: "auto",
|
||||||
|
// Control the line ending. See options at https://github.com/ryanve/eol
|
||||||
|
|
||||||
|
locales: ["en"],
|
||||||
|
// An array of the locales in your applications
|
||||||
|
|
||||||
|
namespaceSeparator: false,
|
||||||
|
// Namespace separator used in your translation keys
|
||||||
|
// If you want to use plain english keys, separators such as `.` and `:` will conflict. You might want to set `keySeparator: false` and `namespaceSeparator: false`. That way, `t('Status: Loading...')` will not think that there are a namespace and three separator dots for instance.
|
||||||
|
|
||||||
|
output: "ui/public/locales/$LOCALE/$NAMESPACE.json",
|
||||||
|
// Supports $LOCALE and $NAMESPACE injection
|
||||||
|
// Supports JSON (.json) and YAML (.yml) file formats
|
||||||
|
// Where to write the locale files relative to process.cwd()
|
||||||
|
|
||||||
|
pluralSeparator: "_",
|
||||||
|
// Plural separator used in your translation keys
|
||||||
|
// If you want to use plain english keys, separators such as `_` might conflict. You might want to set `pluralSeparator` to a different string that does not occur in your keys.
|
||||||
|
|
||||||
|
input: undefined,
|
||||||
|
// An array of globs that describe where to look for source files
|
||||||
|
// relative to the location of the configuration file
|
||||||
|
|
||||||
|
sort: false,
|
||||||
|
// Whether or not to sort the catalog. Can also be a [compareFunction](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#parameters)
|
||||||
|
|
||||||
|
verbose: true,
|
||||||
|
// Display info about the parsing including some stats
|
||||||
|
|
||||||
|
failOnWarnings: true,
|
||||||
|
// Exit with an exit code of 1 on warnings
|
||||||
|
|
||||||
|
failOnUpdate: false,
|
||||||
|
// Exit with an exit code of 1 when translations are updated (for CI purpose)
|
||||||
|
|
||||||
|
customValueTemplate: null,
|
||||||
|
// If you wish to customize the value output the value as an object, you can set your own format.
|
||||||
|
// ${defaultValue} is the default value you set in your translation function.
|
||||||
|
// Any other custom property will be automatically extracted.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
// {
|
||||||
|
// message: "${defaultValue}",
|
||||||
|
// description: "${maxLength}", // t('my-key', {maxLength: 150})
|
||||||
|
// }
|
||||||
|
|
||||||
|
resetDefaultValueLocale: null,
|
||||||
|
// The locale to compare with default values to determine whether a default value has been changed.
|
||||||
|
// If this is set and a default value differs from a translation in the specified locale, all entries
|
||||||
|
// for that key across locales are reset to the default value, and existing translations are moved to
|
||||||
|
// the `_old` file.
|
||||||
|
|
||||||
|
i18nextOptions: null,
|
||||||
|
// If you wish to customize options in internally used i18next instance, you can define an object with any
|
||||||
|
// configuration property supported by i18next (https://www.i18next.com/overview/configuration-options).
|
||||||
|
// { compatibilityJSON: 'v3' } can be used to generate v3 compatible plurals.
|
||||||
|
|
||||||
|
yamlOptions: null,
|
||||||
|
// If you wish to customize options for yaml output, you can define an object here.
|
||||||
|
// Configuration options are here (https://github.com/nodeca/js-yaml#dump-object---options-).
|
||||||
|
// Example:
|
||||||
|
// {
|
||||||
|
// lineWidth: -1,
|
||||||
|
// }
|
||||||
|
}
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -21,25 +21,25 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.emoji-picker-react .emoji {
|
.emoji-picker-react .emoji {
|
||||||
color: #F3F4F6 !important;
|
color: #f3f4f6 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.emoji-picker-react input.emoji-search {
|
.emoji-picker-react input.emoji-search {
|
||||||
background-color: #F9FAFB !important;
|
background-color: #f9fafb !important;
|
||||||
border-radius: 4px !important;
|
border-radius: 4px !important;
|
||||||
border-color: #E5E7EB !important;
|
border-color: #e5e7eb !important;
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
font-size: 12px !important;
|
font-size: 12px !important;
|
||||||
font-family: "Inter" !important;
|
font-family: "Inter" !important;
|
||||||
color: #111827 !important;
|
color: #111827 !important;
|
||||||
caret-color: #7C3AED !important;
|
caret-color: #7c3aed !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.emoji-picker-react input.emoji-search::placeholder {
|
.emoji-picker-react input.emoji-search::placeholder {
|
||||||
font-size: 12px !important;
|
font-size: 12px !important;
|
||||||
font-family: "Inter" !important;
|
font-family: "Inter" !important;
|
||||||
color: #9CA3AF !important;
|
color: #9ca3af !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.emoji-picker-react .emoji-categories button.icn-smileys_people {
|
.emoji-picker-react .emoji-categories button.icn-smileys_people {
|
||||||
@@ -85,13 +85,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.emoji-picker-react .emoji-categories {
|
.emoji-picker-react .emoji-categories {
|
||||||
background-color: #F3F4F6 !important;
|
background-color: #f3f4f6 !important;
|
||||||
padding: 4px !important;
|
padding: 4px !important;
|
||||||
border-radius: 4px !important;
|
border-radius: 4px !important;
|
||||||
margin-bottom: 8px !important;
|
margin-bottom: 8px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.emoji-picker-react .active-category-indicator-wrapper .active-category-indicator {
|
.emoji-picker-react
|
||||||
|
.active-category-indicator-wrapper
|
||||||
|
.active-category-indicator {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import clsx from "clsx"
|
import clsx from "clsx"
|
||||||
import { useNavigate } from "react-router-dom"
|
import { useNavigate } from "react-router-dom"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import ArrowLeftIcon from "../../fundamentals/icons/arrow-left-icon"
|
import ArrowLeftIcon from "../../fundamentals/icons/arrow-left-icon"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -8,7 +9,8 @@ type Props = {
|
|||||||
className?: string
|
className?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const BackButton = ({ path, label = "Go back", className }: Props) => {
|
const BackButton = ({ path, label, className }: Props) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
@@ -19,7 +21,9 @@ const BackButton = ({ path, label = "Go back", className }: Props) => {
|
|||||||
>
|
>
|
||||||
<div className="gap-x-xsmall text-grey-50 inter-grey-40 inter-small-semibold flex items-center">
|
<div className="gap-x-xsmall text-grey-50 inter-grey-40 inter-small-semibold flex items-center">
|
||||||
<ArrowLeftIcon size={20} />
|
<ArrowLeftIcon size={20} />
|
||||||
<span className="ml-1">{label}</span>
|
<span className="ml-1">
|
||||||
|
{label || t("back-button-go-back", "Go back")}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ import { Controller } from "react-hook-form"
|
|||||||
import NestedMultiselect from "../../../../domain/categories/components/multiselect"
|
import NestedMultiselect from "../../../../domain/categories/components/multiselect"
|
||||||
import {
|
import {
|
||||||
FeatureFlag,
|
FeatureFlag,
|
||||||
useFeatureFlag
|
useFeatureFlag,
|
||||||
} from "../../../../providers/feature-flag-provider"
|
} from "../../../../providers/feature-flag-provider"
|
||||||
import { Option } from "../../../../types/shared"
|
import { Option } from "../../../../types/shared"
|
||||||
import { NestedForm } from "../../../../utils/nested-form"
|
import { NestedForm } from "../../../../utils/nested-form"
|
||||||
import InputHeader from "../../../fundamentals/input-header"
|
import InputHeader from "../../../fundamentals/input-header"
|
||||||
import {
|
import {
|
||||||
NextCreateableSelect,
|
NextCreateableSelect,
|
||||||
NextSelect
|
NextSelect,
|
||||||
} from "../../../molecules/select/next-select"
|
} from "../../../molecules/select/next-select"
|
||||||
import TagInput from "../../../molecules/tag-input"
|
import TagInput from "../../../molecules/tag-input"
|
||||||
import useOrganizeData from "./use-organize-data"
|
import useOrganizeData from "./use-organize-data"
|
||||||
|
|||||||
@@ -3,9 +3,7 @@ import { FormImage } from "../../../../types/shared"
|
|||||||
import { NestedForm } from "../../../../utils/nested-form"
|
import { NestedForm } from "../../../../utils/nested-form"
|
||||||
import FileUploadField from "../../../atoms/file-upload-field"
|
import FileUploadField from "../../../atoms/file-upload-field"
|
||||||
import TrashIcon from "../../../fundamentals/icons/trash-icon"
|
import TrashIcon from "../../../fundamentals/icons/trash-icon"
|
||||||
import Actionables, {
|
import Actionables, { ActionType } from "../../../molecules/actionables"
|
||||||
ActionType
|
|
||||||
} from "../../../molecules/actionables"
|
|
||||||
|
|
||||||
export type ThumbnailFormType = {
|
export type ThumbnailFormType = {
|
||||||
images: FormImage[]
|
images: FormImage[]
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useAdminSalesChannels } from "medusa-react"
|
|||||||
import React from "react"
|
import React from "react"
|
||||||
import Tooltip from "../../atoms/tooltip"
|
import Tooltip from "../../atoms/tooltip"
|
||||||
import Badge from "../../fundamentals/badge"
|
import Badge from "../../fundamentals/badge"
|
||||||
|
import { Trans, useTranslation } from "react-i18next"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
channels?: SalesChannel[]
|
channels?: SalesChannel[]
|
||||||
@@ -11,7 +12,10 @@ type Props = {
|
|||||||
const SalesChannelsDisplay = ({ channels = [] }: Props) => {
|
const SalesChannelsDisplay = ({ channels = [] }: Props) => {
|
||||||
const { count } = useAdminSalesChannels()
|
const { count } = useAdminSalesChannels()
|
||||||
const remainder = Math.max(channels.length - 3, 0)
|
const remainder = Math.max(channels.length - 3, 0)
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const availableChannelsCount = channels.length ? channels.length : 0
|
||||||
|
const totalChannelsCount = count || 0
|
||||||
return (
|
return (
|
||||||
<div className="gap-y-small flex flex-col">
|
<div className="gap-y-small flex flex-col">
|
||||||
{channels.length > 0 && (
|
{channels.length > 0 && (
|
||||||
@@ -41,13 +45,21 @@ const SalesChannelsDisplay = ({ channels = [] }: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<p className="inter-base-regular text-grey-50">
|
<p className="inter-base-regular text-grey-50">
|
||||||
|
<Trans
|
||||||
|
i18nKey="sales-channels-display-available-count"
|
||||||
|
availableChannelsCount={availableChannelsCount}
|
||||||
|
totalChannelsCount={totalChannelsCount}
|
||||||
|
>
|
||||||
Available in{" "}
|
Available in{" "}
|
||||||
<span className="inter-base-semibold text-grey-90">
|
<span className="inter-base-semibold text-grey-90">
|
||||||
{channels.length ? channels.length : 0}
|
{{ availableChannelsCount }}
|
||||||
</span>{" "}
|
</span>{" "}
|
||||||
out of{" "}
|
out of{" "}
|
||||||
<span className="inter-base-semibold text-grey-90">{count || 0}</span>{" "}
|
<span className="inter-base-semibold text-grey-90">
|
||||||
|
{{ totalChannelsCount }}
|
||||||
|
</span>{" "}
|
||||||
Sales Channels
|
Sales Channels
|
||||||
|
</Trans>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
File renamed without changes.
@@ -1,4 +1,5 @@
|
|||||||
import React, { useEffect } from "react"
|
import React, { useEffect } from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
import useOutsideClick from "../../../hooks/use-outside-click"
|
import useOutsideClick from "../../../hooks/use-outside-click"
|
||||||
import { usePolling } from "../../../providers/polling-provider"
|
import { usePolling } from "../../../providers/polling-provider"
|
||||||
@@ -8,6 +9,7 @@ import SidedMouthFaceIcon from "../../fundamentals/icons/sided-mouth-face"
|
|||||||
import BatchJobActivityList from "../batch-jobs-activity-list"
|
import BatchJobActivityList from "../batch-jobs-activity-list"
|
||||||
|
|
||||||
const ActivityDrawer = ({ onDismiss }) => {
|
const ActivityDrawer = ({ onDismiss }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const ref = React.useRef<HTMLDivElement>(null)
|
const ref = React.useRef<HTMLDivElement>(null)
|
||||||
const { batchJobs, hasPollingError, refetch } = usePolling()
|
const { batchJobs, hasPollingError, refetch } = usePolling()
|
||||||
useOutsideClick(onDismiss, ref)
|
useOutsideClick(onDismiss, ref)
|
||||||
@@ -21,7 +23,9 @@ const ActivityDrawer = ({ onDismiss }) => {
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
className="bg-grey-0 shadow-dropdown rounded-rounded fixed top-[64px] bottom-2 right-3 flex w-[400px] flex-col overflow-x-hidden rounded"
|
className="bg-grey-0 shadow-dropdown rounded-rounded fixed top-[64px] bottom-2 right-3 flex w-[400px] flex-col overflow-x-hidden rounded"
|
||||||
>
|
>
|
||||||
<div className="inter-large-semibold pt-7 pl-8 pb-1">Activity</div>
|
<div className="inter-large-semibold pt-7 pl-8 pb-1">
|
||||||
|
{t("activity-drawer-activity", "Activity")}
|
||||||
|
</div>
|
||||||
|
|
||||||
{!hasPollingError ? (
|
{!hasPollingError ? (
|
||||||
batchJobs ? (
|
batchJobs ? (
|
||||||
@@ -37,33 +41,44 @@ const ActivityDrawer = ({ onDismiss }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const EmptyActivityDrawer = () => {
|
const EmptyActivityDrawer = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full flex-col items-center justify-center p-4">
|
<div className="flex h-full w-full flex-col items-center justify-center p-4">
|
||||||
<SidedMouthFaceIcon size={36} />
|
<SidedMouthFaceIcon size={36} />
|
||||||
<span className={"inter-large-semibold text-grey-90 mt-4"}>
|
<span className={"inter-large-semibold text-grey-90 mt-4"}>
|
||||||
It's quite in here...
|
{t("activity-drawer-no-notifications-title", "It's quiet in here...")}
|
||||||
</span>
|
</span>
|
||||||
<span className={"text-grey-60 inter-base-regular mt-4 text-center"}>
|
<span className={"text-grey-60 inter-base-regular mt-4 text-center"}>
|
||||||
You don't have any notifications at the moment, but once you do they
|
{t(
|
||||||
will live here.
|
"activity-drawer-no-notifications-description",
|
||||||
|
"You don't have any notifications at the moment, but once you do they will live here."
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const ErrorActivityDrawer = () => {
|
const ErrorActivityDrawer = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full flex-col items-center justify-center p-4">
|
<div className="flex h-full w-full flex-col items-center justify-center p-4">
|
||||||
<SadFaceIcon size={36} />
|
<SadFaceIcon size={36} />
|
||||||
<span className={"inter-large-semibold text-grey-90 mt-4"}>Oh no...</span>
|
<span className={"inter-large-semibold text-grey-90 mt-4"}>
|
||||||
|
{t("activity-drawer-error-title", "Oh no...")}
|
||||||
|
</span>
|
||||||
<span className={"text-grey-60 inter-base-regular mt-2 text-center"}>
|
<span className={"text-grey-60 inter-base-regular mt-2 text-center"}>
|
||||||
Something went wrong while trying to fetch your notifications - We will
|
{t(
|
||||||
keep trying!
|
"activity-drawer-error-description",
|
||||||
|
"Something went wrong while trying to fetch your notifications - We will keep trying!"
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div className="mt-4 flex items-center">
|
<div className="mt-4 flex items-center">
|
||||||
<Spinner size={"small"} variant={"secondary"} />
|
<Spinner size={"small"} variant={"secondary"} />
|
||||||
<span className="ml-2.5">Processing...</span>
|
<span className="ml-2.5">
|
||||||
|
{t("activity-drawer-processing", "Processing...")}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import clsx from "clsx"
|
import clsx from "clsx"
|
||||||
import { useEffect } from "react"
|
import { useEffect } from "react"
|
||||||
import { Controller, useWatch } from "react-hook-form"
|
import { Controller, useWatch } from "react-hook-form"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import { NestedForm } from "../../../utils/nested-form"
|
import { NestedForm } from "../../../utils/nested-form"
|
||||||
import Switch from "../../atoms/switch"
|
import Switch from "../../atoms/switch"
|
||||||
import InfoIcon from "../../fundamentals/icons/info-icon"
|
import InfoIcon from "../../fundamentals/icons/info-icon"
|
||||||
@@ -18,6 +19,7 @@ type Props = {
|
|||||||
|
|
||||||
const AnalyticsConfigForm = ({ form, compact }: Props) => {
|
const AnalyticsConfigForm = ({ form, compact }: Props) => {
|
||||||
const { control, setValue, path } = form
|
const { control, setValue, path } = form
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const watchOptOut = useWatch({
|
const watchOptOut = useWatch({
|
||||||
control,
|
control,
|
||||||
@@ -41,13 +43,14 @@ const AnalyticsConfigForm = ({ form, compact }: Props) => {
|
|||||||
<div className="gap-y-2xsmall flex flex-1 flex-col">
|
<div className="gap-y-2xsmall flex flex-1 flex-col">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<h2 className="inter-base-semibold mr-2">
|
<h2 className="inter-base-semibold mr-2">
|
||||||
Anonymize my usage data{" "}
|
{t("analytics-config-form-title", "Anonymize my usage data")}
|
||||||
</h2>
|
</h2>
|
||||||
{compact && (
|
{compact && (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content="You can choose to anonymize your usage data. If this option is
|
content={t(
|
||||||
selected, we will not collect your personal information, such as
|
"analytics-config-form-description",
|
||||||
your name and email address."
|
"You can choose to anonymize your usage data. If this option is selected, we will not collect your personal information, such as your name and email address."
|
||||||
|
)}
|
||||||
side="top"
|
side="top"
|
||||||
>
|
>
|
||||||
<InfoIcon size="18px" color={"#889096"} />
|
<InfoIcon size="18px" color={"#889096"} />
|
||||||
@@ -56,9 +59,10 @@ const AnalyticsConfigForm = ({ form, compact }: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
{!compact && (
|
{!compact && (
|
||||||
<p className="inter-base-regular text-grey-50">
|
<p className="inter-base-regular text-grey-50">
|
||||||
You can choose to anonymize your usage data. If this option is
|
{t(
|
||||||
selected, we will not collect your personal information, such as
|
"analytics-config-form-description",
|
||||||
your name and email address.
|
"You can choose to anonymize your usage data. If this option is selected, we will not collect your personal information, such as your name and email address."
|
||||||
|
)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -80,11 +84,17 @@ const AnalyticsConfigForm = ({ form, compact }: Props) => {
|
|||||||
<div className="gap-y-2xsmall flex flex-1 flex-col">
|
<div className="gap-y-2xsmall flex flex-1 flex-col">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<h2 className="inter-base-semibold mr-2">
|
<h2 className="inter-base-semibold mr-2">
|
||||||
Opt out of sharing my usage data
|
{t(
|
||||||
|
"analytics-config-form-opt-out",
|
||||||
|
"Opt out of sharing my usage data"
|
||||||
|
)}
|
||||||
</h2>
|
</h2>
|
||||||
{compact && (
|
{compact && (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content="You can always opt out of sharing your usage data at any time."
|
content={t(
|
||||||
|
"analytics-config-form-opt-out-later",
|
||||||
|
"You can always opt out of sharing your usage data at any time."
|
||||||
|
)}
|
||||||
side="top"
|
side="top"
|
||||||
>
|
>
|
||||||
<InfoIcon size="18px" color={"#889096"} />
|
<InfoIcon size="18px" color={"#889096"} />
|
||||||
@@ -93,7 +103,10 @@ const AnalyticsConfigForm = ({ form, compact }: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
{!compact && (
|
{!compact && (
|
||||||
<p className="inter-base-regular text-grey-50">
|
<p className="inter-base-regular text-grey-50">
|
||||||
You can always opt out of sharing your usage data at any time.
|
{t(
|
||||||
|
"analytics-config-form-opt-out-later",
|
||||||
|
"You can always opt out of sharing your usage data at any time."
|
||||||
|
)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import clsx from "clsx"
|
import clsx from "clsx"
|
||||||
import { useForm, useWatch } from "react-hook-form"
|
import { useForm, useWatch } from "react-hook-form"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import useNotification from "../../../hooks/use-notification"
|
import useNotification from "../../../hooks/use-notification"
|
||||||
import { useAnalytics } from "../../../providers/analytics-provider"
|
import { useAnalytics } from "../../../providers/analytics-provider"
|
||||||
import { useAdminCreateAnalyticsConfig } from "../../../services/analytics"
|
import { useAdminCreateAnalyticsConfig } from "../../../services/analytics"
|
||||||
@@ -18,6 +19,7 @@ type AnalyticsPreferenceFormType = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const AnalyticsPreferencesModal = () => {
|
const AnalyticsPreferencesModal = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const notification = useNotification()
|
const notification = useNotification()
|
||||||
const { mutate, isLoading } = useAdminCreateAnalyticsConfig()
|
const { mutate, isLoading } = useAdminCreateAnalyticsConfig()
|
||||||
|
|
||||||
@@ -56,8 +58,11 @@ const AnalyticsPreferencesModal = () => {
|
|||||||
mutate(config, {
|
mutate(config, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
notification(
|
notification(
|
||||||
"Success",
|
t("analytics-preferences-success", "Success"),
|
||||||
"Your preferences were successfully updated",
|
t(
|
||||||
|
"analytics-preferences-your-preferences-were-successfully-updated",
|
||||||
|
"Your preferences were successfully updated"
|
||||||
|
),
|
||||||
"success"
|
"success"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -68,7 +73,11 @@ const AnalyticsPreferencesModal = () => {
|
|||||||
setSubmittingConfig(false)
|
setSubmittingConfig(false)
|
||||||
},
|
},
|
||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
notification("Error", getErrorMessage(err), "error")
|
notification(
|
||||||
|
t("analytics-preferences-error", "Error"),
|
||||||
|
getErrorMessage(err),
|
||||||
|
"error"
|
||||||
|
)
|
||||||
setSubmittingConfig(false)
|
setSubmittingConfig(false)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -80,27 +89,29 @@ const AnalyticsPreferencesModal = () => {
|
|||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
<div className="mt-5xlarge flex w-full max-w-[664px] flex-col">
|
<div className="mt-5xlarge flex w-full max-w-[664px] flex-col">
|
||||||
<h1 className="inter-xlarge-semibold mb-large">
|
<h1 className="inter-xlarge-semibold mb-large">
|
||||||
Help us get better
|
{t(
|
||||||
|
"analytics-preferences-help-us-get-better",
|
||||||
|
"Help us get better"
|
||||||
|
)}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-grey-50">
|
<p className="text-grey-50">
|
||||||
To create the most compelling e-commerce experience we would like
|
{t(
|
||||||
to gain insights in how you use Medusa. User insights allow us to
|
"analytics-preferences-disclaimer",
|
||||||
build a better, more engaging, and more usable products. We only
|
"To create the most compelling e-commerce experience we would like to gain insights in how you use Medusa. User insights allow us to build a better, more engaging, and more usable products. We only collect data for product improvements. Read what data we gather in our"
|
||||||
collect data for product improvements. Read what data we gather in
|
)}{" "}
|
||||||
our{" "}
|
|
||||||
<a
|
<a
|
||||||
href="https://docs.medusajs.com/usage"
|
href="https://docs.medusajs.com/usage"
|
||||||
rel="noreferrer noopener"
|
rel="noreferrer noopener"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="text-violet-60"
|
className="text-violet-60"
|
||||||
>
|
>
|
||||||
documentation
|
{t("analytics-preferences-documentation", "documentation")}
|
||||||
</a>
|
</a>
|
||||||
.
|
.
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-xlarge gap-y-xlarge flex flex-col">
|
<div className="mt-xlarge gap-y-xlarge flex flex-col">
|
||||||
<InputField
|
<InputField
|
||||||
label="Email"
|
label={"Email"}
|
||||||
placeholder="you@company.com"
|
placeholder="you@company.com"
|
||||||
disabled={watchOptOut || watchAnonymize}
|
disabled={watchOptOut || watchAnonymize}
|
||||||
className={clsx("transition-opacity", {
|
className={clsx("transition-opacity", {
|
||||||
@@ -108,7 +119,10 @@ const AnalyticsPreferencesModal = () => {
|
|||||||
})}
|
})}
|
||||||
{...register("email", {
|
{...register("email", {
|
||||||
pattern: {
|
pattern: {
|
||||||
message: "Please enter a valid email",
|
message: t(
|
||||||
|
"analytics-preferences-please-enter-a-valid-email",
|
||||||
|
"Please enter a valid email"
|
||||||
|
),
|
||||||
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
|
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
|
||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
@@ -123,7 +137,7 @@ const AnalyticsPreferencesModal = () => {
|
|||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
onClick={onSubmit}
|
onClick={onSubmit}
|
||||||
>
|
>
|
||||||
Continue
|
{t("analytics-preferences-continue", "Continue")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import PlusIcon from "../../fundamentals/icons/plus-icon"
|
|||||||
import InputHeader from "../../fundamentals/input-header"
|
import InputHeader from "../../fundamentals/input-header"
|
||||||
import Input from "../../molecules/input"
|
import Input from "../../molecules/input"
|
||||||
import Select from "../../molecules/select"
|
import Select from "../../molecules/select"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
type CurrencyInputProps = {
|
type CurrencyInputProps = {
|
||||||
currencyCodes?: string[]
|
currencyCodes?: string[]
|
||||||
@@ -74,6 +75,7 @@ const Root: React.FC<CurrencyInputProps> = ({
|
|||||||
label: code.toUpperCase(),
|
label: code.toUpperCase(),
|
||||||
value: code,
|
value: code,
|
||||||
})) ?? []
|
})) ?? []
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const [selectedCurrency, setSelectedCurrency] = useState<
|
const [selectedCurrency, setSelectedCurrency] = useState<
|
||||||
CurrencyType | undefined
|
CurrencyType | undefined
|
||||||
@@ -132,7 +134,7 @@ const Root: React.FC<CurrencyInputProps> = ({
|
|||||||
{!readOnly ? (
|
{!readOnly ? (
|
||||||
<Select
|
<Select
|
||||||
enableSearch
|
enableSearch
|
||||||
label="Currency"
|
label={t("currency-input-currency", "Currency")}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onCurrencyChange}
|
onChange={onCurrencyChange}
|
||||||
options={options}
|
options={options}
|
||||||
@@ -140,7 +142,7 @@ const Root: React.FC<CurrencyInputProps> = ({
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Input
|
<Input
|
||||||
label="Currency"
|
label={t("currency-input-currency", "Currency")}
|
||||||
value={value?.label}
|
value={value?.label}
|
||||||
readOnly
|
readOnly
|
||||||
className="pointer-events-none"
|
className="pointer-events-none"
|
||||||
@@ -172,6 +174,8 @@ const Amount = forwardRef<HTMLInputElement, AmountInputProps>(
|
|||||||
}: AmountInputProps,
|
}: AmountInputProps,
|
||||||
ref
|
ref
|
||||||
) => {
|
) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const { currencyInfo } = useContext(CurrencyContext)
|
const { currencyInfo } = useContext(CurrencyContext)
|
||||||
const [invalid, setInvalid] = useState<boolean>(false)
|
const [invalid, setInvalid] = useState<boolean>(false)
|
||||||
const [formattedValue, setFormattedValue] = useState<string | undefined>(
|
const [formattedValue, setFormattedValue] = useState<string | undefined>(
|
||||||
@@ -250,7 +254,10 @@ const Amount = forwardRef<HTMLInputElement, AmountInputProps>(
|
|||||||
<Tooltip
|
<Tooltip
|
||||||
open={invalid}
|
open={invalid}
|
||||||
side={"top"}
|
side={"top"}
|
||||||
content={invalidMessage || "Amount is not valid"}
|
content={
|
||||||
|
invalidMessage ||
|
||||||
|
t("currency-input-amount-is-not-valid", "Amount is not valid")
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<span className="inter-base-regular text-grey-40 mr-xsmall">
|
<span className="inter-base-regular text-grey-40 mr-xsmall">
|
||||||
{currencyInfo.symbol_native}
|
{currencyInfo.symbol_native}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { useState } from "react"
|
import React, { useState } from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
import useNotification from "../../hooks/use-notification"
|
import useNotification from "../../hooks/use-notification"
|
||||||
import { getErrorMessage } from "../../utils/error-messages"
|
import { getErrorMessage } from "../../utils/error-messages"
|
||||||
@@ -16,14 +17,15 @@ type DeletePromptProps = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const DeletePrompt: React.FC<DeletePromptProps> = ({
|
const DeletePrompt: React.FC<DeletePromptProps> = ({
|
||||||
heading = "Are you sure you want to delete?",
|
heading,
|
||||||
text = "",
|
text = "",
|
||||||
successText = "Delete successful",
|
successText,
|
||||||
cancelText = "No, cancel",
|
cancelText,
|
||||||
confirmText = "Yes, remove",
|
confirmText,
|
||||||
handleClose,
|
handleClose,
|
||||||
onDelete,
|
onDelete,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const notification = useNotification()
|
const notification = useNotification()
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
|
||||||
@@ -34,7 +36,12 @@ const DeletePrompt: React.FC<DeletePromptProps> = ({
|
|||||||
onDelete()
|
onDelete()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (successText) {
|
if (successText) {
|
||||||
notification("Success", successText, "success")
|
notification(
|
||||||
|
t("organisms-success", "Success"),
|
||||||
|
successText ||
|
||||||
|
t("organisms-delete-successful", "Delete successful"),
|
||||||
|
"success"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => notification("Error", getErrorMessage(err), "error"))
|
.catch((err) => notification("Error", getErrorMessage(err), "error"))
|
||||||
@@ -49,7 +56,13 @@ const DeletePrompt: React.FC<DeletePromptProps> = ({
|
|||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
<Modal.Content>
|
<Modal.Content>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<span className="inter-large-semibold">{heading}</span>
|
<span className="inter-large-semibold">
|
||||||
|
{heading ||
|
||||||
|
t(
|
||||||
|
"organisms-are-you-sure-you-want-to-delete",
|
||||||
|
"Are you sure you want to delete?"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
<span className="inter-base-regular text-grey-50 mt-1">{text}</span>
|
<span className="inter-base-regular text-grey-50 mt-1">{text}</span>
|
||||||
</div>
|
</div>
|
||||||
</Modal.Content>
|
</Modal.Content>
|
||||||
@@ -61,7 +74,7 @@ const DeletePrompt: React.FC<DeletePromptProps> = ({
|
|||||||
size="small"
|
size="small"
|
||||||
onClick={handleClose}
|
onClick={handleClose}
|
||||||
>
|
>
|
||||||
{cancelText}
|
{cancelText || t("organisms-no-cancel", "No, cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
@@ -71,7 +84,7 @@ const DeletePrompt: React.FC<DeletePromptProps> = ({
|
|||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
>
|
>
|
||||||
{confirmText}
|
{confirmText || t("organisms-yes-remove", "Yes, remove")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import * as RadixCollapsible from "@radix-ui/react-collapsible"
|
import * as RadixCollapsible from "@radix-ui/react-collapsible"
|
||||||
import clsx from "clsx"
|
import clsx from "clsx"
|
||||||
import React, { useState } from "react"
|
import React, { useState } from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import ArrowDownIcon from "../../fundamentals/icons/arrow-down-icon"
|
import ArrowDownIcon from "../../fundamentals/icons/arrow-down-icon"
|
||||||
import ArrowUpIcon from "../../fundamentals/icons/arrow-up-icon"
|
import ArrowUpIcon from "../../fundamentals/icons/arrow-up-icon"
|
||||||
|
|
||||||
@@ -17,10 +18,19 @@ const DetailsCollapsible = ({
|
|||||||
contentProps,
|
contentProps,
|
||||||
children,
|
children,
|
||||||
}: DetailsCollapsibleProps) => {
|
}: DetailsCollapsibleProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
|
|
||||||
const Icon = open ? ArrowUpIcon : ArrowDownIcon
|
const Icon = open ? ArrowUpIcon : ArrowDownIcon
|
||||||
const label = open ? "Hide additional details" : "Show additional details"
|
const label = open
|
||||||
|
? t(
|
||||||
|
"details-collapsible-hide-additional-details",
|
||||||
|
"Hide additional details"
|
||||||
|
)
|
||||||
|
: t(
|
||||||
|
"details-collapsible-show-additional-details",
|
||||||
|
"Show additional details"
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RadixCollapsible.Root
|
<RadixCollapsible.Root
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { User } from "@medusajs/medusa"
|
|||||||
import { useAdminUpdateUser } from "medusa-react"
|
import { useAdminUpdateUser } from "medusa-react"
|
||||||
import React, { useEffect } from "react"
|
import React, { useEffect } from "react"
|
||||||
import { useForm } from "react-hook-form"
|
import { useForm } from "react-hook-form"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import useNotification from "../../../hooks/use-notification"
|
import useNotification from "../../../hooks/use-notification"
|
||||||
import { getErrorMessage } from "../../../utils/error-messages"
|
import { getErrorMessage } from "../../../utils/error-messages"
|
||||||
import FormValidator from "../../../utils/form-validator"
|
import FormValidator from "../../../utils/form-validator"
|
||||||
@@ -33,6 +34,7 @@ const EditUserModal: React.FC<EditUserModalProps> = ({
|
|||||||
formState: { errors },
|
formState: { errors },
|
||||||
} = useForm<EditUserModalFormData>()
|
} = useForm<EditUserModalFormData>()
|
||||||
const notification = useNotification()
|
const notification = useNotification()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
reset(mapUser(user))
|
reset(mapUser(user))
|
||||||
@@ -41,11 +43,19 @@ const EditUserModal: React.FC<EditUserModalProps> = ({
|
|||||||
const onSubmit = (data: EditUserModalFormData) => {
|
const onSubmit = (data: EditUserModalFormData) => {
|
||||||
mutate(data, {
|
mutate(data, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
notification("Success", `User was updated`, "success")
|
notification(
|
||||||
|
t("edit-user-modal-success", "Success"),
|
||||||
|
t("edit-user-modal-user-was-updated", "User was updated"),
|
||||||
|
"success"
|
||||||
|
)
|
||||||
onSuccess()
|
onSuccess()
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
notification("Error", getErrorMessage(error), "error")
|
notification(
|
||||||
|
t("edit-user-modal-error", "Error"),
|
||||||
|
getErrorMessage(error),
|
||||||
|
"error"
|
||||||
|
)
|
||||||
},
|
},
|
||||||
onSettled: () => {
|
onSettled: () => {
|
||||||
handleClose()
|
handleClose()
|
||||||
@@ -58,13 +68,18 @@ const EditUserModal: React.FC<EditUserModalProps> = ({
|
|||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
<Modal.Header handleClose={handleClose}>
|
<Modal.Header handleClose={handleClose}>
|
||||||
<span className="inter-xlarge-semibold">Edit User</span>
|
<span className="inter-xlarge-semibold">
|
||||||
|
{t("edit-user-modal-edit-user", "Edit User")}
|
||||||
|
</span>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<Modal.Content>
|
<Modal.Content>
|
||||||
<div className="gap-large mb-base grid w-full grid-cols-2">
|
<div className="gap-large mb-base grid w-full grid-cols-2">
|
||||||
<InputField
|
<InputField
|
||||||
label="First Name"
|
label={t("edit-user-modal-first-name-label", "First Name")}
|
||||||
placeholder="First name..."
|
placeholder={t(
|
||||||
|
"edit-user-modal-first-name-placeholder",
|
||||||
|
"First name..."
|
||||||
|
)}
|
||||||
required
|
required
|
||||||
{...register("first_name", {
|
{...register("first_name", {
|
||||||
required: FormValidator.required("First name"),
|
required: FormValidator.required("First name"),
|
||||||
@@ -74,8 +89,11 @@ const EditUserModal: React.FC<EditUserModalProps> = ({
|
|||||||
errors={errors}
|
errors={errors}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
label="Last Name"
|
label={t("edit-user-modal-last-name-label", "Last Name")}
|
||||||
placeholder="Last name..."
|
placeholder={t(
|
||||||
|
"edit-user-modal-last-name-placeholder",
|
||||||
|
"Last name..."
|
||||||
|
)}
|
||||||
required
|
required
|
||||||
{...register("last_name", {
|
{...register("last_name", {
|
||||||
required: FormValidator.required("Last name"),
|
required: FormValidator.required("Last name"),
|
||||||
@@ -85,7 +103,11 @@ const EditUserModal: React.FC<EditUserModalProps> = ({
|
|||||||
errors={errors}
|
errors={errors}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<InputField label="Email" disabled value={user.email} />
|
<InputField
|
||||||
|
label={t("edit-user-modal-email", "Email")}
|
||||||
|
disabled
|
||||||
|
value={user.email}
|
||||||
|
/>
|
||||||
</Modal.Content>
|
</Modal.Content>
|
||||||
<Modal.Footer>
|
<Modal.Footer>
|
||||||
<div className="flex w-full justify-end">
|
<div className="flex w-full justify-end">
|
||||||
@@ -95,7 +117,7 @@ const EditUserModal: React.FC<EditUserModalProps> = ({
|
|||||||
onClick={handleClose}
|
onClick={handleClose}
|
||||||
className="mr-2"
|
className="mr-2"
|
||||||
>
|
>
|
||||||
Cancel
|
{t("edit-user-modal-cancel", "Cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
@@ -103,7 +125,7 @@ const EditUserModal: React.FC<EditUserModalProps> = ({
|
|||||||
variant="primary"
|
variant="primary"
|
||||||
size="small"
|
size="small"
|
||||||
>
|
>
|
||||||
Save
|
{t("edit-user-modal-save", "Save")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { AxiosError } from "axios"
|
import { AxiosError } from "axios"
|
||||||
import React, { ErrorInfo } from "react"
|
import React, { ErrorInfo } from "react"
|
||||||
import { analyticsOptIn } from "../../../services/analytics"
|
import { analyticsOptIn } from "../../../services/analytics"
|
||||||
|
import { Translation } from "react-i18next"
|
||||||
import Button from "../../fundamentals/button"
|
import Button from "../../fundamentals/button"
|
||||||
import { WRITE_KEY } from "../../../constants/analytics"
|
import { WRITE_KEY } from "../../../constants/analytics"
|
||||||
import { AnalyticsBrowser } from "@segment/analytics-next"
|
import { AnalyticsBrowser } from "@segment/analytics-next"
|
||||||
|
import { TFunction } from "i18next"
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
hasError: boolean
|
hasError: boolean
|
||||||
@@ -16,7 +18,7 @@ type Props = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Analytics instance used for tracking errors
|
// Analytics instance used for tracking errors
|
||||||
let analyticsInstance: ReturnType<typeof AnalyticsBrowser.load> | undefined;
|
let analyticsInstance: ReturnType<typeof AnalyticsBrowser.load> | undefined
|
||||||
|
|
||||||
const analytics = () => {
|
const analytics = () => {
|
||||||
if (!analyticsInstance) {
|
if (!analyticsInstance) {
|
||||||
@@ -75,10 +77,14 @@ class ErrorBoundary extends React.Component<Props, State> {
|
|||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<h1 className="inter-xlarge-semibold mb-xsmall">
|
<h1 className="inter-xlarge-semibold mb-xsmall">
|
||||||
{errorMessage(this.state.status)}
|
<Translation>
|
||||||
|
{(t) => errorMessage(t, this.state.status)}
|
||||||
|
</Translation>
|
||||||
</h1>
|
</h1>
|
||||||
<p className="inter-base-regular text-grey-50">
|
<p className="inter-base-regular text-grey-50">
|
||||||
{errorDescription(this.state.status)}
|
<Translation>
|
||||||
|
{(t) => errorDescription(t, this.state.status)}
|
||||||
|
</Translation>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -88,7 +94,11 @@ class ErrorBoundary extends React.Component<Props, State> {
|
|||||||
variant="primary"
|
variant="primary"
|
||||||
onClick={this.dismissError}
|
onClick={this.dismissError}
|
||||||
>
|
>
|
||||||
Back to dashboard
|
<Translation>
|
||||||
|
{(t) =>
|
||||||
|
t("error-boundary-back-to-dashboard", "Back to dashboard")
|
||||||
|
}
|
||||||
|
</Translation>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -112,43 +122,72 @@ const shouldTrackEvent = async (error: Error) => {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return await analyticsOptIn();
|
return await analyticsOptIn()
|
||||||
}
|
}
|
||||||
|
|
||||||
const errorMessage = (status?: number) => {
|
const errorMessage = (t: TFunction, status?: number) => {
|
||||||
const defaultMessage = "An unknown error occured"
|
const defaultMessage = t(
|
||||||
|
"error-boundary-an-unknown-error-occured",
|
||||||
|
"An unknown error occured"
|
||||||
|
)
|
||||||
|
|
||||||
if (!status) {
|
if (!status) {
|
||||||
return defaultMessage
|
return defaultMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = {
|
const message = {
|
||||||
400: "Bad request",
|
400: t("error-boundary-bad-request", "Bad request"),
|
||||||
401: "You are not logged in",
|
401: t("error-boundary-you-are-not-logged-in", "You are not logged in"),
|
||||||
403: "You do not have permission perform this action",
|
403: t(
|
||||||
404: "Page was not found",
|
"error-boundary-you-do-not-have-permission-perform-this-action",
|
||||||
500: "An unknown server error occured",
|
"You do not have permission perform this action"
|
||||||
503: "Server is currently unavailable",
|
),
|
||||||
|
404: t("error-boundary-page-was-not-found", "Page was not found"),
|
||||||
|
500: t(
|
||||||
|
"error-boundary-an-unknown-server-error-occured",
|
||||||
|
"An unknown server error occured"
|
||||||
|
),
|
||||||
|
503: t("error-boundary-503", "Server is currently unavailable"),
|
||||||
}[status]
|
}[status]
|
||||||
|
|
||||||
return message || defaultMessage
|
return message || defaultMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
const errorDescription = (status?: number) => {
|
const errorDescription = (t: TFunction, status?: number) => {
|
||||||
const defaultDescription =
|
const defaultDescription = t(
|
||||||
|
"error-boundary-500",
|
||||||
"An error occurred with unspecified causes, this is most likely due to a techinical issue on our end. Please try refreshing the page. If the issue keeps happening, contact your administrator."
|
"An error occurred with unspecified causes, this is most likely due to a techinical issue on our end. Please try refreshing the page. If the issue keeps happening, contact your administrator."
|
||||||
|
)
|
||||||
|
|
||||||
if (!status) {
|
if (!status) {
|
||||||
return defaultDescription
|
return defaultDescription
|
||||||
}
|
}
|
||||||
|
|
||||||
const description = {
|
const description = {
|
||||||
400: "The request was malformed, fix your request and please try again.",
|
400: t(
|
||||||
401: "You are not logged in, please log in to proceed.",
|
"error-boundary-400",
|
||||||
403: "You do not have permission perform this action, if you think this is a mistake, contact your administrator.",
|
"The request was malformed, fix your request and please try again."
|
||||||
404: "The page you have requested was not found, please check the URL and try again.",
|
),
|
||||||
500: "The server was not able to handle your request, this is mostly likely due to a techinical issue on our end. Please try again. If the issue keeps happening, contact your administrator.",
|
401: t(
|
||||||
503: "The server is temporarily unavailable, and your request could not be processed. Please try again later. If the issue keeps happening, contact your administrator.",
|
"error-boundary-401",
|
||||||
|
"You are not logged in, please log in to proceed."
|
||||||
|
),
|
||||||
|
403: t(
|
||||||
|
"error-boundary-403",
|
||||||
|
"You do not have permission perform this action, if you think this is a mistake, contact your administrator."
|
||||||
|
),
|
||||||
|
404: t(
|
||||||
|
"error-boundary-404",
|
||||||
|
"The page you have requested was not found, please check the URL and try again."
|
||||||
|
),
|
||||||
|
500: t(
|
||||||
|
"error-boundary-500-2",
|
||||||
|
"The server was not able to handle your request, this is mostly likely due to a techinical issue on our end. Please try again. If the issue keeps happening, contact your administrator."
|
||||||
|
),
|
||||||
|
503: t(
|
||||||
|
"error-boundary-503-2",
|
||||||
|
"The server is temporarily unavailable, and your request could not be processed. Please try again later. If the issue keeps happening, contact your administrator."
|
||||||
|
),
|
||||||
}[status]
|
}[status]
|
||||||
|
|
||||||
return description || defaultDescription
|
return description || defaultDescription
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from "react"
|
import React from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import Button from "../../fundamentals/button"
|
import Button from "../../fundamentals/button"
|
||||||
import Modal from "../../molecules/modal"
|
import Modal from "../../molecules/modal"
|
||||||
|
|
||||||
@@ -15,6 +16,7 @@ const ExportModal: React.FC<ExportModalProps> = ({
|
|||||||
loading,
|
loading,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
return (
|
return (
|
||||||
<Modal handleClose={handleClose}>
|
<Modal handleClose={handleClose}>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
@@ -30,7 +32,7 @@ const ExportModal: React.FC<ExportModalProps> = ({
|
|||||||
overview.
|
overview.
|
||||||
</div> */}
|
</div> */}
|
||||||
<div className="inter-small-regular text-grey-50 mb-4 flex">
|
<div className="inter-small-regular text-grey-50 mb-4 flex">
|
||||||
Initialize an export of your data
|
{t("export-modal-title", "Initialize an export of your data")}
|
||||||
</div>
|
</div>
|
||||||
</Modal.Content>
|
</Modal.Content>
|
||||||
<Modal.Footer>
|
<Modal.Footer>
|
||||||
@@ -41,7 +43,7 @@ const ExportModal: React.FC<ExportModalProps> = ({
|
|||||||
onClick={handleClose}
|
onClick={handleClose}
|
||||||
className="mr-2"
|
className="mr-2"
|
||||||
>
|
>
|
||||||
Cancel
|
{t("export-modal-cancel", "Cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
loading={loading}
|
loading={loading}
|
||||||
@@ -50,7 +52,7 @@ const ExportModal: React.FC<ExportModalProps> = ({
|
|||||||
size="small"
|
size="small"
|
||||||
onClick={onSubmit}
|
onClick={onSubmit}
|
||||||
>
|
>
|
||||||
Export
|
{t("export-modal-export", "Export")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from "react"
|
import React from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import FileUploadField from "../../atoms/file-upload-field"
|
import FileUploadField from "../../atoms/file-upload-field"
|
||||||
import Modal from "../../molecules/modal"
|
import Modal from "../../molecules/modal"
|
||||||
|
|
||||||
@@ -13,11 +14,14 @@ const FileUploadModal: React.FC<FileUploadModalProps> = ({
|
|||||||
filetypes,
|
filetypes,
|
||||||
setFiles,
|
setFiles,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
return (
|
return (
|
||||||
<Modal handleClose={handleClose}>
|
<Modal handleClose={handleClose}>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
<Modal.Header handleClose={handleClose}>
|
<Modal.Header handleClose={handleClose}>
|
||||||
<span className="inter-xlarge-semibold">Upload a new photo</span>
|
<span className="inter-xlarge-semibold">
|
||||||
|
{t("file-upload-modal-upload-a-new-photo", "Upload a new photo")}
|
||||||
|
</span>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<Modal.Content>
|
<Modal.Content>
|
||||||
<div className="h-96">
|
<div className="h-96">
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { useMemo } from "react"
|
import React, { useMemo } from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import { normalizeAmount } from "../../../utils/prices"
|
import { normalizeAmount } from "../../../utils/prices"
|
||||||
import EditIcon from "../../fundamentals/icons/edit-icon"
|
import EditIcon from "../../fundamentals/icons/edit-icon"
|
||||||
import TrashIcon from "../../fundamentals/icons/trash-icon"
|
import TrashIcon from "../../fundamentals/icons/trash-icon"
|
||||||
@@ -6,7 +7,7 @@ import UnpublishIcon from "../../fundamentals/icons/unpublish-icon"
|
|||||||
import StatusIndicator from "../../fundamentals/status-indicator"
|
import StatusIndicator from "../../fundamentals/status-indicator"
|
||||||
import { ActionType } from "../../molecules/actionables"
|
import { ActionType } from "../../molecules/actionables"
|
||||||
import BannerCard from "../../molecules/banner-card"
|
import BannerCard from "../../molecules/banner-card"
|
||||||
import TagGrid from "../../molecules/tag-grid.tsx"
|
import TagGrid from "../../molecules/tag-grid"
|
||||||
|
|
||||||
type GiftCardVariant = {
|
type GiftCardVariant = {
|
||||||
prices: {
|
prices: {
|
||||||
@@ -38,19 +39,23 @@ const GiftCardBanner: React.FC<GiftCardBannerProps> = ({
|
|||||||
onUnpublish,
|
onUnpublish,
|
||||||
onDelete,
|
onDelete,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const actions: ActionType[] = [
|
const actions: ActionType[] = [
|
||||||
{
|
{
|
||||||
label: "Edit",
|
label: t("gift-card-banner-edit", "Edit"),
|
||||||
onClick: onEdit,
|
onClick: onEdit,
|
||||||
icon: <EditIcon size={16} />,
|
icon: <EditIcon size={16} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: status === "published" ? "Unpublish" : "Publish",
|
label:
|
||||||
|
status === "published"
|
||||||
|
? t("gift-card-banner-unpublish", "Unpublish")
|
||||||
|
: t("gift-card-banner-publish", "Publish"),
|
||||||
onClick: onUnpublish,
|
onClick: onUnpublish,
|
||||||
icon: <UnpublishIcon size={16} />,
|
icon: <UnpublishIcon size={16} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Delete",
|
label: t("gift-card-banner-delete", "Delete"),
|
||||||
onClick: onDelete,
|
onClick: onDelete,
|
||||||
icon: <TrashIcon size={16} />,
|
icon: <TrashIcon size={16} />,
|
||||||
variant: "danger",
|
variant: "danger",
|
||||||
@@ -84,7 +89,11 @@ const GiftCardBanner: React.FC<GiftCardBannerProps> = ({
|
|||||||
<TagGrid tags={denominations} badgeVariant="default" />
|
<TagGrid tags={denominations} badgeVariant="default" />
|
||||||
<StatusIndicator
|
<StatusIndicator
|
||||||
variant={status === "published" ? "success" : "danger"}
|
variant={status === "published" ? "success" : "danger"}
|
||||||
title={status === "published" ? "Published" : "Unpublished"}
|
title={
|
||||||
|
status === "published"
|
||||||
|
? t("gift-card-banner-published", "Published")
|
||||||
|
: t("gift-card-banner-unpublished", "Unpublished")
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</BannerCard.Footer>
|
</BannerCard.Footer>
|
||||||
|
|||||||
+30
-7
@@ -2,6 +2,7 @@ import { Product } from "@medusajs/medusa"
|
|||||||
import { useAdminCreateVariant, useAdminStore } from "medusa-react"
|
import { useAdminCreateVariant, useAdminStore } from "medusa-react"
|
||||||
import { useCallback, useMemo } from "react"
|
import { useCallback, useMemo } from "react"
|
||||||
import { useForm } from "react-hook-form"
|
import { useForm } from "react-hook-form"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import useNotification from "../../../hooks/use-notification"
|
import useNotification from "../../../hooks/use-notification"
|
||||||
import { getErrorMessage } from "../../../utils/error-messages"
|
import { getErrorMessage } from "../../../utils/error-messages"
|
||||||
import { nestedForm } from "../../../utils/nested-form"
|
import { nestedForm } from "../../../utils/nested-form"
|
||||||
@@ -31,6 +32,7 @@ type AddDenominationModalFormType = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const AddDenominationModal = ({ open, onClose, giftCard }: Props) => {
|
const AddDenominationModal = ({ open, onClose, giftCard }: Props) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const { mutate, isLoading: isMutating } = useAdminCreateVariant(giftCard.id)
|
const { mutate, isLoading: isMutating } = useAdminCreateVariant(giftCard.id)
|
||||||
|
|
||||||
const { store } = useAdminStore()
|
const { store } = useAdminStore()
|
||||||
@@ -114,8 +116,14 @@ const AddDenominationModal = ({ open, onClose, giftCard }: Props) => {
|
|||||||
mutate(payload, {
|
mutate(payload, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
notification(
|
notification(
|
||||||
"Denomination added",
|
t(
|
||||||
"A new denomination was successfully added",
|
"gift-card-denominations-section-denomination-added",
|
||||||
|
"Denomination added"
|
||||||
|
),
|
||||||
|
t(
|
||||||
|
"gift-card-denominations-section-a-new-denomination-was-successfully-added",
|
||||||
|
"A new denomination was successfully added"
|
||||||
|
),
|
||||||
"success"
|
"success"
|
||||||
)
|
)
|
||||||
handleClose()
|
handleClose()
|
||||||
@@ -124,13 +132,20 @@ const AddDenominationModal = ({ open, onClose, giftCard }: Props) => {
|
|||||||
const errorMessage = () => {
|
const errorMessage = () => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (error.response?.data?.type === "duplicate_error") {
|
if (error.response?.data?.type === "duplicate_error") {
|
||||||
return `A denomination with that default value already exists`
|
return t(
|
||||||
|
"gift-card-denominations-section-a-denomination-with-that-default-value-already-exists",
|
||||||
|
"A denomination with that default value already exists"
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
return getErrorMessage(error)
|
return getErrorMessage(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
notification("Error", errorMessage(), "error")
|
notification(
|
||||||
|
t("gift-card-denominations-section-error", "Error"),
|
||||||
|
errorMessage(),
|
||||||
|
"error"
|
||||||
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -139,7 +154,12 @@ const AddDenominationModal = ({ open, onClose, giftCard }: Props) => {
|
|||||||
<Modal open={open} handleClose={handleClose}>
|
<Modal open={open} handleClose={handleClose}>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
<Modal.Header handleClose={handleClose}>
|
<Modal.Header handleClose={handleClose}>
|
||||||
<h1 className="inter-xlarge-semibold">Add Denomination</h1>
|
<h1 className="inter-xlarge-semibold">
|
||||||
|
{t(
|
||||||
|
"gift-card-denominations-section-add-denomination",
|
||||||
|
"Add Denomination"
|
||||||
|
)}
|
||||||
|
</h1>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<form onSubmit={onSubmit}>
|
<form onSubmit={onSubmit}>
|
||||||
<Modal.Content>
|
<Modal.Content>
|
||||||
@@ -153,7 +173,7 @@ const AddDenominationModal = ({ open, onClose, giftCard }: Props) => {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={handleClose}
|
onClick={handleClose}
|
||||||
>
|
>
|
||||||
Cancel
|
{t("gift-card-denominations-section-cancel", "Cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
@@ -162,7 +182,10 @@ const AddDenominationModal = ({ open, onClose, giftCard }: Props) => {
|
|||||||
disabled={isMutating || !isDirty}
|
disabled={isMutating || !isDirty}
|
||||||
loading={isMutating}
|
loading={isMutating}
|
||||||
>
|
>
|
||||||
Save and close
|
{t(
|
||||||
|
"gift-card-denominations-section-save-and-close",
|
||||||
|
"Save and close"
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
|
|||||||
+26
-6
@@ -7,6 +7,7 @@ import {
|
|||||||
} from "medusa-react"
|
} from "medusa-react"
|
||||||
import { useCallback, useEffect } from "react"
|
import { useCallback, useEffect } from "react"
|
||||||
import { useForm } from "react-hook-form"
|
import { useForm } from "react-hook-form"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import useNotification from "../../../hooks/use-notification"
|
import useNotification from "../../../hooks/use-notification"
|
||||||
import { getErrorMessage } from "../../../utils/error-messages"
|
import { getErrorMessage } from "../../../utils/error-messages"
|
||||||
import { nestedForm } from "../../../utils/nested-form"
|
import { nestedForm } from "../../../utils/nested-form"
|
||||||
@@ -31,6 +32,7 @@ const EditDenominationsModal = ({
|
|||||||
onClose,
|
onClose,
|
||||||
open,
|
open,
|
||||||
}: EditDenominationsModalProps) => {
|
}: EditDenominationsModalProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const { store } = useAdminStore()
|
const { store } = useAdminStore()
|
||||||
const { mutate, isLoading } = useAdminUpdateVariant(denomination.product_id)
|
const { mutate, isLoading } = useAdminUpdateVariant(denomination.product_id)
|
||||||
|
|
||||||
@@ -91,15 +93,25 @@ const EditDenominationsModal = ({
|
|||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
notification(
|
notification(
|
||||||
"Denomination updated",
|
t(
|
||||||
"A new denomination was successfully updated",
|
"gift-card-denominations-section-denomination-updated",
|
||||||
|
"Denomination updated"
|
||||||
|
),
|
||||||
|
t(
|
||||||
|
"gift-card-denominations-section-a-new-denomination-was-successfully-updated",
|
||||||
|
"A new denomination was successfully updated"
|
||||||
|
),
|
||||||
"success"
|
"success"
|
||||||
)
|
)
|
||||||
queryClient.invalidateQueries(adminProductKeys.all)
|
queryClient.invalidateQueries(adminProductKeys.all)
|
||||||
handleClose()
|
handleClose()
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
notification("Error", getErrorMessage(error), "error")
|
notification(
|
||||||
|
t("gift-card-denominations-section-error", "Error"),
|
||||||
|
getErrorMessage(error),
|
||||||
|
"error"
|
||||||
|
)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -109,7 +121,12 @@ const EditDenominationsModal = ({
|
|||||||
<Modal open={open} handleClose={handleClose}>
|
<Modal open={open} handleClose={handleClose}>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
<Modal.Header handleClose={handleClose}>
|
<Modal.Header handleClose={handleClose}>
|
||||||
<h1 className="inter-xlarge-semibold">Edit Denomination</h1>
|
<h1 className="inter-xlarge-semibold">
|
||||||
|
{t(
|
||||||
|
"gift-card-denominations-section-edit-denomination",
|
||||||
|
"Edit Denomination"
|
||||||
|
)}
|
||||||
|
</h1>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<form onSubmit={onSubmit}>
|
<form onSubmit={onSubmit}>
|
||||||
<Modal.Content>
|
<Modal.Content>
|
||||||
@@ -123,7 +140,7 @@ const EditDenominationsModal = ({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={handleClose}
|
onClick={handleClose}
|
||||||
>
|
>
|
||||||
Cancel
|
{t("gift-card-denominations-section-cancel", "Cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
@@ -132,7 +149,10 @@ const EditDenominationsModal = ({
|
|||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
disabled={!isDirty || isLoading}
|
disabled={!isDirty || isLoading}
|
||||||
>
|
>
|
||||||
Save and close
|
{t(
|
||||||
|
"gift-card-denominations-section-save-and-close",
|
||||||
|
"Save and close"
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
|
|||||||
+10
-2
@@ -1,4 +1,5 @@
|
|||||||
import { Product } from "@medusajs/medusa"
|
import { Product } from "@medusajs/medusa"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import useToggleState from "../../../hooks/use-toggle-state"
|
import useToggleState from "../../../hooks/use-toggle-state"
|
||||||
import PlusIcon from "../../fundamentals/icons/plus-icon"
|
import PlusIcon from "../../fundamentals/icons/plus-icon"
|
||||||
import Section from "../section"
|
import Section from "../section"
|
||||||
@@ -12,6 +13,7 @@ type GiftCardDenominationsSectionProps = {
|
|||||||
const GiftCardDenominationsSection = ({
|
const GiftCardDenominationsSection = ({
|
||||||
giftCard,
|
giftCard,
|
||||||
}: GiftCardDenominationsSectionProps) => {
|
}: GiftCardDenominationsSectionProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const {
|
const {
|
||||||
state: addDenomination,
|
state: addDenomination,
|
||||||
close: closeAddDenomination,
|
close: closeAddDenomination,
|
||||||
@@ -21,11 +23,17 @@ const GiftCardDenominationsSection = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Section
|
<Section
|
||||||
title="Denominations"
|
title={t(
|
||||||
|
"gift-card-denominations-section-denominations",
|
||||||
|
"Denominations"
|
||||||
|
)}
|
||||||
forceDropdown
|
forceDropdown
|
||||||
actions={[
|
actions={[
|
||||||
{
|
{
|
||||||
label: "Add Denomination",
|
label: t(
|
||||||
|
"gift-card-denominations-section-add-denomination",
|
||||||
|
"Add Denomination"
|
||||||
|
),
|
||||||
onClick: openAddDenomination,
|
onClick: openAddDenomination,
|
||||||
icon: <PlusIcon size={20} />,
|
icon: <PlusIcon size={20} />,
|
||||||
},
|
},
|
||||||
|
|||||||
+41
-10
@@ -2,6 +2,7 @@ import { MoneyAmount, ProductVariant } from "@medusajs/medusa"
|
|||||||
import { createColumnHelper } from "@tanstack/react-table"
|
import { createColumnHelper } from "@tanstack/react-table"
|
||||||
import { useAdminDeleteVariant, useAdminStore } from "medusa-react"
|
import { useAdminDeleteVariant, useAdminStore } from "medusa-react"
|
||||||
import { useMemo } from "react"
|
import { useMemo } from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import useImperativeDialog from "../../../hooks/use-imperative-dialog"
|
import useImperativeDialog from "../../../hooks/use-imperative-dialog"
|
||||||
import useNotification from "../../../hooks/use-notification"
|
import useNotification from "../../../hooks/use-notification"
|
||||||
import useToggleState from "../../../hooks/use-toggle-state"
|
import useToggleState from "../../../hooks/use-toggle-state"
|
||||||
@@ -17,6 +18,7 @@ const columnHelper = createColumnHelper<ProductVariant>()
|
|||||||
|
|
||||||
export const useDenominationColumns = () => {
|
export const useDenominationColumns = () => {
|
||||||
const { store } = useAdminStore()
|
const { store } = useAdminStore()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const columns = useMemo(() => {
|
const columns = useMemo(() => {
|
||||||
if (!store) {
|
if (!store) {
|
||||||
@@ -27,7 +29,10 @@ export const useDenominationColumns = () => {
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
columnHelper.display({
|
columnHelper.display({
|
||||||
header: "Denomination",
|
header: t(
|
||||||
|
"gift-card-denominations-section-denomination",
|
||||||
|
"Denomination"
|
||||||
|
),
|
||||||
id: "denomination",
|
id: "denomination",
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const defaultDenomination = row.original.prices.find(
|
const defaultDenomination = row.original.prices.find(
|
||||||
@@ -50,7 +55,10 @@ export const useDenominationColumns = () => {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
columnHelper.display({
|
columnHelper.display({
|
||||||
header: "In other currencies",
|
header: t(
|
||||||
|
"gift-card-denominations-section-in-other-currencies",
|
||||||
|
"In other currencies"
|
||||||
|
),
|
||||||
id: "other_currencies",
|
id: "other_currencies",
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const otherCurrencies = row.original.prices.filter(
|
const otherCurrencies = row.original.prices.filter(
|
||||||
@@ -98,7 +106,13 @@ export const useDenominationColumns = () => {
|
|||||||
</ul>
|
</ul>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<span className="text-grey-50 cursor-default">{`, and ${remainder.length} more`}</span>
|
<span className="text-grey-50 cursor-default">
|
||||||
|
{t(
|
||||||
|
"gift-card-denominations-section-and-more",
|
||||||
|
", and {{count}} more",
|
||||||
|
{ count: remainder.length }
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
@@ -118,6 +132,7 @@ export const useDenominationColumns = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Actions = ({ original }: { original: ProductVariant }) => {
|
const Actions = ({ original }: { original: ProductVariant }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const { state, open, close } = useToggleState()
|
const { state, open, close } = useToggleState()
|
||||||
|
|
||||||
const { mutateAsync } = useAdminDeleteVariant(original.product_id)
|
const { mutateAsync } = useAdminDeleteVariant(original.product_id)
|
||||||
@@ -127,21 +142,37 @@ const Actions = ({ original }: { original: ProductVariant }) => {
|
|||||||
|
|
||||||
const onDelete = async () => {
|
const onDelete = async () => {
|
||||||
const shouldDelete = await dialog({
|
const shouldDelete = await dialog({
|
||||||
heading: "Delete denomination",
|
heading: t(
|
||||||
text: "Are you sure you want to delete this denomination?",
|
"gift-card-denominations-section-delete-denomination",
|
||||||
|
"Delete denomination"
|
||||||
|
),
|
||||||
|
text: t(
|
||||||
|
"gift-card-denominations-section-confirm-delete",
|
||||||
|
"Are you sure you want to delete this denomination?"
|
||||||
|
),
|
||||||
})
|
})
|
||||||
|
|
||||||
if (shouldDelete) {
|
if (shouldDelete) {
|
||||||
mutateAsync(original.id, {
|
mutateAsync(original.id, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
notification(
|
notification(
|
||||||
"Denomination deleted",
|
t(
|
||||||
"Denomination was successfully deleted",
|
"gift-card-denominations-section-denomination-deleted",
|
||||||
|
"Denomination deleted"
|
||||||
|
),
|
||||||
|
t(
|
||||||
|
"gift-card-denominations-section-denomination-was-successfully-deleted",
|
||||||
|
"Denomination was successfully deleted"
|
||||||
|
),
|
||||||
"success"
|
"success"
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
notification("Error", getErrorMessage(error), "error")
|
notification(
|
||||||
|
t("gift-card-denominations-section-error", "Error"),
|
||||||
|
getErrorMessage(error),
|
||||||
|
"error"
|
||||||
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -149,12 +180,12 @@ const Actions = ({ original }: { original: ProductVariant }) => {
|
|||||||
|
|
||||||
const actions: ActionType[] = [
|
const actions: ActionType[] = [
|
||||||
{
|
{
|
||||||
label: "Edit",
|
label: t("gift-card-denominations-section-edit", "Edit"),
|
||||||
onClick: open,
|
onClick: open,
|
||||||
icon: <EditIcon size={20} />,
|
icon: <EditIcon size={20} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Delete",
|
label: t("gift-card-denominations-section-delete", "Delete"),
|
||||||
onClick: onDelete,
|
onClick: onDelete,
|
||||||
icon: <TrashIcon size={20} />,
|
icon: <TrashIcon size={20} />,
|
||||||
variant: "danger",
|
variant: "danger",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { useState } from "react"
|
import React, { useState } from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import Button from "../../fundamentals/button"
|
import Button from "../../fundamentals/button"
|
||||||
import DiscordIcon from "../../fundamentals/icons/discord-icon"
|
import DiscordIcon from "../../fundamentals/icons/discord-icon"
|
||||||
import InputField from "../../molecules/input"
|
import InputField from "../../molecules/input"
|
||||||
@@ -12,6 +13,7 @@ type MailDialogProps = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const MailDialog = ({ open, onClose }: MailDialogProps) => {
|
const MailDialog = ({ open, onClose }: MailDialogProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const [subject, setSubject] = useState("")
|
const [subject, setSubject] = useState("")
|
||||||
const [body, setBody] = useState("")
|
const [body, setBody] = useState("")
|
||||||
const [link, setLink] = useState("mailto:support@medusajs.com")
|
const [link, setLink] = useState("mailto:support@medusajs.com")
|
||||||
@@ -30,21 +32,30 @@ const MailDialog = ({ open, onClose }: MailDialogProps) => {
|
|||||||
<Dialog.Content className="bg-grey-0 shadow-dropdown rounded-rounded fixed top-[64px] bottom-2 right-3 flex w-[400px] flex-col justify-between p-8">
|
<Dialog.Content className="bg-grey-0 shadow-dropdown rounded-rounded fixed top-[64px] bottom-2 right-3 flex w-[400px] flex-col justify-between p-8">
|
||||||
<div>
|
<div>
|
||||||
<Dialog.Title className="inter-xlarge-semibold mb-1">
|
<Dialog.Title className="inter-xlarge-semibold mb-1">
|
||||||
How can we help?
|
{t("help-dialog-how-can-we-help", "How can we help?")}
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
<Dialog.Description className="inter-small-regular text-grey-50 mb-6">
|
<Dialog.Description className="inter-small-regular text-grey-50 mb-6">
|
||||||
We usually respond in a few hours
|
{t(
|
||||||
|
"help-dialog-we-usually-respond-in-a-few-hours",
|
||||||
|
"We usually respond in a few hours"
|
||||||
|
)}
|
||||||
</Dialog.Description>
|
</Dialog.Description>
|
||||||
<InputField
|
<InputField
|
||||||
label={"Subject"}
|
label={t("help-dialog-subject", "Subject")}
|
||||||
value={subject}
|
value={subject}
|
||||||
className="mb-4"
|
className="mb-4"
|
||||||
placeholder="What is it about?..."
|
placeholder={t(
|
||||||
|
"help-dialog-what-is-it-about",
|
||||||
|
"What is it about?..."
|
||||||
|
)}
|
||||||
onChange={(e) => setSubject(e.target.value)}
|
onChange={(e) => setSubject(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<TextArea
|
<TextArea
|
||||||
label={"How can we help?"}
|
label={t("help-dialog-how-can-we-help", "How can we help?")}
|
||||||
placeholder="Write a message..."
|
placeholder={t(
|
||||||
|
"help-dialog-write-a-message",
|
||||||
|
"Write a message..."
|
||||||
|
)}
|
||||||
value={body}
|
value={body}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setBody(e.target.value)
|
setBody(e.target.value)
|
||||||
@@ -65,15 +76,21 @@ const MailDialog = ({ open, onClose }: MailDialogProps) => {
|
|||||||
<DiscordIcon size={24} />
|
<DiscordIcon size={24} />
|
||||||
</span>
|
</span>
|
||||||
<p className="text-grey-40 inter-small-regular text-center leading-6">
|
<p className="text-grey-40 inter-small-regular text-center leading-6">
|
||||||
Feel free to join our community of
|
{t(
|
||||||
|
"help-dialog-feel-free-to-join-our-community-of",
|
||||||
|
"Feel free to join our community of"
|
||||||
|
)}
|
||||||
<br />
|
<br />
|
||||||
merchants and e-commerce developers
|
{t(
|
||||||
|
"help-dialog-merchants-and-e-commerce-developers",
|
||||||
|
"merchants and e-commerce developers"
|
||||||
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<a className="w-full" href={link}>
|
<a className="w-full" href={link}>
|
||||||
<Button variant="primary" size="large" className="w-full">
|
<Button variant="primary" size="large" className="w-full">
|
||||||
Send a message
|
{t("help-dialog-send-a-message", "Send a message")}
|
||||||
</Button>
|
</Button>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useAdminCreateInvite } from "medusa-react"
|
import { useAdminCreateInvite } from "medusa-react"
|
||||||
import React from "react"
|
import React from "react"
|
||||||
import { Controller, useForm } from "react-hook-form"
|
import { Controller, useForm } from "react-hook-form"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import useNotification from "../../../hooks/use-notification"
|
import useNotification from "../../../hooks/use-notification"
|
||||||
import { Role } from "../../../types/shared"
|
import { Role } from "../../../types/shared"
|
||||||
import { getErrorMessage } from "../../../utils/error-messages"
|
import { getErrorMessage } from "../../../utils/error-messages"
|
||||||
@@ -20,6 +21,7 @@ type InviteModalFormData = {
|
|||||||
|
|
||||||
const InviteModal: React.FC<InviteModalProps> = ({ handleClose }) => {
|
const InviteModal: React.FC<InviteModalProps> = ({ handleClose }) => {
|
||||||
const notification = useNotification()
|
const notification = useNotification()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const { mutate, isLoading } = useAdminCreateInvite()
|
const { mutate, isLoading } = useAdminCreateInvite()
|
||||||
|
|
||||||
@@ -33,20 +35,34 @@ const InviteModal: React.FC<InviteModalProps> = ({ handleClose }) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
notification("Success", `Invitation sent to ${data.user}`, "success")
|
notification(
|
||||||
|
t("invite-modal-success", "Success"),
|
||||||
|
t(
|
||||||
|
"invite-modal-invitation-sent-to",
|
||||||
|
"Invitation sent to {{user}}",
|
||||||
|
{
|
||||||
|
user: data.user,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"success"
|
||||||
|
)
|
||||||
handleClose()
|
handleClose()
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
notification("Error", getErrorMessage(error), "error")
|
notification(
|
||||||
|
t("invite-modal-error", "Error"),
|
||||||
|
getErrorMessage(error),
|
||||||
|
"error"
|
||||||
|
)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const roleOptions: Role[] = [
|
const roleOptions: Role[] = [
|
||||||
{ value: "member", label: "Member" },
|
{ value: "member", label: t("invite-modal-member", "Member") },
|
||||||
{ value: "admin", label: "Admin" },
|
{ value: "admin", label: t("invite-modal-admin", "Admin") },
|
||||||
{ value: "developer", label: "Developer" },
|
{ value: "developer", label: t("invite-modal-developer", "Developer") },
|
||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -54,12 +70,14 @@ const InviteModal: React.FC<InviteModalProps> = ({ handleClose }) => {
|
|||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
<Modal.Header handleClose={handleClose}>
|
<Modal.Header handleClose={handleClose}>
|
||||||
<span className="inter-xlarge-semibold">Invite Users</span>
|
<span className="inter-xlarge-semibold">
|
||||||
|
{t("invite-modal-invite-users", "Invite Users")}
|
||||||
|
</span>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<Modal.Content>
|
<Modal.Content>
|
||||||
<div className="gap-y-base flex flex-col">
|
<div className="gap-y-base flex flex-col">
|
||||||
<InputField
|
<InputField
|
||||||
label="Email"
|
label={t("invite-modal-email", "Email")}
|
||||||
placeholder="lebron@james.com"
|
placeholder="lebron@james.com"
|
||||||
required
|
required
|
||||||
{...register("user", { required: true })}
|
{...register("user", { required: true })}
|
||||||
@@ -67,12 +85,15 @@ const InviteModal: React.FC<InviteModalProps> = ({ handleClose }) => {
|
|||||||
<Controller
|
<Controller
|
||||||
name="role"
|
name="role"
|
||||||
control={control}
|
control={control}
|
||||||
defaultValue={{ label: "Member", value: "member" }}
|
defaultValue={{
|
||||||
|
label: t("invite-modal-member", "Member"),
|
||||||
|
value: "member",
|
||||||
|
}}
|
||||||
render={({ field: { value, onChange, onBlur, ref } }) => {
|
render={({ field: { value, onChange, onBlur, ref } }) => {
|
||||||
return (
|
return (
|
||||||
<NextSelect
|
<NextSelect
|
||||||
label="Role"
|
label={t("invite-modal-role", "Role")}
|
||||||
placeholder="Select role"
|
placeholder={t("invite-modal-select-role", "Select role")}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
@@ -93,7 +114,7 @@ const InviteModal: React.FC<InviteModalProps> = ({ handleClose }) => {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={handleClose}
|
onClick={handleClose}
|
||||||
>
|
>
|
||||||
Cancel
|
{t("invite-modal-cancel", "Cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
@@ -102,7 +123,7 @@ const InviteModal: React.FC<InviteModalProps> = ({ handleClose }) => {
|
|||||||
className="text-small w-32 justify-center"
|
className="text-small w-32 justify-center"
|
||||||
variant="primary"
|
variant="primary"
|
||||||
>
|
>
|
||||||
Invite
|
{t("invite-modal-invite", "Invite")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useAdminLogin } from "medusa-react"
|
|||||||
import { useForm } from "react-hook-form"
|
import { useForm } from "react-hook-form"
|
||||||
import { useNavigate } from "react-router-dom"
|
import { useNavigate } from "react-router-dom"
|
||||||
import { useWidgets } from "../../../providers/widget-provider"
|
import { useWidgets } from "../../../providers/widget-provider"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import InputError from "../../atoms/input-error"
|
import InputError from "../../atoms/input-error"
|
||||||
import WidgetContainer from "../../extensions/widget-container"
|
import WidgetContainer from "../../extensions/widget-container"
|
||||||
import Button from "../../fundamentals/button"
|
import Button from "../../fundamentals/button"
|
||||||
@@ -25,6 +26,7 @@ const LoginCard = ({ toResetPassword }: LoginCardProps) => {
|
|||||||
} = useForm<FormValues>()
|
} = useForm<FormValues>()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { mutate, isLoading } = useAdminLogin()
|
const { mutate, isLoading } = useAdminLogin()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const { getWidgets } = useWidgets()
|
const { getWidgets } = useWidgets()
|
||||||
|
|
||||||
@@ -38,7 +40,10 @@ const LoginCard = ({ toResetPassword }: LoginCardProps) => {
|
|||||||
"password",
|
"password",
|
||||||
{
|
{
|
||||||
type: "manual",
|
type: "manual",
|
||||||
message: "These credentials do not match our records.",
|
message: t(
|
||||||
|
"login-card-no-match",
|
||||||
|
"These credentials do not match our records."
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
shouldFocus: true,
|
shouldFocus: true,
|
||||||
@@ -62,17 +67,17 @@ const LoginCard = ({ toResetPassword }: LoginCardProps) => {
|
|||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
<h1 className="inter-xlarge-semibold text-grey-90 mb-large text-[20px]">
|
<h1 className="inter-xlarge-semibold text-grey-90 mb-large text-[20px]">
|
||||||
Log in to Medusa
|
{t("login-card-log-in-to-medusa", "Log in to Medusa")}
|
||||||
</h1>
|
</h1>
|
||||||
<div>
|
<div>
|
||||||
<SigninInput
|
<SigninInput
|
||||||
placeholder="Email"
|
placeholder={t("login-card-email", "Email")}
|
||||||
{...register("email", { required: true })}
|
{...register("email", { required: true })}
|
||||||
autoComplete="email"
|
autoComplete="email"
|
||||||
className="mb-small"
|
className="mb-small"
|
||||||
/>
|
/>
|
||||||
<SigninInput
|
<SigninInput
|
||||||
placeholder="Password"
|
placeholder={t("login-card-password", "Password")}
|
||||||
type={"password"}
|
type={"password"}
|
||||||
{...register("password", { required: true })}
|
{...register("password", { required: true })}
|
||||||
autoComplete="current-password"
|
autoComplete="current-password"
|
||||||
@@ -93,7 +98,7 @@ const LoginCard = ({ toResetPassword }: LoginCardProps) => {
|
|||||||
className="inter-small-regular text-grey-50 mt-8 cursor-pointer"
|
className="inter-small-regular text-grey-50 mt-8 cursor-pointer"
|
||||||
onClick={toResetPassword}
|
onClick={toResetPassword}
|
||||||
>
|
>
|
||||||
Forgot your password?
|
{t("login-card-forgot-your-password", "Forgot your password?")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { useEffect, useState } from "react"
|
import React, { useEffect, useState } from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import Button from "../../fundamentals/button"
|
import Button from "../../fundamentals/button"
|
||||||
import PlusIcon from "../../fundamentals/icons/plus-icon"
|
import PlusIcon from "../../fundamentals/icons/plus-icon"
|
||||||
import TrashIcon from "../../fundamentals/icons/trash-icon"
|
import TrashIcon from "../../fundamentals/icons/trash-icon"
|
||||||
@@ -20,6 +21,7 @@ const Metadata: React.FC<AddMetadataProps> = ({
|
|||||||
setMetadata,
|
setMetadata,
|
||||||
heading = "Metadata",
|
heading = "Metadata",
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const [localData, setLocalData] = useState<MetadataField[]>([])
|
const [localData, setLocalData] = useState<MetadataField[]>([])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -79,7 +81,7 @@ const Metadata: React.FC<AddMetadataProps> = ({
|
|||||||
onClick={addKeyPair}
|
onClick={addKeyPair}
|
||||||
>
|
>
|
||||||
<PlusIcon size={20} />
|
<PlusIcon size={20} />
|
||||||
Add Metadata
|
{t("metadata-add-metadata", "Add Metadata")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+21
-7
@@ -1,6 +1,7 @@
|
|||||||
import { Product } from "@medusajs/medusa"
|
import { Product } from "@medusajs/medusa"
|
||||||
import { useEffect } from "react"
|
import { useEffect } from "react"
|
||||||
import { useForm } from "react-hook-form"
|
import { useForm } from "react-hook-form"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import useEditProductActions from "../../../hooks/use-edit-product-actions"
|
import useEditProductActions from "../../../hooks/use-edit-product-actions"
|
||||||
import { countries } from "../../../utils/countries"
|
import { countries } from "../../../utils/countries"
|
||||||
import { nestedForm } from "../../../utils/nested-form"
|
import { nestedForm } from "../../../utils/nested-form"
|
||||||
@@ -23,6 +24,7 @@ type AttributesForm = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const AttributeModal = ({ product, open, onClose }: Props) => {
|
const AttributeModal = ({ product, open, onClose }: Props) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const { onUpdate, updating } = useEditProductActions(product.id)
|
const { onUpdate, updating } = useEditProductActions(product.id)
|
||||||
const form = useForm<AttributesForm>({
|
const form = useForm<AttributesForm>({
|
||||||
defaultValues: getDefaultValues(product),
|
defaultValues: getDefaultValues(product),
|
||||||
@@ -67,21 +69,33 @@ const AttributeModal = ({ product, open, onClose }: Props) => {
|
|||||||
<Modal open={open} handleClose={onReset} isLargeModal>
|
<Modal open={open} handleClose={onReset} isLargeModal>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
<Modal.Header handleClose={onReset}>
|
<Modal.Header handleClose={onReset}>
|
||||||
<h1 className="inter-xlarge-semibold m-0">Edit Attributes</h1>
|
<h1 className="inter-xlarge-semibold m-0">
|
||||||
|
{t("product-attributes-section-edit-attributes", "Edit Attributes")}
|
||||||
|
</h1>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<form onSubmit={onSubmit}>
|
<form onSubmit={onSubmit}>
|
||||||
<Modal.Content>
|
<Modal.Content>
|
||||||
<div className="mb-xlarge">
|
<div className="mb-xlarge">
|
||||||
<h2 className="inter-large-semibold mb-2xsmall">Dimensions</h2>
|
<h2 className="inter-large-semibold mb-2xsmall">
|
||||||
|
{t("product-attributes-section-dimensions", "Dimensions")}
|
||||||
|
</h2>
|
||||||
<p className="inter-base-regular text-grey-50 mb-large">
|
<p className="inter-base-regular text-grey-50 mb-large">
|
||||||
Configure to calculate the most accurate shipping rates
|
{t(
|
||||||
|
"product-attributes-section-configure-to-calculate-the-most-accurate-shipping-rates",
|
||||||
|
"Configure to calculate the most accurate shipping rates"
|
||||||
|
)}
|
||||||
</p>
|
</p>
|
||||||
<DimensionsForm form={nestedForm(form, "dimensions")} />
|
<DimensionsForm form={nestedForm(form, "dimensions")} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="inter-large-semibold mb-2xsmall">Customs</h2>
|
<h2 className="inter-large-semibold mb-2xsmall">
|
||||||
|
{t("product-attributes-section-customs", "Customs")}
|
||||||
|
</h2>
|
||||||
<p className="inter-base-regular text-grey-50 mb-large">
|
<p className="inter-base-regular text-grey-50 mb-large">
|
||||||
Configure to calculate the most accurate shipping rates
|
{t(
|
||||||
|
"product-attributes-section-configure-to-calculate-the-most-accurate-shipping-rates",
|
||||||
|
"Configure to calculate the most accurate shipping rates"
|
||||||
|
)}
|
||||||
</p>
|
</p>
|
||||||
<CustomsForm form={nestedForm(form, "customs")} />
|
<CustomsForm form={nestedForm(form, "customs")} />
|
||||||
</div>
|
</div>
|
||||||
@@ -94,7 +108,7 @@ const AttributeModal = ({ product, open, onClose }: Props) => {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={onReset}
|
onClick={onReset}
|
||||||
>
|
>
|
||||||
Cancel
|
{t("product-attributes-section-cancel", "Cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
@@ -103,7 +117,7 @@ const AttributeModal = ({ product, open, onClose }: Props) => {
|
|||||||
disabled={!isDirty}
|
disabled={!isDirty}
|
||||||
loading={updating}
|
loading={updating}
|
||||||
>
|
>
|
||||||
Save
|
{t("product-attributes-section-save", "Save")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Product } from "@medusajs/medusa"
|
import { Product } from "@medusajs/medusa"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import useToggleState from "../../../hooks/use-toggle-state"
|
import useToggleState from "../../../hooks/use-toggle-state"
|
||||||
import EditIcon from "../../fundamentals/icons/edit-icon"
|
import EditIcon from "../../fundamentals/icons/edit-icon"
|
||||||
import { ActionType } from "../../molecules/actionables"
|
import { ActionType } from "../../molecules/actionables"
|
||||||
@@ -10,6 +11,7 @@ type Props = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ProductAttributesSection = ({ product }: Props) => {
|
const ProductAttributesSection = ({ product }: Props) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const { state, toggle, close } = useToggleState()
|
const { state, toggle, close } = useToggleState()
|
||||||
|
|
||||||
const actions: ActionType[] = [
|
const actions: ActionType[] = [
|
||||||
@@ -24,7 +26,9 @@ const ProductAttributesSection = ({ product }: Props) => {
|
|||||||
<>
|
<>
|
||||||
<Section title="Attributes" actions={actions} forceDropdown>
|
<Section title="Attributes" actions={actions} forceDropdown>
|
||||||
<div className="gap-y-xsmall mb-large mt-base flex flex-col">
|
<div className="gap-y-xsmall mb-large mt-base flex flex-col">
|
||||||
<h2 className="inter-base-semibold">Dimensions</h2>
|
<h2 className="inter-base-semibold">
|
||||||
|
{t("product-attributes-section-dimensions", "Dimensions")}
|
||||||
|
</h2>
|
||||||
<div className="gap-y-xsmall flex flex-col">
|
<div className="gap-y-xsmall flex flex-col">
|
||||||
<Attribute attribute="Height" value={product.height} />
|
<Attribute attribute="Height" value={product.height} />
|
||||||
<Attribute attribute="Width" value={product.width} />
|
<Attribute attribute="Width" value={product.width} />
|
||||||
@@ -33,12 +37,23 @@ const ProductAttributesSection = ({ product }: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="gap-y-xsmall flex flex-col">
|
<div className="gap-y-xsmall flex flex-col">
|
||||||
<h2 className="inter-base-semibold">Customs</h2>
|
<h2 className="inter-base-semibold">
|
||||||
|
{t("product-attributes-section-customs", "Customs")}
|
||||||
|
</h2>
|
||||||
<div className="gap-y-xsmall flex flex-col">
|
<div className="gap-y-xsmall flex flex-col">
|
||||||
<Attribute attribute="MID Code" value={product.mid_code} />
|
|
||||||
<Attribute attribute="HS Code" value={product.hs_code} />
|
|
||||||
<Attribute
|
<Attribute
|
||||||
attribute="Country of origin"
|
attribute={t("product-attributes-section-mid-code", "MID Code")}
|
||||||
|
value={product.mid_code}
|
||||||
|
/>
|
||||||
|
<Attribute
|
||||||
|
attribute={t("product-attributes-section-hs-code", "HS Code")}
|
||||||
|
value={product.hs_code}
|
||||||
|
/>
|
||||||
|
<Attribute
|
||||||
|
attribute={t(
|
||||||
|
"product-attributes-section-country-of-origin",
|
||||||
|
"Country of origin"
|
||||||
|
)}
|
||||||
value={product.origin_country}
|
value={product.origin_country}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+18
-2
@@ -1,5 +1,6 @@
|
|||||||
import { Product, SalesChannel } from "@medusajs/medusa"
|
import { Product, SalesChannel } from "@medusajs/medusa"
|
||||||
import { useAdminUpdateProduct } from "medusa-react"
|
import { useAdminUpdateProduct } from "medusa-react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import SalesChannelsModal from "../../forms/product/sales-channels-modal"
|
import SalesChannelsModal from "../../forms/product/sales-channels-modal"
|
||||||
import useNotification from "../../../hooks/use-notification"
|
import useNotification from "../../../hooks/use-notification"
|
||||||
|
|
||||||
@@ -11,6 +12,7 @@ type Props = {
|
|||||||
|
|
||||||
const ChannelsModal = ({ product, open, onClose }: Props) => {
|
const ChannelsModal = ({ product, open, onClose }: Props) => {
|
||||||
const notification = useNotification()
|
const notification = useNotification()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const { mutateAsync } = useAdminUpdateProduct(product.id)
|
const { mutateAsync } = useAdminUpdateProduct(product.id)
|
||||||
|
|
||||||
@@ -19,9 +21,23 @@ const ChannelsModal = ({ product, open, onClose }: Props) => {
|
|||||||
await mutateAsync({
|
await mutateAsync({
|
||||||
sales_channels: channels.map((c) => ({ id: c.id })),
|
sales_channels: channels.map((c) => ({ id: c.id })),
|
||||||
})
|
})
|
||||||
notification("Success", "Successfully updated sales channels", "success")
|
notification(
|
||||||
|
t("product-general-section-success", "Success"),
|
||||||
|
t(
|
||||||
|
"product-general-section-successfully-updated-sales-channels",
|
||||||
|
"Successfully updated sales channels"
|
||||||
|
),
|
||||||
|
"success"
|
||||||
|
)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
notification("Error", "Failed to update sales channels", "error")
|
notification(
|
||||||
|
t("product-general-section-error", "Error"),
|
||||||
|
t(
|
||||||
|
"product-general-section-failed-to-update-sales-channels",
|
||||||
|
"Failed to update sales channels"
|
||||||
|
),
|
||||||
|
"error"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+15
-5
@@ -1,3 +1,4 @@
|
|||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import DiscountableForm, {
|
import DiscountableForm, {
|
||||||
DiscountableFormType,
|
DiscountableFormType,
|
||||||
} from "../../forms/product/discountable-form"
|
} from "../../forms/product/discountable-form"
|
||||||
@@ -33,6 +34,7 @@ type GeneralFormWrapper = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const GeneralModal = ({ product, open, onClose }: Props) => {
|
const GeneralModal = ({ product, open, onClose }: Props) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const { onUpdate, updating } = useEditProductActions(product.id)
|
const { onUpdate, updating } = useEditProductActions(product.id)
|
||||||
const form = useForm<GeneralFormWrapper>({
|
const form = useForm<GeneralFormWrapper>({
|
||||||
defaultValues: getDefaultValues(product),
|
defaultValues: getDefaultValues(product),
|
||||||
@@ -95,7 +97,10 @@ const GeneralModal = ({ product, open, onClose }: Props) => {
|
|||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
<Modal.Header handleClose={onReset}>
|
<Modal.Header handleClose={onReset}>
|
||||||
<h1 className="inter-xlarge-semibold m-0">
|
<h1 className="inter-xlarge-semibold m-0">
|
||||||
Edit General Information
|
{t(
|
||||||
|
"product-general-section-edit-general-information",
|
||||||
|
"Edit General Information"
|
||||||
|
)}
|
||||||
</h1>
|
</h1>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<form onSubmit={onSubmit}>
|
<form onSubmit={onSubmit}>
|
||||||
@@ -106,7 +111,10 @@ const GeneralModal = ({ product, open, onClose }: Props) => {
|
|||||||
/>
|
/>
|
||||||
<div className="my-xlarge">
|
<div className="my-xlarge">
|
||||||
<h2 className="inter-base-semibold mb-base">
|
<h2 className="inter-base-semibold mb-base">
|
||||||
Organize {product.is_giftcard ? "Gift Card" : "Product"}
|
Organize{" "}
|
||||||
|
{product.is_giftcard
|
||||||
|
? t("product-general-section-gift-card", "Gift Card")
|
||||||
|
: t("product-general-section-product", "Product")}
|
||||||
</h2>
|
</h2>
|
||||||
<OrganizeForm form={nestedForm(form, "organize")} />
|
<OrganizeForm form={nestedForm(form, "organize")} />
|
||||||
</div>
|
</div>
|
||||||
@@ -115,7 +123,9 @@ const GeneralModal = ({ product, open, onClose }: Props) => {
|
|||||||
isGiftCard={product.is_giftcard}
|
isGiftCard={product.is_giftcard}
|
||||||
/>
|
/>
|
||||||
<div className="mt-xlarge">
|
<div className="mt-xlarge">
|
||||||
<h2 className="inter-base-semibold mb-base">Metadata</h2>
|
<h2 className="inter-base-semibold mb-base">
|
||||||
|
{t("product-general-section-metadata", "Metadata")}
|
||||||
|
</h2>
|
||||||
<MetadataForm form={nestedForm(form, "metadata")} />
|
<MetadataForm form={nestedForm(form, "metadata")} />
|
||||||
</div>
|
</div>
|
||||||
</Modal.Content>
|
</Modal.Content>
|
||||||
@@ -127,7 +137,7 @@ const GeneralModal = ({ product, open, onClose }: Props) => {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={onReset}
|
onClick={onReset}
|
||||||
>
|
>
|
||||||
Cancel
|
{t("product-general-section-cancel", "Cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
@@ -136,7 +146,7 @@ const GeneralModal = ({ product, open, onClose }: Props) => {
|
|||||||
disabled={!isDirty}
|
disabled={!isDirty}
|
||||||
loading={updating}
|
loading={updating}
|
||||||
>
|
>
|
||||||
Save
|
{t("product-general-section-save", "Save")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Product } from "@medusajs/medusa"
|
import { Product } from "@medusajs/medusa"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import useEditProductActions from "../../../hooks/use-edit-product-actions"
|
import useEditProductActions from "../../../hooks/use-edit-product-actions"
|
||||||
import useToggleState from "../../../hooks/use-toggle-state"
|
import useToggleState from "../../../hooks/use-toggle-state"
|
||||||
import {
|
import {
|
||||||
@@ -22,6 +23,7 @@ type Props = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ProductGeneralSection = ({ product }: Props) => {
|
const ProductGeneralSection = ({ product }: Props) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const { onDelete, onStatusChange } = useEditProductActions(product.id)
|
const { onDelete, onStatusChange } = useEditProductActions(product.id)
|
||||||
const {
|
const {
|
||||||
state: infoState,
|
state: infoState,
|
||||||
@@ -39,12 +41,15 @@ const ProductGeneralSection = ({ product }: Props) => {
|
|||||||
|
|
||||||
const actions: ActionType[] = [
|
const actions: ActionType[] = [
|
||||||
{
|
{
|
||||||
label: "Edit General Information",
|
label: t(
|
||||||
|
"product-general-section-edit-general-information",
|
||||||
|
"Edit General Information"
|
||||||
|
),
|
||||||
onClick: toggleInfo,
|
onClick: toggleInfo,
|
||||||
icon: <EditIcon size={20} />,
|
icon: <EditIcon size={20} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Delete",
|
label: t("product-general-section-delete", "Delete"),
|
||||||
onClick: onDelete,
|
onClick: onDelete,
|
||||||
variant: "danger",
|
variant: "danger",
|
||||||
icon: <TrashIcon size={20} />,
|
icon: <TrashIcon size={20} />,
|
||||||
@@ -53,7 +58,10 @@ const ProductGeneralSection = ({ product }: Props) => {
|
|||||||
|
|
||||||
if (isFeatureEnabled("sales_channels")) {
|
if (isFeatureEnabled("sales_channels")) {
|
||||||
actions.splice(1, 0, {
|
actions.splice(1, 0, {
|
||||||
label: "Edit Sales Channels",
|
label: t(
|
||||||
|
"product-general-section-edit-sales-channels",
|
||||||
|
"Edit Sales Channels"
|
||||||
|
),
|
||||||
onClick: toggleChannels,
|
onClick: toggleChannels,
|
||||||
icon: <ChannelsIcon size={20} />,
|
icon: <ChannelsIcon size={20} />,
|
||||||
})
|
})
|
||||||
@@ -68,8 +76,8 @@ const ProductGeneralSection = ({ product }: Props) => {
|
|||||||
status={
|
status={
|
||||||
<StatusSelector
|
<StatusSelector
|
||||||
isDraft={product?.status === "draft"}
|
isDraft={product?.status === "draft"}
|
||||||
activeState="Published"
|
activeState={t("product-general-section-published", "Published")}
|
||||||
draftState="Draft"
|
draftState={t("product-general-section-draft", "Draft")}
|
||||||
onChange={() => onStatusChange(product.status)}
|
onChange={() => onStatusChange(product.status)}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
@@ -123,33 +131,50 @@ const Detail = ({ title, value }: DetailProps) => {
|
|||||||
|
|
||||||
const ProductDetails = ({ product }: Props) => {
|
const ProductDetails = ({ product }: Props) => {
|
||||||
const { isFeatureEnabled } = useFeatureFlag()
|
const { isFeatureEnabled } = useFeatureFlag()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-8 flex flex-col gap-y-3">
|
<div className="mt-8 flex flex-col gap-y-3">
|
||||||
<h2 className="inter-base-semibold">Details</h2>
|
<h2 className="inter-base-semibold">
|
||||||
<Detail title="Subtitle" value={product.subtitle} />
|
{t("product-general-section-details", "Details")}
|
||||||
<Detail title="Handle" value={product.handle} />
|
</h2>
|
||||||
<Detail title="Type" value={product.type?.value} />
|
<Detail
|
||||||
<Detail title="Collection" value={product.collection?.title} />
|
title={t("product-general-section-subtitle", "Subtitle")}
|
||||||
|
value={product.subtitle}
|
||||||
|
/>
|
||||||
|
<Detail
|
||||||
|
title={t("product-general-section-handle", "Handle")}
|
||||||
|
value={product.handle}
|
||||||
|
/>
|
||||||
|
<Detail
|
||||||
|
title={t("product-general-section-type", "Type")}
|
||||||
|
value={product.type?.value}
|
||||||
|
/>
|
||||||
|
<Detail
|
||||||
|
title={t("product-general-section-collection", "Collection")}
|
||||||
|
value={product.collection?.title}
|
||||||
|
/>
|
||||||
{isFeatureEnabled(FeatureFlag.PRODUCT_CATEGORIES) && (
|
{isFeatureEnabled(FeatureFlag.PRODUCT_CATEGORIES) && (
|
||||||
<Detail
|
<Detail
|
||||||
title="Category"
|
title={t("product-general-section-category", "Category")}
|
||||||
value={product.categories.map((c) => c.name)}
|
value={product.categories.map((c) => c.name)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Detail
|
<Detail
|
||||||
title="Discountable"
|
title={t("product-general-section-discountable", "Discountable")}
|
||||||
value={product.discountable ? "True" : "False"}
|
value={
|
||||||
|
product.discountable
|
||||||
|
? t("product-general-section-true", "True")
|
||||||
|
: t("product-general-section-false", "False")
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<Detail
|
<Detail
|
||||||
title="Metadata"
|
title={t("product-general-section-metadata", "Metadata")}
|
||||||
value={
|
value={
|
||||||
Object.entries(product.metadata || {}).length > 0
|
Object.entries(product.metadata || {}).length > 0
|
||||||
? `${Object.entries(product.metadata || {}).length} ${
|
? t("product-general-section-count", "{{count}}", {
|
||||||
Object.keys(product.metadata || {}).length === 1
|
count: Object.keys(product.metadata || {}).length,
|
||||||
? "item"
|
})
|
||||||
: "items"
|
|
||||||
}`
|
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -176,10 +201,13 @@ const ProductTags = ({ product }: Props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ProductSalesChannels = ({ product }: Props) => {
|
const ProductSalesChannels = ({ product }: Props) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
return (
|
return (
|
||||||
<FeatureToggle featureFlag="sales_channels">
|
<FeatureToggle featureFlag="sales_channels">
|
||||||
<div className="mt-xlarge">
|
<div className="mt-xlarge">
|
||||||
<h2 className="inter-base-semibold mb-xsmall">Sales channels</h2>
|
<h2 className="inter-base-semibold mb-xsmall">
|
||||||
|
{t("product-general-section-sales-channels", "Sales channels")}
|
||||||
|
</h2>
|
||||||
<SalesChannelsDisplay channels={product.sales_channels} />
|
<SalesChannelsDisplay channels={product.sales_channels} />
|
||||||
</div>
|
</div>
|
||||||
</FeatureToggle>
|
</FeatureToggle>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Product } from "@medusajs/medusa"
|
import { Product } from "@medusajs/medusa"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import useToggleState from "../../../hooks/use-toggle-state"
|
import useToggleState from "../../../hooks/use-toggle-state"
|
||||||
import { ActionType } from "../../molecules/actionables"
|
import { ActionType } from "../../molecules/actionables"
|
||||||
import Section from "../../organisms/section"
|
import Section from "../../organisms/section"
|
||||||
@@ -10,10 +11,11 @@ type Props = {
|
|||||||
|
|
||||||
const ProductMediaSection = ({ product }: Props) => {
|
const ProductMediaSection = ({ product }: Props) => {
|
||||||
const { state, close, toggle } = useToggleState()
|
const { state, close, toggle } = useToggleState()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const actions: ActionType[] = [
|
const actions: ActionType[] = [
|
||||||
{
|
{
|
||||||
label: "Edit Media",
|
label: t("product-media-section-edit-media", "Edit Media"),
|
||||||
onClick: toggle,
|
onClick: toggle,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
+26
-7
@@ -1,6 +1,7 @@
|
|||||||
import { Product } from "@medusajs/medusa"
|
import { Product } from "@medusajs/medusa"
|
||||||
import { useEffect } from "react"
|
import { useEffect } from "react"
|
||||||
import { useForm } from "react-hook-form"
|
import { useForm } from "react-hook-form"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import useEditProductActions from "../../../hooks/use-edit-product-actions"
|
import useEditProductActions from "../../../hooks/use-edit-product-actions"
|
||||||
import useNotification from "../../../hooks/use-notification"
|
import useNotification from "../../../hooks/use-notification"
|
||||||
import { FormImage } from "../../../types/shared"
|
import { FormImage } from "../../../types/shared"
|
||||||
@@ -21,6 +22,7 @@ type MediaFormWrapper = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const MediaModal = ({ product, open, onClose }: Props) => {
|
const MediaModal = ({ product, open, onClose }: Props) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const { onUpdate, updating } = useEditProductActions(product.id)
|
const { onUpdate, updating } = useEditProductActions(product.id)
|
||||||
const form = useForm<MediaFormWrapper>({
|
const form = useForm<MediaFormWrapper>({
|
||||||
defaultValues: getDefaultValues(product),
|
defaultValues: getDefaultValues(product),
|
||||||
@@ -49,17 +51,27 @@ const MediaModal = ({ product, open, onClose }: Props) => {
|
|||||||
try {
|
try {
|
||||||
preppedImages = await prepareImages(data.media.images)
|
preppedImages = await prepareImages(data.media.images)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
let errorMessage = "Something went wrong while trying to upload images."
|
let errorMessage = t(
|
||||||
|
"product-media-section-upload-images-error",
|
||||||
|
"Something went wrong while trying to upload images."
|
||||||
|
)
|
||||||
const response = (error as any).response as Response
|
const response = (error as any).response as Response
|
||||||
|
|
||||||
if (response.status === 500) {
|
if (response.status === 500) {
|
||||||
errorMessage =
|
errorMessage =
|
||||||
errorMessage +
|
errorMessage +
|
||||||
" " +
|
" " +
|
||||||
|
t(
|
||||||
|
"product-media-section-file-service-not-configured",
|
||||||
"You might not have a file service configured. Please contact your administrator"
|
"You might not have a file service configured. Please contact your administrator"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
notification("Error", errorMessage, "error")
|
notification(
|
||||||
|
t("product-media-section-error", "Error"),
|
||||||
|
errorMessage,
|
||||||
|
"error"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const urls = preppedImages.map((image) => image.url)
|
const urls = preppedImages.map((image) => image.url)
|
||||||
@@ -76,14 +88,21 @@ const MediaModal = ({ product, open, onClose }: Props) => {
|
|||||||
<Modal open={open} handleClose={onReset} isLargeModal>
|
<Modal open={open} handleClose={onReset} isLargeModal>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
<Modal.Header handleClose={onReset}>
|
<Modal.Header handleClose={onReset}>
|
||||||
<h1 className="inter-xlarge-semibold m-0">Edit Media</h1>
|
<h1 className="inter-xlarge-semibold m-0">
|
||||||
|
{t("product-media-section-edit-media", "Edit Media")}
|
||||||
|
</h1>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<form onSubmit={onSubmit}>
|
<form onSubmit={onSubmit}>
|
||||||
<Modal.Content>
|
<Modal.Content>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="inter-large-semibold mb-2xsmall">Media</h2>
|
<h2 className="inter-large-semibold mb-2xsmall">
|
||||||
|
{t("product-media-section-media", "Media")}
|
||||||
|
</h2>
|
||||||
<p className="inter-base-regular text-grey-50 mb-large">
|
<p className="inter-base-regular text-grey-50 mb-large">
|
||||||
Add images to your product.
|
{t(
|
||||||
|
"product-media-section-add-images-to-your-product",
|
||||||
|
"Add images to your product."
|
||||||
|
)}
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<MediaForm form={nestedForm(form, "media")} />
|
<MediaForm form={nestedForm(form, "media")} />
|
||||||
@@ -98,7 +117,7 @@ const MediaModal = ({ product, open, onClose }: Props) => {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={onReset}
|
onClick={onReset}
|
||||||
>
|
>
|
||||||
Cancel
|
{t("product-media-section-cancel", "Cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
@@ -107,7 +126,7 @@ const MediaModal = ({ product, open, onClose }: Props) => {
|
|||||||
disabled={!isDirty}
|
disabled={!isDirty}
|
||||||
loading={updating}
|
loading={updating}
|
||||||
>
|
>
|
||||||
Save and close
|
{t("product-media-section-save-and-close", "Save and close")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Product } from "@medusajs/medusa"
|
import { Product } from "@medusajs/medusa"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import JSONView from "../../molecules/json-view"
|
import JSONView from "../../molecules/json-view"
|
||||||
import Section from "../section"
|
import Section from "../section"
|
||||||
|
|
||||||
@@ -8,8 +9,15 @@ type Props = {
|
|||||||
|
|
||||||
/** Temporary component, should be replaced with <RawJson /> but since the design is different we will use this to not break the existing design across admin. */
|
/** Temporary component, should be replaced with <RawJson /> but since the design is different we will use this to not break the existing design across admin. */
|
||||||
const ProductRawSection = ({ product }: Props) => {
|
const ProductRawSection = ({ product }: Props) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
return (
|
return (
|
||||||
<Section title={product.is_giftcard ? "Raw Gift Card" : "Raw Product"}>
|
<Section
|
||||||
|
title={
|
||||||
|
product.is_giftcard
|
||||||
|
? t("product-raw-section-raw-gift-card", "Raw Gift Card")
|
||||||
|
: t("product-raw-section-raw-product", "Raw Product")
|
||||||
|
}
|
||||||
|
>
|
||||||
<div className="pt-base">
|
<div className="pt-base">
|
||||||
<JSONView data={product} />
|
<JSONView data={product} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Product } from "@medusajs/medusa"
|
import { Product } from "@medusajs/medusa"
|
||||||
import clsx from "clsx"
|
import clsx from "clsx"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import useEditProductActions from "../../../hooks/use-edit-product-actions"
|
import useEditProductActions from "../../../hooks/use-edit-product-actions"
|
||||||
import useNotification from "../../../hooks/use-notification"
|
import useNotification from "../../../hooks/use-notification"
|
||||||
import useToggleState from "../../../hooks/use-toggle-state"
|
import useToggleState from "../../../hooks/use-toggle-state"
|
||||||
@@ -14,6 +15,7 @@ type Props = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ProductThumbnailSection = ({ product }: Props) => {
|
const ProductThumbnailSection = ({ product }: Props) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const { onUpdate, updating } = useEditProductActions(product.id)
|
const { onUpdate, updating } = useEditProductActions(product.id)
|
||||||
const { state, toggle, close } = useToggleState()
|
const { state, toggle, close } = useToggleState()
|
||||||
|
|
||||||
@@ -27,10 +29,21 @@ const ProductThumbnailSection = ({ product }: Props) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
notification("Success", "Successfully deleted thumbnail", "success")
|
notification(
|
||||||
|
t("product-thumbnail-section-success", "Success"),
|
||||||
|
t(
|
||||||
|
"product-thumbnail-section-successfully-deleted-thumbnail",
|
||||||
|
"Successfully deleted thumbnail"
|
||||||
|
),
|
||||||
|
"success"
|
||||||
|
)
|
||||||
},
|
},
|
||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
notification("Error", getErrorMessage(err), "error")
|
notification(
|
||||||
|
t("product-thumbnail-section-error", "Error"),
|
||||||
|
getErrorMessage(err),
|
||||||
|
"error"
|
||||||
|
)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -48,7 +61,9 @@ const ProductThumbnailSection = ({ product }: Props) => {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={toggle}
|
onClick={toggle}
|
||||||
>
|
>
|
||||||
{product.thumbnail ? "Edit" : "Upload"}
|
{product.thumbnail
|
||||||
|
? t("product-thumbnail-section-edit", "Edit")
|
||||||
|
: t("product-thumbnail-section-upload", "Upload")}
|
||||||
</Button>
|
</Button>
|
||||||
{product.thumbnail && (
|
{product.thumbnail && (
|
||||||
<TwoStepDelete onDelete={handleDelete} deleting={updating} />
|
<TwoStepDelete onDelete={handleDelete} deleting={updating} />
|
||||||
|
|||||||
+31
-8
@@ -1,6 +1,7 @@
|
|||||||
import { Product } from "@medusajs/medusa"
|
import { Product } from "@medusajs/medusa"
|
||||||
import { useEffect } from "react"
|
import { useEffect } from "react"
|
||||||
import { useForm } from "react-hook-form"
|
import { useForm } from "react-hook-form"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import useEditProductActions from "../../../hooks/use-edit-product-actions"
|
import useEditProductActions from "../../../hooks/use-edit-product-actions"
|
||||||
import useNotification from "../../../hooks/use-notification"
|
import useNotification from "../../../hooks/use-notification"
|
||||||
import { FormImage } from "../../../types/shared"
|
import { FormImage } from "../../../types/shared"
|
||||||
@@ -23,6 +24,7 @@ type ThumbnailFormWrapper = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ThumbnailModal = ({ product, open, onClose }: Props) => {
|
const ThumbnailModal = ({ product, open, onClose }: Props) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const { onUpdate, updating } = useEditProductActions(product.id)
|
const { onUpdate, updating } = useEditProductActions(product.id)
|
||||||
const form = useForm<ThumbnailFormWrapper>({
|
const form = useForm<ThumbnailFormWrapper>({
|
||||||
defaultValues: getDefaultValues(product),
|
defaultValues: getDefaultValues(product),
|
||||||
@@ -51,18 +53,27 @@ const ThumbnailModal = ({ product, open, onClose }: Props) => {
|
|||||||
try {
|
try {
|
||||||
preppedImages = await prepareImages(data.thumbnail.images)
|
preppedImages = await prepareImages(data.thumbnail.images)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
let errorMessage =
|
let errorMessage = t(
|
||||||
|
"product-thumbnail-section-upload-thumbnail-error",
|
||||||
"Something went wrong while trying to upload the thumbnail."
|
"Something went wrong while trying to upload the thumbnail."
|
||||||
|
)
|
||||||
const response = (error as any).response as Response
|
const response = (error as any).response as Response
|
||||||
|
|
||||||
if (response.status === 500) {
|
if (response.status === 500) {
|
||||||
errorMessage =
|
errorMessage =
|
||||||
errorMessage +
|
errorMessage +
|
||||||
" " +
|
" " +
|
||||||
|
t(
|
||||||
|
"product-thumbnail-section-you-might-not-have-a-file-service-configured-please-contact-your-administrator",
|
||||||
"You might not have a file service configured. Please contact your administrator"
|
"You might not have a file service configured. Please contact your administrator"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
notification("Error", errorMessage, "error")
|
notification(
|
||||||
|
t("product-thumbnail-section-error", "Error"),
|
||||||
|
errorMessage,
|
||||||
|
"error"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const url = preppedImages?.[0]?.url
|
const url = preppedImages?.[0]?.url
|
||||||
@@ -80,14 +91,23 @@ const ThumbnailModal = ({ product, open, onClose }: Props) => {
|
|||||||
<Modal open={open} handleClose={onReset} isLargeModal>
|
<Modal open={open} handleClose={onReset} isLargeModal>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
<Modal.Header handleClose={onReset}>
|
<Modal.Header handleClose={onReset}>
|
||||||
<h1 className="inter-xlarge-semibold m-0">Upload Thumbnail</h1>
|
<h1 className="inter-xlarge-semibold m-0">
|
||||||
|
{t(
|
||||||
|
"product-thumbnail-section-upload-thumbnail",
|
||||||
|
"Upload Thumbnail"
|
||||||
|
)}
|
||||||
|
</h1>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<form onSubmit={onSubmit}>
|
<form onSubmit={onSubmit}>
|
||||||
<Modal.Content>
|
<Modal.Content>
|
||||||
<h2 className="inter-large-semibold mb-2xsmall">Thumbnail</h2>
|
<h2 className="inter-large-semibold mb-2xsmall">
|
||||||
|
{t("product-thumbnail-section-thumbnail", "Thumbnail")}
|
||||||
|
</h2>
|
||||||
<p className="inter-base-regular text-grey-50 mb-large">
|
<p className="inter-base-regular text-grey-50 mb-large">
|
||||||
Used to represent your product during checkout, social sharing and
|
{t(
|
||||||
more.
|
"product-thumbnail-section-used-to-represent-your-product-during-checkout-social-sharing-and-more",
|
||||||
|
"Used to represent your product during checkout, social sharing and more."
|
||||||
|
)}
|
||||||
</p>
|
</p>
|
||||||
<ThumbnailForm form={nestedForm(form, "thumbnail")} />
|
<ThumbnailForm form={nestedForm(form, "thumbnail")} />
|
||||||
</Modal.Content>
|
</Modal.Content>
|
||||||
@@ -99,7 +119,7 @@ const ThumbnailModal = ({ product, open, onClose }: Props) => {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={onReset}
|
onClick={onReset}
|
||||||
>
|
>
|
||||||
Cancel
|
{t("product-thumbnail-section-cancel", "Cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
@@ -108,7 +128,10 @@ const ThumbnailModal = ({ product, open, onClose }: Props) => {
|
|||||||
disabled={!isDirty}
|
disabled={!isDirty}
|
||||||
loading={updating}
|
loading={updating}
|
||||||
>
|
>
|
||||||
Save and close
|
{t(
|
||||||
|
"product-thumbnail-section-save-and-close",
|
||||||
|
"Save and close"
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Product, ProductVariant } from "@medusajs/medusa"
|
import { Product, ProductVariant } from "@medusajs/medusa"
|
||||||
import React from "react"
|
import React from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import { ActionType } from "../../molecules/actionables"
|
import { ActionType } from "../../molecules/actionables"
|
||||||
import { CollapsibleTree } from "../../molecules/collapsible-tree"
|
import { CollapsibleTree } from "../../molecules/collapsible-tree"
|
||||||
|
|
||||||
@@ -58,6 +59,7 @@ const ProductVariantTree: React.FC<ProductVariantTreeProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ProductVariantLeaf = ({ sku, title, prices = [] }: LeafProps) => {
|
const ProductVariantLeaf = ({ sku, title, prices = [] }: LeafProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const filteredPrices = prices.filter((pr) => pr.price_list_id)
|
const filteredPrices = prices.filter((pr) => pr.price_list_id)
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-1">
|
<div className="flex flex-1">
|
||||||
@@ -68,12 +70,14 @@ const ProductVariantLeaf = ({ sku, title, prices = [] }: LeafProps) => {
|
|||||||
<div className="text-grey-50 flex flex-1 items-center justify-end">
|
<div className="text-grey-50 flex flex-1 items-center justify-end">
|
||||||
<div className="text-grey-50 mr-xsmall">
|
<div className="text-grey-50 mr-xsmall">
|
||||||
{filteredPrices.length ? (
|
{filteredPrices.length ? (
|
||||||
<span>{`${filteredPrices.length} price${
|
<span>
|
||||||
filteredPrices.length > 1 ? "s" : ""
|
{t("product-variant-tree-count", "{{count}}", {
|
||||||
}`}</span>
|
count: filteredPrices.length,
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<span className="inter-small-semibold text-orange-40">
|
<span className="inter-small-semibold text-orange-40">
|
||||||
Add prices
|
{t("product-variant-tree-add-prices", "Add prices")}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+7
-3
@@ -1,5 +1,6 @@
|
|||||||
import { AdminPostProductsProductVariantsReq, Product } from "@medusajs/medusa"
|
import { AdminPostProductsProductVariantsReq, Product } from "@medusajs/medusa"
|
||||||
import { useEffect } from "react"
|
import { useEffect } from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import EditFlowVariantForm, {
|
import EditFlowVariantForm, {
|
||||||
EditFlowVariantFormType,
|
EditFlowVariantFormType,
|
||||||
} from "../../forms/product/variant-form/edit-flow-variant-form"
|
} from "../../forms/product/variant-form/edit-flow-variant-form"
|
||||||
@@ -22,6 +23,7 @@ type Props = {
|
|||||||
|
|
||||||
const AddVariantModal = ({ open, onClose, product }: Props) => {
|
const AddVariantModal = ({ open, onClose, product }: Props) => {
|
||||||
const context = useLayeredModal()
|
const context = useLayeredModal()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const { client } = useMedusa()
|
const { client } = useMedusa()
|
||||||
const form = useForm<EditFlowVariantFormType>({
|
const form = useForm<EditFlowVariantFormType>({
|
||||||
@@ -93,7 +95,9 @@ const AddVariantModal = ({ open, onClose, product }: Props) => {
|
|||||||
<LayeredModal context={context} open={open} handleClose={resetAndClose}>
|
<LayeredModal context={context} open={open} handleClose={resetAndClose}>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
<Modal.Header handleClose={resetAndClose}>
|
<Modal.Header handleClose={resetAndClose}>
|
||||||
<h1 className="inter-xlarge-semibold">Add Variant</h1>
|
<h1 className="inter-xlarge-semibold">
|
||||||
|
{t("product-variants-section-add-variant", "Add Variant")}
|
||||||
|
</h1>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<form onSubmit={onSubmit}>
|
<form onSubmit={onSubmit}>
|
||||||
<Modal.Content>
|
<Modal.Content>
|
||||||
@@ -107,7 +111,7 @@ const AddVariantModal = ({ open, onClose, product }: Props) => {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={resetAndClose}
|
onClick={resetAndClose}
|
||||||
>
|
>
|
||||||
Cancel
|
{t("product-variants-section-cancel", "Cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
@@ -115,7 +119,7 @@ const AddVariantModal = ({ open, onClose, product }: Props) => {
|
|||||||
type="submit"
|
type="submit"
|
||||||
loading={addingVariant}
|
loading={addingVariant}
|
||||||
>
|
>
|
||||||
Save and close
|
{t("product-variants-section-save-and-close", "Save and close")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
|
|||||||
+11
-3
@@ -1,3 +1,4 @@
|
|||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import EditFlowVariantForm, {
|
import EditFlowVariantForm, {
|
||||||
EditFlowVariantFormType,
|
EditFlowVariantFormType,
|
||||||
} from "../../forms/product/variant-inventory-form/edit-flow-variant-form"
|
} from "../../forms/product/variant-inventory-form/edit-flow-variant-form"
|
||||||
@@ -30,6 +31,7 @@ type Props = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const EditVariantInventoryModal = ({ onClose, product, variant }: Props) => {
|
const EditVariantInventoryModal = ({ onClose, product, variant }: Props) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const { client } = useMedusa()
|
const { client } = useMedusa()
|
||||||
const layeredModalContext = useContext(LayeredModalContext)
|
const layeredModalContext = useContext(LayeredModalContext)
|
||||||
const {
|
const {
|
||||||
@@ -191,7 +193,12 @@ const EditVariantInventoryModal = ({ onClose, product, variant }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<LayeredModal context={layeredModalContext} handleClose={handleClose}>
|
<LayeredModal context={layeredModalContext} handleClose={handleClose}>
|
||||||
<Modal.Header handleClose={handleClose}>
|
<Modal.Header handleClose={handleClose}>
|
||||||
<h1 className="inter-xlarge-semibold">Edit stock & inventory</h1>
|
<h1 className="inter-xlarge-semibold">
|
||||||
|
{t(
|
||||||
|
"product-variants-section-edit-stock-inventory",
|
||||||
|
"Edit stock & inventory"
|
||||||
|
)}
|
||||||
|
</h1>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
{!isLoadingInventory && (
|
{!isLoadingInventory && (
|
||||||
<StockForm
|
<StockForm
|
||||||
@@ -222,6 +229,7 @@ const StockForm = ({
|
|||||||
handleClose: () => void
|
handleClose: () => void
|
||||||
updatingVariant: boolean
|
updatingVariant: boolean
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const form = useForm<EditFlowVariantFormType>({
|
const form = useForm<EditFlowVariantFormType>({
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
defaultValues: getEditVariantDefaultValues(variantInventory, variant),
|
defaultValues: getEditVariantDefaultValues(variantInventory, variant),
|
||||||
@@ -259,7 +267,7 @@ const StockForm = ({
|
|||||||
handleClose()
|
handleClose()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Cancel
|
{t("product-variants-section-cancel", "Cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
@@ -268,7 +276,7 @@ const StockForm = ({
|
|||||||
disabled={!isDirty}
|
disabled={!isDirty}
|
||||||
loading={updatingVariant}
|
loading={updatingVariant}
|
||||||
>
|
>
|
||||||
Save and close
|
{t("product-variants-section-save-and-close", "Save and close")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
|
|||||||
+5
-3
@@ -1,4 +1,5 @@
|
|||||||
import { Product, ProductVariant } from "@medusajs/medusa"
|
import { Product, ProductVariant } from "@medusajs/medusa"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import EditFlowVariantForm, {
|
import EditFlowVariantForm, {
|
||||||
EditFlowVariantFormType,
|
EditFlowVariantFormType,
|
||||||
} from "../../forms/product/variant-form/edit-flow-variant-form"
|
} from "../../forms/product/variant-form/edit-flow-variant-form"
|
||||||
@@ -30,6 +31,7 @@ const EditVariantModal = ({
|
|||||||
variant,
|
variant,
|
||||||
isDuplicate = false,
|
isDuplicate = false,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const form = useForm<EditFlowVariantFormType>({
|
const form = useForm<EditFlowVariantFormType>({
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
defaultValues: getEditVariantDefaultValues(variant, product),
|
defaultValues: getEditVariantDefaultValues(variant, product),
|
||||||
@@ -111,7 +113,7 @@ const EditVariantModal = ({
|
|||||||
<LayeredModal context={layeredModalContext} handleClose={handleClose}>
|
<LayeredModal context={layeredModalContext} handleClose={handleClose}>
|
||||||
<Modal.Header handleClose={handleClose}>
|
<Modal.Header handleClose={handleClose}>
|
||||||
<h1 className="inter-xlarge-semibold">
|
<h1 className="inter-xlarge-semibold">
|
||||||
Edit Variant
|
{t("product-variants-section-edit-variant", "Edit Variant")}
|
||||||
{variant.title && (
|
{variant.title && (
|
||||||
<span className="inter-xlarge-regular text-grey-50">
|
<span className="inter-xlarge-regular text-grey-50">
|
||||||
{" "}
|
{" "}
|
||||||
@@ -132,7 +134,7 @@ const EditVariantModal = ({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={handleClose}
|
onClick={handleClose}
|
||||||
>
|
>
|
||||||
Cancel
|
{t("product-variants-section-cancel", "Cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
@@ -141,7 +143,7 @@ const EditVariantModal = ({
|
|||||||
disabled={!isDirty && !isDuplicate}
|
disabled={!isDirty && !isDuplicate}
|
||||||
loading={addingVariant || updatingVariant}
|
loading={addingVariant || updatingVariant}
|
||||||
>
|
>
|
||||||
Save and close
|
{t("product-variants-section-save-and-close", "Save and close")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
|
|||||||
+7
-4
@@ -4,6 +4,7 @@ import {
|
|||||||
ProductVariant,
|
ProductVariant,
|
||||||
} from "@medusajs/medusa"
|
} from "@medusajs/medusa"
|
||||||
import React, { useContext, useEffect, useMemo } from "react"
|
import React, { useContext, useEffect, useMemo } from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import EditFlowVariantForm, {
|
import EditFlowVariantForm, {
|
||||||
EditFlowVariantFormType,
|
EditFlowVariantFormType,
|
||||||
} from "../../../forms/product/variant-form/edit-flow-variant-form"
|
} from "../../../forms/product/variant-form/edit-flow-variant-form"
|
||||||
@@ -23,6 +24,7 @@ type Props = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const EditVariantScreen = ({ variant, product }: Props) => {
|
const EditVariantScreen = ({ variant, product }: Props) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const { onClose } = useEditVariantsModal()
|
const { onClose } = useEditVariantsModal()
|
||||||
const form = useForm<EditFlowVariantFormType>({
|
const form = useForm<EditFlowVariantFormType>({
|
||||||
defaultValues: getEditVariantDefaultValues(variant, product),
|
defaultValues: getEditVariantDefaultValues(variant, product),
|
||||||
@@ -67,7 +69,7 @@ const EditVariantScreen = ({ variant, product }: Props) => {
|
|||||||
<Modal.Footer>
|
<Modal.Footer>
|
||||||
<div className="gap-x-xsmall flex w-full items-center justify-end">
|
<div className="gap-x-xsmall flex w-full items-center justify-end">
|
||||||
<Button variant="secondary" size="small" type="button">
|
<Button variant="secondary" size="small" type="button">
|
||||||
Cancel
|
{t("edit-variants-modal-cancel", "Cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
@@ -77,7 +79,7 @@ const EditVariantScreen = ({ variant, product }: Props) => {
|
|||||||
loading={updatingVariant}
|
loading={updatingVariant}
|
||||||
onClick={onSubmitAndBack}
|
onClick={onSubmitAndBack}
|
||||||
>
|
>
|
||||||
Save and go back
|
{t("edit-variants-modal-save-and-go-back", "Save and go back")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
@@ -87,7 +89,7 @@ const EditVariantScreen = ({ variant, product }: Props) => {
|
|||||||
loading={updatingVariant}
|
loading={updatingVariant}
|
||||||
onClick={onSubmitAndClose}
|
onClick={onSubmitAndClose}
|
||||||
>
|
>
|
||||||
Save and close
|
{t("edit-variants-modal-save-and-close", "Save and close")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
@@ -133,11 +135,12 @@ export const createUpdatePayload = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const useEditVariantScreen = (props: Props) => {
|
export const useEditVariantScreen = (props: Props) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const { pop } = React.useContext(LayeredModalContext)
|
const { pop } = React.useContext(LayeredModalContext)
|
||||||
|
|
||||||
const screen = useMemo(() => {
|
const screen = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
title: "Edit Variant",
|
title: t("edit-variants-modal-edit-variant", "Edit Variant"),
|
||||||
subtitle: props.variant.title,
|
subtitle: props.variant.title,
|
||||||
onBack: pop,
|
onBack: pop,
|
||||||
view: <EditVariantScreen {...props} />,
|
view: <EditVariantScreen {...props} />,
|
||||||
|
|||||||
+20
-6
@@ -6,6 +6,7 @@ import {
|
|||||||
useFieldArray,
|
useFieldArray,
|
||||||
useForm,
|
useForm,
|
||||||
} from "react-hook-form"
|
} from "react-hook-form"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import useEditProductActions from "../../../../hooks/use-edit-product-actions"
|
import useEditProductActions from "../../../../hooks/use-edit-product-actions"
|
||||||
import Button from "../../../fundamentals/button"
|
import Button from "../../../fundamentals/button"
|
||||||
import Modal from "../../../molecules/modal"
|
import Modal from "../../../molecules/modal"
|
||||||
@@ -36,6 +37,7 @@ export type EditVariantsForm = {
|
|||||||
const EditVariantsModal = ({ open, onClose, product }: Props) => {
|
const EditVariantsModal = ({ open, onClose, product }: Props) => {
|
||||||
const context = useContext(LayeredModalContext)
|
const context = useContext(LayeredModalContext)
|
||||||
const { onUpdate, updating } = useEditProductActions(product.id)
|
const { onUpdate, updating } = useEditProductActions(product.id)
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const form = useForm<EditVariantsForm>({
|
const form = useForm<EditVariantsForm>({
|
||||||
defaultValues: getDefaultValues(product),
|
defaultValues: getDefaultValues(product),
|
||||||
@@ -109,8 +111,11 @@ const EditVariantsModal = ({ open, onClose, product }: Props) => {
|
|||||||
() => {
|
() => {
|
||||||
resetAndClose()
|
resetAndClose()
|
||||||
},
|
},
|
||||||
|
t(
|
||||||
|
"edit-variants-modal-update-success",
|
||||||
"Variants were successfully updated"
|
"Variants were successfully updated"
|
||||||
)
|
)
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -122,20 +127,29 @@ const EditVariantsModal = ({ open, onClose, product }: Props) => {
|
|||||||
<LayeredModal handleClose={resetAndClose} open={open} context={context}>
|
<LayeredModal handleClose={resetAndClose} open={open} context={context}>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
<Modal.Header handleClose={resetAndClose}>
|
<Modal.Header handleClose={resetAndClose}>
|
||||||
<h1 className="inter-xlarge-semibold">Edit Variants</h1>
|
<h1 className="inter-xlarge-semibold">
|
||||||
|
{t("edit-variants-modal-edit-variants", "Edit Variants")}
|
||||||
|
</h1>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<FormProvider {...form}>
|
<FormProvider {...form}>
|
||||||
<form onSubmit={onSubmit}>
|
<form onSubmit={onSubmit}>
|
||||||
<Modal.Content>
|
<Modal.Content>
|
||||||
<h2 className="inter-base-semibold mb-small">
|
<h2 className="inter-base-semibold mb-small">
|
||||||
Product variants{" "}
|
{t(
|
||||||
|
"edit-variants-modal-product-variants",
|
||||||
|
"Product variants"
|
||||||
|
)}{" "}
|
||||||
<span className="inter-base-regular text-grey-50">
|
<span className="inter-base-regular text-grey-50">
|
||||||
({product.variants.length})
|
({product.variants.length})
|
||||||
</span>
|
</span>
|
||||||
</h2>
|
</h2>
|
||||||
<div className="pr-base inter-small-semibold text-grey-50 mb-small grid grid-cols-[1fr_1fr_48px]">
|
<div className="pr-base inter-small-semibold text-grey-50 mb-small grid grid-cols-[1fr_1fr_48px]">
|
||||||
<p className="col-start-1 col-end-1 text-left">Variant</p>
|
<p className="col-start-1 col-end-1 text-left">
|
||||||
<p className="col-start-2 col-end-2 text-right">Inventory</p>
|
{t("edit-variants-modal-variant", "Variant")}
|
||||||
|
</p>
|
||||||
|
<p className="col-start-2 col-end-2 text-right">
|
||||||
|
{t("edit-variants-modal-inventory", "Inventory")}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>{fields.map((card, i) => renderCard(card, i))}</div>
|
<div>{fields.map((card, i) => renderCard(card, i))}</div>
|
||||||
</Modal.Content>
|
</Modal.Content>
|
||||||
@@ -147,7 +161,7 @@ const EditVariantsModal = ({ open, onClose, product }: Props) => {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={resetAndClose}
|
onClick={resetAndClose}
|
||||||
>
|
>
|
||||||
Cancel
|
{t("edit-variants-modal-cancel", "Cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
@@ -156,7 +170,7 @@ const EditVariantsModal = ({ open, onClose, product }: Props) => {
|
|||||||
loading={updating}
|
loading={updating}
|
||||||
disabled={updating || !isDirty}
|
disabled={updating || !isDirty}
|
||||||
>
|
>
|
||||||
Save and close
|
{t("edit-variants-modal-save-and-close", "Save and close")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
|
|||||||
+3
-1
@@ -4,6 +4,7 @@ import type { Identifier, XYCoord } from "dnd-core"
|
|||||||
import { useContext, useMemo, useRef } from "react"
|
import { useContext, useMemo, useRef } from "react"
|
||||||
import { useDrag, useDrop } from "react-dnd"
|
import { useDrag, useDrop } from "react-dnd"
|
||||||
import { useFormContext } from "react-hook-form"
|
import { useFormContext } from "react-hook-form"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import { VariantItem } from "."
|
import { VariantItem } from "."
|
||||||
import { DragItem } from "../../../../types/shared"
|
import { DragItem } from "../../../../types/shared"
|
||||||
import FormValidator from "../../../../utils/form-validator"
|
import FormValidator from "../../../../utils/form-validator"
|
||||||
@@ -35,6 +36,7 @@ export const VariantCard = ({
|
|||||||
moveCard,
|
moveCard,
|
||||||
product,
|
product,
|
||||||
}: VariantCardProps) => {
|
}: VariantCardProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
@@ -49,7 +51,7 @@ export const VariantCard = ({
|
|||||||
const actions: ActionType[] = useMemo(() => {
|
const actions: ActionType[] = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
label: "Edit Variant",
|
label: t("edit-variants-modal-edit-variant", "Edit Variant"),
|
||||||
icon: <EditIcon size={20} className="text-grey-50" />,
|
icon: <EditIcon size={20} className="text-grey-50" />,
|
||||||
onClick: () => push(editVariantScreen),
|
onClick: () => push(editVariantScreen),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import OptionsProvider, { useOptionsContext } from "./options-provider"
|
import OptionsProvider, { useOptionsContext } from "./options-provider"
|
||||||
import { Product, ProductVariant, VariantInventory } from "@medusajs/medusa"
|
import { Product, ProductVariant, VariantInventory } from "@medusajs/medusa"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
import { ActionType } from "../../molecules/actionables"
|
import { ActionType } from "../../molecules/actionables"
|
||||||
import AddVariantModal from "./add-variant-modal"
|
import AddVariantModal from "./add-variant-modal"
|
||||||
@@ -28,6 +29,7 @@ type Props = {
|
|||||||
const ProductVariantsSection = ({ product }: Props) => {
|
const ProductVariantsSection = ({ product }: Props) => {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const { client } = useMedusa()
|
const { client } = useMedusa()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const { isFeatureEnabled } = useFeatureFlag()
|
const { isFeatureEnabled } = useFeatureFlag()
|
||||||
|
|
||||||
@@ -69,22 +71,22 @@ const ProductVariantsSection = ({ product }: Props) => {
|
|||||||
|
|
||||||
const actions: ActionType[] = [
|
const actions: ActionType[] = [
|
||||||
{
|
{
|
||||||
label: "Add Variant",
|
label: t("product-variants-section-add-variant", "Add Variant"),
|
||||||
onClick: toggleAddVariant,
|
onClick: toggleAddVariant,
|
||||||
icon: <PlusIcon size="20" />,
|
icon: <PlusIcon size="20" />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Edit Prices",
|
label: t("product-variants-section-edit-prices", "Edit Prices"),
|
||||||
onClick: toggleEditPrices,
|
onClick: toggleEditPrices,
|
||||||
icon: <DollarSignIcon size="20" />,
|
icon: <DollarSignIcon size="20" />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Edit Variants",
|
label: t("product-variants-section-edit-variants", "Edit Variants"),
|
||||||
onClick: toggleEditVariants,
|
onClick: toggleEditVariants,
|
||||||
icon: <EditIcon size="20" />,
|
icon: <EditIcon size="20" />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Edit Options",
|
label: t("product-variants-section-edit-options", "Edit Options"),
|
||||||
onClick: toggleOptions,
|
onClick: toggleOptions,
|
||||||
icon: <GearIcon size="20" />,
|
icon: <GearIcon size="20" />,
|
||||||
},
|
},
|
||||||
@@ -130,7 +132,7 @@ const ProductVariantsSection = ({ product }: Props) => {
|
|||||||
<ProductOptions />
|
<ProductOptions />
|
||||||
<div className="mt-xlarge">
|
<div className="mt-xlarge">
|
||||||
<h2 className="inter-large-semibold mb-base">
|
<h2 className="inter-large-semibold mb-base">
|
||||||
Product variants{" "}
|
{t("product-variants-section-product-variants", "Product variants")}{" "}
|
||||||
<span className="inter-large-regular text-grey-50">
|
<span className="inter-large-regular text-grey-50">
|
||||||
({product.variants.length})
|
({product.variants.length})
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
+35
-9
@@ -6,6 +6,7 @@ import {
|
|||||||
} from "medusa-react"
|
} from "medusa-react"
|
||||||
import { useEffect, useMemo } from "react"
|
import { useEffect, useMemo } from "react"
|
||||||
import { useFieldArray, useForm } from "react-hook-form"
|
import { useFieldArray, useForm } from "react-hook-form"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import useNotification from "../../../hooks/use-notification"
|
import useNotification from "../../../hooks/use-notification"
|
||||||
import FormValidator from "../../../utils/form-validator"
|
import FormValidator from "../../../utils/form-validator"
|
||||||
import Button from "../../fundamentals/button"
|
import Button from "../../fundamentals/button"
|
||||||
@@ -41,6 +42,7 @@ const OptionsModal = ({ product, open, onClose }: Props) => {
|
|||||||
product.id
|
product.id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const { t } = useTranslation()
|
||||||
const { refetch } = useOptionsContext()
|
const { refetch } = useOptionsContext()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -140,7 +142,14 @@ const OptionsModal = ({ product, open, onClose }: Props) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (errors.length === toCreate.length + toUpdate.length + toDelete.length) {
|
if (errors.length === toCreate.length + toUpdate.length + toDelete.length) {
|
||||||
notification("Error", "Failed to update product options", "error")
|
notification(
|
||||||
|
t("product-variants-section-error", "Error"),
|
||||||
|
t(
|
||||||
|
"product-variants-section-failed-to-update-product-options",
|
||||||
|
"Failed to update product options"
|
||||||
|
),
|
||||||
|
"error"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,7 +162,14 @@ const OptionsModal = ({ product, open, onClose }: Props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
refetch()
|
refetch()
|
||||||
notification("Success", "Successfully updated product options", "success")
|
notification(
|
||||||
|
t("product-variants-section-success", "Success"),
|
||||||
|
t(
|
||||||
|
"product-variants-section-successfully-updated-product-options",
|
||||||
|
"Successfully updated product options"
|
||||||
|
),
|
||||||
|
"success"
|
||||||
|
)
|
||||||
handleClose()
|
handleClose()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -161,13 +177,19 @@ const OptionsModal = ({ product, open, onClose }: Props) => {
|
|||||||
<Modal open={open} handleClose={handleClose}>
|
<Modal open={open} handleClose={handleClose}>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
<Modal.Header handleClose={handleClose}>
|
<Modal.Header handleClose={handleClose}>
|
||||||
<h1 className="inter-xlarge-semibold">Edit Options</h1>
|
<h1 className="inter-xlarge-semibold">
|
||||||
|
{t("product-variants-section-edit-options", "Edit Options")}
|
||||||
|
</h1>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<form onSubmit={onSubmit}>
|
<form onSubmit={onSubmit}>
|
||||||
<Modal.Content>
|
<Modal.Content>
|
||||||
<h2 className="inter-large-semibold mb-base">Product options</h2>
|
<h2 className="inter-large-semibold mb-base">
|
||||||
|
{t("product-variants-section-product-options", "Product options")}
|
||||||
|
</h2>
|
||||||
<div className="gap-y-small flex flex-col">
|
<div className="gap-y-small flex flex-col">
|
||||||
<p className="inter-small-semibold text-grey-50">Option title</p>
|
<p className="inter-small-semibold text-grey-50">
|
||||||
|
{t("product-variants-section-option-title", "Option title")}
|
||||||
|
</p>
|
||||||
<div className="gap-y-xsmall flex flex-col">
|
<div className="gap-y-xsmall flex flex-col">
|
||||||
{fields.map((field, index) => {
|
{fields.map((field, index) => {
|
||||||
return (
|
return (
|
||||||
@@ -179,7 +201,10 @@ const OptionsModal = ({ product, open, onClose }: Props) => {
|
|||||||
key={field.id}
|
key={field.id}
|
||||||
placeholder="Color"
|
placeholder="Color"
|
||||||
{...register(`options.${index}.title`, {
|
{...register(`options.${index}.title`, {
|
||||||
required: "Option title is required",
|
required: t(
|
||||||
|
"product-variants-section-option-title-is-required",
|
||||||
|
"Option title is required"
|
||||||
|
),
|
||||||
minLength:
|
minLength:
|
||||||
FormValidator.minOneCharRule("Option title"),
|
FormValidator.minOneCharRule("Option title"),
|
||||||
pattern: FormValidator.whiteSpaceRule("Option title"),
|
pattern: FormValidator.whiteSpaceRule("Option title"),
|
||||||
@@ -205,7 +230,8 @@ const OptionsModal = ({ product, open, onClose }: Props) => {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={handleAddAnOption}
|
onClick={handleAddAnOption}
|
||||||
>
|
>
|
||||||
<PlusIcon size="20" /> Add an option
|
<PlusIcon size="20" />{" "}
|
||||||
|
{t("product-variants-section-add-an-option", "Add an option")}
|
||||||
</Button>
|
</Button>
|
||||||
</Modal.Content>
|
</Modal.Content>
|
||||||
<Modal.Footer>
|
<Modal.Footer>
|
||||||
@@ -216,7 +242,7 @@ const OptionsModal = ({ product, open, onClose }: Props) => {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={handleClose}
|
onClick={handleClose}
|
||||||
>
|
>
|
||||||
Cancel
|
{t("product-variants-section-cancel", "Cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
@@ -225,7 +251,7 @@ const OptionsModal = ({ product, open, onClose }: Props) => {
|
|||||||
disabled={!isDirty}
|
disabled={!isDirty}
|
||||||
loading={isSubmitting}
|
loading={isSubmitting}
|
||||||
>
|
>
|
||||||
Save and close
|
{t("product-variants-section-save-and-close", "Save and close")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Column, useTable } from "react-table"
|
|||||||
|
|
||||||
import { ProductVariant } from "@medusajs/medusa"
|
import { ProductVariant } from "@medusajs/medusa"
|
||||||
import { useMemo, useState } from "react"
|
import { useMemo, useState } from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import { useFeatureFlag } from "../../../providers/feature-flag-provider"
|
import { useFeatureFlag } from "../../../providers/feature-flag-provider"
|
||||||
import BuildingsIcon from "../../fundamentals/icons/buildings-icon"
|
import BuildingsIcon from "../../fundamentals/icons/buildings-icon"
|
||||||
import DuplicateIcon from "../../fundamentals/icons/duplicate-icon"
|
import DuplicateIcon from "../../fundamentals/icons/duplicate-icon"
|
||||||
@@ -22,6 +23,7 @@ type Props = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const useVariantsTableColumns = (inventoryIsEnabled = false) => {
|
export const useVariantsTableColumns = (inventoryIsEnabled = false) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const columns = useMemo<Column<ProductVariant>[]>(() => {
|
const columns = useMemo<Column<ProductVariant>[]>(() => {
|
||||||
const quantityColumns = []
|
const quantityColumns = []
|
||||||
if (!inventoryIsEnabled) {
|
if (!inventoryIsEnabled) {
|
||||||
@@ -29,7 +31,9 @@ export const useVariantsTableColumns = (inventoryIsEnabled = false) => {
|
|||||||
Header: () => {
|
Header: () => {
|
||||||
return (
|
return (
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<span>Inventory</span>
|
<span>
|
||||||
|
{t("product-variants-section-inventory", "Inventory")}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -47,12 +51,12 @@ export const useVariantsTableColumns = (inventoryIsEnabled = false) => {
|
|||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
Header: "Title",
|
Header: t("product-variants-section-title", "Title"),
|
||||||
id: "title",
|
id: "title",
|
||||||
accessor: "title",
|
accessor: "title",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "SKU",
|
Header: t("product-variants-section-sku", "SKU"),
|
||||||
id: "sku",
|
id: "sku",
|
||||||
accessor: "sku",
|
accessor: "sku",
|
||||||
maxWidth: 264,
|
maxWidth: 264,
|
||||||
@@ -65,7 +69,7 @@ export const useVariantsTableColumns = (inventoryIsEnabled = false) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "EAN",
|
Header: t("product-variants-section-ean", "EAN"),
|
||||||
id: "ean",
|
id: "ean",
|
||||||
accessor: "ean",
|
accessor: "ean",
|
||||||
maxWidth: 264,
|
maxWidth: 264,
|
||||||
@@ -85,6 +89,7 @@ export const useVariantsTableColumns = (inventoryIsEnabled = false) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const VariantsTable = ({ variants, actions }: Props) => {
|
const VariantsTable = ({ variants, actions }: Props) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const { isFeatureEnabled } = useFeatureFlag()
|
const { isFeatureEnabled } = useFeatureFlag()
|
||||||
const hasInventoryService = isFeatureEnabled("inventoryService")
|
const hasInventoryService = isFeatureEnabled("inventoryService")
|
||||||
const columns = useVariantsTableColumns(hasInventoryService)
|
const columns = useVariantsTableColumns(hasInventoryService)
|
||||||
@@ -112,20 +117,26 @@ const VariantsTable = ({ variants, actions }: Props) => {
|
|||||||
const inventoryManagementActions = []
|
const inventoryManagementActions = []
|
||||||
if (hasInventoryService) {
|
if (hasInventoryService) {
|
||||||
inventoryManagementActions.push({
|
inventoryManagementActions.push({
|
||||||
label: "Manage inventory",
|
label: t(
|
||||||
|
"product-variants-section-manage-inventory",
|
||||||
|
"Manage inventory"
|
||||||
|
),
|
||||||
icon: <BuildingsIcon size="20" />,
|
icon: <BuildingsIcon size="20" />,
|
||||||
onClick: () => updateVariantInventory(variant),
|
onClick: () => updateVariantInventory(variant),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
label: "Edit Variant",
|
label: t("product-variants-section-edit-variant", "Edit Variant"),
|
||||||
icon: <EditIcon size="20" />,
|
icon: <EditIcon size="20" />,
|
||||||
onClick: () => updateVariant(variant),
|
onClick: () => updateVariant(variant),
|
||||||
},
|
},
|
||||||
...inventoryManagementActions,
|
...inventoryManagementActions,
|
||||||
{
|
{
|
||||||
label: "Duplicate Variant",
|
label: t(
|
||||||
|
"product-variants-section-duplicate-variant",
|
||||||
|
"Duplicate Variant"
|
||||||
|
),
|
||||||
onClick: () =>
|
onClick: () =>
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
duplicateVariant({
|
duplicateVariant({
|
||||||
@@ -135,7 +146,10 @@ const VariantsTable = ({ variants, actions }: Props) => {
|
|||||||
icon: <DuplicateIcon size="20" />,
|
icon: <DuplicateIcon size="20" />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Delete Variant",
|
label: t(
|
||||||
|
"product-variants-section-delete-variant-label",
|
||||||
|
"Delete Variant"
|
||||||
|
),
|
||||||
onClick: () => setVariantToRemove(variant),
|
onClick: () => setVariantToRemove(variant),
|
||||||
icon: <TrashIcon size="20" />,
|
icon: <TrashIcon size="20" />,
|
||||||
variant: "danger",
|
variant: "danger",
|
||||||
@@ -185,11 +199,23 @@ const VariantsTable = ({ variants, actions }: Props) => {
|
|||||||
<DeletePrompt
|
<DeletePrompt
|
||||||
onDelete={async () => deleteVariant(variantToRemove.id)}
|
onDelete={async () => deleteVariant(variantToRemove.id)}
|
||||||
handleClose={() => setVariantToRemove(null)}
|
handleClose={() => setVariantToRemove(null)}
|
||||||
confirmText="Yes, delete"
|
confirmText={t(
|
||||||
heading="Delete variant"
|
"product-variants-section-yes-delete",
|
||||||
text={`Are you sure you want to delete this variant? ${
|
"Yes, delete"
|
||||||
|
)}
|
||||||
|
heading={t(
|
||||||
|
"product-variants-section-delete-variant-heading",
|
||||||
|
"Delete variant"
|
||||||
|
)}
|
||||||
|
text={`${t(
|
||||||
|
"product-variants-section-confirm-delete",
|
||||||
|
"Are you sure you want to delete this variant? "
|
||||||
|
)}${
|
||||||
isFeatureEnabled("inventoryService")
|
isFeatureEnabled("inventoryService")
|
||||||
? " Note: Deleting the variant will also remove inventory items and levels"
|
? t(
|
||||||
|
"product-variants-section-note-deleting-the-variant-will-also-remove-inventory-items-and-levels",
|
||||||
|
" Note: Deleting the variant will also remove inventory items and levels"
|
||||||
|
)
|
||||||
: ""
|
: ""
|
||||||
}`}
|
}`}
|
||||||
successText={false}
|
successText={false}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useAdminSendResetPasswordToken } from "medusa-react"
|
import { useAdminSendResetPasswordToken } from "medusa-react"
|
||||||
import React, { useState } from "react"
|
import React, { useState } from "react"
|
||||||
import { useForm } from "react-hook-form"
|
import { useForm } from "react-hook-form"
|
||||||
|
import { Trans, useTranslation } from "react-i18next"
|
||||||
import useNotification from "../../../hooks/use-notification"
|
import useNotification from "../../../hooks/use-notification"
|
||||||
import { getErrorMessage } from "../../../utils/error-messages"
|
import { getErrorMessage } from "../../../utils/error-messages"
|
||||||
import FormValidator from "../../../utils/form-validator"
|
import FormValidator from "../../../utils/form-validator"
|
||||||
@@ -22,6 +23,7 @@ const emailRegex = new RegExp(
|
|||||||
)
|
)
|
||||||
|
|
||||||
const ResetTokenCard: React.FC<ResetTokenCardProps> = ({ goBack }) => {
|
const ResetTokenCard: React.FC<ResetTokenCardProps> = ({ goBack }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const [mailSent, setSentMail] = useState(false)
|
const [mailSent, setSentMail] = useState(false)
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
@@ -42,7 +44,11 @@ const ResetTokenCard: React.FC<ResetTokenCardProps> = ({ goBack }) => {
|
|||||||
setSentMail(true)
|
setSentMail(true)
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
notification("Error", getErrorMessage(error), "error")
|
notification(
|
||||||
|
t("reset-token-card-error", "Error"),
|
||||||
|
getErrorMessage(error),
|
||||||
|
"error"
|
||||||
|
)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -52,25 +58,30 @@ const ResetTokenCard: React.FC<ResetTokenCardProps> = ({ goBack }) => {
|
|||||||
<form onSubmit={onSubmit}>
|
<form onSubmit={onSubmit}>
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
<h1 className="inter-xlarge-semibold text-grey-90 mb-xsmall text-[20px]">
|
<h1 className="inter-xlarge-semibold text-grey-90 mb-xsmall text-[20px]">
|
||||||
Reset your password
|
{t("reset-token-card-reset-your-password", "Reset your password")}
|
||||||
</h1>
|
</h1>
|
||||||
<span className="inter-base-regular text-grey-50 mb-large text-center">
|
<span className="inter-base-regular text-grey-50 mb-large text-center">
|
||||||
Enter your email address below, and we'll
|
<Trans t={t} i18nKey="reset-token-card-password-reset-description">
|
||||||
|
Enter your email address below, and we'll
|
||||||
<br />
|
<br />
|
||||||
send you instructions on how to reset
|
send you instructions on how to reset
|
||||||
<br />
|
<br />
|
||||||
your password.
|
your password.
|
||||||
|
</Trans>
|
||||||
</span>
|
</span>
|
||||||
{!mailSent ? (
|
{!mailSent ? (
|
||||||
<>
|
<>
|
||||||
<div className="w-[280px]">
|
<div className="w-[280px]">
|
||||||
<SigninInput
|
<SigninInput
|
||||||
placeholder="Email"
|
placeholder={t("reset-token-card-email", "Email")}
|
||||||
{...register("email", {
|
{...register("email", {
|
||||||
required: FormValidator.required("Email"),
|
required: FormValidator.required("Email"),
|
||||||
pattern: {
|
pattern: {
|
||||||
value: emailRegex,
|
value: emailRegex,
|
||||||
message: "This is not a valid email",
|
message: t(
|
||||||
|
"reset-token-card-this-is-not-a-valid-email",
|
||||||
|
"This is not a valid email"
|
||||||
|
),
|
||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
@@ -83,7 +94,10 @@ const ResetTokenCard: React.FC<ResetTokenCardProps> = ({ goBack }) => {
|
|||||||
type="submit"
|
type="submit"
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
>
|
>
|
||||||
Send reset instructions
|
{t(
|
||||||
|
"reset-token-card-send-reset-instructions",
|
||||||
|
"Send reset instructions"
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
@@ -93,7 +107,10 @@ const ResetTokenCard: React.FC<ResetTokenCardProps> = ({ goBack }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="gap-y-2xsmall flex flex-col">
|
<div className="gap-y-2xsmall flex flex-col">
|
||||||
<span className="inter-base-regular">
|
<span className="inter-base-regular">
|
||||||
Successfully sent you an email
|
{t(
|
||||||
|
"reset-token-card-successfully-sent-you-an-email",
|
||||||
|
"Successfully sent you an email"
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -102,7 +119,7 @@ const ResetTokenCard: React.FC<ResetTokenCardProps> = ({ goBack }) => {
|
|||||||
className="inter-small-regular text-grey-50 mt-8 cursor-pointer"
|
className="inter-small-regular text-grey-50 mt-8 cursor-pointer"
|
||||||
onClick={goBack}
|
onClick={goBack}
|
||||||
>
|
>
|
||||||
Go back to sign in
|
{t("reset-token-card-go-back-to-sign-in", "Go back to sign in")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import clsx from "clsx"
|
import clsx from "clsx"
|
||||||
import React from "react"
|
import React from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import { formatAmountWithSymbol } from "../../../utils/prices"
|
import { formatAmountWithSymbol } from "../../../utils/prices"
|
||||||
import Button from "../../fundamentals/button"
|
import Button from "../../fundamentals/button"
|
||||||
import MinusIcon from "../../fundamentals/icons/minus-icon"
|
import MinusIcon from "../../fundamentals/icons/minus-icon"
|
||||||
@@ -39,11 +40,16 @@ const RMAReturnProductsTable: React.FC<RMAReturnProductsTableProps> = ({
|
|||||||
handleRemoveItem,
|
handleRemoveItem,
|
||||||
handleToAddQuantity,
|
handleToAddQuantity,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
return (
|
return (
|
||||||
<Table>
|
<Table>
|
||||||
<Table.HeadRow className="text-grey-50 inter-small-semibold">
|
<Table.HeadRow className="text-grey-50 inter-small-semibold">
|
||||||
<Table.HeadCell>Product Details</Table.HeadCell>
|
<Table.HeadCell>
|
||||||
<Table.HeadCell className="pr-8 text-right">Quantity</Table.HeadCell>
|
{t("rma-return-product-table-product-details", "Product Details")}
|
||||||
|
</Table.HeadCell>
|
||||||
|
<Table.HeadCell className="pr-8 text-right">
|
||||||
|
{t("rma-return-product-table-quantity", "Quantity")}
|
||||||
|
</Table.HeadCell>
|
||||||
<Table.HeadCell className="text-right">
|
<Table.HeadCell className="text-right">
|
||||||
{isAdditionalItems ? "Unit Price" : "Refundable"}
|
{isAdditionalItems ? "Unit Price" : "Refundable"}
|
||||||
</Table.HeadCell>
|
</Table.HeadCell>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { LineItem, Order } from "@medusajs/medusa"
|
import { LineItem, Order } from "@medusajs/medusa"
|
||||||
import clsx from "clsx"
|
import clsx from "clsx"
|
||||||
import React, { Fragment, useContext } from "react"
|
import React, { Fragment, useContext } from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import RMAReturnReasonSubModal from "../../../domain/orders/details/rma-sub-modals/return-reasons"
|
import RMAReturnReasonSubModal from "../../../domain/orders/details/rma-sub-modals/return-reasons"
|
||||||
import Medusa from "../../../services/api"
|
import Medusa from "../../../services/api"
|
||||||
import { isLineItemCanceled } from "../../../utils/is-line-item"
|
import { isLineItemCanceled } from "../../../utils/is-line-item"
|
||||||
@@ -32,6 +33,7 @@ const RMASelectProductTable: React.FC<RMASelectProductTableProps> = ({
|
|||||||
setToReturn,
|
setToReturn,
|
||||||
isSwapOrClaim = false,
|
isSwapOrClaim = false,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const { push, pop } = useContext(LayeredModalContext)
|
const { push, pop } = useContext(LayeredModalContext)
|
||||||
|
|
||||||
const handleQuantity = (change, item) => {
|
const handleQuantity = (change, item) => {
|
||||||
@@ -112,9 +114,15 @@ const RMASelectProductTable: React.FC<RMASelectProductTableProps> = ({
|
|||||||
<Table>
|
<Table>
|
||||||
<Table.Head className="border-none">
|
<Table.Head className="border-none">
|
||||||
<Table.HeadRow className="text-grey-50 inter-small-semibold">
|
<Table.HeadRow className="text-grey-50 inter-small-semibold">
|
||||||
<Table.HeadCell colSpan={2}>Product Details</Table.HeadCell>
|
<Table.HeadCell colSpan={2}>
|
||||||
<Table.HeadCell className="pr-8 text-right">Quantity</Table.HeadCell>
|
{t("rma-select-product-table-product-details", "Product Details")}
|
||||||
<Table.HeadCell className="text-right">Refundable</Table.HeadCell>
|
</Table.HeadCell>
|
||||||
|
<Table.HeadCell className="pr-8 text-right">
|
||||||
|
{t("rma-select-product-table-quantity", "Quantity")}
|
||||||
|
</Table.HeadCell>
|
||||||
|
<Table.HeadCell className="text-right">
|
||||||
|
{t("rma-select-product-table-refundable", "Refundable")}
|
||||||
|
</Table.HeadCell>
|
||||||
<Table.HeadCell></Table.HeadCell>
|
<Table.HeadCell></Table.HeadCell>
|
||||||
</Table.HeadRow>
|
</Table.HeadRow>
|
||||||
</Table.Head>
|
</Table.Head>
|
||||||
@@ -231,11 +239,13 @@ const RMASelectProductTable: React.FC<RMASelectProductTableProps> = ({
|
|||||||
<span className="ml-2">
|
<span className="ml-2">
|
||||||
{toReturn[item.id]?.images?.length > 0 && (
|
{toReturn[item.id]?.images?.length > 0 && (
|
||||||
<>
|
<>
|
||||||
({toReturn[item.id]?.images?.length} image{" "}
|
{t(
|
||||||
{toReturn[item.id]?.images?.length > 1
|
"rma-select-product-table-images-witch-count",
|
||||||
? "s"
|
"{{count}}",
|
||||||
: ""}
|
{
|
||||||
)
|
count: toReturn[item.id]?.images?.length,
|
||||||
|
}
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
@@ -263,7 +273,10 @@ const RMASelectProductTable: React.FC<RMASelectProductTableProps> = ({
|
|||||||
size="small"
|
size="small"
|
||||||
className="border-grey-20 border"
|
className="border-grey-20 border"
|
||||||
>
|
>
|
||||||
Select Reason
|
{t(
|
||||||
|
"rma-select-product-table-select-reason",
|
||||||
|
"Select Reason"
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useAdminStore } from "medusa-react"
|
import { useAdminStore } from "medusa-react"
|
||||||
import React, { useState } from "react"
|
import React, { useState } from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
import { useFeatureFlag } from "../../../providers/feature-flag-provider"
|
import { useFeatureFlag } from "../../../providers/feature-flag-provider"
|
||||||
import { useRoutes } from "../../../providers/route-provider"
|
import { useRoutes } from "../../../providers/route-provider"
|
||||||
@@ -19,6 +20,7 @@ import UserMenu from "../../molecules/user-menu"
|
|||||||
const ICON_SIZE = 20
|
const ICON_SIZE = 20
|
||||||
|
|
||||||
const Sidebar: React.FC = () => {
|
const Sidebar: React.FC = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const [currentlyOpen, setCurrentlyOpen] = useState(-1)
|
const [currentlyOpen, setCurrentlyOpen] = useState(-1)
|
||||||
|
|
||||||
const { isFeatureEnabled } = useFeatureFlag()
|
const { isFeatureEnabled } = useFeatureFlag()
|
||||||
@@ -50,7 +52,9 @@ const Sidebar: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="my-base flex flex-col px-2">
|
<div className="my-base flex flex-col px-2">
|
||||||
<span className="text-grey-50 text-small font-medium">Store</span>
|
<span className="text-grey-50 text-small font-medium">
|
||||||
|
{t("sidebar-store", "Store")}
|
||||||
|
</span>
|
||||||
<span className="text-grey-90 text-medium font-medium">
|
<span className="text-grey-90 text-medium font-medium">
|
||||||
{store?.name}
|
{store?.name}
|
||||||
</span>
|
</span>
|
||||||
@@ -60,19 +64,19 @@ const Sidebar: React.FC = () => {
|
|||||||
pageLink={"/a/orders"}
|
pageLink={"/a/orders"}
|
||||||
icon={<CartIcon size={ICON_SIZE} />}
|
icon={<CartIcon size={ICON_SIZE} />}
|
||||||
triggerHandler={triggerHandler}
|
triggerHandler={triggerHandler}
|
||||||
text={"Orders"}
|
text={t("sidebar-orders", "Orders")}
|
||||||
/>
|
/>
|
||||||
<SidebarMenuItem
|
<SidebarMenuItem
|
||||||
pageLink={"/a/products"}
|
pageLink={"/a/products"}
|
||||||
icon={<TagIcon size={ICON_SIZE} />}
|
icon={<TagIcon size={ICON_SIZE} />}
|
||||||
text={"Products"}
|
text={t("sidebar-products", "Products")}
|
||||||
triggerHandler={triggerHandler}
|
triggerHandler={triggerHandler}
|
||||||
/>
|
/>
|
||||||
{isFeatureEnabled("product_categories") && (
|
{isFeatureEnabled("product_categories") && (
|
||||||
<SidebarMenuItem
|
<SidebarMenuItem
|
||||||
pageLink={"/a/product-categories"}
|
pageLink={"/a/product-categories"}
|
||||||
icon={<SwatchIcon size={ICON_SIZE} />}
|
icon={<SwatchIcon size={ICON_SIZE} />}
|
||||||
text={"Categories"}
|
text={t("sidebar-categories", "Categories")}
|
||||||
triggerHandler={triggerHandler}
|
triggerHandler={triggerHandler}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -80,33 +84,33 @@ const Sidebar: React.FC = () => {
|
|||||||
pageLink={"/a/customers"}
|
pageLink={"/a/customers"}
|
||||||
icon={<UsersIcon size={ICON_SIZE} />}
|
icon={<UsersIcon size={ICON_SIZE} />}
|
||||||
triggerHandler={triggerHandler}
|
triggerHandler={triggerHandler}
|
||||||
text={"Customers"}
|
text={t("sidebar-customers", "Customers")}
|
||||||
/>
|
/>
|
||||||
{inventoryEnabled && (
|
{inventoryEnabled && (
|
||||||
<SidebarMenuItem
|
<SidebarMenuItem
|
||||||
pageLink={"/a/inventory"}
|
pageLink={"/a/inventory"}
|
||||||
icon={<BuildingsIcon size={ICON_SIZE} />}
|
icon={<BuildingsIcon size={ICON_SIZE} />}
|
||||||
triggerHandler={triggerHandler}
|
triggerHandler={triggerHandler}
|
||||||
text={"Inventory"}
|
text={t("sidebar-inventory", "Inventory")}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<SidebarMenuItem
|
<SidebarMenuItem
|
||||||
pageLink={"/a/discounts"}
|
pageLink={"/a/discounts"}
|
||||||
icon={<SaleIcon size={ICON_SIZE} />}
|
icon={<SaleIcon size={ICON_SIZE} />}
|
||||||
triggerHandler={triggerHandler}
|
triggerHandler={triggerHandler}
|
||||||
text={"Discounts"}
|
text={t("sidebar-discounts", "Discounts")}
|
||||||
/>
|
/>
|
||||||
<SidebarMenuItem
|
<SidebarMenuItem
|
||||||
pageLink={"/a/gift-cards"}
|
pageLink={"/a/gift-cards"}
|
||||||
icon={<GiftIcon size={ICON_SIZE} />}
|
icon={<GiftIcon size={ICON_SIZE} />}
|
||||||
triggerHandler={triggerHandler}
|
triggerHandler={triggerHandler}
|
||||||
text={"Gift Cards"}
|
text={t("sidebar-gift-cards", "Gift Cards")}
|
||||||
/>
|
/>
|
||||||
<SidebarMenuItem
|
<SidebarMenuItem
|
||||||
pageLink={"/a/pricing"}
|
pageLink={"/a/pricing"}
|
||||||
icon={<CashIcon size={ICON_SIZE} />}
|
icon={<CashIcon size={ICON_SIZE} />}
|
||||||
triggerHandler={triggerHandler}
|
triggerHandler={triggerHandler}
|
||||||
text={"Pricing"}
|
text={t("sidebar-pricing", "Pricing")}
|
||||||
/>
|
/>
|
||||||
{getLinks().map(({ path, label, icon }, index) => {
|
{getLinks().map(({ path, label, icon }, index) => {
|
||||||
const cleanLink = path.replace("/a/", "")
|
const cleanLink = path.replace("/a/", "")
|
||||||
@@ -127,7 +131,7 @@ const Sidebar: React.FC = () => {
|
|||||||
pageLink={"/a/settings"}
|
pageLink={"/a/settings"}
|
||||||
icon={<GearIcon size={ICON_SIZE} />}
|
icon={<GearIcon size={ICON_SIZE} />}
|
||||||
triggerHandler={triggerHandler}
|
triggerHandler={triggerHandler}
|
||||||
text={"Settings"}
|
text={t("sidebar-settings", "Settings")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import { SkeletonProvider } from "../../../providers/skeleton-provider"
|
import { SkeletonProvider } from "../../../providers/skeleton-provider"
|
||||||
import Skeleton from "../../atoms/skeleton"
|
import Skeleton from "../../atoms/skeleton"
|
||||||
import ArrowLeftIcon from "../../fundamentals/icons/arrow-left-icon"
|
import ArrowLeftIcon from "../../fundamentals/icons/arrow-left-icon"
|
||||||
@@ -24,6 +25,7 @@ export const TablePagination = ({
|
|||||||
hasPrev,
|
hasPrev,
|
||||||
},
|
},
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const soothedOffset = count > 0 ? offset + 1 : 0
|
const soothedOffset = count > 0 ? offset + 1 : 0
|
||||||
const soothedPageCount = Math.max(1, pageCount)
|
const soothedPageCount = Math.max(1, pageCount)
|
||||||
|
|
||||||
@@ -35,11 +37,31 @@ export const TablePagination = ({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Skeleton>
|
<Skeleton>
|
||||||
<div>{`${soothedOffset} - ${pageSize} of ${count} ${title}`}</div>
|
<div>
|
||||||
|
{t(
|
||||||
|
"table-container-soothed-offset",
|
||||||
|
"{{soothedOffset}} - {{pageSize}} of {{count}} {{title}}",
|
||||||
|
{
|
||||||
|
soothedOffset,
|
||||||
|
pageSize,
|
||||||
|
count,
|
||||||
|
title,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
<div className="flex space-x-4">
|
<div className="flex space-x-4">
|
||||||
<Skeleton>
|
<Skeleton>
|
||||||
<div>{`${currentPage} of ${soothedPageCount}`}</div>
|
<div>
|
||||||
|
{t(
|
||||||
|
"table-container-current-page",
|
||||||
|
"{{currentPage}} of {{soothedPageCount}}",
|
||||||
|
{
|
||||||
|
currentPage,
|
||||||
|
soothedPageCount,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import clsx from "clsx"
|
import clsx from "clsx"
|
||||||
import { useAdminCreateNote, useAdminOrder } from "medusa-react"
|
import { useAdminCreateNote, useAdminOrder } from "medusa-react"
|
||||||
import React, { useState } from "react"
|
import React, { useState } from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
import RegisterClaimMenu from "../../../domain/orders/details/claim/register-claim-menu"
|
import RegisterClaimMenu from "../../../domain/orders/details/claim/register-claim-menu"
|
||||||
import ReturnMenu from "../../../domain/orders/details/returns"
|
import ReturnMenu from "../../../domain/orders/details/returns"
|
||||||
@@ -55,6 +56,7 @@ type TimelineProps = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Timeline: React.FC<TimelineProps> = ({ orderId }) => {
|
const Timeline: React.FC<TimelineProps> = ({ orderId }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const { orderRelations } = useOrdersExpandParam()
|
const { orderRelations } = useOrdersExpandParam()
|
||||||
|
|
||||||
const { events, refetch } = useBuildTimeline(orderId)
|
const { events, refetch } = useBuildTimeline(orderId)
|
||||||
@@ -76,17 +78,17 @@ const Timeline: React.FC<TimelineProps> = ({ orderId }) => {
|
|||||||
const actions: ActionType[] = [
|
const actions: ActionType[] = [
|
||||||
{
|
{
|
||||||
icon: <BackIcon size={20} />,
|
icon: <BackIcon size={20} />,
|
||||||
label: "Request Return",
|
label: t("timeline-request-return", "Request Return"),
|
||||||
onClick: () => setShowRequestReturn(true),
|
onClick: () => setShowRequestReturn(true),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <RefreshIcon size={20} />,
|
icon: <RefreshIcon size={20} />,
|
||||||
label: "Register Exchange",
|
label: t("timeline-register-exchange", "Register Exchange"),
|
||||||
onClick: () => setshowCreateSwap(true),
|
onClick: () => setshowCreateSwap(true),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <AlertIcon size={20} />,
|
icon: <AlertIcon size={20} />,
|
||||||
label: "Register Claim",
|
label: t("timeline-register-claim", "Register Claim"),
|
||||||
onClick: openRegisterClaim,
|
onClick: openRegisterClaim,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -102,8 +104,18 @@ const Timeline: React.FC<TimelineProps> = ({ orderId }) => {
|
|||||||
value: value,
|
value: value,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onSuccess: () => notification("Success", "Added note", "success"),
|
onSuccess: () =>
|
||||||
onError: (err) => notification("Error", getErrorMessage(err), "error"),
|
notification(
|
||||||
|
t("timeline-success", "Success"),
|
||||||
|
t("timeline-added-note", "Added note"),
|
||||||
|
"success"
|
||||||
|
),
|
||||||
|
onError: (err) =>
|
||||||
|
notification(
|
||||||
|
t("timeline-error", "Error"),
|
||||||
|
getErrorMessage(err),
|
||||||
|
"error"
|
||||||
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -113,7 +125,9 @@ const Timeline: React.FC<TimelineProps> = ({ orderId }) => {
|
|||||||
<div className="rounded-rounded border-grey-20 bg-grey-0 h-full w-5/12 border">
|
<div className="rounded-rounded border-grey-20 bg-grey-0 h-full w-5/12 border">
|
||||||
<div className="border-grey-20 py-large px-xlarge border-b">
|
<div className="border-grey-20 py-large px-xlarge border-b">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h3 className="inter-xlarge-semibold">Timeline</h3>
|
<h3 className="inter-xlarge-semibold">
|
||||||
|
{t("timeline-timeline", "Timeline")}
|
||||||
|
</h3>
|
||||||
<div
|
<div
|
||||||
className={clsx({
|
className={clsx({
|
||||||
"pointer-events-none opacity-50": !events,
|
"pointer-events-none opacity-50": !events,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import NotificationBell from "../../molecules/notification-bell"
|
|||||||
import SearchBar from "../../molecules/search-bar"
|
import SearchBar from "../../molecules/search-bar"
|
||||||
import ActivityDrawer from "../activity-drawer"
|
import ActivityDrawer from "../activity-drawer"
|
||||||
import MailDialog from "../help-dialog"
|
import MailDialog from "../help-dialog"
|
||||||
|
import LanguageMenu from "../../molecules/language-menu"
|
||||||
|
|
||||||
const Topbar: React.FC = () => {
|
const Topbar: React.FC = () => {
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import clsx from "clsx"
|
import clsx from "clsx"
|
||||||
import { ReactNode, useState } from "react"
|
import { ReactNode, useState } from "react"
|
||||||
import { useHref } from "react-router-dom"
|
import { useHref } from "react-router-dom"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
import Tooltip from "../../atoms/tooltip"
|
import Tooltip from "../../atoms/tooltip"
|
||||||
import Button from "../../fundamentals/button"
|
import Button from "../../fundamentals/button"
|
||||||
@@ -83,16 +84,18 @@ type UploadSummaryProps = {
|
|||||||
*/
|
*/
|
||||||
function UploadSummary(props: UploadSummaryProps) {
|
function UploadSummary(props: UploadSummaryProps) {
|
||||||
const { creations, updates, type } = props
|
const { creations, updates, type } = props
|
||||||
|
const { t } = useTranslation()
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-6">
|
<div className="flex gap-6">
|
||||||
<div className="text-small text-grey-90 flex items-center">
|
<div className="text-small text-grey-90 flex items-center">
|
||||||
<CheckCircleIcon color="#9CA3AF" className="mr-2" />
|
<CheckCircleIcon color="#9CA3AF" className="mr-2" />
|
||||||
<span className="font-semibold"> {creations || 0} </span> new{" "}
|
<span className="font-semibold"> {creations || 0} </span>{" "}
|
||||||
{type}
|
{t("upload-modal-new", "new")} {type}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-small text-grey-90 flex items-center">
|
<div className="text-small text-grey-90 flex items-center">
|
||||||
<WarningCircleIcon fill="#9CA3AF" className="mr-2" />
|
<WarningCircleIcon fill="#9CA3AF" className="mr-2" />
|
||||||
<span className="font-semibold">{updates || 0} </span> updates
|
<span className="font-semibold">{updates || 0} </span>{" "}
|
||||||
|
{t("upload-modal-updates", "updates")}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -106,6 +109,7 @@ type DropAreaProps = {
|
|||||||
* Component handles an CSV file drop.
|
* Component handles an CSV file drop.
|
||||||
*/
|
*/
|
||||||
function DropArea(props: DropAreaProps) {
|
function DropArea(props: DropAreaProps) {
|
||||||
|
const { t } = useTranslation()
|
||||||
const [isDragOver, setIsDragOver] = useState(false)
|
const [isDragOver, setIsDragOver] = useState(false)
|
||||||
|
|
||||||
const handleFileDrop = (e) => {
|
const handleFileDrop = (e) => {
|
||||||
@@ -139,11 +143,11 @@ function DropArea(props: DropAreaProps) {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span className="text-grey-50 text-small">
|
<span className="text-grey-50 text-small">
|
||||||
Drop your file here, or
|
{t("upload-modal-drop-your-file-here-or", "Drop your file here, or")}
|
||||||
<a className="text-violet-60">
|
<a className="text-violet-60">
|
||||||
<label className="cursor-pointer" htmlFor="upload-form-file">
|
<label className="cursor-pointer" htmlFor="upload-form-file">
|
||||||
{" "}
|
{" "}
|
||||||
click to browse.
|
{t("upload-modal-click-to-browse", "click to browse.")}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
@@ -156,7 +160,10 @@ function DropArea(props: DropAreaProps) {
|
|||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
<span className="text-grey-40 text-small">
|
<span className="text-grey-40 text-small">
|
||||||
Only .csv files are supported.
|
{t(
|
||||||
|
"upload-modal-only-csv-files-are-supported",
|
||||||
|
"Only .csv files are supported."
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -203,6 +210,7 @@ function UploadModal(props: UploadModalProps) {
|
|||||||
type,
|
type,
|
||||||
} = props
|
} = props
|
||||||
const [uploadFile, setUploadFile] = useState<File>()
|
const [uploadFile, setUploadFile] = useState<File>()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const { name, size } = uploadFile || {}
|
const { name, size } = uploadFile || {}
|
||||||
|
|
||||||
@@ -222,7 +230,7 @@ function UploadModal(props: UploadModalProps) {
|
|||||||
<Modal.Content>
|
<Modal.Content>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span className="text-grey-90 inter-large-semibold py-4 text-2xl">
|
<span className="text-grey-90 inter-large-semibold py-4 text-2xl">
|
||||||
Import {fileTitle}
|
Import {{ fileTitle }}
|
||||||
</span>
|
</span>
|
||||||
<button onClick={onClose} className="text-grey-50 cursor-pointer">
|
<button onClick={onClose} className="text-grey-50 cursor-pointer">
|
||||||
<CrossIcon size={20} />
|
<CrossIcon size={20} />
|
||||||
@@ -230,7 +238,9 @@ function UploadModal(props: UploadModalProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="text-grey-90 inter-large-semibold mb-1 text-base">
|
<div className="text-grey-90 inter-large-semibold mb-1 text-base">
|
||||||
Import {fileTitle}
|
{t("upload-modal-import-file-title", "Import {{fileTitle}}", {
|
||||||
|
fileTitle,
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-grey-50 mb-4 text-base">{description1Text}</p>
|
<p className="text-grey-50 mb-4 text-base">{description1Text}</p>
|
||||||
@@ -293,7 +303,7 @@ function UploadModal(props: UploadModalProps) {
|
|||||||
size="small"
|
size="small"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
>
|
>
|
||||||
Cancel
|
{t("upload-modal-cancel", "Cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
@@ -303,7 +313,7 @@ function UploadModal(props: UploadModalProps) {
|
|||||||
className="text-small"
|
className="text-small"
|
||||||
onClick={onSubmit}
|
onClick={onSubmit}
|
||||||
>
|
>
|
||||||
Import List
|
{t("upload-modal-import-list", "Import List")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Product } from "@medusajs/medusa"
|
import { Product } from "@medusajs/medusa"
|
||||||
import { useAdminProducts } from "medusa-react"
|
import { useAdminProducts } from "medusa-react"
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import Button from "../../fundamentals/button"
|
import Button from "../../fundamentals/button"
|
||||||
import Modal from "../../molecules/modal"
|
import Modal from "../../molecules/modal"
|
||||||
import { SelectableTable } from "../selectable-table"
|
import { SelectableTable } from "../selectable-table"
|
||||||
@@ -24,6 +25,7 @@ const AddProductsModal = ({
|
|||||||
initialSelection,
|
initialSelection,
|
||||||
onSave,
|
onSave,
|
||||||
}: AddProductsModalProps) => {
|
}: AddProductsModalProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
/* ************* Data ************ */
|
/* ************* Data ************ */
|
||||||
|
|
||||||
const params = useQueryFilters(defaultQueryProps)
|
const params = useQueryFilters(defaultQueryProps)
|
||||||
@@ -62,7 +64,9 @@ const AddProductsModal = ({
|
|||||||
<Modal open handleClose={close}>
|
<Modal open handleClose={close}>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
<Modal.Header handleClose={close}>
|
<Modal.Header handleClose={close}>
|
||||||
<h2 className="inter-xlarge-semibold">Add Products</h2>
|
<h2 className="inter-xlarge-semibold">
|
||||||
|
{t("add-products-modal-add-products", "Add Products")}
|
||||||
|
</h2>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<Modal.Content>
|
<Modal.Content>
|
||||||
<div className="flex h-full min-h-[300px] w-full flex-col justify-between ">
|
<div className="flex h-full min-h-[300px] w-full flex-col justify-between ">
|
||||||
@@ -78,7 +82,10 @@ const AddProductsModal = ({
|
|||||||
totalCount={count}
|
totalCount={count}
|
||||||
options={{
|
options={{
|
||||||
enableSearch: true,
|
enableSearch: true,
|
||||||
searchPlaceholder: "Search by name or description...",
|
searchPlaceholder: t(
|
||||||
|
"add-products-modal-search-by-name-or-description",
|
||||||
|
"Search by name or description..."
|
||||||
|
),
|
||||||
}}
|
}}
|
||||||
{...params}
|
{...params}
|
||||||
/>
|
/>
|
||||||
@@ -91,14 +98,14 @@ const AddProductsModal = ({
|
|||||||
className="rounded-rounded h-8 w-[128px]"
|
className="rounded-rounded h-8 w-[128px]"
|
||||||
onClick={close}
|
onClick={close}
|
||||||
>
|
>
|
||||||
Cancel
|
{t("add-products-modal-cancel", "Cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
className="rounded-rounded h-8 w-[128px]"
|
className="rounded-rounded h-8 w-[128px]"
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
>
|
>
|
||||||
Save
|
{t("add-products-modal-save", "Save")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
|
|||||||
+22
-3
@@ -1,13 +1,20 @@
|
|||||||
import { Product } from "@medusajs/medusa"
|
import { Product } from "@medusajs/medusa"
|
||||||
import clsx from "clsx"
|
import clsx from "clsx"
|
||||||
import { Column, HeaderGroup, Row } from "react-table"
|
import { Column, HeaderGroup, Row } from "react-table"
|
||||||
|
import { Translation } from "react-i18next"
|
||||||
import ImagePlaceholder from "../../fundamentals/image-placeholder"
|
import ImagePlaceholder from "../../fundamentals/image-placeholder"
|
||||||
import Table from "../../molecules/table"
|
import Table from "../../molecules/table"
|
||||||
import { decideStatus } from "../collection-product-table/utils"
|
import { decideStatus } from "../collection-product-table/utils"
|
||||||
|
|
||||||
export const columns: Column<Product>[] = [
|
export const columns: Column<Product>[] = [
|
||||||
{
|
{
|
||||||
Header: <div className="pl-4">Product Details</div>,
|
Header: (
|
||||||
|
<div className="pl-4">
|
||||||
|
<Translation>
|
||||||
|
{(t) => t("add-products-modal-product-details", "Product Details")}
|
||||||
|
</Translation>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
accessor: "title",
|
accessor: "title",
|
||||||
Cell: ({ row: { original } }) => (
|
Cell: ({ row: { original } }) => (
|
||||||
<div className="flex w-[400px] items-center pl-4">
|
<div className="flex w-[400px] items-center pl-4">
|
||||||
@@ -33,7 +40,13 @@ export const columns: Column<Product>[] = [
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: <div>Status</div>,
|
Header: (
|
||||||
|
<div>
|
||||||
|
<Translation>
|
||||||
|
{(t) => t("add-products-modal-status", "Status")}
|
||||||
|
</Translation>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
accessor: "status",
|
accessor: "status",
|
||||||
Cell: ({ cell: { value } }) => (
|
Cell: ({ cell: { value } }) => (
|
||||||
<Table.Cell className="pr-base w-[10%]">
|
<Table.Cell className="pr-base w-[10%]">
|
||||||
@@ -42,7 +55,13 @@ export const columns: Column<Product>[] = [
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: <div className="flex items-center justify-end pr-4">Variants</div>,
|
Header: (
|
||||||
|
<div className="flex items-center justify-end pr-4">
|
||||||
|
<Translation>
|
||||||
|
{(t) => t("add-products-modal-variants", "Variants")}
|
||||||
|
</Translation>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
accessor: "variants",
|
accessor: "variants",
|
||||||
Cell: ({ row: { original } }) => (
|
Cell: ({ row: { original } }) => (
|
||||||
<Table.Cell className="flex items-center justify-end pr-4">
|
<Table.Cell className="flex items-center justify-end pr-4">
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Controller } from "react-hook-form"
|
import { Controller } from "react-hook-form"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import { Option } from "../../types/shared"
|
import { Option } from "../../types/shared"
|
||||||
import FormValidator from "../../utils/form-validator"
|
import FormValidator from "../../utils/form-validator"
|
||||||
import { nestedForm, NestedForm } from "../../utils/nested-form"
|
import { nestedForm, NestedForm } from "../../utils/nested-form"
|
||||||
@@ -47,11 +48,14 @@ const AddressForm = ({
|
|||||||
control,
|
control,
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
} = form
|
} = form
|
||||||
|
const { t } = useTranslation()
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{(type === AddressType.SHIPPING || type === AddressType.BILLING) && (
|
{(type === AddressType.SHIPPING || type === AddressType.BILLING) && (
|
||||||
<>
|
<>
|
||||||
<span className="inter-base-semibold">General</span>
|
<span className="inter-base-semibold">
|
||||||
|
{t("templates-general", "General")}
|
||||||
|
</span>
|
||||||
<div className="gap-large mt-4 mb-8 grid grid-cols-2">
|
<div className="gap-large mt-4 mb-8 grid grid-cols-2">
|
||||||
<Input
|
<Input
|
||||||
{...register(path("first_name"), {
|
{...register(path("first_name"), {
|
||||||
@@ -60,8 +64,8 @@ const AddressForm = ({
|
|||||||
: false,
|
: false,
|
||||||
pattern: FormValidator.whiteSpaceRule("First name"),
|
pattern: FormValidator.whiteSpaceRule("First name"),
|
||||||
})}
|
})}
|
||||||
placeholder="First Name"
|
placeholder={t("templates-first-name", "First Name")}
|
||||||
label="First Name"
|
label={t("templates-first-name", "First Name")}
|
||||||
required={required}
|
required={required}
|
||||||
errors={errors}
|
errors={errors}
|
||||||
/>
|
/>
|
||||||
@@ -72,8 +76,8 @@ const AddressForm = ({
|
|||||||
: false,
|
: false,
|
||||||
pattern: FormValidator.whiteSpaceRule("Last name"),
|
pattern: FormValidator.whiteSpaceRule("Last name"),
|
||||||
})}
|
})}
|
||||||
placeholder="Last Name"
|
placeholder={t("templates-last-name", "Last Name")}
|
||||||
label="Last Name"
|
label={t("templates-last-name", "Last Name")}
|
||||||
required={required}
|
required={required}
|
||||||
errors={errors}
|
errors={errors}
|
||||||
/>
|
/>
|
||||||
@@ -81,14 +85,14 @@ const AddressForm = ({
|
|||||||
{...form.register(path("company"), {
|
{...form.register(path("company"), {
|
||||||
pattern: FormValidator.whiteSpaceRule("Company"),
|
pattern: FormValidator.whiteSpaceRule("Company"),
|
||||||
})}
|
})}
|
||||||
placeholder="Company"
|
placeholder={t("templates-company", "Company")}
|
||||||
label="Company"
|
label={t("templates-company", "Company")}
|
||||||
errors={errors}
|
errors={errors}
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
{...form.register(path("phone"))}
|
{...form.register(path("phone"))}
|
||||||
placeholder="Phone"
|
placeholder={t("templates-phone", "Phone")}
|
||||||
label="Phone"
|
label={t("templates-phone", "Phone")}
|
||||||
errors={errors}
|
errors={errors}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -98,10 +102,10 @@ const AddressForm = ({
|
|||||||
<span className="inter-base-semibold">
|
<span className="inter-base-semibold">
|
||||||
{`${
|
{`${
|
||||||
type === AddressType.BILLING
|
type === AddressType.BILLING
|
||||||
? "Billing Address"
|
? t("templates-billing-address", "Billing Address")
|
||||||
: type === AddressType.SHIPPING
|
: type === AddressType.SHIPPING
|
||||||
? "Shipping Address"
|
? t("templates-shipping-address", "Shipping Address")
|
||||||
: "Address"
|
: t("templates-address", "Address")
|
||||||
}`}
|
}`}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -111,8 +115,8 @@ const AddressForm = ({
|
|||||||
required: required ? FormValidator.required("Address 1") : false,
|
required: required ? FormValidator.required("Address 1") : false,
|
||||||
pattern: FormValidator.whiteSpaceRule("Address 1"),
|
pattern: FormValidator.whiteSpaceRule("Address 1"),
|
||||||
})}
|
})}
|
||||||
placeholder="Address 1"
|
placeholder={t("templates-address-1", "Address 1")}
|
||||||
label="Address 1"
|
label={t("templates-address-1", "Address 1")}
|
||||||
required={required}
|
required={required}
|
||||||
errors={errors}
|
errors={errors}
|
||||||
/>
|
/>
|
||||||
@@ -120,8 +124,8 @@ const AddressForm = ({
|
|||||||
{...form.register(path("address_2"), {
|
{...form.register(path("address_2"), {
|
||||||
pattern: FormValidator.whiteSpaceRule("Address 2"),
|
pattern: FormValidator.whiteSpaceRule("Address 2"),
|
||||||
})}
|
})}
|
||||||
placeholder="Address 2"
|
placeholder={t("templates-address-2", "Address 2")}
|
||||||
label="Address 2"
|
label={t("templates-address-2", "Address 2")}
|
||||||
errors={errors}
|
errors={errors}
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
@@ -129,15 +133,15 @@ const AddressForm = ({
|
|||||||
required: required ? FormValidator.required("Postal code") : false,
|
required: required ? FormValidator.required("Postal code") : false,
|
||||||
pattern: FormValidator.whiteSpaceRule("Postal code"),
|
pattern: FormValidator.whiteSpaceRule("Postal code"),
|
||||||
})}
|
})}
|
||||||
placeholder="Postal code"
|
placeholder={t("templates-postal-code", "Postal code")}
|
||||||
label="Postal code"
|
label={t("templates-postal-code", "Postal code")}
|
||||||
required={required}
|
required={required}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
errors={errors}
|
errors={errors}
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
placeholder="City"
|
placeholder={t("templates-city", "City")}
|
||||||
label="City"
|
label={t("templates-city", "City")}
|
||||||
{...form.register(path("city"), {
|
{...form.register(path("city"), {
|
||||||
required: required ? FormValidator.required("City") : false,
|
required: required ? FormValidator.required("City") : false,
|
||||||
pattern: FormValidator.whiteSpaceRule("City"),
|
pattern: FormValidator.whiteSpaceRule("City"),
|
||||||
@@ -149,8 +153,8 @@ const AddressForm = ({
|
|||||||
{...form.register(path("province"), {
|
{...form.register(path("province"), {
|
||||||
pattern: FormValidator.whiteSpaceRule("Province"),
|
pattern: FormValidator.whiteSpaceRule("Province"),
|
||||||
})}
|
})}
|
||||||
placeholder="Province"
|
placeholder={t("templates-province", "Province")}
|
||||||
label="Province"
|
label={t("templates-province", "Province")}
|
||||||
errors={errors}
|
errors={errors}
|
||||||
/>
|
/>
|
||||||
<Controller
|
<Controller
|
||||||
@@ -162,7 +166,7 @@ const AddressForm = ({
|
|||||||
render={({ field: { value, onChange } }) => {
|
render={({ field: { value, onChange } }) => {
|
||||||
return (
|
return (
|
||||||
<NextSelect
|
<NextSelect
|
||||||
label="Country"
|
label={t("templates-country", "Country")}
|
||||||
required={required}
|
required={required}
|
||||||
value={value}
|
value={value}
|
||||||
options={countryOptions}
|
options={countryOptions}
|
||||||
@@ -176,7 +180,9 @@ const AddressForm = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-xlarge gap-y-base flex flex-col">
|
<div className="mt-xlarge gap-y-base flex flex-col">
|
||||||
<span className="inter-base-semibold">Metadata</span>
|
<span className="inter-base-semibold">
|
||||||
|
{t("templates-metadata", "Metadata")}
|
||||||
|
</span>
|
||||||
<MetadataForm form={nestedForm(form, "metadata")} />
|
<MetadataForm form={nestedForm(form, "metadata")} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
} from "medusa-react"
|
} from "medusa-react"
|
||||||
import React, { useEffect } from "react"
|
import React, { useEffect } from "react"
|
||||||
import { useForm } from "react-hook-form"
|
import { useForm } from "react-hook-form"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import useNotification from "../../../hooks/use-notification"
|
import useNotification from "../../../hooks/use-notification"
|
||||||
import { getErrorMessage } from "../../../utils/error-messages"
|
import { getErrorMessage } from "../../../utils/error-messages"
|
||||||
import { nestedForm } from "../../../utils/nested-form"
|
import { nestedForm } from "../../../utils/nested-form"
|
||||||
@@ -36,6 +37,7 @@ const CollectionModal: React.FC<CollectionModalProps> = ({
|
|||||||
isEdit = false,
|
isEdit = false,
|
||||||
collection,
|
collection,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const { mutate: update, isLoading: updating } = useAdminUpdateCollection(
|
const { mutate: update, isLoading: updating } = useAdminUpdateCollection(
|
||||||
collection?.id!
|
collection?.id!
|
||||||
)
|
)
|
||||||
@@ -93,14 +95,21 @@ const CollectionModal: React.FC<CollectionModalProps> = ({
|
|||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
notification(
|
notification(
|
||||||
"Success",
|
t("collection-modal-success", "Success"),
|
||||||
"Successfully updated collection",
|
t(
|
||||||
|
"collection-modal-successfully-updated-collection",
|
||||||
|
"Successfully updated collection"
|
||||||
|
),
|
||||||
"success"
|
"success"
|
||||||
)
|
)
|
||||||
onClose()
|
onClose()
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
notification("Error", getErrorMessage(error), "error")
|
notification(
|
||||||
|
t("collection-modal-error", "Error"),
|
||||||
|
getErrorMessage(error),
|
||||||
|
"error"
|
||||||
|
)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -114,14 +123,21 @@ const CollectionModal: React.FC<CollectionModalProps> = ({
|
|||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
notification(
|
notification(
|
||||||
"Success",
|
t("collection-modal-success", "Success"),
|
||||||
"Successfully created collection",
|
t(
|
||||||
|
"collection-modal-successfully-created-collection",
|
||||||
|
"Successfully created collection"
|
||||||
|
),
|
||||||
"success"
|
"success"
|
||||||
)
|
)
|
||||||
onClose()
|
onClose()
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
notification("Error", getErrorMessage(error), "error")
|
notification(
|
||||||
|
t("collection-modal-error", "Error"),
|
||||||
|
getErrorMessage(error),
|
||||||
|
"error"
|
||||||
|
)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -134,37 +150,57 @@ const CollectionModal: React.FC<CollectionModalProps> = ({
|
|||||||
<Modal.Header handleClose={onClose}>
|
<Modal.Header handleClose={onClose}>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="inter-xlarge-semibold mb-2xsmall">
|
<h1 className="inter-xlarge-semibold mb-2xsmall">
|
||||||
{isEdit ? "Edit Collection" : "Add Collection"}
|
{isEdit
|
||||||
|
? t("collection-modal-edit-collection", "Edit Collection")
|
||||||
|
: t("collection-modal-add-collection", "Add Collection")}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="inter-small-regular text-grey-50">
|
<p className="inter-small-regular text-grey-50">
|
||||||
To create a collection, all you need is a title and a handle.
|
{t(
|
||||||
|
"collection-modal-description",
|
||||||
|
"To create a collection, all you need is a title and a handle."
|
||||||
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<form onSubmit={handleSubmit(submit)}>
|
<form onSubmit={handleSubmit(submit)}>
|
||||||
<Modal.Content>
|
<Modal.Content>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="inter-base-semibold mb-base">Details</h2>
|
<h2 className="inter-base-semibold mb-base">
|
||||||
|
{t("collection-modal-details", "Details")}
|
||||||
|
</h2>
|
||||||
<div className="gap-x-base flex items-center">
|
<div className="gap-x-base flex items-center">
|
||||||
<InputField
|
<InputField
|
||||||
label="Title"
|
label={t("collection-modal-title-label", "Title")}
|
||||||
required
|
required
|
||||||
placeholder="Sunglasses"
|
placeholder={t(
|
||||||
|
"collection-modal-title-placeholder",
|
||||||
|
"Sunglasses"
|
||||||
|
)}
|
||||||
{...register("title", { required: true })}
|
{...register("title", { required: true })}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
label="Handle"
|
label={t("collection-modal-handle-label", "Handle")}
|
||||||
placeholder="sunglasses"
|
placeholder={t(
|
||||||
|
"collection-modal-handle-placeholder",
|
||||||
|
"sunglasses"
|
||||||
|
)}
|
||||||
{...register("handle")}
|
{...register("handle")}
|
||||||
prefix="/"
|
prefix="/"
|
||||||
tooltip={
|
tooltip={
|
||||||
<IconTooltip content="URL Slug for the collection. Will be auto generated if left blank." />
|
<IconTooltip
|
||||||
|
content={t(
|
||||||
|
"collection-modal-slug-description",
|
||||||
|
"URL Slug for the collection. Will be auto generated if left blank."
|
||||||
|
)}
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-xlarge">
|
<div className="mt-xlarge">
|
||||||
<h2 className="inter-base-semibold mb-base">Metadata</h2>
|
<h2 className="inter-base-semibold mb-base">
|
||||||
|
{t("collection-modal-metadata", "Metadata")}
|
||||||
|
</h2>
|
||||||
<MetadataForm form={nestedForm(form, "metadata")} />
|
<MetadataForm form={nestedForm(form, "metadata")} />
|
||||||
</div>
|
</div>
|
||||||
</Modal.Content>
|
</Modal.Content>
|
||||||
@@ -176,14 +212,19 @@ const CollectionModal: React.FC<CollectionModalProps> = ({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
>
|
>
|
||||||
Cancel
|
{t("collection-modal-cancel", "Cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
size="small"
|
size="small"
|
||||||
loading={isEdit ? updating : creating}
|
loading={isEdit ? updating : creating}
|
||||||
>
|
>
|
||||||
{`${isEdit ? "Save" : "Publish"} collection`}
|
{isEdit
|
||||||
|
? t("collection-modal-save-collection", "Save collection")
|
||||||
|
: t(
|
||||||
|
"collection-modal-publish-collection",
|
||||||
|
"Publish collection"
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
|
|||||||
+12
-5
@@ -1,6 +1,7 @@
|
|||||||
import { useAdminProducts } from "medusa-react"
|
import { useAdminProducts } from "medusa-react"
|
||||||
import React, { useEffect, useState } from "react"
|
import React, { useEffect, useState } from "react"
|
||||||
import { usePagination, useRowSelect, useTable } from "react-table"
|
import { usePagination, useRowSelect, useTable } from "react-table"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import { useDebounce } from "../../../hooks/use-debounce"
|
import { useDebounce } from "../../../hooks/use-debounce"
|
||||||
import Button from "../../fundamentals/button"
|
import Button from "../../fundamentals/button"
|
||||||
import IndeterminateCheckbox from "../../molecules/indeterminate-checkbox"
|
import IndeterminateCheckbox from "../../molecules/indeterminate-checkbox"
|
||||||
@@ -21,6 +22,7 @@ const AddProductsTable: React.FC<AddProductsTableProps> = ({
|
|||||||
onClose,
|
onClose,
|
||||||
}) => {
|
}) => {
|
||||||
const PAGE_SIZE = 10
|
const PAGE_SIZE = 10
|
||||||
|
const { t } = useTranslation()
|
||||||
const [query, setQuery] = useState("")
|
const [query, setQuery] = useState("")
|
||||||
const [offset, setOffset] = useState(0)
|
const [offset, setOffset] = useState(0)
|
||||||
const [numPages, setNumPages] = useState(0)
|
const [numPages, setNumPages] = useState(0)
|
||||||
@@ -157,7 +159,9 @@ const AddProductsTable: React.FC<AddProductsTableProps> = ({
|
|||||||
<Modal handleClose={onClose}>
|
<Modal handleClose={onClose}>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
<Modal.Header handleClose={onClose}>
|
<Modal.Header handleClose={onClose}>
|
||||||
<h3 className="inter-xlarge-semibold">Add Products</h3>
|
<h3 className="inter-xlarge-semibold">
|
||||||
|
{t("collection-product-table-add-products", "Add Products")}
|
||||||
|
</h3>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<Modal.Content>
|
<Modal.Content>
|
||||||
<TableContainer
|
<TableContainer
|
||||||
@@ -168,7 +172,7 @@ const AddProductsTable: React.FC<AddProductsTableProps> = ({
|
|||||||
count: count!,
|
count: count!,
|
||||||
offset: offset,
|
offset: offset,
|
||||||
pageSize: offset + rows.length,
|
pageSize: offset + rows.length,
|
||||||
title: "Products",
|
title: t("collection-product-table-products", "Products"),
|
||||||
currentPage: pageIndex + 1,
|
currentPage: pageIndex + 1,
|
||||||
pageCount: pageCount,
|
pageCount: pageCount,
|
||||||
nextPage: handleNext,
|
nextPage: handleNext,
|
||||||
@@ -181,7 +185,10 @@ const AddProductsTable: React.FC<AddProductsTableProps> = ({
|
|||||||
enableSearch
|
enableSearch
|
||||||
handleSearch={handleSearch}
|
handleSearch={handleSearch}
|
||||||
searchValue={query}
|
searchValue={query}
|
||||||
searchPlaceholder="Search Products"
|
searchPlaceholder={t(
|
||||||
|
"collection-product-table-search-products",
|
||||||
|
"Search Products"
|
||||||
|
)}
|
||||||
{...getTableProps()}
|
{...getTableProps()}
|
||||||
className="flex-grow"
|
className="flex-grow"
|
||||||
>
|
>
|
||||||
@@ -212,7 +219,7 @@ const AddProductsTable: React.FC<AddProductsTableProps> = ({
|
|||||||
className="w-eventButton"
|
className="w-eventButton"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
>
|
>
|
||||||
Cancel
|
{t("collection-product-table-cancel", "Cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
@@ -221,7 +228,7 @@ const AddProductsTable: React.FC<AddProductsTableProps> = ({
|
|||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
Save
|
{t("collection-product-table-save", "Save")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useAdminProducts } from "medusa-react"
|
import { useAdminProducts } from "medusa-react"
|
||||||
import React, { useEffect, useState } from "react"
|
import React, { useEffect, useState } from "react"
|
||||||
import { Column, usePagination, useRowSelect, useTable } from "react-table"
|
import { Column, usePagination, useRowSelect, useTable } from "react-table"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import { useDebounce } from "../../../hooks/use-debounce"
|
import { useDebounce } from "../../../hooks/use-debounce"
|
||||||
import IndeterminateCheckbox from "../../molecules/indeterminate-checkbox"
|
import IndeterminateCheckbox from "../../molecules/indeterminate-checkbox"
|
||||||
import Table from "../../molecules/table"
|
import Table from "../../molecules/table"
|
||||||
@@ -17,6 +18,7 @@ const CollectionProductTable: React.FC<CollectionProductTableProps> = ({
|
|||||||
addedProducts,
|
addedProducts,
|
||||||
setProducts,
|
setProducts,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const [query, setQuery] = useState("")
|
const [query, setQuery] = useState("")
|
||||||
const [limit, setLimit] = useState(10)
|
const [limit, setLimit] = useState(10)
|
||||||
const [offset, setOffset] = useState(0)
|
const [offset, setOffset] = useState(0)
|
||||||
@@ -39,18 +41,18 @@ const CollectionProductTable: React.FC<CollectionProductTableProps> = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setFilteringOptions([
|
setFilteringOptions([
|
||||||
{
|
{
|
||||||
title: "Sort by",
|
title: t("collection-product-table-sort-by", "Sort by"),
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
title: "All",
|
title: t("collection-product-table-all", "All"),
|
||||||
onClick: () => {},
|
onClick: () => {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Newest",
|
title: t("collection-product-table-newest", "Newest"),
|
||||||
onClick: () => {},
|
onClick: () => {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Oldest",
|
title: t("collection-product-table-oldest", "Oldest"),
|
||||||
onClick: () => {},
|
onClick: () => {},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -152,7 +154,7 @@ const CollectionProductTable: React.FC<CollectionProductTableProps> = ({
|
|||||||
count: count!,
|
count: count!,
|
||||||
offset: offset,
|
offset: offset,
|
||||||
pageSize: offset + rows.length,
|
pageSize: offset + rows.length,
|
||||||
title: "Products",
|
title: t("collection-product-table-products", "Products"),
|
||||||
currentPage: pageIndex + 1,
|
currentPage: pageIndex + 1,
|
||||||
pageCount: pageCount,
|
pageCount: pageCount,
|
||||||
nextPage: handleNext,
|
nextPage: handleNext,
|
||||||
@@ -164,7 +166,10 @@ const CollectionProductTable: React.FC<CollectionProductTableProps> = ({
|
|||||||
<Table
|
<Table
|
||||||
enableSearch
|
enableSearch
|
||||||
handleSearch={handleSearch}
|
handleSearch={handleSearch}
|
||||||
searchPlaceholder="Search Products"
|
searchPlaceholder={t(
|
||||||
|
"collection-product-table-search-products",
|
||||||
|
"Search Products"
|
||||||
|
)}
|
||||||
filteringOptions={filteringOptions}
|
filteringOptions={filteringOptions}
|
||||||
{...getTableProps()}
|
{...getTableProps()}
|
||||||
className="h-full"
|
className="h-full"
|
||||||
|
|||||||
+7
-5
@@ -1,10 +1,12 @@
|
|||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import { FilteringOptionProps } from "../../molecules/table/filtering-option"
|
import { FilteringOptionProps } from "../../molecules/table/filtering-option"
|
||||||
import { SimpleProductType } from "./utils"
|
import { SimpleProductType } from "./utils"
|
||||||
|
|
||||||
// TODO: Redo this with server side sorting
|
// TODO: Redo this with server side sorting
|
||||||
|
|
||||||
const useSortingOptions = (products: SimpleProductType[]) => {
|
const useSortingOptions = (products: SimpleProductType[]) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const [options, setOptions] = useState<FilteringOptionProps[]>([])
|
const [options, setOptions] = useState<FilteringOptionProps[]>([])
|
||||||
const [sortedProducts, setSortedProducts] =
|
const [sortedProducts, setSortedProducts] =
|
||||||
useState<SimpleProductType[]>(products)
|
useState<SimpleProductType[]>(products)
|
||||||
@@ -42,16 +44,16 @@ const useSortingOptions = (products: SimpleProductType[]) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setOptions([
|
setOptions([
|
||||||
{
|
{
|
||||||
title: "Sort by",
|
title: t("collection-product-table-sort-by", "Sort by"),
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
title: "All",
|
title: t("collection-product-table-all", "All"),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
setSortedProducts(products)
|
setSortedProducts(products)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Newest",
|
title: t("collection-product-table-newest", "Newest"),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
const sorted = products.sort(sortByNewest)
|
const sorted = products.sort(sortByNewest)
|
||||||
console.log(sorted)
|
console.log(sorted)
|
||||||
@@ -59,7 +61,7 @@ const useSortingOptions = (products: SimpleProductType[]) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Oldest",
|
title: t("collection-product-table-oldest", "Oldest"),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
const sorted = products.sort(sortByOldest)
|
const sorted = products.sort(sortByOldest)
|
||||||
console.log(sorted)
|
console.log(sorted)
|
||||||
@@ -67,7 +69,7 @@ const useSortingOptions = (products: SimpleProductType[]) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Title",
|
title: t("collection-product-table-title", "Title"),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
const sorted = products.sort(sortByTitle)
|
const sorted = products.sort(sortByTitle)
|
||||||
console.log(sorted)
|
console.log(sorted)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Translation } from "react-i18next"
|
||||||
import StatusIndicator from "../../fundamentals/status-indicator"
|
import StatusIndicator from "../../fundamentals/status-indicator"
|
||||||
|
|
||||||
export type SimpleProductType = {
|
export type SimpleProductType = {
|
||||||
@@ -11,13 +12,52 @@ export type SimpleProductType = {
|
|||||||
export const decideStatus = (status: string) => {
|
export const decideStatus = (status: string) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case "published":
|
case "published":
|
||||||
return <StatusIndicator title="Published" variant="success" />
|
return (
|
||||||
|
<Translation>
|
||||||
|
{(t) => (
|
||||||
|
<StatusIndicator
|
||||||
|
title={t(
|
||||||
|
"collection-product-table-decide-status-published",
|
||||||
|
"Published"
|
||||||
|
)}
|
||||||
|
variant="success"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Translation>
|
||||||
|
)
|
||||||
case "draft":
|
case "draft":
|
||||||
return <StatusIndicator title="Draft" variant="default" />
|
return (
|
||||||
|
<Translation>
|
||||||
|
{(t) => (
|
||||||
|
<StatusIndicator
|
||||||
|
title={t("collection-product-table-draft", "Draft")}
|
||||||
|
variant="default"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Translation>
|
||||||
|
)
|
||||||
case "proposed":
|
case "proposed":
|
||||||
return <StatusIndicator title="Proposed" variant="warning" />
|
return (
|
||||||
|
<Translation>
|
||||||
|
{(t) => (
|
||||||
|
<StatusIndicator
|
||||||
|
title={t("collection-product-table-proposed", "Proposed")}
|
||||||
|
variant="warning"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Translation>
|
||||||
|
)
|
||||||
case "rejected":
|
case "rejected":
|
||||||
return <StatusIndicator title="Rejected" variant="danger" />
|
return (
|
||||||
|
<Translation>
|
||||||
|
{(t) => (
|
||||||
|
<StatusIndicator
|
||||||
|
title={t("collection-product-table-rejected", "Rejected")}
|
||||||
|
variant="danger"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Translation>
|
||||||
|
)
|
||||||
default:
|
default:
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-3
@@ -1,6 +1,7 @@
|
|||||||
import { useAdminProducts } from "medusa-react"
|
import { useAdminProducts } from "medusa-react"
|
||||||
import React, { useEffect, useState } from "react"
|
import React, { useEffect, useState } from "react"
|
||||||
import { usePagination, useTable } from "react-table"
|
import { usePagination, useTable } from "react-table"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import { useDebounce } from "../../../hooks/use-debounce"
|
import { useDebounce } from "../../../hooks/use-debounce"
|
||||||
import Medusa from "../../../services/api"
|
import Medusa from "../../../services/api"
|
||||||
import Button from "../../fundamentals/button"
|
import Button from "../../fundamentals/button"
|
||||||
@@ -25,6 +26,7 @@ const ViewProductsTable: React.FC<ViewProductsTableProps> = ({
|
|||||||
const [numPages, setNumPages] = useState(0)
|
const [numPages, setNumPages] = useState(0)
|
||||||
const [currentPage, setCurrentPage] = useState(0)
|
const [currentPage, setCurrentPage] = useState(0)
|
||||||
const debouncedSearchTerm = useDebounce(query, 500)
|
const debouncedSearchTerm = useDebounce(query, 500)
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const [showDelete, setShowDelete] = useState(false)
|
const [showDelete, setShowDelete] = useState(false)
|
||||||
const [idToDelete, setIdToDelete] = useState<string | undefined>(undefined)
|
const [idToDelete, setIdToDelete] = useState<string | undefined>(undefined)
|
||||||
@@ -157,7 +159,10 @@ const ViewProductsTable: React.FC<ViewProductsTableProps> = ({
|
|||||||
<Table
|
<Table
|
||||||
enableSearch
|
enableSearch
|
||||||
handleSearch={handleSearch}
|
handleSearch={handleSearch}
|
||||||
searchPlaceholder="Search Products"
|
searchPlaceholder={t(
|
||||||
|
"collection-product-table-search-products",
|
||||||
|
"Search Products"
|
||||||
|
)}
|
||||||
{...getTableProps()}
|
{...getTableProps()}
|
||||||
className="h-full"
|
className="h-full"
|
||||||
>
|
>
|
||||||
@@ -183,8 +188,14 @@ const ViewProductsTable: React.FC<ViewProductsTableProps> = ({
|
|||||||
<DeletePrompt
|
<DeletePrompt
|
||||||
onDelete={async () => handleRemoveProduct()}
|
onDelete={async () => handleRemoveProduct()}
|
||||||
handleClose={() => setShowDelete(!showDelete)}
|
handleClose={() => setShowDelete(!showDelete)}
|
||||||
heading="Remove product from collection"
|
heading={t(
|
||||||
successText="Product removed from collection"
|
"collection-product-table-remove-product-from-collection",
|
||||||
|
"Remove product from collection"
|
||||||
|
)}
|
||||||
|
successText={t(
|
||||||
|
"collection-product-table-product-removed-from-collection",
|
||||||
|
"Product removed from collection"
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
+9
-4
@@ -1,19 +1,24 @@
|
|||||||
import { useAdminDeleteCollection } from "medusa-react"
|
import { useAdminDeleteCollection } from "medusa-react"
|
||||||
import { useNavigate } from "react-router-dom"
|
import { useNavigate } from "react-router-dom"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import useImperativeDialog from "../../../hooks/use-imperative-dialog"
|
import useImperativeDialog from "../../../hooks/use-imperative-dialog"
|
||||||
import EditIcon from "../../fundamentals/icons/edit-icon"
|
import EditIcon from "../../fundamentals/icons/edit-icon"
|
||||||
import TrashIcon from "../../fundamentals/icons/trash-icon"
|
import TrashIcon from "../../fundamentals/icons/trash-icon"
|
||||||
import { ActionType } from "../../molecules/actionables"
|
import { ActionType } from "../../molecules/actionables"
|
||||||
|
|
||||||
const useCollectionActions = (collection) => {
|
const useCollectionActions = (collection) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const dialog = useImperativeDialog()
|
const dialog = useImperativeDialog()
|
||||||
const deleteCollection = useAdminDeleteCollection(collection?.id)
|
const deleteCollection = useAdminDeleteCollection(collection?.id)
|
||||||
|
|
||||||
const handleDelete = async () => {
|
const handleDelete = async () => {
|
||||||
const shouldDelete = await dialog({
|
const shouldDelete = await dialog({
|
||||||
heading: "Delete Collection",
|
heading: t("collections-table-delete-collection", "Delete Collection"),
|
||||||
text: "Are you sure you want to delete this collection?",
|
text: t(
|
||||||
|
"collections-table-confirm-delete",
|
||||||
|
"Are you sure you want to delete this collection?"
|
||||||
|
),
|
||||||
})
|
})
|
||||||
|
|
||||||
if (shouldDelete) {
|
if (shouldDelete) {
|
||||||
@@ -23,12 +28,12 @@ const useCollectionActions = (collection) => {
|
|||||||
|
|
||||||
const getActions = (coll): ActionType[] => [
|
const getActions = (coll): ActionType[] => [
|
||||||
{
|
{
|
||||||
label: "Edit",
|
label: t("collections-table-edit", "Edit"),
|
||||||
onClick: () => navigate(`/a/collections/${coll.id}`),
|
onClick: () => navigate(`/a/collections/${coll.id}`),
|
||||||
icon: <EditIcon size={20} />,
|
icon: <EditIcon size={20} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Delete",
|
label: t("collections-table-delete", "Delete"),
|
||||||
variant: "danger",
|
variant: "danger",
|
||||||
onClick: handleDelete,
|
onClick: handleDelete,
|
||||||
icon: <TrashIcon size={20} />,
|
icon: <TrashIcon size={20} />,
|
||||||
|
|||||||
+7
-5
@@ -1,24 +1,26 @@
|
|||||||
import moment from "moment"
|
import moment from "moment"
|
||||||
import { useMemo } from "react"
|
import { useMemo } from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import Tooltip from "../../atoms/tooltip"
|
import Tooltip from "../../atoms/tooltip"
|
||||||
|
|
||||||
const useCollectionTableColumn = () => {
|
const useCollectionTableColumn = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const columns = useMemo(
|
const columns = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
Header: "Title",
|
Header: t("collections-table-title", "Title"),
|
||||||
accessor: "title",
|
accessor: "title",
|
||||||
Cell: ({ row: { original } }) => {
|
Cell: ({ row: { original } }) => {
|
||||||
return <div className="flex items-center">{original.title}</div>
|
return <div className="flex items-center">{original.title}</div>
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Handle",
|
Header: t("collections-table-handle", "Handle"),
|
||||||
accessor: "handle",
|
accessor: "handle",
|
||||||
Cell: ({ cell: { value } }) => <div>/{value}</div>,
|
Cell: ({ cell: { value } }) => <div>/{value}</div>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Created At",
|
Header: t("collections-table-created-at", "Created At"),
|
||||||
accessor: "created_at",
|
accessor: "created_at",
|
||||||
Cell: ({ cell: { value } }) => (
|
Cell: ({ cell: { value } }) => (
|
||||||
<Tooltip content={moment(value).format("DD MMM YYYY hh:mm A")}>
|
<Tooltip content={moment(value).format("DD MMM YYYY hh:mm A")}>
|
||||||
@@ -27,7 +29,7 @@ const useCollectionTableColumn = () => {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Updated At",
|
Header: t("collections-table-updated-at", "Updated At"),
|
||||||
accessor: "updated_at",
|
accessor: "updated_at",
|
||||||
Cell: ({ cell: { value } }) => (
|
Cell: ({ cell: { value } }) => (
|
||||||
<Tooltip content={moment(value).format("DD MMM YYYY hh:mm A")}>
|
<Tooltip content={moment(value).format("DD MMM YYYY hh:mm A")}>
|
||||||
@@ -36,7 +38,7 @@ const useCollectionTableColumn = () => {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Products",
|
Header: t("collections-table-products", "Products"),
|
||||||
accessor: "products",
|
accessor: "products",
|
||||||
Cell: ({ cell: { value } }) => {
|
Cell: ({ cell: { value } }) => {
|
||||||
return <div>{value?.length || "-"}</div>
|
return <div>{value?.length || "-"}</div>
|
||||||
|
|||||||
+19
-5
@@ -13,6 +13,7 @@ import {
|
|||||||
useSortBy,
|
useSortBy,
|
||||||
useTable,
|
useTable,
|
||||||
} from "react-table"
|
} from "react-table"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import useQueryFilters from "../../../hooks/use-query-filters"
|
import useQueryFilters from "../../../hooks/use-query-filters"
|
||||||
import useSetSearchParams from "../../../hooks/use-set-search-params"
|
import useSetSearchParams from "../../../hooks/use-set-search-params"
|
||||||
import DetailsIcon from "../../fundamentals/details-icon"
|
import DetailsIcon from "../../fundamentals/details-icon"
|
||||||
@@ -95,22 +96,34 @@ function CustomerGroupsTableRow(props: CustomerGroupsTableRowProps) {
|
|||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const notification = useNotification()
|
const notification = useNotification()
|
||||||
const { mutate } = useAdminDeleteCustomerGroup(row.original.id)
|
const { mutate } = useAdminDeleteCustomerGroup(row.original.id)
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const actions: ActionType[] = [
|
const actions: ActionType[] = [
|
||||||
{
|
{
|
||||||
label: "Details",
|
label: t("customer-group-table-details", "Details"),
|
||||||
onClick: () => navigate(row.original.id),
|
onClick: () => navigate(row.original.id),
|
||||||
icon: <DetailsIcon size={20} />,
|
icon: <DetailsIcon size={20} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Delete",
|
label: t("customer-group-table-delete", "Delete"),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
mutate(undefined, {
|
mutate(undefined, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
notification("Success", "Group deleted", "success")
|
notification(
|
||||||
|
t("customer-group-table-success", "Success"),
|
||||||
|
t("customer-group-table-group-deleted", "Group deleted"),
|
||||||
|
"success"
|
||||||
|
)
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
notification("Error", "Failed to delete the group", "error")
|
notification(
|
||||||
|
t("customer-group-table-error", "Error"),
|
||||||
|
t(
|
||||||
|
"customer-group-table-failed-to-delete-the-group",
|
||||||
|
"Failed to delete the group"
|
||||||
|
),
|
||||||
|
"error"
|
||||||
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -151,6 +164,7 @@ type CustomerGroupsTableProps = ReturnType<typeof useQueryFilters> & {
|
|||||||
function CustomerGroupsTable(props: CustomerGroupsTableProps) {
|
function CustomerGroupsTable(props: CustomerGroupsTableProps) {
|
||||||
const { customerGroups, queryObject, count, paginate, setQuery, isLoading } =
|
const { customerGroups, queryObject, count, paginate, setQuery, isLoading } =
|
||||||
props
|
props
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const tableConfig: TableOptions<CustomerGroup> = {
|
const tableConfig: TableOptions<CustomerGroup> = {
|
||||||
columns: CUSTOMER_GROUPS_TABLE_COLUMNS,
|
columns: CUSTOMER_GROUPS_TABLE_COLUMNS,
|
||||||
@@ -209,7 +223,7 @@ function CustomerGroupsTable(props: CustomerGroupsTableProps) {
|
|||||||
count: count,
|
count: count,
|
||||||
offset: queryObject.offset,
|
offset: queryObject.offset,
|
||||||
pageSize: queryObject.offset + table.rows.length,
|
pageSize: queryObject.offset + table.rows.length,
|
||||||
title: "Customer groups",
|
title: t("customer-group-table-customer-groups", "Customer groups"),
|
||||||
currentPage: table.state.pageIndex + 1,
|
currentPage: table.state.pageIndex + 1,
|
||||||
pageCount: table.pageCount,
|
pageCount: table.pageCount,
|
||||||
nextPage: handleNext,
|
nextPage: handleNext,
|
||||||
|
|||||||
+12
-3
@@ -8,6 +8,7 @@ import {
|
|||||||
} from "react-table"
|
} from "react-table"
|
||||||
|
|
||||||
import { Customer } from "@medusajs/medusa"
|
import { Customer } from "@medusajs/medusa"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
import { useNavigate } from "react-router-dom"
|
import { useNavigate } from "react-router-dom"
|
||||||
import useQueryFilters from "../../../hooks/use-query-filters"
|
import useQueryFilters from "../../../hooks/use-query-filters"
|
||||||
@@ -81,10 +82,11 @@ function CustomersListTableRow(props: CustomersListTableRowProps) {
|
|||||||
const { row, removeCustomers } = props
|
const { row, removeCustomers } = props
|
||||||
|
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const actions = [
|
const actions = [
|
||||||
{
|
{
|
||||||
label: "Details",
|
label: t("customer-group-table-details", "Details"),
|
||||||
onClick: () => navigate(`/a/customers/${row.original.id}`),
|
onClick: () => navigate(`/a/customers/${row.original.id}`),
|
||||||
icon: <DetailsIcon size={20} />,
|
icon: <DetailsIcon size={20} />,
|
||||||
},
|
},
|
||||||
@@ -94,7 +96,10 @@ function CustomersListTableRow(props: CustomersListTableRowProps) {
|
|||||||
// icon: <MailIcon size={20} />,
|
// icon: <MailIcon size={20} />,
|
||||||
// },
|
// },
|
||||||
{
|
{
|
||||||
label: "Delete from the group",
|
label: t(
|
||||||
|
"customer-group-table-delete-from-the-group",
|
||||||
|
"Delete from the group"
|
||||||
|
),
|
||||||
variant: "danger",
|
variant: "danger",
|
||||||
onClick: () =>
|
onClick: () =>
|
||||||
removeCustomers({
|
removeCustomers({
|
||||||
@@ -124,6 +129,7 @@ function CustomersListTableRow(props: CustomersListTableRowProps) {
|
|||||||
* Render a list of customers that belong to a customer group.
|
* Render a list of customers that belong to a customer group.
|
||||||
*/
|
*/
|
||||||
function CustomersListTable(props: CustomersListTableProps) {
|
function CustomersListTable(props: CustomersListTableProps) {
|
||||||
|
const { t } = useTranslation()
|
||||||
const {
|
const {
|
||||||
customers,
|
customers,
|
||||||
removeCustomers,
|
removeCustomers,
|
||||||
@@ -186,7 +192,10 @@ function CustomersListTable(props: CustomersListTableProps) {
|
|||||||
count: count!,
|
count: count!,
|
||||||
offset: queryObject.offset,
|
offset: queryObject.offset,
|
||||||
pageSize: queryObject.offset + table.rows.length,
|
pageSize: queryObject.offset + table.rows.length,
|
||||||
title: "Customer Groups",
|
title: t(
|
||||||
|
"customer-group-table-customer-groups-title",
|
||||||
|
"Customer Groups"
|
||||||
|
),
|
||||||
currentPage: table.state.pageIndex + 1,
|
currentPage: table.state.pageIndex + 1,
|
||||||
pageCount: table.pageCount,
|
pageCount: table.pageCount,
|
||||||
nextPage: handleNext,
|
nextPage: handleNext,
|
||||||
|
|||||||
+10
-6
@@ -1,6 +1,7 @@
|
|||||||
import { Customer } from "@medusajs/medusa"
|
import { Customer } from "@medusajs/medusa"
|
||||||
import { useAdminCustomerGroups, useAdminCustomers } from "medusa-react"
|
import { useAdminCustomerGroups, useAdminCustomers } from "medusa-react"
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import {
|
import {
|
||||||
HeaderGroup,
|
HeaderGroup,
|
||||||
Row,
|
Row,
|
||||||
@@ -74,6 +75,7 @@ type EditCustomersTableProps = {
|
|||||||
* Container for the "edit customers" table.
|
* Container for the "edit customers" table.
|
||||||
*/
|
*/
|
||||||
function EditCustomersTable(props: EditCustomersTableProps) {
|
function EditCustomersTable(props: EditCustomersTableProps) {
|
||||||
|
const { t } = useTranslation()
|
||||||
const { setSelectedCustomerIds, selectedCustomerIds, handleSubmit, onClose } =
|
const { setSelectedCustomerIds, selectedCustomerIds, handleSubmit, onClose } =
|
||||||
props
|
props
|
||||||
|
|
||||||
@@ -131,10 +133,10 @@ function EditCustomersTable(props: EditCustomersTableProps) {
|
|||||||
|
|
||||||
const filteringOptions = [
|
const filteringOptions = [
|
||||||
{
|
{
|
||||||
title: "Groups",
|
title: t("customer-group-table-groups", "Groups"),
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
title: "All",
|
title: t("customer-group-table-all", "All"),
|
||||||
onClick: () => setActiveGroupId(null),
|
onClick: () => setActiveGroupId(null),
|
||||||
},
|
},
|
||||||
...(customer_groups || []).map((g) => ({
|
...(customer_groups || []).map((g) => ({
|
||||||
@@ -176,7 +178,9 @@ function EditCustomersTable(props: EditCustomersTableProps) {
|
|||||||
<Modal handleClose={onClose}>
|
<Modal handleClose={onClose}>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
<Modal.Header handleClose={onClose}>
|
<Modal.Header handleClose={onClose}>
|
||||||
<h3 className="inter-xlarge-semibold">Edit Customers</h3>
|
<h3 className="inter-xlarge-semibold">
|
||||||
|
{t("customer-group-table-edit-customers", "Edit Customers")}
|
||||||
|
</h3>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
|
|
||||||
<Modal.Content>
|
<Modal.Content>
|
||||||
@@ -188,7 +192,7 @@ function EditCustomersTable(props: EditCustomersTableProps) {
|
|||||||
count: count!,
|
count: count!,
|
||||||
offset: queryObject.offset,
|
offset: queryObject.offset,
|
||||||
pageSize: queryObject.offset + table.rows.length,
|
pageSize: queryObject.offset + table.rows.length,
|
||||||
title: "Customers",
|
title: t("customer-group-table-customers", "Customers"),
|
||||||
currentPage: table.state.pageIndex + 1,
|
currentPage: table.state.pageIndex + 1,
|
||||||
pageCount: table.pageCount,
|
pageCount: table.pageCount,
|
||||||
nextPage: handleNext,
|
nextPage: handleNext,
|
||||||
@@ -228,7 +232,7 @@ function EditCustomersTable(props: EditCustomersTableProps) {
|
|||||||
className="w-eventButton"
|
className="w-eventButton"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
>
|
>
|
||||||
Cancel
|
{t("customer-group-table-cancel", "Cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
@@ -236,7 +240,7 @@ function EditCustomersTable(props: EditCustomersTableProps) {
|
|||||||
className="w-eventButton"
|
className="w-eventButton"
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
>
|
>
|
||||||
Save
|
{t("customer-group-table-save", "Save")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Order } from "@medusajs/medusa"
|
|||||||
import { useAdminOrders } from "medusa-react"
|
import { useAdminOrders } from "medusa-react"
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
import { useTable, usePagination } from "react-table"
|
import { useTable, usePagination } from "react-table"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import RefreshIcon from "../../fundamentals/icons/refresh-icon"
|
import RefreshIcon from "../../fundamentals/icons/refresh-icon"
|
||||||
import Table from "../../molecules/table"
|
import Table from "../../molecules/table"
|
||||||
import TableContainer from "../../organisms/table-container"
|
import TableContainer from "../../organisms/table-container"
|
||||||
@@ -15,6 +16,7 @@ type Props = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const CustomerOrdersTable = ({ id }: Props) => {
|
const CustomerOrdersTable = ({ id }: Props) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const [selectedOrderForTransfer, setSelectedOrderForTransfer] =
|
const [selectedOrderForTransfer, setSelectedOrderForTransfer] =
|
||||||
useState<Order | null>(null)
|
useState<Order | null>(null)
|
||||||
|
|
||||||
@@ -85,7 +87,7 @@ const CustomerOrdersTable = ({ id }: Props) => {
|
|||||||
count: count!,
|
count: count!,
|
||||||
offset,
|
offset,
|
||||||
pageSize: offset + rows.length,
|
pageSize: offset + rows.length,
|
||||||
title: "Orders",
|
title: t("customer-orders-table-orders", "Orders"),
|
||||||
currentPage: pageIndex + 1,
|
currentPage: pageIndex + 1,
|
||||||
pageCount: pageCount,
|
pageCount: pageCount,
|
||||||
nextPage: handleNext,
|
nextPage: handleNext,
|
||||||
@@ -118,7 +120,10 @@ const CustomerOrdersTable = ({ id }: Props) => {
|
|||||||
forceDropdown
|
forceDropdown
|
||||||
actions={[
|
actions={[
|
||||||
{
|
{
|
||||||
label: "Transfer order",
|
label: t(
|
||||||
|
"customer-orders-table-transfer-order",
|
||||||
|
"Transfer order"
|
||||||
|
),
|
||||||
icon: <RefreshIcon size={"20"} />,
|
icon: <RefreshIcon size={"20"} />,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
setSelectedOrderForTransfer(row.original as Order)
|
setSelectedOrderForTransfer(row.original as Order)
|
||||||
|
|||||||
+93
-21
@@ -2,49 +2,113 @@ import { Order } from "@medusajs/medusa"
|
|||||||
import moment from "moment"
|
import moment from "moment"
|
||||||
import { useMemo, useRef } from "react"
|
import { useMemo, useRef } from "react"
|
||||||
import { Column } from "react-table"
|
import { Column } from "react-table"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import { useObserveWidth } from "../../../hooks/use-observe-width"
|
import { useObserveWidth } from "../../../hooks/use-observe-width"
|
||||||
import { stringDisplayPrice } from "../../../utils/prices"
|
import { stringDisplayPrice } from "../../../utils/prices"
|
||||||
import Tooltip from "../../atoms/tooltip"
|
import Tooltip from "../../atoms/tooltip"
|
||||||
import ImagePlaceholder from "../../fundamentals/image-placeholder"
|
import ImagePlaceholder from "../../fundamentals/image-placeholder"
|
||||||
import StatusIndicator from "../../fundamentals/status-indicator"
|
import StatusIndicator from "../../fundamentals/status-indicator"
|
||||||
|
import { TFunction } from "i18next"
|
||||||
|
|
||||||
const decidePaymentStatus = (status: string) => {
|
const decidePaymentStatus = (status: string, t: TFunction) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case "captured":
|
case "captured":
|
||||||
return <StatusIndicator variant="success" title={"Paid"} />
|
return (
|
||||||
|
<StatusIndicator
|
||||||
|
variant="success"
|
||||||
|
title={t("customer-orders-table-paid", "Paid")}
|
||||||
|
/>
|
||||||
|
)
|
||||||
case "awaiting":
|
case "awaiting":
|
||||||
return <StatusIndicator variant="warning" title={"Awaiting"} />
|
return (
|
||||||
|
<StatusIndicator
|
||||||
|
variant="warning"
|
||||||
|
title={t("customer-orders-table-awaiting", "Awaiting")}
|
||||||
|
/>
|
||||||
|
)
|
||||||
case "requires":
|
case "requires":
|
||||||
return <StatusIndicator variant="danger" title={"Requires action"} />
|
return (
|
||||||
|
<StatusIndicator
|
||||||
|
variant="danger"
|
||||||
|
title={t("customer-orders-table-requires-action", "Requires action")}
|
||||||
|
/>
|
||||||
|
)
|
||||||
default:
|
default:
|
||||||
return <StatusIndicator variant="primary" title={"N/A"} />
|
return (
|
||||||
|
<StatusIndicator
|
||||||
|
variant="primary"
|
||||||
|
title={t("customer-orders-table-n-a", "N/A")}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const decideFulfillmentStatus = (status: string) => {
|
const decideFulfillmentStatus = (status: string, t: TFunction) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case "fulfilled":
|
case "fulfilled":
|
||||||
return <StatusIndicator variant="success" title={"Fulfilled"} />
|
return (
|
||||||
|
<StatusIndicator
|
||||||
|
variant="success"
|
||||||
|
title={t("customer-orders-table-fulfilled", "Fulfilled")}
|
||||||
|
/>
|
||||||
|
)
|
||||||
case "shipped":
|
case "shipped":
|
||||||
return <StatusIndicator variant="success" title={"Shipped"} />
|
return (
|
||||||
|
<StatusIndicator
|
||||||
|
variant="success"
|
||||||
|
title={t("customer-orders-table-shipped", "Shipped")}
|
||||||
|
/>
|
||||||
|
)
|
||||||
case "not_fulfilled":
|
case "not_fulfilled":
|
||||||
return <StatusIndicator variant="default" title={"Not fulfilled"} />
|
return (
|
||||||
|
<StatusIndicator
|
||||||
|
variant="default"
|
||||||
|
title={t("customer-orders-table-not-fulfilled", "Not fulfilled")}
|
||||||
|
/>
|
||||||
|
)
|
||||||
case "partially_fulfilled":
|
case "partially_fulfilled":
|
||||||
return <StatusIndicator variant="warning" title={"Partially fulfilled"} />
|
return (
|
||||||
|
<StatusIndicator
|
||||||
|
variant="warning"
|
||||||
|
title={t(
|
||||||
|
"customer-orders-table-partially-fulfilled",
|
||||||
|
"Partially fulfilled"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
case "partially_shipped":
|
case "partially_shipped":
|
||||||
return <StatusIndicator variant="warning" title={"Partially shipped"} />
|
return (
|
||||||
|
<StatusIndicator
|
||||||
|
variant="warning"
|
||||||
|
title={t(
|
||||||
|
"customer-orders-table-partially-shipped",
|
||||||
|
"Partially shipped"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
case "requires":
|
case "requires":
|
||||||
return <StatusIndicator variant="danger" title={"Requires action"} />
|
return (
|
||||||
|
<StatusIndicator
|
||||||
|
variant="danger"
|
||||||
|
title={t("customer-orders-table-requires-action", "Requires action")}
|
||||||
|
/>
|
||||||
|
)
|
||||||
default:
|
default:
|
||||||
return <StatusIndicator variant="primary" title={"N/A"} />
|
return (
|
||||||
|
<StatusIndicator
|
||||||
|
variant="primary"
|
||||||
|
title={t("customer-orders-table-n-a", "N/A")}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useCustomerOrdersColumns = (): Column<Order>[] => {
|
export const useCustomerOrdersColumns = (): Column<Order>[] => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const columns = useMemo(() => {
|
const columns = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
Header: "Order",
|
Header: t("customer-orders-table-order", "Order"),
|
||||||
accessor: "display_id",
|
accessor: "display_id",
|
||||||
Cell: ({ value }) => {
|
Cell: ({ value }) => {
|
||||||
return <span className="text-grey-90">#{value}</span>
|
return <span className="text-grey-90">#{value}</span>
|
||||||
@@ -95,7 +159,11 @@ export const useCustomerOrdersColumns = (): Column<Order>[] => {
|
|||||||
</div>
|
</div>
|
||||||
{remainder > 0 && (
|
{remainder > 0 && (
|
||||||
<span className="text-grey-40 inter-small-regular">
|
<span className="text-grey-40 inter-small-regular">
|
||||||
+ {remainder} more
|
{t(
|
||||||
|
"customer-orders-table-remainder-more",
|
||||||
|
"+ {{remainder}} more",
|
||||||
|
{ remainder }
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -103,28 +171,32 @@ export const useCustomerOrdersColumns = (): Column<Order>[] => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Date",
|
Header: t("customer-orders-table-date", "Date"),
|
||||||
accessor: "created_at",
|
accessor: "created_at",
|
||||||
Cell: ({ value }) => {
|
Cell: ({ value }) => {
|
||||||
return moment(value).format("DD MMM YYYY hh:mm")
|
return moment(value).format("DD MMM YYYY hh:mm")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Fulfillment",
|
Header: t("customer-orders-table-fulfillment", "Fulfillment"),
|
||||||
accessor: "fulfillment_status",
|
accessor: "fulfillment_status",
|
||||||
Cell: ({ value }) => {
|
Cell: ({ value }) => {
|
||||||
return decideFulfillmentStatus(value)
|
return decideFulfillmentStatus(value, t)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Status",
|
Header: t("customer-orders-table-status", "Status"),
|
||||||
accessor: "payment_status",
|
accessor: "payment_status",
|
||||||
Cell: ({ value }) => {
|
Cell: ({ value }) => {
|
||||||
return decidePaymentStatus(value)
|
return decidePaymentStatus(value, t)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: () => <div className="text-right">Total</div>,
|
Header: () => (
|
||||||
|
<div className="text-right">
|
||||||
|
{t("customer-orders-table-total", "Total")}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
accessor: "total",
|
accessor: "total",
|
||||||
Cell: ({
|
Cell: ({
|
||||||
value,
|
value,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useAdminCustomers } from "medusa-react"
|
|||||||
import qs from "qs"
|
import qs from "qs"
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import { useNavigate } from "react-router-dom"
|
import { useNavigate } from "react-router-dom"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import { usePagination, useTable } from "react-table"
|
import { usePagination, useTable } from "react-table"
|
||||||
import DetailsIcon from "../../fundamentals/details-icon"
|
import DetailsIcon from "../../fundamentals/details-icon"
|
||||||
import EditIcon from "../../fundamentals/icons/edit-icon"
|
import EditIcon from "../../fundamentals/icons/edit-icon"
|
||||||
@@ -19,6 +20,7 @@ const defaultQueryProps = {
|
|||||||
|
|
||||||
const CustomerTable = () => {
|
const CustomerTable = () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
reset,
|
reset,
|
||||||
@@ -139,7 +141,7 @@ const CustomerTable = () => {
|
|||||||
count: count!,
|
count: count!,
|
||||||
offset: queryObject.offset,
|
offset: queryObject.offset,
|
||||||
pageSize: queryObject.offset + rows.length,
|
pageSize: queryObject.offset + rows.length,
|
||||||
title: "Customers",
|
title: t("customer-table-customers", "Customers"),
|
||||||
currentPage: pageIndex + 1,
|
currentPage: pageIndex + 1,
|
||||||
pageCount: pageCount,
|
pageCount: pageCount,
|
||||||
nextPage: handleNext,
|
nextPage: handleNext,
|
||||||
@@ -174,12 +176,12 @@ const CustomerTable = () => {
|
|||||||
color={"inherit"}
|
color={"inherit"}
|
||||||
actions={[
|
actions={[
|
||||||
{
|
{
|
||||||
label: "Edit",
|
label: t("customer-table-edit", "Edit"),
|
||||||
onClick: () => navigate(row.original.id),
|
onClick: () => navigate(row.original.id),
|
||||||
icon: <EditIcon size={20} />,
|
icon: <EditIcon size={20} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Details",
|
label: t("customer-table-details", "Details"),
|
||||||
onClick: () => navigate(row.original.id),
|
onClick: () => navigate(row.original.id),
|
||||||
icon: <DetailsIcon size={20} />,
|
icon: <DetailsIcon size={20} />,
|
||||||
},
|
},
|
||||||
|
|||||||
+10
-4
@@ -1,18 +1,20 @@
|
|||||||
import moment from "moment"
|
import moment from "moment"
|
||||||
import { useMemo } from "react"
|
import { useMemo } from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import { getColor } from "../../../utils/color"
|
import { getColor } from "../../../utils/color"
|
||||||
import CustomerAvatarItem from "../../molecules/customer-avatar-item"
|
import CustomerAvatarItem from "../../molecules/customer-avatar-item"
|
||||||
|
|
||||||
export const useCustomerColumns = () => {
|
export const useCustomerColumns = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const columns = useMemo(
|
const columns = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
Header: "Date added",
|
Header: t("customer-table-date-added", "Date added"),
|
||||||
accessor: "created_at", // accessor is the "key" in the data
|
accessor: "created_at", // accessor is the "key" in the data
|
||||||
Cell: ({ cell: { value } }) => moment(value).format("DD MMM YYYY"),
|
Cell: ({ cell: { value } }) => moment(value).format("DD MMM YYYY"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Name",
|
Header: t("customer-table-name", "Name"),
|
||||||
accessor: "customer",
|
accessor: "customer",
|
||||||
Cell: ({ row }) => (
|
Cell: ({ row }) => (
|
||||||
<CustomerAvatarItem
|
<CustomerAvatarItem
|
||||||
@@ -22,7 +24,7 @@ export const useCustomerColumns = () => {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Email",
|
Header: t("customer-table-email", "Email"),
|
||||||
accessor: "email",
|
accessor: "email",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -31,7 +33,11 @@ export const useCustomerColumns = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: "orders",
|
accessor: "orders",
|
||||||
Header: () => <div className="text-right">Orders</div>,
|
Header: () => (
|
||||||
|
<div className="text-right">
|
||||||
|
{t("customer-table-orders", "Orders")}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
Cell: ({ cell: { value } }) => (
|
Cell: ({ cell: { value } }) => (
|
||||||
<div className="text-right">{value?.length || 0}</div>
|
<div className="text-right">{value?.length || 0}</div>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import clsx from "clsx"
|
import clsx from "clsx"
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import FilterDropdownContainer from "../../../components/molecules/filter-dropdown/container"
|
import FilterDropdownContainer from "../../../components/molecules/filter-dropdown/container"
|
||||||
import FilterDropdownItem from "../../../components/molecules/filter-dropdown/item"
|
import FilterDropdownItem from "../../../components/molecules/filter-dropdown/item"
|
||||||
import SaveFilterItem from "../../../components/molecules/filter-dropdown/save-field"
|
import SaveFilterItem from "../../../components/molecules/filter-dropdown/save-field"
|
||||||
@@ -26,6 +27,7 @@ const DiscountFilters = ({
|
|||||||
submitFilters,
|
submitFilters,
|
||||||
clearFilters,
|
clearFilters,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const [tempState, setTempState] = useState(filters)
|
const [tempState, setTempState] = useState(filters)
|
||||||
const [name, setName] = useState("")
|
const [name, setName] = useState("")
|
||||||
|
|
||||||
@@ -88,7 +90,7 @@ const DiscountFilters = ({
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="rounded-rounded bg-grey-5 border-grey-20 inter-small-semibold flex h-6 items-center border px-2">
|
<div className="rounded-rounded bg-grey-5 border-grey-20 inter-small-semibold flex h-6 items-center border px-2">
|
||||||
Filters
|
{t("discount-filter-dropdown-filters", "Filters")}
|
||||||
<div className="text-grey-40 ml-1 flex items-center rounded">
|
<div className="text-grey-40 ml-1 flex items-center rounded">
|
||||||
<span className="text-violet-60 inter-small-semibold">
|
<span className="text-violet-60 inter-small-semibold">
|
||||||
{numberOfFilters ? numberOfFilters : "0"}
|
{numberOfFilters ? numberOfFilters : "0"}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useAdminDiscounts } from "medusa-react"
|
|||||||
import qs from "qs"
|
import qs from "qs"
|
||||||
import React, { useEffect, useState } from "react"
|
import React, { useEffect, useState } from "react"
|
||||||
import { usePagination, useTable } from "react-table"
|
import { usePagination, useTable } from "react-table"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import { useAnalytics } from "../../../providers/analytics-provider"
|
import { useAnalytics } from "../../../providers/analytics-provider"
|
||||||
import Table from "../../molecules/table"
|
import Table from "../../molecules/table"
|
||||||
import TableContainer from "../../organisms/table-container"
|
import TableContainer from "../../organisms/table-container"
|
||||||
@@ -16,6 +17,7 @@ const DEFAULT_PAGE_SIZE = 15
|
|||||||
const defaultQueryProps = {}
|
const defaultQueryProps = {}
|
||||||
|
|
||||||
const DiscountTable: React.FC = () => {
|
const DiscountTable: React.FC = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const {
|
const {
|
||||||
removeTab,
|
removeTab,
|
||||||
setTab,
|
setTab,
|
||||||
@@ -157,7 +159,7 @@ const DiscountTable: React.FC = () => {
|
|||||||
count: count!,
|
count: count!,
|
||||||
offset: queryObject.offset,
|
offset: queryObject.offset,
|
||||||
pageSize: queryObject.offset + rows.length,
|
pageSize: queryObject.offset + rows.length,
|
||||||
title: "Discounts",
|
title: t("discount-table-discounts", "Discounts"),
|
||||||
currentPage: pageIndex + 1,
|
currentPage: pageIndex + 1,
|
||||||
pageCount: pageCount,
|
pageCount: pageCount,
|
||||||
nextPage: handleNext,
|
nextPage: handleNext,
|
||||||
@@ -181,7 +183,10 @@ const DiscountTable: React.FC = () => {
|
|||||||
}
|
}
|
||||||
enableSearch
|
enableSearch
|
||||||
handleSearch={setQuery}
|
handleSearch={setQuery}
|
||||||
searchPlaceholder="Search by code or description..."
|
searchPlaceholder={t(
|
||||||
|
"discount-table-search-by-code-or-description",
|
||||||
|
"Search by code or description..."
|
||||||
|
)}
|
||||||
searchValue={query}
|
searchValue={query}
|
||||||
{...getTableProps()}
|
{...getTableProps()}
|
||||||
>
|
>
|
||||||
|
|||||||
+15
-2
@@ -1,10 +1,12 @@
|
|||||||
import { useAdminCreateDiscount } from "medusa-react"
|
import { useAdminCreateDiscount } from "medusa-react"
|
||||||
import { useNavigate } from "react-router-dom"
|
import { useNavigate } from "react-router-dom"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import useNotification from "../../../hooks/use-notification"
|
import useNotification from "../../../hooks/use-notification"
|
||||||
import { getErrorMessage } from "../../../utils/error-messages"
|
import { getErrorMessage } from "../../../utils/error-messages"
|
||||||
import { removeFalsy } from "../../../utils/remove-nullish"
|
import { removeFalsy } from "../../../utils/remove-nullish"
|
||||||
|
|
||||||
const useCopyPromotion = () => {
|
const useCopyPromotion = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const notification = useNotification()
|
const notification = useNotification()
|
||||||
const createPromotion = useAdminCreateDiscount()
|
const createPromotion = useAdminCreateDiscount()
|
||||||
@@ -60,10 +62,21 @@ const useCopyPromotion = () => {
|
|||||||
await createPromotion.mutate(copy, {
|
await createPromotion.mutate(copy, {
|
||||||
onSuccess: (result) => {
|
onSuccess: (result) => {
|
||||||
navigate(`/a/discounts/${result.discount.id}`)
|
navigate(`/a/discounts/${result.discount.id}`)
|
||||||
notification("Success", "Successfully copied discount", "success")
|
notification(
|
||||||
|
t("discount-table-success", "Success"),
|
||||||
|
t(
|
||||||
|
"discount-table-successfully-copied-discount",
|
||||||
|
"Successfully copied discount"
|
||||||
|
),
|
||||||
|
"success"
|
||||||
|
)
|
||||||
},
|
},
|
||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
notification("Error", getErrorMessage(err), "error")
|
notification(
|
||||||
|
t("discount-table-error", "Error"),
|
||||||
|
getErrorMessage(err),
|
||||||
|
"error"
|
||||||
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+50
-15
@@ -1,5 +1,6 @@
|
|||||||
import { end, parse } from "iso8601-duration"
|
import { end, parse } from "iso8601-duration"
|
||||||
import { useMemo } from "react"
|
import { useMemo } from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import { formatAmountWithSymbol } from "../../../utils/prices"
|
import { formatAmountWithSymbol } from "../../../utils/prices"
|
||||||
import Badge from "../../fundamentals/badge"
|
import Badge from "../../fundamentals/badge"
|
||||||
import StatusDot from "../../fundamentals/status-indicator"
|
import StatusDot from "../../fundamentals/status-indicator"
|
||||||
@@ -34,19 +35,44 @@ const getPromotionStatus = (promotion) => {
|
|||||||
return PromotionStatus.DISABLED
|
return PromotionStatus.DISABLED
|
||||||
}
|
}
|
||||||
|
|
||||||
const getPromotionStatusDot = (promotion) => {
|
const getPromotionStatusDot = (promotion, t) => {
|
||||||
const status = getPromotionStatus(promotion)
|
const status = getPromotionStatus(promotion)
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case PromotionStatus.SCHEDULED:
|
case PromotionStatus.SCHEDULED:
|
||||||
return <StatusDot title="Scheduled" variant="warning" />
|
return (
|
||||||
|
<StatusDot
|
||||||
|
title={t("discount-table-scheduled", "Scheduled")}
|
||||||
|
variant="warning"
|
||||||
|
/>
|
||||||
|
)
|
||||||
case PromotionStatus.EXPIRED:
|
case PromotionStatus.EXPIRED:
|
||||||
return <StatusDot title="Expired" variant="danger" />
|
return (
|
||||||
|
<StatusDot
|
||||||
|
title={t("discount-table-expired", "Expired")}
|
||||||
|
variant="danger"
|
||||||
|
/>
|
||||||
|
)
|
||||||
case PromotionStatus.ACTIVE:
|
case PromotionStatus.ACTIVE:
|
||||||
return <StatusDot title="Active" variant="success" />
|
return (
|
||||||
|
<StatusDot
|
||||||
|
title={t("discount-table-active", "Active")}
|
||||||
|
variant="success"
|
||||||
|
/>
|
||||||
|
)
|
||||||
case PromotionStatus.DISABLED:
|
case PromotionStatus.DISABLED:
|
||||||
return <StatusDot title="Disabled" variant="default" />
|
return (
|
||||||
|
<StatusDot
|
||||||
|
title={t("discount-table-disabled", "Disabled")}
|
||||||
|
variant="default"
|
||||||
|
/>
|
||||||
|
)
|
||||||
default:
|
default:
|
||||||
return <StatusDot title="Disabled" variant="default" />
|
return (
|
||||||
|
<StatusDot
|
||||||
|
title={t("discount-table-disabled", "Disabled")}
|
||||||
|
variant="default"
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +86,7 @@ const getCurrencySymbol = (promotion) => {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
const getPromotionAmount = (promotion) => {
|
const getPromotionAmount = (promotion, t) => {
|
||||||
switch (promotion.rule.type) {
|
switch (promotion.rule.type) {
|
||||||
case "fixed":
|
case "fixed":
|
||||||
if (!promotion.regions?.length) {
|
if (!promotion.regions?.length) {
|
||||||
@@ -73,17 +99,18 @@ const getPromotionAmount = (promotion) => {
|
|||||||
case "percentage":
|
case "percentage":
|
||||||
return `${promotion.rule.value}%`
|
return `${promotion.rule.value}%`
|
||||||
case "free_shipping":
|
case "free_shipping":
|
||||||
return "Free Shipping"
|
return t("discount-table-free-shipping", "Free Shipping")
|
||||||
default:
|
default:
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const usePromotionTableColumns = () => {
|
export const usePromotionTableColumns = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const columns = useMemo(
|
const columns = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
Header: <div className="pl-2">Code</div>,
|
Header: <div className="pl-2">{t("discount-table-code", "Code")}</div>,
|
||||||
accessor: "code",
|
accessor: "code",
|
||||||
Cell: ({ cell: { value } }) => (
|
Cell: ({ cell: { value } }) => (
|
||||||
<div className="overflow-hidden">
|
<div className="overflow-hidden">
|
||||||
@@ -94,16 +121,20 @@ export const usePromotionTableColumns = () => {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Description",
|
Header: t("discount-table-description", "Description"),
|
||||||
accessor: "rule.description",
|
accessor: "rule.description",
|
||||||
Cell: ({ cell: { value } }) => value,
|
Cell: ({ cell: { value } }) => value,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: <div className="text-right">Amount</div>,
|
Header: (
|
||||||
|
<div className="text-right">
|
||||||
|
{t("discount-table-amount", "Amount")}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
id: "amount",
|
id: "amount",
|
||||||
Cell: ({ row: { original } }) => {
|
Cell: ({ row: { original } }) => {
|
||||||
return (
|
return (
|
||||||
<div className="text-right">{getPromotionAmount(original)}</div>
|
<div className="text-right">{getPromotionAmount(original, t)}</div>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -115,14 +146,18 @@ export const usePromotionTableColumns = () => {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Status",
|
Header: t("discount-table-status", "Status"),
|
||||||
accessor: "ends_at",
|
accessor: "ends_at",
|
||||||
Cell: ({ row: { original } }) => (
|
Cell: ({ row: { original } }) => (
|
||||||
<div>{getPromotionStatusDot(original)}</div>
|
<div>{getPromotionStatusDot(original, t)}</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: () => <div className="text-right">Redemptions</div>,
|
Header: () => (
|
||||||
|
<div className="text-right">
|
||||||
|
{t("discount-table-redemptions", "Redemptions")}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
accessor: "usage_count",
|
accessor: "usage_count",
|
||||||
Cell: ({ row: { original } }) => {
|
Cell: ({ row: { original } }) => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
+27
-10
@@ -1,3 +1,4 @@
|
|||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import { useAdminDeleteDiscount, useAdminUpdateDiscount } from "medusa-react"
|
import { useAdminDeleteDiscount, useAdminUpdateDiscount } from "medusa-react"
|
||||||
import useImperativeDialog from "../../../hooks/use-imperative-dialog"
|
import useImperativeDialog from "../../../hooks/use-imperative-dialog"
|
||||||
import useNotification from "../../../hooks/use-notification"
|
import useNotification from "../../../hooks/use-notification"
|
||||||
@@ -11,6 +12,7 @@ import useCopyPromotion from "./use-copy-promotion"
|
|||||||
import { useNavigate } from "react-router-dom"
|
import { useNavigate } from "react-router-dom"
|
||||||
|
|
||||||
const usePromotionActions = (promotion) => {
|
const usePromotionActions = (promotion) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const notification = useNotification()
|
const notification = useNotification()
|
||||||
const dialog = useImperativeDialog()
|
const dialog = useImperativeDialog()
|
||||||
@@ -22,8 +24,11 @@ const usePromotionActions = (promotion) => {
|
|||||||
|
|
||||||
const handleDelete = async () => {
|
const handleDelete = async () => {
|
||||||
const shouldDelete = await dialog({
|
const shouldDelete = await dialog({
|
||||||
heading: "Delete Discount",
|
heading: t("discount-table-delete-discount", "Delete Discount"),
|
||||||
text: "Are you sure you want to delete this Discount?",
|
text: t(
|
||||||
|
"discount-table-confirm-delete",
|
||||||
|
"Are you sure you want to delete this Discount?"
|
||||||
|
),
|
||||||
})
|
})
|
||||||
|
|
||||||
if (shouldDelete) {
|
if (shouldDelete) {
|
||||||
@@ -39,7 +44,9 @@ const usePromotionActions = (promotion) => {
|
|||||||
onClick: () => navigate(`/a/discounts/${promotion.id}`),
|
onClick: () => navigate(`/a/discounts/${promotion.id}`),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: promotion.is_disabled ? "Publish" : "Unpublish",
|
label: promotion.is_disabled
|
||||||
|
? t("discount-table-publish", "Publish")
|
||||||
|
: t("discount-table-unpublish", "Unpublish"),
|
||||||
icon: promotion.is_disabled ? (
|
icon: promotion.is_disabled ? (
|
||||||
<PublishIcon size={20} />
|
<PublishIcon size={20} />
|
||||||
) : (
|
) : (
|
||||||
@@ -53,26 +60,36 @@ const usePromotionActions = (promotion) => {
|
|||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
notification(
|
notification(
|
||||||
"Success",
|
t("discount-table-success", "Success"),
|
||||||
`Successfully ${
|
promotion.is_disabled
|
||||||
promotion.is_disabled ? "published" : "unpublished"
|
? t(
|
||||||
} discount`,
|
"discount-table-successfully-published-discount",
|
||||||
|
"Successfully published discount"
|
||||||
|
)
|
||||||
|
: t(
|
||||||
|
"discount-table-successfully-unpublished-discount",
|
||||||
|
"Successfully unpublished discount"
|
||||||
|
),
|
||||||
"success"
|
"success"
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onError: (err) =>
|
onError: (err) =>
|
||||||
notification("Error", getErrorMessage(err), "error"),
|
notification(
|
||||||
|
t("discount-table-error", "Error"),
|
||||||
|
getErrorMessage(err),
|
||||||
|
"error"
|
||||||
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Duplicate",
|
label: t("discount-table-duplicate", "Duplicate"),
|
||||||
icon: <DuplicateIcon size={20} />,
|
icon: <DuplicateIcon size={20} />,
|
||||||
onClick: () => copyPromotion(promotion),
|
onClick: () => copyPromotion(promotion),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Delete",
|
label: t("discount-table-delete", "Delete"),
|
||||||
icon: <TrashIcon size={20} />,
|
icon: <TrashIcon size={20} />,
|
||||||
variant: "danger",
|
variant: "danger",
|
||||||
onClick: handleDelete,
|
onClick: handleDelete,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useAdminDraftOrders } from "medusa-react"
|
|||||||
import { Fragment, useEffect, useState } from "react"
|
import { Fragment, useEffect, useState } from "react"
|
||||||
import { useLocation } from "react-router-dom"
|
import { useLocation } from "react-router-dom"
|
||||||
import { usePagination, useTable } from "react-table"
|
import { usePagination, useTable } from "react-table"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import Table from "../../molecules/table"
|
import Table from "../../molecules/table"
|
||||||
import TableContainer from "../../organisms/table-container"
|
import TableContainer from "../../organisms/table-container"
|
||||||
import useDraftOrderTableColumns from "./use-draft-order-column"
|
import useDraftOrderTableColumns from "./use-draft-order-column"
|
||||||
@@ -11,6 +12,7 @@ const DEFAULT_PAGE_SIZE = 15
|
|||||||
|
|
||||||
const DraftOrderTable = () => {
|
const DraftOrderTable = () => {
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
reset,
|
reset,
|
||||||
@@ -107,7 +109,7 @@ const DraftOrderTable = () => {
|
|||||||
count: count!,
|
count: count!,
|
||||||
offset: queryObject.offset,
|
offset: queryObject.offset,
|
||||||
pageSize: queryObject.offset + rows.length,
|
pageSize: queryObject.offset + rows.length,
|
||||||
title: "Draft Orders",
|
title: t("draft-order-table-draft-orders", "Draft Orders"),
|
||||||
currentPage: pageIndex + 1,
|
currentPage: pageIndex + 1,
|
||||||
pageCount: pageCount,
|
pageCount: pageCount,
|
||||||
nextPage: handleNext,
|
nextPage: handleNext,
|
||||||
|
|||||||
+19
-7
@@ -1,24 +1,36 @@
|
|||||||
import moment from "moment"
|
import moment from "moment"
|
||||||
import { useMemo } from "react"
|
import { useMemo } from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import { getColor } from "../../../utils/color"
|
import { getColor } from "../../../utils/color"
|
||||||
import StatusDot from "../../fundamentals/status-indicator"
|
import StatusDot from "../../fundamentals/status-indicator"
|
||||||
import CustomerAvatarItem from "../../molecules/customer-avatar-item"
|
import CustomerAvatarItem from "../../molecules/customer-avatar-item"
|
||||||
import Table from "../../molecules/table"
|
import Table from "../../molecules/table"
|
||||||
|
|
||||||
const useDraftOrderTableColumns = () => {
|
const useDraftOrderTableColumns = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const decideStatus = (status) => {
|
const decideStatus = (status) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case "completed":
|
case "completed":
|
||||||
return <StatusDot variant="success" title={"Completed"} />
|
return (
|
||||||
|
<StatusDot
|
||||||
|
variant="success"
|
||||||
|
title={t("draft-order-table-completed", "Completed")}
|
||||||
|
/>
|
||||||
|
)
|
||||||
default:
|
default:
|
||||||
return <StatusDot variant="primary" title={"Open"} />
|
return (
|
||||||
|
<StatusDot
|
||||||
|
variant="primary"
|
||||||
|
title={t("draft-order-table-open", "Open")}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const columns = useMemo(
|
const columns = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
Header: "Draft",
|
Header: t("draft-order-table-draft", "Draft"),
|
||||||
accessor: "display_id",
|
accessor: "display_id",
|
||||||
Cell: ({ cell: { value, getCellProps } }) => (
|
Cell: ({ cell: { value, getCellProps } }) => (
|
||||||
<Table.Cell
|
<Table.Cell
|
||||||
@@ -28,7 +40,7 @@ const useDraftOrderTableColumns = () => {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Order",
|
Header: t("draft-order-table-order", "Order"),
|
||||||
accessor: "order",
|
accessor: "order",
|
||||||
Cell: ({ cell: { value, getCellProps } }) => {
|
Cell: ({ cell: { value, getCellProps } }) => {
|
||||||
return (
|
return (
|
||||||
@@ -39,7 +51,7 @@ const useDraftOrderTableColumns = () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Date added",
|
Header: t("draft-order-table-date-added", "Date added"),
|
||||||
accessor: "created_at",
|
accessor: "created_at",
|
||||||
Cell: ({ cell: { value, getCellProps } }) => (
|
Cell: ({ cell: { value, getCellProps } }) => (
|
||||||
<Table.Cell {...getCellProps()}>
|
<Table.Cell {...getCellProps()}>
|
||||||
@@ -48,7 +60,7 @@ const useDraftOrderTableColumns = () => {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Customer",
|
Header: t("draft-order-table-customer", "Customer"),
|
||||||
accessor: "cart",
|
accessor: "cart",
|
||||||
Cell: ({ row, cell: { value, getCellProps } }) => (
|
Cell: ({ row, cell: { value, getCellProps } }) => (
|
||||||
<Table.Cell {...getCellProps()}>
|
<Table.Cell {...getCellProps()}>
|
||||||
@@ -64,7 +76,7 @@ const useDraftOrderTableColumns = () => {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Status",
|
Header: t("draft-order-table-status", "Status"),
|
||||||
accessor: "status",
|
accessor: "status",
|
||||||
Cell: ({ cell: { value, getCellProps } }) => (
|
Cell: ({ cell: { value, getCellProps } }) => (
|
||||||
<Table.Cell {...getCellProps()} className="pr-2">
|
<Table.Cell {...getCellProps()} className="pr-2">
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import clsx from "clsx"
|
import clsx from "clsx"
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import FilterDropdownContainer from "../../../components/molecules/filter-dropdown/container"
|
import FilterDropdownContainer from "../../../components/molecules/filter-dropdown/container"
|
||||||
import FilterDropdownItem from "../../../components/molecules/filter-dropdown/item"
|
import FilterDropdownItem from "../../../components/molecules/filter-dropdown/item"
|
||||||
import SaveFilterItem from "../../../components/molecules/filter-dropdown/save-field"
|
import SaveFilterItem from "../../../components/molecules/filter-dropdown/save-field"
|
||||||
import TabFilter from "../../../components/molecules/filter-tab"
|
import TabFilter from "../../../components/molecules/filter-tab"
|
||||||
import PlusIcon from "../../fundamentals/icons/plus-icon"
|
import PlusIcon from "../../fundamentals/icons/plus-icon"
|
||||||
|
import { TFunction } from "i18next"
|
||||||
|
|
||||||
const statusFilters = [
|
const statusFilters = [
|
||||||
"completed",
|
"completed",
|
||||||
@@ -34,12 +36,12 @@ const fulfillmentFilters = [
|
|||||||
"canceled",
|
"canceled",
|
||||||
]
|
]
|
||||||
|
|
||||||
const dateFilters = [
|
const dateFilters = (t: TFunction) => [
|
||||||
"is in the last",
|
t("gift-card-filter-dropdown-is-in-the-last", "is in the last"),
|
||||||
"is older than",
|
t("gift-card-filter-dropdown-is-older-than", "is older than"),
|
||||||
"is after",
|
t("gift-card-filter-dropdown-is-after", "is after"),
|
||||||
"is before",
|
t("gift-card-filter-dropdown-is-before", "is before"),
|
||||||
"is equal to",
|
t("gift-card-filter-dropdown-is-equal-to", "is equal to"),
|
||||||
]
|
]
|
||||||
|
|
||||||
const OrderFilters = ({
|
const OrderFilters = ({
|
||||||
@@ -52,6 +54,7 @@ const OrderFilters = ({
|
|||||||
submitFilters,
|
submitFilters,
|
||||||
clearFilters,
|
clearFilters,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const [tempState, setTempState] = useState(filters)
|
const [tempState, setTempState] = useState(filters)
|
||||||
const [name, setName] = useState("")
|
const [name, setName] = useState("")
|
||||||
|
|
||||||
@@ -114,7 +117,7 @@ const OrderFilters = ({
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="rounded-rounded bg-grey-5 border-grey-20 inter-small-semibold flex h-6 items-center border px-2">
|
<div className="rounded-rounded bg-grey-5 border-grey-20 inter-small-semibold flex h-6 items-center border px-2">
|
||||||
Filters
|
{t("gift-card-filter-dropdown-filters", "Filters")}
|
||||||
<div className="text-grey-40 ml-1 flex items-center rounded">
|
<div className="text-grey-40 ml-1 flex items-center rounded">
|
||||||
<span className="text-violet-60 inter-small-semibold">
|
<span className="text-violet-60 inter-small-semibold">
|
||||||
{numberOfFilters ? numberOfFilters : "0"}
|
{numberOfFilters ? numberOfFilters : "0"}
|
||||||
@@ -128,29 +131,35 @@ const OrderFilters = ({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<FilterDropdownItem
|
<FilterDropdownItem
|
||||||
filterTitle="Status"
|
filterTitle={t("gift-card-filter-dropdown-status", "Status")}
|
||||||
options={statusFilters}
|
options={statusFilters}
|
||||||
filters={tempState.status.filter}
|
filters={tempState.status.filter}
|
||||||
open={tempState.status.open}
|
open={tempState.status.open}
|
||||||
setFilter={(val) => setSingleFilter("status", val)}
|
setFilter={(val) => setSingleFilter("status", val)}
|
||||||
/>
|
/>
|
||||||
<FilterDropdownItem
|
<FilterDropdownItem
|
||||||
filterTitle="Payment Status"
|
filterTitle={t(
|
||||||
|
"gift-card-filter-dropdown-payment-status",
|
||||||
|
"Payment Status"
|
||||||
|
)}
|
||||||
options={paymentFilters}
|
options={paymentFilters}
|
||||||
filters={tempState.payment.filter}
|
filters={tempState.payment.filter}
|
||||||
open={tempState.payment.open}
|
open={tempState.payment.open}
|
||||||
setFilter={(val) => setSingleFilter("payment", val)}
|
setFilter={(val) => setSingleFilter("payment", val)}
|
||||||
/>
|
/>
|
||||||
<FilterDropdownItem
|
<FilterDropdownItem
|
||||||
filterTitle="Fulfillment Status"
|
filterTitle={t(
|
||||||
|
"gift-card-filter-dropdown-fulfillment-status",
|
||||||
|
"Fulfillment Status"
|
||||||
|
)}
|
||||||
options={fulfillmentFilters}
|
options={fulfillmentFilters}
|
||||||
filters={tempState.fulfillment.filter}
|
filters={tempState.fulfillment.filter}
|
||||||
open={tempState.fulfillment.open}
|
open={tempState.fulfillment.open}
|
||||||
setFilter={(val) => setSingleFilter("fulfillment", val)}
|
setFilter={(val) => setSingleFilter("fulfillment", val)}
|
||||||
/>
|
/>
|
||||||
<FilterDropdownItem
|
<FilterDropdownItem
|
||||||
filterTitle="Date"
|
filterTitle={t("gift-card-filter-dropdown-date", "Date")}
|
||||||
options={dateFilters}
|
options={dateFilters(t)}
|
||||||
filters={tempState.date.filter}
|
filters={tempState.date.filter}
|
||||||
open={tempState.date.open}
|
open={tempState.date.open}
|
||||||
setFilter={(val) => setSingleFilter("date", val)}
|
setFilter={(val) => setSingleFilter("date", val)}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import qs from "qs"
|
|||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import { useLocation } from "react-router-dom"
|
import { useLocation } from "react-router-dom"
|
||||||
import { usePagination, useTable } from "react-table"
|
import { usePagination, useTable } from "react-table"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import Spinner from "../../atoms/spinner"
|
import Spinner from "../../atoms/spinner"
|
||||||
import Table from "../../molecules/table"
|
import Table from "../../molecules/table"
|
||||||
import TableContainer from "../../organisms/table-container"
|
import TableContainer from "../../organisms/table-container"
|
||||||
@@ -17,6 +18,7 @@ const defaultQueryProps = {}
|
|||||||
|
|
||||||
const GiftCardTable = () => {
|
const GiftCardTable = () => {
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
reset,
|
reset,
|
||||||
@@ -130,7 +132,7 @@ const GiftCardTable = () => {
|
|||||||
count: count!,
|
count: count!,
|
||||||
offset: queryObject.offset,
|
offset: queryObject.offset,
|
||||||
pageSize: queryObject.offset + rows.length,
|
pageSize: queryObject.offset + rows.length,
|
||||||
title: "Gift cards",
|
title: t("gift-card-table-gift-cards", "Gift cards"),
|
||||||
currentPage: pageIndex + 1,
|
currentPage: pageIndex + 1,
|
||||||
pageCount: pageCount,
|
pageCount: pageCount,
|
||||||
nextPage: handleNext,
|
nextPage: handleNext,
|
||||||
|
|||||||
+17
-7
@@ -1,15 +1,17 @@
|
|||||||
import moment from "moment"
|
import moment from "moment"
|
||||||
import { useMemo } from "react"
|
import { useMemo } from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import { formatAmountWithSymbol } from "../../../utils/prices"
|
import { formatAmountWithSymbol } from "../../../utils/prices"
|
||||||
import StatusIndicator from "../../fundamentals/status-indicator"
|
import StatusIndicator from "../../fundamentals/status-indicator"
|
||||||
import IconTooltip from "../../molecules/icon-tooltip"
|
import IconTooltip from "../../molecules/icon-tooltip"
|
||||||
import Table from "../../molecules/table"
|
import Table from "../../molecules/table"
|
||||||
|
|
||||||
const useGiftCardTableColums = () => {
|
const useGiftCardTableColums = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const columns = useMemo(
|
const columns = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
Header: <div className="pl-2">Code</div>,
|
Header: <div className="pl-2">{t("gift-card-table-code", "Code")}</div>,
|
||||||
accessor: "code",
|
accessor: "code",
|
||||||
Cell: ({ cell: { value }, index }) => (
|
Cell: ({ cell: { value }, index }) => (
|
||||||
<Table.Cell
|
<Table.Cell
|
||||||
@@ -21,7 +23,7 @@ const useGiftCardTableColums = () => {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Order",
|
Header: t("gift-card-table-order", "Order"),
|
||||||
accessor: "order",
|
accessor: "order",
|
||||||
Cell: ({ cell: { value }, index }) => (
|
Cell: ({ cell: { value }, index }) => (
|
||||||
<Table.Cell
|
<Table.Cell
|
||||||
@@ -37,7 +39,7 @@ const useGiftCardTableColums = () => {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Original Amount",
|
Header: t("gift-card-table-original-amount", "Original Amount"),
|
||||||
accessor: "value",
|
accessor: "value",
|
||||||
Cell: ({ row, cell: { value }, index }) => (
|
Cell: ({ row, cell: { value }, index }) => (
|
||||||
<Table.Cell key={index}>
|
<Table.Cell key={index}>
|
||||||
@@ -56,7 +58,7 @@ const useGiftCardTableColums = () => {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Balance",
|
Header: t("gift-card-table-balance", "Balance"),
|
||||||
accessor: "balance",
|
accessor: "balance",
|
||||||
Cell: ({ row, cell: { value }, index }) => (
|
Cell: ({ row, cell: { value }, index }) => (
|
||||||
<Table.Cell key={index}>
|
<Table.Cell key={index}>
|
||||||
@@ -69,11 +71,19 @@ const useGiftCardTableColums = () => {
|
|||||||
) : (
|
) : (
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<span>N / A</span>
|
<span>N / A</span>
|
||||||
<IconTooltip content={"Region has been deleted"} />
|
<IconTooltip
|
||||||
|
content={t(
|
||||||
|
"gift-card-table-region-has-been-deleted",
|
||||||
|
"Region has been deleted"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<StatusIndicator title="None" variant="danger" />
|
<StatusIndicator
|
||||||
|
title={t("gift-card-table-none", "None")}
|
||||||
|
variant="danger"
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
),
|
),
|
||||||
@@ -81,7 +91,7 @@ const useGiftCardTableColums = () => {
|
|||||||
{
|
{
|
||||||
Header: () => (
|
Header: () => (
|
||||||
<div className="rounded-rounded flex w-full justify-end pr-2">
|
<div className="rounded-rounded flex w-full justify-end pr-2">
|
||||||
Created
|
{t("gift-card-table-created", "Created")}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
accessor: "created_at",
|
accessor: "created_at",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useMemo } from "react"
|
import { useMemo } from "react"
|
||||||
import { Controller } from "react-hook-form"
|
import { Controller } from "react-hook-form"
|
||||||
import { Column, useTable } from "react-table"
|
import { Column, useTable } from "react-table"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import { FormImage } from "../../../types/shared"
|
import { FormImage } from "../../../types/shared"
|
||||||
import { NestedForm } from "../../../utils/nested-form"
|
import { NestedForm } from "../../../utils/nested-form"
|
||||||
import Button from "../../fundamentals/button"
|
import Button from "../../fundamentals/button"
|
||||||
@@ -18,6 +19,7 @@ type ImageTableProps = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ImageTable = ({ data, form, onDelete }: ImageTableProps) => {
|
const ImageTable = ({ data, form, onDelete }: ImageTableProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const { control, register, path } = form
|
const { control, register, path } = form
|
||||||
|
|
||||||
const columns = useMemo<
|
const columns = useMemo<
|
||||||
@@ -46,7 +48,7 @@ const ImageTable = ({ data, form, onDelete }: ImageTableProps) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: () => <span>File name</span>,
|
Header: () => <span>{t("image-table-file-name", "File name")}</span>,
|
||||||
accessor: "nativeFile",
|
accessor: "nativeFile",
|
||||||
Cell: ({ cell }) => {
|
Cell: ({ cell }) => {
|
||||||
return (
|
return (
|
||||||
@@ -72,8 +74,13 @@ const ImageTable = ({ data, form, onDelete }: ImageTableProps) => {
|
|||||||
{
|
{
|
||||||
Header: () => (
|
Header: () => (
|
||||||
<div className="flex items-center justify-center gap-x-[6px]">
|
<div className="flex items-center justify-center gap-x-[6px]">
|
||||||
<span>Thumbnail</span>
|
<span>{t("image-table-thumbnail", "Thumbnail")}</span>
|
||||||
<IconTooltip content="Select which image you want to use as the thumbnail for this product" />
|
<IconTooltip
|
||||||
|
content={t(
|
||||||
|
"image-table-select-thumbnail-image-for-product",
|
||||||
|
"Select which image you want to use as the thumbnail for this product"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
id: "thumbnail",
|
id: "thumbnail",
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
useAdminVariant,
|
useAdminVariant,
|
||||||
} from "medusa-react"
|
} from "medusa-react"
|
||||||
import { useLocation, useNavigate } from "react-router-dom"
|
import { useLocation, useNavigate } from "react-router-dom"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
import Button from "../../fundamentals/button"
|
import Button from "../../fundamentals/button"
|
||||||
import ImagePlaceholder from "../../fundamentals/image-placeholder"
|
import ImagePlaceholder from "../../fundamentals/image-placeholder"
|
||||||
@@ -84,6 +85,7 @@ const InventoryTable: React.FC<InventoryTableProps> = () => {
|
|||||||
const { store } = useAdminStore()
|
const { store } = useAdminStore()
|
||||||
|
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const { stock_locations, isLoading: locationsLoading } =
|
const { stock_locations, isLoading: locationsLoading } =
|
||||||
useAdminStockLocations()
|
useAdminStockLocations()
|
||||||
@@ -217,7 +219,7 @@ const InventoryTable: React.FC<InventoryTableProps> = () => {
|
|||||||
count: count || 0,
|
count: count || 0,
|
||||||
offset: offs,
|
offset: offs,
|
||||||
pageSize: offs + rows.length,
|
pageSize: offs + rows.length,
|
||||||
title: "Inventory Items",
|
title: t("inventory-table-inventory-items", "Inventory Items"),
|
||||||
currentPage: pageIndex + 1,
|
currentPage: pageIndex + 1,
|
||||||
pageCount: pageCount,
|
pageCount: pageCount,
|
||||||
nextPage: handleNext,
|
nextPage: handleNext,
|
||||||
@@ -300,6 +302,7 @@ const InventoryRow = ({
|
|||||||
const inventory = row.original
|
const inventory = row.original
|
||||||
|
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
state: isShowingAdjustAvailabilityModal,
|
state: isShowingAdjustAvailabilityModal,
|
||||||
@@ -312,7 +315,10 @@ const InventoryRow = ({
|
|||||||
|
|
||||||
const actions = [
|
const actions = [
|
||||||
{
|
{
|
||||||
label: "Adjust Availability",
|
label: t(
|
||||||
|
"inventory-table-actions-adjust-availability",
|
||||||
|
"Adjust Availability"
|
||||||
|
),
|
||||||
onClick: showAdjustAvailabilityModal,
|
onClick: showAdjustAvailabilityModal,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -320,7 +326,7 @@ const InventoryRow = ({
|
|||||||
if (productId) {
|
if (productId) {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
label: "View Product",
|
label: t("inventory-table-view-product", "View Product"),
|
||||||
onClick: () => navigate(`/a/products/${productId}`),
|
onClick: () => navigate(`/a/products/${productId}`),
|
||||||
},
|
},
|
||||||
...actions,
|
...actions,
|
||||||
@@ -370,6 +376,7 @@ const AdjustAvailabilityModal = ({
|
|||||||
const inventoryVariantId = inventory.variants?.[0]?.id
|
const inventoryVariantId = inventory.variants?.[0]?.id
|
||||||
const locationLevel = inventory.location_levels?.[0]
|
const locationLevel = inventory.location_levels?.[0]
|
||||||
|
|
||||||
|
const { t } = useTranslation()
|
||||||
const { variant, isLoading } = useAdminVariant(inventoryVariantId || "")
|
const { variant, isLoading } = useAdminVariant(inventoryVariantId || "")
|
||||||
const {
|
const {
|
||||||
mutate: updateLocationLevelForInventoryItem,
|
mutate: updateLocationLevelForInventoryItem,
|
||||||
@@ -396,8 +403,11 @@ const AdjustAvailabilityModal = ({
|
|||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
notification(
|
notification(
|
||||||
"Success",
|
t("inventory-table-success", "Success"),
|
||||||
"Inventory item updated successfully",
|
t(
|
||||||
|
"inventory-table-inventory-item-updated-successfully",
|
||||||
|
"Inventory item updated successfully"
|
||||||
|
),
|
||||||
"success"
|
"success"
|
||||||
)
|
)
|
||||||
handleClose()
|
handleClose()
|
||||||
@@ -412,7 +422,9 @@ const AdjustAvailabilityModal = ({
|
|||||||
<Modal handleClose={handleClose}>
|
<Modal handleClose={handleClose}>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
<Modal.Header handleClose={handleClose}>
|
<Modal.Header handleClose={handleClose}>
|
||||||
<h1 className="inter-large-semibold">Adjust availability</h1>
|
<h1 className="inter-large-semibold">
|
||||||
|
{t("inventory-table-adjust-availability", "Adjust availability")}
|
||||||
|
</h1>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<Modal.Content>
|
<Modal.Content>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
@@ -468,7 +480,7 @@ const AdjustAvailabilityModal = ({
|
|||||||
className="border"
|
className="border"
|
||||||
onClick={handleClose}
|
onClick={handleClose}
|
||||||
>
|
>
|
||||||
Cancel
|
{t("inventory-table-cancel", "Cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
@@ -477,7 +489,7 @@ const AdjustAvailabilityModal = ({
|
|||||||
loading={isSubmitting}
|
loading={isSubmitting}
|
||||||
onClick={onSubmit}
|
onClick={onSubmit}
|
||||||
>
|
>
|
||||||
Save and close
|
{t("inventory-table-save-and-close", "Save and close")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
|
|||||||
+7
-5
@@ -6,16 +6,18 @@ import { InventoryLevelDTO } from "@medusajs/types"
|
|||||||
import Tooltip from "../../atoms/tooltip"
|
import Tooltip from "../../atoms/tooltip"
|
||||||
import { useMemo } from "react"
|
import { useMemo } from "react"
|
||||||
import { useNavigate } from "react-router-dom"
|
import { useNavigate } from "react-router-dom"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
const useInventoryTableColumn = ({
|
const useInventoryTableColumn = ({
|
||||||
location_id,
|
location_id,
|
||||||
}: {
|
}: {
|
||||||
location_id: string
|
location_id: string
|
||||||
}): [Column<DecoratedInventoryItemDTO>[]] => {
|
}): [Column<DecoratedInventoryItemDTO>[]] => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const columns = useMemo(
|
const columns = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
Header: "Item",
|
Header: t("inventory-table-item", "Item"),
|
||||||
accessor: "title",
|
accessor: "title",
|
||||||
Cell: ({ row: { original } }) => {
|
Cell: ({ row: { original } }) => {
|
||||||
return (
|
return (
|
||||||
@@ -36,18 +38,18 @@ const useInventoryTableColumn = ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Variant",
|
Header: t("inventory-table-variant", "Variant"),
|
||||||
Cell: ({ row: { original } }) => {
|
Cell: ({ row: { original } }) => {
|
||||||
return <div>{original?.variants[0]?.title || "-"}</div>
|
return <div>{original?.variants[0]?.title || "-"}</div>
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "SKU",
|
Header: t("inventory-table-sku", "SKU"),
|
||||||
accessor: "sku",
|
accessor: "sku",
|
||||||
Cell: ({ cell: { value } }) => value,
|
Cell: ({ cell: { value } }) => value,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Reserved",
|
Header: t("inventory-table-reserved", "Reserved"),
|
||||||
accessor: "reserved_quantity",
|
accessor: "reserved_quantity",
|
||||||
Cell: ({ row: { original } }) => {
|
Cell: ({ row: { original } }) => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
@@ -85,7 +87,7 @@ const useInventoryTableColumn = ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "In stock",
|
Header: t("inventory-table-in-stock", "In stock"),
|
||||||
accessor: "stocked_quantity",
|
accessor: "stocked_quantity",
|
||||||
Cell: ({ row: { original } }) => {
|
Cell: ({ row: { original } }) => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import clsx from "clsx"
|
import clsx from "clsx"
|
||||||
import { useAdminRegions, useAdminSalesChannels } from "medusa-react"
|
import { useAdminRegions, useAdminSalesChannels } from "medusa-react"
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import FilterDropdownContainer from "../../../components/molecules/filter-dropdown/container"
|
import FilterDropdownContainer from "../../../components/molecules/filter-dropdown/container"
|
||||||
import FilterDropdownItem from "../../../components/molecules/filter-dropdown/item"
|
import FilterDropdownItem from "../../../components/molecules/filter-dropdown/item"
|
||||||
import SaveFilterItem from "../../../components/molecules/filter-dropdown/save-field"
|
import SaveFilterItem from "../../../components/molecules/filter-dropdown/save-field"
|
||||||
import TabFilter from "../../../components/molecules/filter-tab"
|
import TabFilter from "../../../components/molecules/filter-tab"
|
||||||
import PlusIcon from "../../fundamentals/icons/plus-icon"
|
import PlusIcon from "../../fundamentals/icons/plus-icon"
|
||||||
import FeatureToggle from "../../fundamentals/feature-toggle"
|
|
||||||
import { useFeatureFlag } from "../../../providers/feature-flag-provider"
|
import { useFeatureFlag } from "../../../providers/feature-flag-provider"
|
||||||
|
|
||||||
const REGION_PAGE_SIZE = 10
|
const REGION_PAGE_SIZE = 10
|
||||||
@@ -58,6 +58,7 @@ const OrderFilters = ({
|
|||||||
submitFilters,
|
submitFilters,
|
||||||
clearFilters,
|
clearFilters,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const [tempState, setTempState] = useState(filters)
|
const [tempState, setTempState] = useState(filters)
|
||||||
const [name, setName] = useState("")
|
const [name, setName] = useState("")
|
||||||
|
|
||||||
@@ -154,7 +155,7 @@ const OrderFilters = ({
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="rounded-rounded bg-grey-5 border-grey-20 inter-small-semibold flex h-6 items-center border px-2">
|
<div className="rounded-rounded bg-grey-5 border-grey-20 inter-small-semibold flex h-6 items-center border px-2">
|
||||||
Filters
|
{t("order-filter-dropdown-filters", "Filters")}
|
||||||
<div className="text-grey-40 ml-1 flex items-center rounded">
|
<div className="text-grey-40 ml-1 flex items-center rounded">
|
||||||
<span className="text-violet-60 inter-small-semibold">
|
<span className="text-violet-60 inter-small-semibold">
|
||||||
{numberOfFilters ? numberOfFilters : "0"}
|
{numberOfFilters ? numberOfFilters : "0"}
|
||||||
@@ -168,28 +169,34 @@ const OrderFilters = ({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<FilterDropdownItem
|
<FilterDropdownItem
|
||||||
filterTitle="Status"
|
filterTitle={t("order-filter-dropdown-status", "Status")}
|
||||||
options={statusFilters}
|
options={statusFilters}
|
||||||
filters={tempState.status.filter}
|
filters={tempState.status.filter}
|
||||||
open={tempState.status.open}
|
open={tempState.status.open}
|
||||||
setFilter={(val) => setSingleFilter("status", val)}
|
setFilter={(val) => setSingleFilter("status", val)}
|
||||||
/>
|
/>
|
||||||
<FilterDropdownItem
|
<FilterDropdownItem
|
||||||
filterTitle="Payment Status"
|
filterTitle={t(
|
||||||
|
"order-filter-dropdown-payment-status",
|
||||||
|
"Payment Status"
|
||||||
|
)}
|
||||||
options={paymentFilters}
|
options={paymentFilters}
|
||||||
filters={tempState.payment.filter}
|
filters={tempState.payment.filter}
|
||||||
open={tempState.payment.open}
|
open={tempState.payment.open}
|
||||||
setFilter={(val) => setSingleFilter("payment", val)}
|
setFilter={(val) => setSingleFilter("payment", val)}
|
||||||
/>
|
/>
|
||||||
<FilterDropdownItem
|
<FilterDropdownItem
|
||||||
filterTitle="Fulfillment Status"
|
filterTitle={t(
|
||||||
|
"order-filter-dropdown-fulfillment-status",
|
||||||
|
"Fulfillment Status"
|
||||||
|
)}
|
||||||
options={fulfillmentFilters}
|
options={fulfillmentFilters}
|
||||||
filters={tempState.fulfillment.filter}
|
filters={tempState.fulfillment.filter}
|
||||||
open={tempState.fulfillment.open}
|
open={tempState.fulfillment.open}
|
||||||
setFilter={(val) => setSingleFilter("fulfillment", val)}
|
setFilter={(val) => setSingleFilter("fulfillment", val)}
|
||||||
/>
|
/>
|
||||||
<FilterDropdownItem
|
<FilterDropdownItem
|
||||||
filterTitle="Regions"
|
filterTitle={t("order-filter-dropdown-regions", "Regions")}
|
||||||
options={
|
options={
|
||||||
regions?.map((region) => ({
|
regions?.map((region) => ({
|
||||||
value: region.id,
|
value: region.id,
|
||||||
@@ -209,7 +216,10 @@ const OrderFilters = ({
|
|||||||
/>
|
/>
|
||||||
{isSalesChannelsEnabled && (
|
{isSalesChannelsEnabled && (
|
||||||
<FilterDropdownItem
|
<FilterDropdownItem
|
||||||
filterTitle="Sales Channel"
|
filterTitle={t(
|
||||||
|
"order-filter-dropdown-sales-channel",
|
||||||
|
"Sales Channel"
|
||||||
|
)}
|
||||||
options={
|
options={
|
||||||
sales_channels?.map((salesChannel) => ({
|
sales_channels?.map((salesChannel) => ({
|
||||||
value: salesChannel.id,
|
value: salesChannel.id,
|
||||||
@@ -223,7 +233,7 @@ const OrderFilters = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<FilterDropdownItem
|
<FilterDropdownItem
|
||||||
filterTitle="Date"
|
filterTitle={t("order-filter-dropdown-date", "Date")}
|
||||||
options={dateFilters}
|
options={dateFilters}
|
||||||
filters={tempState.date.filter}
|
filters={tempState.date.filter}
|
||||||
open={tempState.date.open}
|
open={tempState.date.open}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import moment from "moment"
|
import moment from "moment"
|
||||||
import { useMemo } from "react"
|
import { useMemo } from "react"
|
||||||
import ReactCountryFlag from "react-country-flag"
|
import ReactCountryFlag from "react-country-flag"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import { getColor } from "../../../utils/color"
|
import { getColor } from "../../../utils/color"
|
||||||
import { isoAlpha2Countries } from "../../../utils/countries"
|
import { isoAlpha2Countries } from "../../../utils/countries"
|
||||||
import { formatAmountWithSymbol } from "../../../utils/prices"
|
import { formatAmountWithSymbol } from "../../../utils/prices"
|
||||||
@@ -9,32 +10,52 @@ import StatusDot from "../../fundamentals/status-indicator"
|
|||||||
import CustomerAvatarItem from "../../molecules/customer-avatar-item"
|
import CustomerAvatarItem from "../../molecules/customer-avatar-item"
|
||||||
|
|
||||||
const useOrderTableColums = () => {
|
const useOrderTableColums = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const decideStatus = (status) => {
|
const decideStatus = (status) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case "captured":
|
case "captured":
|
||||||
return <StatusDot variant="success" title={"Paid"} />
|
return (
|
||||||
|
<StatusDot variant="success" title={t("order-table-paid", "Paid")} />
|
||||||
|
)
|
||||||
case "awaiting":
|
case "awaiting":
|
||||||
return <StatusDot variant="default" title={"Awaiting"} />
|
return (
|
||||||
|
<StatusDot
|
||||||
|
variant="default"
|
||||||
|
title={t("order-table-awaiting", "Awaiting")}
|
||||||
|
/>
|
||||||
|
)
|
||||||
case "requires_action":
|
case "requires_action":
|
||||||
return <StatusDot variant="danger" title={"Requires action"} />
|
return (
|
||||||
|
<StatusDot
|
||||||
|
variant="danger"
|
||||||
|
title={t("order-table-requires-action", "Requires action")}
|
||||||
|
/>
|
||||||
|
)
|
||||||
case "canceled":
|
case "canceled":
|
||||||
return <StatusDot variant="warning" title={"Canceled"} />
|
return (
|
||||||
|
<StatusDot
|
||||||
|
variant="warning"
|
||||||
|
title={t("order-table-canceled", "Canceled")}
|
||||||
|
/>
|
||||||
|
)
|
||||||
default:
|
default:
|
||||||
return <StatusDot variant="primary" title={"N/A"} />
|
return (
|
||||||
|
<StatusDot variant="primary" title={t("order-table-n-a", "N/A")} />
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const columns = useMemo(
|
const columns = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
Header: <div className="pl-2">Order</div>,
|
Header: <div className="pl-2">{t("order-table-order", "Order")}</div>,
|
||||||
accessor: "display_id",
|
accessor: "display_id",
|
||||||
Cell: ({ cell: { value } }) => (
|
Cell: ({ cell: { value } }) => (
|
||||||
<p className="text-grey-90 group-hover:text-violet-60 min-w-[100px] pl-2">{`#${value}`}</p>
|
<p className="text-grey-90 group-hover:text-violet-60 min-w-[100px] pl-2">{`#${value}`}</p>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Date added",
|
Header: t("order-table-date-added", "Date added"),
|
||||||
accessor: "created_at",
|
accessor: "created_at",
|
||||||
Cell: ({ cell: { value } }) => (
|
Cell: ({ cell: { value } }) => (
|
||||||
<div>
|
<div>
|
||||||
@@ -45,7 +66,7 @@ const useOrderTableColums = () => {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Customer",
|
Header: t("order-table-customer", "Customer"),
|
||||||
accessor: "customer",
|
accessor: "customer",
|
||||||
Cell: ({ row, cell: { value } }) => (
|
Cell: ({ row, cell: { value } }) => (
|
||||||
<div>
|
<div>
|
||||||
@@ -64,22 +85,24 @@ const useOrderTableColums = () => {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Fulfillment",
|
Header: t("order-table-fulfillment", "Fulfillment"),
|
||||||
accessor: "fulfillment_status",
|
accessor: "fulfillment_status",
|
||||||
Cell: ({ cell: { value } }) => value,
|
Cell: ({ cell: { value } }) => value,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Payment status",
|
Header: t("order-table-payment-status", "Payment status"),
|
||||||
accessor: "payment_status",
|
accessor: "payment_status",
|
||||||
Cell: ({ cell: { value } }) => decideStatus(value),
|
Cell: ({ cell: { value } }) => decideStatus(value),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Sales Channel",
|
Header: t("order-table-sales-channel", "Sales Channel"),
|
||||||
accessor: "sales_channel",
|
accessor: "sales_channel",
|
||||||
Cell: ({ cell: { value } }) => value?.name ?? "N/A",
|
Cell: ({ cell: { value } }) => value?.name ?? "N/A",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: () => <div className="text-right">Total</div>,
|
Header: () => (
|
||||||
|
<div className="text-right">{t("order-table-total", "Total")}</div>
|
||||||
|
),
|
||||||
accessor: "total",
|
accessor: "total",
|
||||||
Cell: ({ row, cell: { value } }) => (
|
Cell: ({ row, cell: { value } }) => (
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { omit } from "lodash"
|
import { omit } from "lodash"
|
||||||
import qs from "qs"
|
import qs from "qs"
|
||||||
import { useMemo, useReducer, useState } from "react"
|
import { useMemo, useReducer, useState } from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import { relativeDateFormatToTimestamp } from "../../../utils/time"
|
import { relativeDateFormatToTimestamp } from "../../../utils/time"
|
||||||
|
|
||||||
type OrderDateFilter = null | {
|
type OrderDateFilter = null | {
|
||||||
@@ -156,6 +157,8 @@ export const useOrderFilters = (
|
|||||||
existing?: string,
|
existing?: string,
|
||||||
defaultFilters: OrderDefaultFilters | null = null
|
defaultFilters: OrderDefaultFilters | null = null
|
||||||
) => {
|
) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
if (existing && existing[0] === "?") {
|
if (existing && existing[0] === "?") {
|
||||||
existing = existing.substring(1)
|
existing = existing.substring(1)
|
||||||
}
|
}
|
||||||
@@ -365,11 +368,11 @@ export const useOrderFilters = (
|
|||||||
const availableTabs = useMemo(() => {
|
const availableTabs = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
label: "Complete",
|
label: t("order-table-filters-complete", "Complete"),
|
||||||
value: "complete",
|
value: "complete",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Incomplete",
|
label: t("order-table-filters-incomplete", "Incomplete"),
|
||||||
value: "incomplete",
|
value: "incomplete",
|
||||||
},
|
},
|
||||||
...tabs,
|
...tabs,
|
||||||
@@ -423,7 +426,6 @@ export const useOrderFilters = (
|
|||||||
const clean = omit(repObj, ["limit", "offset"])
|
const clean = omit(repObj, ["limit", "offset"])
|
||||||
const repString = qs.stringify(clean, { skipNulls: true })
|
const repString = qs.stringify(clean, { skipNulls: true })
|
||||||
|
|
||||||
|
|
||||||
const storedString = localStorage.getItem("orders::filters")
|
const storedString = localStorage.getItem("orders::filters")
|
||||||
|
|
||||||
let existing: null | object = null
|
let existing: null | object = null
|
||||||
|
|||||||
+5
-3
@@ -1,5 +1,6 @@
|
|||||||
import clsx from "clsx"
|
import clsx from "clsx"
|
||||||
import { useEffect, useMemo, useState } from "react"
|
import { useEffect, useMemo, useState } from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import PlusIcon from "../../../components/fundamentals/icons/plus-icon"
|
import PlusIcon from "../../../components/fundamentals/icons/plus-icon"
|
||||||
import FilterDropdownContainer from "../../../components/molecules/filter-dropdown/container"
|
import FilterDropdownContainer from "../../../components/molecules/filter-dropdown/container"
|
||||||
import FilterDropdownItem from "../../../components/molecules/filter-dropdown/item"
|
import FilterDropdownItem from "../../../components/molecules/filter-dropdown/item"
|
||||||
@@ -19,6 +20,7 @@ const PriceListsFilter = ({
|
|||||||
onRemoveTab,
|
onRemoveTab,
|
||||||
onSaveTab,
|
onSaveTab,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const [tempState, setTempState] = useState(filters)
|
const [tempState, setTempState] = useState(filters)
|
||||||
const [name, setName] = useState("")
|
const [name, setName] = useState("")
|
||||||
|
|
||||||
@@ -82,7 +84,7 @@ const PriceListsFilter = ({
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="rounded-rounded bg-grey-5 border-grey-20 inter-small-semibold flex h-6 items-center border px-2">
|
<div className="rounded-rounded bg-grey-5 border-grey-20 inter-small-semibold flex h-6 items-center border px-2">
|
||||||
Filters
|
{t("price-list-table-filters", "Filters")}
|
||||||
<div className="text-grey-40 ml-1 flex items-center rounded">
|
<div className="text-grey-40 ml-1 flex items-center rounded">
|
||||||
<span className="text-violet-60 inter-small-semibold">
|
<span className="text-violet-60 inter-small-semibold">
|
||||||
{numberOfFilters ? numberOfFilters : "0"}
|
{numberOfFilters ? numberOfFilters : "0"}
|
||||||
@@ -96,14 +98,14 @@ const PriceListsFilter = ({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<FilterDropdownItem
|
<FilterDropdownItem
|
||||||
filterTitle="Status"
|
filterTitle={t("price-list-table-status", "Status")}
|
||||||
options={statusFilters}
|
options={statusFilters}
|
||||||
filters={tempState.status.filter}
|
filters={tempState.status.filter}
|
||||||
open={tempState.status.open}
|
open={tempState.status.open}
|
||||||
setFilter={(v) => setSingleFilter("status", v)}
|
setFilter={(v) => setSingleFilter("status", v)}
|
||||||
/>
|
/>
|
||||||
<FilterDropdownItem
|
<FilterDropdownItem
|
||||||
filterTitle="Type"
|
filterTitle={t("price-list-table-type", "Type")}
|
||||||
options={typeFilters}
|
options={typeFilters}
|
||||||
filters={tempState.type.filter}
|
filters={tempState.type.filter}
|
||||||
open={tempState.type.open}
|
open={tempState.type.open}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
UseSortByColumnProps,
|
UseSortByColumnProps,
|
||||||
useTable,
|
useTable,
|
||||||
} from "react-table"
|
} from "react-table"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import { useDebounce } from "../../../hooks/use-debounce"
|
import { useDebounce } from "../../../hooks/use-debounce"
|
||||||
import Table, { TableProps } from "../../molecules/table"
|
import Table, { TableProps } from "../../molecules/table"
|
||||||
import TableContainer from "../../organisms/table-container"
|
import TableContainer from "../../organisms/table-container"
|
||||||
@@ -121,6 +122,7 @@ export function PriceListTable(props: PriceListTableProps) {
|
|||||||
autoResetPage: false,
|
autoResetPage: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { t } = useTranslation()
|
||||||
const table = useTable(tableConfig, useSortBy, usePagination, useRowSelect)
|
const table = useTable(tableConfig, useSortBy, usePagination, useRowSelect)
|
||||||
|
|
||||||
// ********* HANDLERS *********
|
// ********* HANDLERS *********
|
||||||
@@ -164,7 +166,7 @@ export function PriceListTable(props: PriceListTableProps) {
|
|||||||
count: count!,
|
count: count!,
|
||||||
offset: queryObject.offset,
|
offset: queryObject.offset,
|
||||||
pageSize: queryObject.offset + table.rows.length,
|
pageSize: queryObject.offset + table.rows.length,
|
||||||
title: "Price Lists",
|
title: t("price-list-table-price-lists", "Price Lists"),
|
||||||
currentPage: table.state.pageIndex + 1,
|
currentPage: table.state.pageIndex + 1,
|
||||||
pageCount: table.pageCount,
|
pageCount: table.pageCount,
|
||||||
nextPage: handleNext,
|
nextPage: handleNext,
|
||||||
|
|||||||
+15
-2
@@ -1,5 +1,6 @@
|
|||||||
import { useAdminCreatePriceList } from "medusa-react"
|
import { useAdminCreatePriceList } from "medusa-react"
|
||||||
import { useNavigate } from "react-router-dom"
|
import { useNavigate } from "react-router-dom"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import useNotification from "../../../hooks/use-notification"
|
import useNotification from "../../../hooks/use-notification"
|
||||||
import { getErrorMessage } from "../../../utils/error-messages"
|
import { getErrorMessage } from "../../../utils/error-messages"
|
||||||
|
|
||||||
@@ -7,6 +8,7 @@ const useCopyPriceList = () => {
|
|||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const notification = useNotification()
|
const notification = useNotification()
|
||||||
const createPriceList = useAdminCreatePriceList()
|
const createPriceList = useAdminCreatePriceList()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const handleCopyPriceList = async (priceList) => {
|
const handleCopyPriceList = async (priceList) => {
|
||||||
const copy: any = {
|
const copy: any = {
|
||||||
@@ -44,9 +46,20 @@ const useCopyPriceList = () => {
|
|||||||
try {
|
try {
|
||||||
const data = await createPriceList.mutateAsync(copy)
|
const data = await createPriceList.mutateAsync(copy)
|
||||||
navigate(`/a/pricing/${data.price_list.id}`)
|
navigate(`/a/pricing/${data.price_list.id}`)
|
||||||
notification("Success", "Successfully copied price list", "success")
|
notification(
|
||||||
|
t("price-list-table-success", "Success"),
|
||||||
|
t(
|
||||||
|
"price-list-table-successfully-copied-price-list",
|
||||||
|
"Successfully copied price list"
|
||||||
|
),
|
||||||
|
"success"
|
||||||
|
)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
notification("Error", getErrorMessage(err), "error")
|
notification(
|
||||||
|
t("price-list-table-error", "Error"),
|
||||||
|
getErrorMessage(err),
|
||||||
|
"error"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+32
-11
@@ -1,4 +1,5 @@
|
|||||||
import { useAdminDeletePriceList, useAdminUpdatePriceList } from "medusa-react"
|
import { useAdminDeletePriceList, useAdminUpdatePriceList } from "medusa-react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import useImperativeDialog from "../../../hooks/use-imperative-dialog"
|
import useImperativeDialog from "../../../hooks/use-imperative-dialog"
|
||||||
import useNotification from "../../../hooks/use-notification"
|
import useNotification from "../../../hooks/use-notification"
|
||||||
import { getErrorMessage } from "../../../utils/error-messages"
|
import { getErrorMessage } from "../../../utils/error-messages"
|
||||||
@@ -9,6 +10,7 @@ import { isActive } from "./utils"
|
|||||||
import PublishIcon from "../../fundamentals/icons/publish-icon"
|
import PublishIcon from "../../fundamentals/icons/publish-icon"
|
||||||
|
|
||||||
const usePriceListActions = (priceList) => {
|
const usePriceListActions = (priceList) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const dialog = useImperativeDialog()
|
const dialog = useImperativeDialog()
|
||||||
const notification = useNotification()
|
const notification = useNotification()
|
||||||
const updatePrice = useAdminUpdatePriceList(priceList?.id)
|
const updatePrice = useAdminUpdatePriceList(priceList?.id)
|
||||||
@@ -16,19 +18,30 @@ const usePriceListActions = (priceList) => {
|
|||||||
|
|
||||||
const onDelete = async () => {
|
const onDelete = async () => {
|
||||||
const shouldDelete = await dialog({
|
const shouldDelete = await dialog({
|
||||||
heading: "Delete Price List",
|
heading: t("price-list-table-delete-price-list", "Delete Price List"),
|
||||||
text: "Are you sure you want to delete this price list?",
|
text: t(
|
||||||
|
"price-list-table-confirm-delete",
|
||||||
|
"Are you sure you want to delete this price list?"
|
||||||
|
),
|
||||||
})
|
})
|
||||||
if (shouldDelete) {
|
if (shouldDelete) {
|
||||||
deletePrice.mutate(undefined, {
|
deletePrice.mutate(undefined, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
notification(
|
notification(
|
||||||
"Success",
|
t("price-list-table-success", "Success"),
|
||||||
"Successfully deleted the price list",
|
t(
|
||||||
|
"price-list-table-successfully-deleted-the-price-list",
|
||||||
|
"Successfully deleted the price list"
|
||||||
|
),
|
||||||
"success"
|
"success"
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onError: (err) => notification("Error", getErrorMessage(err), "error"),
|
onError: (err) =>
|
||||||
|
notification(
|
||||||
|
t("price-list-table-error", "Error"),
|
||||||
|
getErrorMessage(err),
|
||||||
|
"error"
|
||||||
|
),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,10 +54,16 @@ const usePriceListActions = (priceList) => {
|
|||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
notification(
|
notification(
|
||||||
"Success",
|
t("price-list-table-success", "Success"),
|
||||||
`Successfully ${
|
isActive(priceList)
|
||||||
isActive(priceList) ? "unpublished" : "published"
|
? t(
|
||||||
} price list`,
|
"price-list-table-successfully-unpublished-price-list",
|
||||||
|
"Successfully unpublished price list"
|
||||||
|
)
|
||||||
|
: t(
|
||||||
|
"price-list-table-successfully-published-price-list",
|
||||||
|
"Successfully published price list"
|
||||||
|
),
|
||||||
"success"
|
"success"
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -54,7 +73,9 @@ const usePriceListActions = (priceList) => {
|
|||||||
|
|
||||||
const getActions = (): ActionType[] => [
|
const getActions = (): ActionType[] => [
|
||||||
{
|
{
|
||||||
label: isActive(priceList) ? "Unpublish" : "Publish",
|
label: isActive(priceList)
|
||||||
|
? t("price-list-table-unpublish", "Unpublish")
|
||||||
|
: t("price-list-table-publish", "Publish"),
|
||||||
onClick: onUpdate,
|
onClick: onUpdate,
|
||||||
icon: isActive(priceList) ? (
|
icon: isActive(priceList) ? (
|
||||||
<UnpublishIcon size={20} />
|
<UnpublishIcon size={20} />
|
||||||
@@ -63,7 +84,7 @@ const usePriceListActions = (priceList) => {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Delete",
|
label: t("price-list-table-delete", "Delete"),
|
||||||
onClick: onDelete,
|
onClick: onDelete,
|
||||||
icon: <TrashIcon size={20} />,
|
icon: <TrashIcon size={20} />,
|
||||||
variant: "danger",
|
variant: "danger",
|
||||||
|
|||||||
+14
-5
@@ -2,16 +2,18 @@ import { PriceList } from "@medusajs/medusa"
|
|||||||
import { isArray } from "lodash"
|
import { isArray } from "lodash"
|
||||||
import { useMemo } from "react"
|
import { useMemo } from "react"
|
||||||
import { Column } from "react-table"
|
import { Column } from "react-table"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import Actionables from "../../molecules/actionables"
|
import Actionables from "../../molecules/actionables"
|
||||||
import Table from "../../molecules/table"
|
import Table from "../../molecules/table"
|
||||||
import usePriceListActions from "./use-price-list-actions"
|
import usePriceListActions from "./use-price-list-actions"
|
||||||
import { formatPriceListGroups, getPriceListStatus } from "./utils"
|
import { formatPriceListGroups, getPriceListStatus } from "./utils"
|
||||||
|
|
||||||
export const usePriceListTableColumns = () => {
|
export const usePriceListTableColumns = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const columns = useMemo<Column<PriceList>[]>(
|
const columns = useMemo<Column<PriceList>[]>(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
Header: "Name",
|
Header: t("price-list-table-name", "Name"),
|
||||||
accessor: "name",
|
accessor: "name",
|
||||||
Cell: ({ cell: { value } }) => (
|
Cell: ({ cell: { value } }) => (
|
||||||
<Table.Cell>
|
<Table.Cell>
|
||||||
@@ -20,19 +22,19 @@ export const usePriceListTableColumns = () => {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Description",
|
Header: t("price-list-table-description", "Description"),
|
||||||
accessor: "description",
|
accessor: "description",
|
||||||
Cell: ({ cell: { value } }) => <Table.Cell>{value}</Table.Cell>,
|
Cell: ({ cell: { value } }) => <Table.Cell>{value}</Table.Cell>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Status",
|
Header: t("price-list-table-status", "Status"),
|
||||||
accessor: "status",
|
accessor: "status",
|
||||||
Cell: ({ row: { original } }) => (
|
Cell: ({ row: { original } }) => (
|
||||||
<Table.Cell>{getPriceListStatus(original)}</Table.Cell>
|
<Table.Cell>{getPriceListStatus(original)}</Table.Cell>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Groups",
|
Header: t("price-list-table-groups", "Groups"),
|
||||||
accessor: "customer_groups",
|
accessor: "customer_groups",
|
||||||
Cell: ({ cell: { value } }) => {
|
Cell: ({ cell: { value } }) => {
|
||||||
const groups: string[] = isArray(value)
|
const groups: string[] = isArray(value)
|
||||||
@@ -42,7 +44,14 @@ export const usePriceListTableColumns = () => {
|
|||||||
return (
|
return (
|
||||||
<Table.Cell>
|
<Table.Cell>
|
||||||
{group}
|
{group}
|
||||||
{other && <span className="text-grey-40"> + {other} more</span>}
|
{other && (
|
||||||
|
<span className="text-grey-40">
|
||||||
|
{" "}
|
||||||
|
{t("price-list-table-other-more", "+ {{other}} more", {
|
||||||
|
other,
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { MoneyAmount, ProductVariant } from "@medusajs/medusa"
|
import { MoneyAmount, ProductVariant } from "@medusajs/medusa"
|
||||||
import React from "react"
|
import React from "react"
|
||||||
import { Control, Controller, useForm, useWatch } from "react-hook-form"
|
import { Control, Controller, useForm, useWatch } from "react-hook-form"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import Checkbox, { CheckboxProps } from "../../atoms/checkbox"
|
import Checkbox, { CheckboxProps } from "../../atoms/checkbox"
|
||||||
import Button from "../../fundamentals/button"
|
import Button from "../../fundamentals/button"
|
||||||
import Modal from "../../molecules/modal"
|
import Modal from "../../molecules/modal"
|
||||||
@@ -35,6 +36,7 @@ const PriceOverrides = ({
|
|||||||
defaultVariant,
|
defaultVariant,
|
||||||
isEdit = false,
|
isEdit = false,
|
||||||
}: PriceOverridesType) => {
|
}: PriceOverridesType) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const [mode, setMode] = React.useState(MODES.SELECTED_ONLY)
|
const [mode, setMode] = React.useState(MODES.SELECTED_ONLY)
|
||||||
const {
|
const {
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
@@ -92,11 +94,17 @@ const PriceOverrides = ({
|
|||||||
>
|
>
|
||||||
<RadioGroup.SimpleItem
|
<RadioGroup.SimpleItem
|
||||||
value={MODES.SELECTED_ONLY}
|
value={MODES.SELECTED_ONLY}
|
||||||
label="Apply overrides on selected variants"
|
label={t(
|
||||||
|
"price-overrides-apply-overrides-on-selected-variants",
|
||||||
|
"Apply overrides on selected variants"
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
<RadioGroup.SimpleItem
|
<RadioGroup.SimpleItem
|
||||||
value={MODES.APPLY_ALL}
|
value={MODES.APPLY_ALL}
|
||||||
label="Apply on all variants"
|
label={t(
|
||||||
|
"price-overrides-apply-on-all-variants",
|
||||||
|
"Apply on all variants"
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</RadioGroup.Root>
|
</RadioGroup.Root>
|
||||||
)}
|
)}
|
||||||
@@ -120,7 +128,9 @@ const PriceOverrides = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="pt-8">
|
<div className="pt-8">
|
||||||
<h6 className="inter-base-semibold">Prices</h6>
|
<h6 className="inter-base-semibold">
|
||||||
|
{t("price-overrides-prices", "Prices")}
|
||||||
|
</h6>
|
||||||
<div className="pt-4">
|
<div className="pt-4">
|
||||||
{prices.map((price, idx) => (
|
{prices.map((price, idx) => (
|
||||||
<Controller
|
<Controller
|
||||||
@@ -154,7 +164,7 @@ const PriceOverrides = ({
|
|||||||
size="large"
|
size="large"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
>
|
>
|
||||||
Cancel
|
{t("price-overrides-cancel", "Cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="large"
|
size="large"
|
||||||
@@ -163,7 +173,7 @@ const PriceOverrides = ({
|
|||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
loading={isSubmitting}
|
loading={isSubmitting}
|
||||||
>
|
>
|
||||||
Save and close
|
{t("price-overrides-save-and-close", "Save and close")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import useToggleState from "../../../hooks/use-toggle-state"
|
import useToggleState from "../../../hooks/use-toggle-state"
|
||||||
import { currencies } from "../../../utils/currencies"
|
import { currencies } from "../../../utils/currencies"
|
||||||
import Button from "../../fundamentals/button"
|
import Button from "../../fundamentals/button"
|
||||||
@@ -6,6 +7,7 @@ import EyeOffIcon from "../../fundamentals/icons/eye-off-icon"
|
|||||||
import MedusaPriceInput from "../../organisms/medusa-price-input"
|
import MedusaPriceInput from "../../organisms/medusa-price-input"
|
||||||
|
|
||||||
const PriceAmount = ({ value, onChange }) => {
|
const PriceAmount = ({ value, onChange }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const { state: showRegions, toggle } = useToggleState()
|
const { state: showRegions, toggle } = useToggleState()
|
||||||
|
|
||||||
const currencyName = currencies[value.currency_code?.toUpperCase()]?.name
|
const currencyName = currencies[value.currency_code?.toUpperCase()]?.name
|
||||||
@@ -28,7 +30,7 @@ const PriceAmount = ({ value, onChange }) => {
|
|||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{showRegions ? <EyeOffIcon size={20} /> : <EyeIcon size={20} />}
|
{showRegions ? <EyeOffIcon size={20} /> : <EyeIcon size={20} />}
|
||||||
<span>Show regions</span>
|
<span>{t("price-overrides-show-regions", "Show regions")}</span>
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import qs from "qs"
|
|||||||
import React, { useEffect, useState } from "react"
|
import React, { useEffect, useState } from "react"
|
||||||
import { useLocation } from "react-router-dom"
|
import { useLocation } from "react-router-dom"
|
||||||
import { usePagination, useTable } from "react-table"
|
import { usePagination, useTable } from "react-table"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import ProductsFilter from "../../../domain/products/filter-dropdown"
|
import ProductsFilter from "../../../domain/products/filter-dropdown"
|
||||||
import { useAnalytics } from "../../../providers/analytics-provider"
|
import { useAnalytics } from "../../../providers/analytics-provider"
|
||||||
import { useFeatureFlag } from "../../../providers/feature-flag-provider"
|
import { useFeatureFlag } from "../../../providers/feature-flag-provider"
|
||||||
@@ -26,6 +27,7 @@ const defaultQueryProps = {
|
|||||||
|
|
||||||
const ProductTable = () => {
|
const ProductTable = () => {
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const { isFeatureEnabled } = useFeatureFlag()
|
const { isFeatureEnabled } = useFeatureFlag()
|
||||||
const { trackNumberOfProducts } = useAnalytics()
|
const { trackNumberOfProducts } = useAnalytics()
|
||||||
@@ -185,7 +187,7 @@ const ProductTable = () => {
|
|||||||
count: count!,
|
count: count!,
|
||||||
offset: offs,
|
offset: offs,
|
||||||
pageSize: offs + rows.length,
|
pageSize: offs + rows.length,
|
||||||
title: "Products",
|
title: t("product-table-products", "Products"),
|
||||||
currentPage: pageIndex + 1,
|
currentPage: pageIndex + 1,
|
||||||
pageCount: pageCount,
|
pageCount: pageCount,
|
||||||
nextPage: handleNext,
|
nextPage: handleNext,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { AdminPostProductsReq, Product } from "@medusajs/medusa"
|
|||||||
import { omit } from "lodash"
|
import { omit } from "lodash"
|
||||||
import { useAdminCreateProduct } from "medusa-react"
|
import { useAdminCreateProduct } from "medusa-react"
|
||||||
import { useNavigate } from "react-router-dom"
|
import { useNavigate } from "react-router-dom"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import useNotification from "../../../hooks/use-notification"
|
import useNotification from "../../../hooks/use-notification"
|
||||||
import { ProductStatus } from "../../../types/shared"
|
import { ProductStatus } from "../../../types/shared"
|
||||||
import { getErrorMessage } from "../../../utils/error-messages"
|
import { getErrorMessage } from "../../../utils/error-messages"
|
||||||
@@ -37,6 +38,7 @@ const useCopyProduct = () => {
|
|||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const notification = useNotification()
|
const notification = useNotification()
|
||||||
const { mutate } = useAdminCreateProduct()
|
const { mutate } = useAdminCreateProduct()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const handleCopyProduct = (product: Product) => {
|
const handleCopyProduct = (product: Product) => {
|
||||||
const {
|
const {
|
||||||
@@ -103,7 +105,7 @@ const useCopyProduct = () => {
|
|||||||
"product",
|
"product",
|
||||||
"product_id",
|
"product_id",
|
||||||
"variant_rank",
|
"variant_rank",
|
||||||
"purchasable"
|
"purchasable",
|
||||||
])
|
])
|
||||||
|
|
||||||
const variantBase = Object.entries(rest).reduce((acc, [key, value]) => {
|
const variantBase = Object.entries(rest).reduce((acc, [key, value]) => {
|
||||||
@@ -181,10 +183,21 @@ const useCopyProduct = () => {
|
|||||||
mutate(base as AdminPostProductsReq, {
|
mutate(base as AdminPostProductsReq, {
|
||||||
onSuccess: ({ product: copiedProduct }) => {
|
onSuccess: ({ product: copiedProduct }) => {
|
||||||
navigate(`/a/products/${copiedProduct.id}`)
|
navigate(`/a/products/${copiedProduct.id}`)
|
||||||
notification("Success", "Created a new product", "success")
|
notification(
|
||||||
|
t("product-table-copy-success", "Success"),
|
||||||
|
t(
|
||||||
|
"product-table-copy-created-a-new-product",
|
||||||
|
"Created a new product"
|
||||||
|
),
|
||||||
|
"success"
|
||||||
|
)
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
notification("Error", getErrorMessage(error), "error")
|
notification(
|
||||||
|
t("product-table-copy-error", "Error"),
|
||||||
|
getErrorMessage(error),
|
||||||
|
"error"
|
||||||
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Loaded 100 of 347 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user