From b65a632e07147beee9347dbe10a7cbc491b108de Mon Sep 17 00:00:00 2001 From: Jakub Majorek Date: Tue, 10 Aug 2021 16:38:48 +0200 Subject: [PATCH] Add product variant translation view (#1285) * Add product variant translation view * Final adjustments --- locale/defaultMessages.json | 53 +++++- src/fragments/translations.ts | 35 ++++ .../ProductVariantTranslationFragment.ts | 64 +++++++ .../ProductContextSwitcher.tsx | 169 ++++++++++++++++++ .../ProductContextSwitcher/index.ts | 2 + .../TranslationFields/TranslationFields.tsx | 4 + .../TranslationFieldsRich.tsx | 4 +- .../TranslationsAttributesPage.tsx | 2 + .../TranslationsCategoriesPage.tsx | 2 + .../TranslationsCollectionsPage.tsx | 2 + .../TranslationsPagesPage.tsx | 3 + .../TranslationsProductVariantsPage.tsx | 136 ++++++++++++++ .../TranslationsProductVariantsPage/index.ts | 2 + .../TranslationsProductsPage.tsx | 11 ++ .../TranslationsSalesPage.tsx | 1 + .../TranslationsShippingMethodPage.tsx | 1 + .../TranslationsVouchersPage.tsx | 1 + src/translations/index.tsx | 36 ++++ src/translations/mutations.ts | 35 ++++ src/translations/queries.ts | 42 +++++ src/translations/types/ProductVariantList.ts | 29 +++ .../types/ProductVariantTranslationDetails.ts | 79 ++++++++ .../types/UpdateProductVariantTranslations.ts | 52 ++++++ src/translations/urls.ts | 24 ++- .../views/TranslationsEntities.tsx | 7 +- .../views/TranslationsProductVariants.tsx | 156 ++++++++++++++++ .../views/TranslationsProducts.tsx | 1 + 27 files changed, 940 insertions(+), 13 deletions(-) create mode 100644 src/fragments/types/ProductVariantTranslationFragment.ts create mode 100644 src/translations/components/ProductContextSwitcher/ProductContextSwitcher.tsx create mode 100644 src/translations/components/ProductContextSwitcher/index.ts create mode 100644 src/translations/components/TranslationsProductVariantsPage/TranslationsProductVariantsPage.tsx create mode 100644 src/translations/components/TranslationsProductVariantsPage/index.ts create mode 100644 src/translations/types/ProductVariantList.ts create mode 100644 src/translations/types/ProductVariantTranslationDetails.ts create mode 100644 src/translations/types/UpdateProductVariantTranslations.ts create mode 100644 src/translations/views/TranslationsProductVariants.tsx diff --git a/locale/defaultMessages.json b/locale/defaultMessages.json index 228c1901e..7f112ea1e 100644 --- a/locale/defaultMessages.json +++ b/locale/defaultMessages.json @@ -1583,6 +1583,10 @@ "context": "alert", "string": "Channel limit reached" }, + "src_dot_channels_dot_pages_dot_ChannelsListPage_dot_3277722884": { + "context": "created channels counter", + "string": "{count}/{max} channels used" + }, "src_dot_channels_dot_pages_dot_ChannelsListPage_dot_3511613983": { "context": "channel name", "string": "Channel Name" @@ -3445,6 +3449,13 @@ "context": "orders section name", "string": "Orders" }, + "src_dot_orders_dot_components_dot_2214147779": { + "context": "alert", + "string": "Order limit reached" + }, + "src_dot_orders_dot_components_dot_3769643084": { + "string": "You have reached your order limit, you will be billed extra for orders above limit. If you would like to up your limit, contact your administration staff about raising your limits." + }, "src_dot_orders_dot_components_dot_DraftOrderChannelSectionCard_dot_1243773440": { "context": "section header", "string": "Sales channel" @@ -3670,6 +3681,10 @@ "context": "button", "string": "Add products" }, + "src_dot_orders_dot_components_dot_OrderDraftListPage_dot_1629108523": { + "context": "placed orders counter", + "string": "{count}/{max} orders" + }, "src_dot_orders_dot_components_dot_OrderDraftListPage_dot_2826235371": { "context": "button", "string": "Create order" @@ -4010,9 +4025,9 @@ "context": "generate invoice button", "string": "Generate" }, - "src_dot_orders_dot_components_dot_OrderListPage_dot_2214147779": { - "context": "alert", - "string": "Order limit reached" + "src_dot_orders_dot_components_dot_OrderListPage_dot_1629108523": { + "context": "placed order counter", + "string": "{count}/{max} orders" }, "src_dot_orders_dot_components_dot_OrderListPage_dot_2225897825": { "context": "button", @@ -4025,9 +4040,6 @@ "src_dot_orders_dot_components_dot_OrderListPage_dot_355376157": { "string": "Search Orders..." }, - "src_dot_orders_dot_components_dot_OrderListPage_dot_3769643084": { - "string": "You have reached your order limit, you will be billed extra for orders above limit. If you would like to up your limit, contact your administration staff about raising your limits." - }, "src_dot_orders_dot_components_dot_OrderListPage_dot_875489544": { "context": "tab name", "string": "All Orders" @@ -5382,6 +5394,10 @@ "context": "alert", "string": "SKU limit reached" }, + "src_dot_products_dot_components_dot_ProductListPage_dot_3155791658": { + "context": "created products counter", + "string": "{count}/{max} SKUs used" + }, "src_dot_products_dot_components_dot_ProductListPage_dot_3550330425": { "string": "Search Products..." }, @@ -6508,6 +6524,10 @@ "context": "button", "string": "Invite staff member" }, + "src_dot_staff_dot_components_dot_StaffListPage_dot_938945387": { + "context": "used staff users counter", + "string": "{count}/{max} members" + }, "src_dot_staff_dot_components_dot_StaffListPage_dot_active": { "context": "staff member's account", "string": "Active" @@ -6675,6 +6695,12 @@ "context": "translations section name", "string": "Translations" }, + "src_dot_translations_dot_components_dot_ProductContextSwitcher_dot_1932340772": { + "string": "Translating" + }, + "src_dot_translations_dot_components_dot_ProductContextSwitcher_dot_197468239": { + "string": "Main Product" + }, "src_dot_translations_dot_components_dot_TranslationFields_dot_1308081812": { "string": "{numberOfFields} Translations, {numberOfTranslatedFields} Completed" }, @@ -6815,6 +6841,17 @@ "src_dot_translations_dot_components_dot_TranslationsPagesPage_dot_432157284": { "string": "Page Title" }, + "src_dot_translations_dot_components_dot_TranslationsProductVariantsPage_dot_4165966072": { + "context": "attribute list", + "string": "Attribute {number}" + }, + "src_dot_translations_dot_components_dot_TranslationsProductVariantsPage_dot_847507870": { + "context": "header", + "string": "Translation Product Variant \"{productName}\" - {languageCode}" + }, + "src_dot_translations_dot_components_dot_TranslationsProductVariantsPage_dot_953327101": { + "string": "Variant Name" + }, "src_dot_translations_dot_components_dot_TranslationsProductsPage_dot_1406947243": { "string": "Search Engine Description" }, @@ -7118,6 +7155,10 @@ "src_dot_warehouses_dot_components_dot_WarehouseInfo_dot_2622674857": { "string": "Warehouse Name" }, + "src_dot_warehouses_dot_components_dot_WarehouseListPage_dot_1082745946": { + "context": "used warehouses counter", + "string": "{count}/{max} warehouses used" + }, "src_dot_warehouses_dot_components_dot_WarehouseListPage_dot_2304765290": { "string": "Search Warehouse" }, diff --git a/src/fragments/translations.ts b/src/fragments/translations.ts index 7cf528e78..64f029419 100644 --- a/src/fragments/translations.ts +++ b/src/fragments/translations.ts @@ -84,6 +84,41 @@ export const productTranslationFragment = gql` } } `; + +export const productVariantTranslationFragment = gql` + fragment ProductVariantTranslationFragment on ProductVariantTranslatableContent { + productVariant { + id + } + name + translation(languageCode: $language) { + id + name + language { + code + language + } + } + attributeValues { + id + name + richText + attributeValue { + id + } + translation(languageCode: $language) { + id + name + richText + language { + code + language + } + } + } + } +`; + export const saleTranslationFragment = gql` fragment SaleTranslationFragment on SaleTranslatableContent { sale { diff --git a/src/fragments/types/ProductVariantTranslationFragment.ts b/src/fragments/types/ProductVariantTranslationFragment.ts new file mode 100644 index 000000000..686986bb3 --- /dev/null +++ b/src/fragments/types/ProductVariantTranslationFragment.ts @@ -0,0 +1,64 @@ +/* tslint:disable */ +/* eslint-disable */ +// @generated +// This file was automatically generated and should not be edited. + +import { LanguageCodeEnum } from "./../../types/globalTypes"; + +// ==================================================== +// GraphQL fragment: ProductVariantTranslationFragment +// ==================================================== + +export interface ProductVariantTranslationFragment_productVariant { + __typename: "ProductVariant"; + id: string; +} + +export interface ProductVariantTranslationFragment_translation_language { + __typename: "LanguageDisplay"; + code: LanguageCodeEnum; + language: string; +} + +export interface ProductVariantTranslationFragment_translation { + __typename: "ProductVariantTranslation"; + id: string; + name: string; + language: ProductVariantTranslationFragment_translation_language; +} + +export interface ProductVariantTranslationFragment_attributeValues_attributeValue { + __typename: "AttributeValue"; + id: string; +} + +export interface ProductVariantTranslationFragment_attributeValues_translation_language { + __typename: "LanguageDisplay"; + code: LanguageCodeEnum; + language: string; +} + +export interface ProductVariantTranslationFragment_attributeValues_translation { + __typename: "AttributeValueTranslation"; + id: string; + name: string; + richText: any | null; + language: ProductVariantTranslationFragment_attributeValues_translation_language; +} + +export interface ProductVariantTranslationFragment_attributeValues { + __typename: "AttributeValueTranslatableContent"; + id: string; + name: string; + richText: any | null; + attributeValue: ProductVariantTranslationFragment_attributeValues_attributeValue | null; + translation: ProductVariantTranslationFragment_attributeValues_translation | null; +} + +export interface ProductVariantTranslationFragment { + __typename: "ProductVariantTranslatableContent"; + productVariant: ProductVariantTranslationFragment_productVariant | null; + name: string; + translation: ProductVariantTranslationFragment_translation | null; + attributeValues: ProductVariantTranslationFragment_attributeValues[]; +} diff --git a/src/translations/components/ProductContextSwitcher/ProductContextSwitcher.tsx b/src/translations/components/ProductContextSwitcher/ProductContextSwitcher.tsx new file mode 100644 index 000000000..f971d54f6 --- /dev/null +++ b/src/translations/components/ProductContextSwitcher/ProductContextSwitcher.tsx @@ -0,0 +1,169 @@ +import { + Card, + ClickAwayListener, + Grow, + MenuItem, + MenuList as Menu, + Paper, + Popper, + Typography +} from "@material-ui/core"; +import ArrowDropDown from "@material-ui/icons/ArrowDropDown"; +import useNavigator from "@saleor/hooks/useNavigator"; +import { makeStyles } from "@saleor/macaw-ui"; +import { + languageEntityUrl, + productVariantUrl, + TranslatableEntities +} from "@saleor/translations/urls"; +import classNames from "classnames"; +import React from "react"; +import { FormattedMessage, useIntl } from "react-intl"; + +import { useProductVariantList } from "../../queries"; + +export interface ProductContextSwitcherProps { + productId: string; + selectedId: string; + languageCode: string; +} + +const useStyles = makeStyles( + theme => ({ + arrow: { + color: theme.palette.primary.main, + transition: theme.transitions.duration.standard + "ms" + }, + container: { + display: "flex", + alignItems: "center", + paddingBottom: theme.spacing(1), + marginRight: theme.spacing(1) + }, + label: { + paddingRight: theme.spacing(1) + }, + menuContainer: { + cursor: "pointer", + display: "flex", + justifyContent: "space-between", + minWidth: 90, + padding: theme.spacing(), + position: "relative" + }, + menuItem: { + textAlign: "justify" + }, + menuPaper: { + maxHeight: `calc(100vh - ${theme.spacing(2)}px)`, + overflow: "scroll" + }, + popover: { + zIndex: 1 + }, + rotate: { + transform: "rotate(180deg)" + } + }), + { name: "ProductContextSwitcher" } +); +const ProductContextSwitcher: React.FC = ({ + languageCode, + productId, + selectedId +}) => { + const classes = useStyles(); + const navigate = useNavigator(); + const intl = useIntl(); + const { data } = useProductVariantList({ + variables: { id: productId } + }); + + const [isExpanded, setExpandedState] = React.useState(false); + const anchor = React.useRef(); + + const items = [ + { + label: intl.formatMessage({ defaultMessage: "Main Product" }), + value: productId, + onClick: () => + navigate( + languageEntityUrl( + languageCode, + TranslatableEntities.products, + productId + ) + ) + }, + ...(data?.product?.variants?.map(({ name, sku, id }) => ({ + label: name || sku, + value: id, + onClick: () => navigate(productVariantUrl(languageCode, productId, id)) + })) || []) + ]; + + return ( +
+ + : + +
+ setExpandedState(!isExpanded)} + > + + {items.find(({ value }) => value === selectedId)?.label || "-"} + + + + + {({ TransitionProps, placement }) => ( + + + setExpandedState(false)} + mouseEvent="onClick" + > + + {items.map(({ label, value, onClick }) => ( + { + setExpandedState(false); + onClick(); + }} + > + {label} + + ))} + + + + + )} + +
+
+ ); +}; +ProductContextSwitcher.displayName = "ProductContextSwitcher"; +export default ProductContextSwitcher; diff --git a/src/translations/components/ProductContextSwitcher/index.ts b/src/translations/components/ProductContextSwitcher/index.ts new file mode 100644 index 000000000..78d812a6b --- /dev/null +++ b/src/translations/components/ProductContextSwitcher/index.ts @@ -0,0 +1,2 @@ +export * from "./ProductContextSwitcher"; +export { default } from "./ProductContextSwitcher"; diff --git a/src/translations/components/TranslationFields/TranslationFields.tsx b/src/translations/components/TranslationFields/TranslationFields.tsx index 3f40c0af3..8126a5709 100644 --- a/src/translations/components/TranslationFields/TranslationFields.tsx +++ b/src/translations/components/TranslationFields/TranslationFields.tsx @@ -38,6 +38,7 @@ export interface TranslationFieldsProps { initialState: boolean; saveButtonState: ConfirmButtonTransitionState; pagination?: Pagination; + richTextResetKey: string; // temporary workaround TODO: fix rich text editor onEdit: (field: string) => void; onDiscard: () => void; onSubmit: (field: TranslationField, data: string | OutputData) => void; @@ -120,6 +121,7 @@ const TranslationFields: React.FC = props => { title, saveButtonState, pagination, + richTextResetKey, onEdit, onDiscard, onSubmit @@ -187,6 +189,7 @@ const TranslationFields: React.FC = props => { /> ) : ( = props => { /> ) : ( void; onSubmit: (data: OutputData) => void; } @@ -23,6 +24,7 @@ const TranslationFieldsRich: React.FC = ({ edit, initial, saveButtonState, + resetKey, onDiscard, onSubmit }) => { @@ -59,7 +61,7 @@ const TranslationFieldsRich: React.FC = ({ ) : ( - + ); }; diff --git a/src/translations/components/TranslationsAttributesPage/TranslationsAttributesPage.tsx b/src/translations/components/TranslationsAttributesPage/TranslationsAttributesPage.tsx index 9492194d7..e455a91be 100644 --- a/src/translations/components/TranslationsAttributesPage/TranslationsAttributesPage.tsx +++ b/src/translations/components/TranslationsAttributesPage/TranslationsAttributesPage.tsx @@ -107,6 +107,7 @@ const TranslationsAttributesPage: React.FC = ({ } ]} saveButtonState={saveButtonState} + richTextResetKey={languageCode} onEdit={onEdit} onDiscard={onDiscard} onSubmit={onSubmit} @@ -142,6 +143,7 @@ const TranslationsAttributesPage: React.FC = ({ ) || [] } saveButtonState={saveButtonState} + richTextResetKey={languageCode} pagination={{ settings, onUpdateListSettings, diff --git a/src/translations/components/TranslationsCategoriesPage/TranslationsCategoriesPage.tsx b/src/translations/components/TranslationsCategoriesPage/TranslationsCategoriesPage.tsx index b25494c26..00155ee82 100644 --- a/src/translations/components/TranslationsCategoriesPage/TranslationsCategoriesPage.tsx +++ b/src/translations/components/TranslationsCategoriesPage/TranslationsCategoriesPage.tsx @@ -83,6 +83,7 @@ const TranslationsCategoriesPage: React.FC = ({ } ]} saveButtonState={saveButtonState} + richTextResetKey={languageCode} onEdit={onEdit} onDiscard={onDiscard} onSubmit={onSubmit} @@ -116,6 +117,7 @@ const TranslationsCategoriesPage: React.FC = ({ } ]} saveButtonState={saveButtonState} + richTextResetKey={languageCode} onEdit={onEdit} onDiscard={onDiscard} onSubmit={onSubmit} diff --git a/src/translations/components/TranslationsCollectionsPage/TranslationsCollectionsPage.tsx b/src/translations/components/TranslationsCollectionsPage/TranslationsCollectionsPage.tsx index ef5ab68f8..31593309c 100644 --- a/src/translations/components/TranslationsCollectionsPage/TranslationsCollectionsPage.tsx +++ b/src/translations/components/TranslationsCollectionsPage/TranslationsCollectionsPage.tsx @@ -84,6 +84,7 @@ const TranslationsCollectionsPage: React.FC = } ]} saveButtonState={saveButtonState} + richTextResetKey={languageCode} onEdit={onEdit} onDiscard={onDiscard} onSubmit={onSubmit} @@ -117,6 +118,7 @@ const TranslationsCollectionsPage: React.FC = } ]} saveButtonState={saveButtonState} + richTextResetKey={languageCode} onEdit={onEdit} onDiscard={onDiscard} onSubmit={onSubmit} diff --git a/src/translations/components/TranslationsPagesPage/TranslationsPagesPage.tsx b/src/translations/components/TranslationsPagesPage/TranslationsPagesPage.tsx index 432a22588..1fc1b084c 100644 --- a/src/translations/components/TranslationsPagesPage/TranslationsPagesPage.tsx +++ b/src/translations/components/TranslationsPagesPage/TranslationsPagesPage.tsx @@ -88,6 +88,7 @@ const TranslationsPagesPage: React.FC = ({ } ]} saveButtonState={saveButtonState} + richTextResetKey={languageCode} onEdit={onEdit} onDiscard={onDiscard} onSubmit={onSubmit} @@ -122,6 +123,7 @@ const TranslationsPagesPage: React.FC = ({ } ]} saveButtonState={saveButtonState} + richTextResetKey={languageCode} onEdit={onEdit} onDiscard={onDiscard} onSubmit={onSubmit} @@ -153,6 +155,7 @@ const TranslationsPagesPage: React.FC = ({ })) || [] } saveButtonState={saveButtonState} + richTextResetKey={languageCode} onEdit={onEdit} onDiscard={onDiscard} onSubmit={onAttributeValueSubmit} diff --git a/src/translations/components/TranslationsProductVariantsPage/TranslationsProductVariantsPage.tsx b/src/translations/components/TranslationsProductVariantsPage/TranslationsProductVariantsPage.tsx new file mode 100644 index 000000000..bead55b65 --- /dev/null +++ b/src/translations/components/TranslationsProductVariantsPage/TranslationsProductVariantsPage.tsx @@ -0,0 +1,136 @@ +import CardSpacer from "@saleor/components/CardSpacer"; +import Container from "@saleor/components/Container"; +import LanguageSwitch from "@saleor/components/LanguageSwitch"; +import PageHeader from "@saleor/components/PageHeader"; +import { ProductVariantTranslationFragment } from "@saleor/fragments/types/ProductVariantTranslationFragment"; +import { commonMessages, sectionNames } from "@saleor/intl"; +import { Backlink } from "@saleor/macaw-ui"; +import { getStringOrPlaceholder } from "@saleor/misc"; +import { + TranslationInputFieldName, + TranslationsEntitiesPageProps +} from "@saleor/translations/types"; +import React from "react"; +import { useIntl } from "react-intl"; + +import { LanguageCodeEnum } from "../../../types/globalTypes"; +import ProductContextSwitcher from "../ProductContextSwitcher"; +import TranslationFields from "../TranslationFields"; + +export interface TranslationsProductsPageProps + extends TranslationsEntitiesPageProps { + data: ProductVariantTranslationFragment; + productId: string; + variantId: string; + onAttributeValueSubmit: TranslationsEntitiesPageProps["onSubmit"]; +} + +const TranslationsProductsPage: React.FC = ({ + activeField, + disabled, + languageCode, + languages, + data, + saveButtonState, + productId, + variantId, + onBack, + onDiscard, + onEdit, + onLanguageChange, + onSubmit, + onAttributeValueSubmit +}) => { + const intl = useIntl(); + + return ( + + + {intl.formatMessage(sectionNames.products)} + + + + + + + + {data?.attributeValues?.length > 0 && ( + <> + ({ + id: attrVal.attributeValue.id, + displayName: intl.formatMessage( + { + defaultMessage: "Attribute {number}", + description: "attribute list" + }, + { + number: i + 1 + } + ), + name: attrVal?.name, + translation: attrVal?.translation?.richText || null, + type: "rich" as "rich", + value: attrVal?.richText + })) || [] + } + saveButtonState={saveButtonState} + richTextResetKey={languageCode} + onEdit={onEdit} + onDiscard={onDiscard} + onSubmit={onAttributeValueSubmit} + /> + + + )} + + ); +}; +TranslationsProductsPage.displayName = "TranslationsProductsPage"; +export default TranslationsProductsPage; diff --git a/src/translations/components/TranslationsProductVariantsPage/index.ts b/src/translations/components/TranslationsProductVariantsPage/index.ts new file mode 100644 index 000000000..74dba27a3 --- /dev/null +++ b/src/translations/components/TranslationsProductVariantsPage/index.ts @@ -0,0 +1,2 @@ +export { default } from "./TranslationsProductVariantsPage"; +export * from "./TranslationsProductVariantsPage"; diff --git a/src/translations/components/TranslationsProductsPage/TranslationsProductsPage.tsx b/src/translations/components/TranslationsProductsPage/TranslationsProductsPage.tsx index abe144e81..89c87bc9d 100644 --- a/src/translations/components/TranslationsProductsPage/TranslationsProductsPage.tsx +++ b/src/translations/components/TranslationsProductsPage/TranslationsProductsPage.tsx @@ -14,15 +14,18 @@ import React from "react"; import { useIntl } from "react-intl"; import { LanguageCodeEnum } from "../../../types/globalTypes"; +import ProductContextSwitcher from "../ProductContextSwitcher"; import TranslationFields from "../TranslationFields"; export interface TranslationsProductsPageProps extends TranslationsEntitiesPageProps { data: ProductTranslationFragment; + productId: string; onAttributeValueSubmit: TranslationsEntitiesPageProps["onSubmit"]; } const TranslationsProductsPage: React.FC = ({ + productId, activeField, disabled, languageCode, @@ -56,6 +59,11 @@ const TranslationsProductsPage: React.FC = ({ } )} > + = ({ } ]} saveButtonState={saveButtonState} + richTextResetKey={languageCode} onEdit={onEdit} onDiscard={onDiscard} onSubmit={onSubmit} @@ -121,6 +130,7 @@ const TranslationsProductsPage: React.FC = ({ } ]} saveButtonState={saveButtonState} + richTextResetKey={languageCode} onEdit={onEdit} onDiscard={onDiscard} onSubmit={onSubmit} @@ -152,6 +162,7 @@ const TranslationsProductsPage: React.FC = ({ })) || [] } saveButtonState={saveButtonState} + richTextResetKey={languageCode} onEdit={onEdit} onDiscard={onDiscard} onSubmit={onAttributeValueSubmit} diff --git a/src/translations/components/TranslationsSalesPage/TranslationsSalesPage.tsx b/src/translations/components/TranslationsSalesPage/TranslationsSalesPage.tsx index 3976caa44..14597333f 100644 --- a/src/translations/components/TranslationsSalesPage/TranslationsSalesPage.tsx +++ b/src/translations/components/TranslationsSalesPage/TranslationsSalesPage.tsx @@ -76,6 +76,7 @@ const TranslationsSalesPage: React.FC = ({ } ]} saveButtonState={saveButtonState} + richTextResetKey={languageCode} onEdit={onEdit} onDiscard={onDiscard} onSubmit={onSubmit} diff --git a/src/translations/components/TranslationsShippingMethodPage/TranslationsShippingMethodPage.tsx b/src/translations/components/TranslationsShippingMethodPage/TranslationsShippingMethodPage.tsx index 15aaa33b9..4bad81107 100644 --- a/src/translations/components/TranslationsShippingMethodPage/TranslationsShippingMethodPage.tsx +++ b/src/translations/components/TranslationsShippingMethodPage/TranslationsShippingMethodPage.tsx @@ -87,6 +87,7 @@ const TranslationsShippingMethodPage: React.FC = ({ } ]} saveButtonState={saveButtonState} + richTextResetKey={languageCode} onEdit={onEdit} onDiscard={onDiscard} onSubmit={onSubmit} diff --git a/src/translations/index.tsx b/src/translations/index.tsx index 54b49184a..2ec435f38 100644 --- a/src/translations/index.tsx +++ b/src/translations/index.tsx @@ -29,6 +29,9 @@ import TranslationsPagesComponent, { import TranslationsProductsComponent, { TranslationsProductsQueryParams } from "./views/TranslationsProducts"; +import TranslationsProductVariantsComponent, { + TranslationsProductVariantsQueryParams +} from "./views/TranslationsProductVariants"; import TranslationsSaleComponent, { TranslationsSalesQueryParams } from "./views/TranslationsSales"; @@ -107,6 +110,28 @@ const TranslationsProducts: React.FC = ({ /> ); }; +type TranslationsProductVariantProps = RouteComponentProps<{ + productId: string; + id: string; + languageCode: string; +}>; +const TranslationsProductVariants: React.FC = ({ + location, + match +}) => { + const qs = parseQs(location.search.substr(1)); + const params: TranslationsProductVariantsQueryParams = { + activeField: qs.activeField + }; + return ( + + ); +}; const TranslationsSales: React.FC = ({ location, match @@ -214,6 +239,17 @@ const TranslationsRouter: React.FC = () => { )} component={TranslationsProducts} /> + (updateProductTranslations); +const updateProductVariantTranslations = gql` + mutation UpdateProductVariantTranslations( + $id: ID! + $input: NameTranslationInput! + $language: LanguageCodeEnum! + ) { + productVariantTranslate(id: $id, input: $input, languageCode: $language) { + errors { + field + message + } + productVariant { + id + name + translation(languageCode: $language) { + id + name + language { + code + language + } + } + } + } + } +`; +export const TypedUpdateProductVariantTranslations = TypedMutation< + UpdateProductVariantTranslations, + UpdateProductVariantTranslationsVariables +>(updateProductVariantTranslations); + const updateCategoryTranslations = gql` mutation UpdateCategoryTranslations( $id: ID! diff --git a/src/translations/queries.ts b/src/translations/queries.ts index 5dc40e243..99ed22220 100644 --- a/src/translations/queries.ts +++ b/src/translations/queries.ts @@ -6,6 +6,7 @@ import { collectionTranslationFragment, pageTranslationFragment, productTranslationFragment, + productVariantTranslationFragment, saleTranslationFragment, shippingMethodTranslationFragment, voucherTranslationFragment @@ -54,6 +55,14 @@ import { ProductTranslations, ProductTranslationsVariables } from "./types/ProductTranslations"; +import { + ProductVariantList, + ProductVariantListVariables +} from "./types/ProductVariantList"; +import { + ProductVariantTranslationDetails, + ProductVariantTranslationDetailsVariables +} from "./types/ProductVariantTranslationDetails"; import { SaleTranslationDetails, SaleTranslationDetailsVariables @@ -356,6 +365,39 @@ export const useProductTranslationDetails = makeQuery< ProductTranslationDetailsVariables >(productTranslationDetails); +const productVariantList = gql` + query ProductVariantList($id: ID!) { + product(id: $id) { + id + variants { + id + name + sku + } + } + } +`; +export const useProductVariantList = makeQuery< + ProductVariantList, + ProductVariantListVariables +>(productVariantList); + +const productVariantTranslationDetails = gql` + ${productVariantTranslationFragment} + query ProductVariantTranslationDetails( + $id: ID! + $language: LanguageCodeEnum! + ) { + translation(kind: VARIANT, id: $id) { + ...ProductVariantTranslationFragment + } + } +`; +export const useProductVariantTranslationDetails = makeQuery< + ProductVariantTranslationDetails, + ProductVariantTranslationDetailsVariables +>(productVariantTranslationDetails); + const categoryTranslationDetails = gql` ${categoryTranslationFragment} query CategoryTranslationDetails($id: ID!, $language: LanguageCodeEnum!) { diff --git a/src/translations/types/ProductVariantList.ts b/src/translations/types/ProductVariantList.ts new file mode 100644 index 000000000..869b1ce6b --- /dev/null +++ b/src/translations/types/ProductVariantList.ts @@ -0,0 +1,29 @@ +/* tslint:disable */ +/* eslint-disable */ +// @generated +// This file was automatically generated and should not be edited. + +// ==================================================== +// GraphQL query operation: ProductVariantList +// ==================================================== + +export interface ProductVariantList_product_variants { + __typename: "ProductVariant"; + id: string; + name: string; + sku: string; +} + +export interface ProductVariantList_product { + __typename: "Product"; + id: string; + variants: (ProductVariantList_product_variants | null)[] | null; +} + +export interface ProductVariantList { + product: ProductVariantList_product | null; +} + +export interface ProductVariantListVariables { + id: string; +} diff --git a/src/translations/types/ProductVariantTranslationDetails.ts b/src/translations/types/ProductVariantTranslationDetails.ts new file mode 100644 index 000000000..fd4a47978 --- /dev/null +++ b/src/translations/types/ProductVariantTranslationDetails.ts @@ -0,0 +1,79 @@ +/* tslint:disable */ +/* eslint-disable */ +// @generated +// This file was automatically generated and should not be edited. + +import { LanguageCodeEnum } from "./../../types/globalTypes"; + +// ==================================================== +// GraphQL query operation: ProductVariantTranslationDetails +// ==================================================== + +export interface ProductVariantTranslationDetails_translation_ProductTranslatableContent { + __typename: "ProductTranslatableContent" | "CollectionTranslatableContent" | "CategoryTranslatableContent" | "AttributeTranslatableContent" | "AttributeValueTranslatableContent" | "PageTranslatableContent" | "ShippingMethodTranslatableContent" | "SaleTranslatableContent" | "VoucherTranslatableContent" | "MenuItemTranslatableContent"; +} + +export interface ProductVariantTranslationDetails_translation_ProductVariantTranslatableContent_productVariant { + __typename: "ProductVariant"; + id: string; +} + +export interface ProductVariantTranslationDetails_translation_ProductVariantTranslatableContent_translation_language { + __typename: "LanguageDisplay"; + code: LanguageCodeEnum; + language: string; +} + +export interface ProductVariantTranslationDetails_translation_ProductVariantTranslatableContent_translation { + __typename: "ProductVariantTranslation"; + id: string; + name: string; + language: ProductVariantTranslationDetails_translation_ProductVariantTranslatableContent_translation_language; +} + +export interface ProductVariantTranslationDetails_translation_ProductVariantTranslatableContent_attributeValues_attributeValue { + __typename: "AttributeValue"; + id: string; +} + +export interface ProductVariantTranslationDetails_translation_ProductVariantTranslatableContent_attributeValues_translation_language { + __typename: "LanguageDisplay"; + code: LanguageCodeEnum; + language: string; +} + +export interface ProductVariantTranslationDetails_translation_ProductVariantTranslatableContent_attributeValues_translation { + __typename: "AttributeValueTranslation"; + id: string; + name: string; + richText: any | null; + language: ProductVariantTranslationDetails_translation_ProductVariantTranslatableContent_attributeValues_translation_language; +} + +export interface ProductVariantTranslationDetails_translation_ProductVariantTranslatableContent_attributeValues { + __typename: "AttributeValueTranslatableContent"; + id: string; + name: string; + richText: any | null; + attributeValue: ProductVariantTranslationDetails_translation_ProductVariantTranslatableContent_attributeValues_attributeValue | null; + translation: ProductVariantTranslationDetails_translation_ProductVariantTranslatableContent_attributeValues_translation | null; +} + +export interface ProductVariantTranslationDetails_translation_ProductVariantTranslatableContent { + __typename: "ProductVariantTranslatableContent"; + productVariant: ProductVariantTranslationDetails_translation_ProductVariantTranslatableContent_productVariant | null; + name: string; + translation: ProductVariantTranslationDetails_translation_ProductVariantTranslatableContent_translation | null; + attributeValues: ProductVariantTranslationDetails_translation_ProductVariantTranslatableContent_attributeValues[]; +} + +export type ProductVariantTranslationDetails_translation = ProductVariantTranslationDetails_translation_ProductTranslatableContent | ProductVariantTranslationDetails_translation_ProductVariantTranslatableContent; + +export interface ProductVariantTranslationDetails { + translation: ProductVariantTranslationDetails_translation | null; +} + +export interface ProductVariantTranslationDetailsVariables { + id: string; + language: LanguageCodeEnum; +} diff --git a/src/translations/types/UpdateProductVariantTranslations.ts b/src/translations/types/UpdateProductVariantTranslations.ts new file mode 100644 index 000000000..b844cad40 --- /dev/null +++ b/src/translations/types/UpdateProductVariantTranslations.ts @@ -0,0 +1,52 @@ +/* tslint:disable */ +/* eslint-disable */ +// @generated +// This file was automatically generated and should not be edited. + +import { NameTranslationInput, LanguageCodeEnum } from "./../../types/globalTypes"; + +// ==================================================== +// GraphQL mutation operation: UpdateProductVariantTranslations +// ==================================================== + +export interface UpdateProductVariantTranslations_productVariantTranslate_errors { + __typename: "TranslationError"; + field: string | null; + message: string | null; +} + +export interface UpdateProductVariantTranslations_productVariantTranslate_productVariant_translation_language { + __typename: "LanguageDisplay"; + code: LanguageCodeEnum; + language: string; +} + +export interface UpdateProductVariantTranslations_productVariantTranslate_productVariant_translation { + __typename: "ProductVariantTranslation"; + id: string; + name: string; + language: UpdateProductVariantTranslations_productVariantTranslate_productVariant_translation_language; +} + +export interface UpdateProductVariantTranslations_productVariantTranslate_productVariant { + __typename: "ProductVariant"; + id: string; + name: string; + translation: UpdateProductVariantTranslations_productVariantTranslate_productVariant_translation | null; +} + +export interface UpdateProductVariantTranslations_productVariantTranslate { + __typename: "ProductVariantTranslate"; + errors: UpdateProductVariantTranslations_productVariantTranslate_errors[]; + productVariant: UpdateProductVariantTranslations_productVariantTranslate_productVariant | null; +} + +export interface UpdateProductVariantTranslations { + productVariantTranslate: UpdateProductVariantTranslations_productVariantTranslate | null; +} + +export interface UpdateProductVariantTranslationsVariables { + id: string; + input: NameTranslationInput; + language: LanguageCodeEnum; +} diff --git a/src/translations/urls.ts b/src/translations/urls.ts index 0e390e6d9..3d737989c 100644 --- a/src/translations/urls.ts +++ b/src/translations/urls.ts @@ -7,6 +7,7 @@ import { TranslationsEntitiesListFilterTab } from "./components/TranslationsEnti export enum TranslatableEntities { categories = "categories", products = "products", + productVariants = "variants", collections = "collections", sales = "sales", vouchers = "vouchers", @@ -35,10 +36,25 @@ export const languageEntitiesUrl = ( export const languageEntityPath = ( code: string, entity: TranslatableEntities, - id: string -) => urlJoin(languageEntitiesPath(code), entity.toString(), id); + id: string, + ...args: string[] +) => urlJoin(languageEntitiesPath(code), entity.toString(), id, ...args); export const languageEntityUrl = ( code: string, entity: TranslatableEntities, - id: string -) => languageEntityPath(code, entity, encodeURIComponent(id)); + id: string, + ...args: string[] +) => languageEntityPath(code, entity, encodeURIComponent(id), ...args); + +export const productVariantUrl = ( + code: string, + productId: string, + variantId: string +) => + languageEntityUrl( + code, + TranslatableEntities.products, + productId, + TranslatableEntities.productVariants, + variantId + ); diff --git a/src/translations/views/TranslationsEntities.tsx b/src/translations/views/TranslationsEntities.tsx index 26cca27c1..889010bcf 100644 --- a/src/translations/views/TranslationsEntities.tsx +++ b/src/translations/views/TranslationsEntities.tsx @@ -194,9 +194,12 @@ const TranslationsEntities: React.FC = ({ node.translation?.description, node.translation?.name, node.translation?.seoDescription, - node.translation?.seoTitle + node.translation?.seoTitle, + ...(node.attributeValues?.map( + ({ translation }) => translation?.richText + ) || []) ]), - max: 4 + max: 4 + (node.attributeValues?.length || 0) }, id: node?.product?.id, name: node?.product?.name diff --git a/src/translations/views/TranslationsProductVariants.tsx b/src/translations/views/TranslationsProductVariants.tsx new file mode 100644 index 000000000..c6f8159e1 --- /dev/null +++ b/src/translations/views/TranslationsProductVariants.tsx @@ -0,0 +1,156 @@ +import { OutputData } from "@editorjs/editorjs"; +import useNavigator from "@saleor/hooks/useNavigator"; +import useNotifier from "@saleor/hooks/useNotifier"; +import useShop from "@saleor/hooks/useShop"; +import { commonMessages } from "@saleor/intl"; +import { stringify as stringifyQs } from "qs"; +import React from "react"; +import { useIntl } from "react-intl"; + +import { maybe } from "../../misc"; +import { LanguageCodeEnum } from "../../types/globalTypes"; +import TranslationsProductVariantsPage from "../components/TranslationsProductVariantsPage"; +import { + TypedUpdateAttributeValueTranslations, + TypedUpdateProductVariantTranslations +} from "../mutations"; +import { useProductVariantTranslationDetails } from "../queries"; +import { TranslationField, TranslationInputFieldName } from "../types"; +import { + languageEntitiesUrl, + productVariantUrl, + TranslatableEntities +} from "../urls"; +import { getParsedTranslationInputData } from "../utils"; + +export interface TranslationsProductVariantsQueryParams { + activeField: string; +} +export interface TranslationsProductVariantsProps { + id: string; + productId: string; + languageCode: LanguageCodeEnum; + params: TranslationsProductVariantsQueryParams; +} + +const TranslationsProductVariants: React.FC = ({ + id, + productId, + languageCode, + params +}) => { + const navigate = useNavigator(); + const notify = useNotifier(); + const shop = useShop(); + const intl = useIntl(); + + const productVariantTranslations = useProductVariantTranslationDetails({ + variables: { id, language: languageCode } + }); + + const onEdit = (field: string) => + navigate( + "?" + + stringifyQs({ + activeField: field + }), + true + ); + + const onUpdate = (errors: unknown[]) => { + if (errors.length === 0) { + productVariantTranslations.refetch(); + notify({ + status: "success", + text: intl.formatMessage(commonMessages.savedChanges) + }); + navigate("?", true); + } + }; + + const onDiscard = () => { + navigate("?", true); + }; + + return ( + onUpdate(data.productVariantTranslate.errors)} + > + {(updateTranslations, updateTranslationsOpts) => ( + onUpdate(data.attributeValueTranslate.errors)} + > + {updateAttributeValueTranslations => { + const handleSubmit = ( + { name: fieldName }: TranslationField, + data: string + ) => { + updateTranslations({ + variables: { + id, + input: getParsedTranslationInputData({ + data, + fieldName + }), + language: languageCode + } + }); + }; + + const handleAttributeValueSubmit = ( + { id }: TranslationField, + data: OutputData + ) => { + updateAttributeValueTranslations({ + variables: { + id, + input: { richText: JSON.stringify(data) }, + language: languageCode + } + }); + }; + + const translation = productVariantTranslations?.data?.translation; + + return ( + shop.languages, [])} + saveButtonState={updateTranslationsOpts.status} + onBack={() => + navigate( + languageEntitiesUrl(languageCode, { + tab: TranslatableEntities.products + }) + ) + } + onEdit={onEdit} + onDiscard={onDiscard} + onLanguageChange={lang => + navigate(productVariantUrl(lang, productId, id)) + } + onSubmit={handleSubmit} + onAttributeValueSubmit={handleAttributeValueSubmit} + data={ + translation?.__typename === + "ProductVariantTranslatableContent" + ? translation + : null + } + /> + ); + }} + + )} + + ); +}; +TranslationsProductVariants.displayName = "TranslationsProductVariants"; +export default TranslationsProductVariants; diff --git a/src/translations/views/TranslationsProducts.tsx b/src/translations/views/TranslationsProducts.tsx index 3c62e5768..dad16da98 100644 --- a/src/translations/views/TranslationsProducts.tsx +++ b/src/translations/views/TranslationsProducts.tsx @@ -112,6 +112,7 @@ const TranslationsProducts: React.FC = ({ return (