diff --git a/apps/sfy-eu-blocks-app/admin-block-extension/locales/de.json b/apps/sfy-eu-blocks-app/admin-block-extension/locales/de.json index 4ab4d37..5a9e547 100644 --- a/apps/sfy-eu-blocks-app/admin-block-extension/locales/de.json +++ b/apps/sfy-eu-blocks-app/admin-block-extension/locales/de.json @@ -12,7 +12,8 @@ "invalid": "Ungültige Energieklasse. Bitte wählen Sie einen Wert von A bis F." }, "labelUrl": { - "invalidFormat": "Bitte geben Sie eine gültige URL für das Etikett ein." + "invalidFormat": "Bitte geben Sie eine gültige URL für das Etikett ein.", + "mustEndWithPdf": "Die URL muss mit .pdf enden." } }, "banner": { @@ -27,6 +28,13 @@ "content2": ", sind Eigentum der Europäischen Union, lizenziert unter der ", "link2Text": "Creative Commons Namensnennung 4.0 International (CC BY 4.0) Lizenz", "content3": ", und werden wie besehen bereitgestellt." + }, + + }, + "warning": { + "noDatasheetForLocale": { + "title": "Achtung", + "content": "Kein Datenblatt für die Hauptsprache {{locale}} wurde gefunden. Sie können eine neue Sprache hinzufügen, indem Sie zusätzliche Metafelder bearbeiten." } }, "error": { @@ -38,7 +46,8 @@ }, "success": { "energyLabelFound": "Energielabel für '{{productName}}' erfolgreich geladen", - "energyLabelUpdated": "Energielabel für '{{productName}}' erfolgreich aktualisiert" + "energyLabelUpdated": "Energielabel für '{{productName}}' erfolgreich aktualisiert", + "energyLabelReset": "Energielabel zurückgesetzt" } }, "button": { diff --git a/apps/sfy-eu-blocks-app/admin-block-extension/locales/en.default.json b/apps/sfy-eu-blocks-app/admin-block-extension/locales/en.default.json index 355aebb..18f86c2 100644 --- a/apps/sfy-eu-blocks-app/admin-block-extension/locales/en.default.json +++ b/apps/sfy-eu-blocks-app/admin-block-extension/locales/en.default.json @@ -12,7 +12,8 @@ "invalid": "Invalid energy class. Please select a value from A to F." }, "labelUrl": { - "invalidFormat": "Please enter a valid URL for the label." + "invalidFormat": "Please enter a valid URL for the label.", + "mustEndWithPdf": "The URL must end with .pdf." } }, "banner": { @@ -27,6 +28,13 @@ "content2": ", are owned by the European Union, are licensed under the ", "link2Text": "Creative Commons Attribution 4.0 International (CC BY 4.0) license", "content3": ", and are provided as is." + }, + + }, + "warning": { + "noDatasheetForLocale": { + "title": "Warning", + "content": "No datasheet was found for the primary locale {{locale}}. You can add a new locale by modifying additional metafields." } }, "error": { @@ -38,7 +46,8 @@ }, "success": { "energyLabelFound": "Energy Label for '{{productName}}' successfully loaded", - "energyLabelUpdated": "Energy Label for '{{productName}}' successfully updated" + "energyLabelUpdated": "Energy Label for '{{productName}}' successfully updated", + "energyLabelReset": "Energy Label reset" } }, "button": { diff --git a/apps/sfy-eu-blocks-app/admin-block-extension/src/components/EnergyLabelPreview.tsx b/apps/sfy-eu-blocks-app/admin-block-extension/src/components/EnergyLabelPreview.tsx new file mode 100644 index 0000000..351217b --- /dev/null +++ b/apps/sfy-eu-blocks-app/admin-block-extension/src/components/EnergyLabelPreview.tsx @@ -0,0 +1,49 @@ +import { + Banner, + BlockStack, + Image, + InlineStack, + Link, + Paragraph, + Pressable +} from '@shopify/ui-extensions-react/admin'; +import React from 'react'; + +import { coreApiConfig, t } from '../environment'; +import { TEnergyLabel } from '../lib'; + +export const EnergyLabelPreview: React.FC = (props) => { + const { energyLabel, primaryLocale } = props; + const sheetUrl = React.useMemo( + () => (energyLabel.sheetUrlMap as Record)[primaryLocale], + [energyLabel.sheetUrlMap, primaryLocale] + ); + + return ( + + + + {`Energy + + + Product Datasheet + + + {sheetUrl == null && ( + + + {t('banner.warning.noDatasheetForLocale.content', { locale: primaryLocale })} + + + )} + + ); +}; + +interface TProps { + energyLabel: TEnergyLabel; + primaryLocale: string; +} diff --git a/apps/sfy-eu-blocks-app/admin-block-extension/src/components/UpdateEnergyLabelMetaFieldsSection.tsx b/apps/sfy-eu-blocks-app/admin-block-extension/src/components/UpdateEnergyLabelMetaFieldsSection.tsx index 090e6ed..1a1a483 100644 --- a/apps/sfy-eu-blocks-app/admin-block-extension/src/components/UpdateEnergyLabelMetaFieldsSection.tsx +++ b/apps/sfy-eu-blocks-app/admin-block-extension/src/components/UpdateEnergyLabelMetaFieldsSection.tsx @@ -19,12 +19,14 @@ import { $banner, $energyLabel, $updateEnergyLabelMetafieldForm, - deleteEnergyLabelFromMetafields + deleteEnergyLabelFromMetafields, + TEnergyLabel } from '../lib'; +import { EnergyLabelPreview } from './EnergyLabelPreview'; import { FormTextField } from './FormTextField'; export const UpdateEnergyLabelMetaFieldsBlock: React.FC = (props) => { - const { productId } = props; + const { productId, energyLabel, primaryLocale } = props; const { handleSubmit, field } = useForm($updateEnergyLabelMetafieldForm); const isSubmitting = useGlobalState($updateEnergyLabelMetafieldForm.isSubmitting); const [formChanged, setFormChanged] = React.useState(false); @@ -32,12 +34,8 @@ export const UpdateEnergyLabelMetaFieldsBlock: React.FC = (props) => { // gid:/shopify/Product/9436272754952 -> 9436272754952 const productIdNumber = React.useMemo(() => productId.replace(/^(.*[\\\/])/, ''), [productId]); - // To disable save button - useGlobalState($updateEnergyLabelMetafieldForm.fields.energyClass); - useGlobalState($updateEnergyLabelMetafieldForm.fields.pdfLabelUrl); - React.useEffect(() => { - $updateEnergyLabelMetafieldForm.fields.energyClass.listen(() => { + $updateEnergyLabelMetafieldForm.fields.energyClass.listen((data) => { setFormChanged(hasFormChanged($updateEnergyLabelMetafieldForm)); }); $updateEnergyLabelMetafieldForm.fields.pdfLabelUrl.listen(() => { @@ -53,7 +51,7 @@ export const UpdateEnergyLabelMetaFieldsBlock: React.FC = (props) => { } $banner.set({ tone: 'success', - content: 'todo', + content: t('banner.success.energyLabelReset'), source: 'RESET_METAFIELD' }); $energyLabel.set(null); @@ -96,6 +94,7 @@ export const UpdateEnergyLabelMetaFieldsBlock: React.FC = (props) => { field={field('pdfLabelUrl')} disabled={isSubmitting} /> + {/* */} @@ -135,6 +134,10 @@ export const UpdateEnergyLabelMetaFieldsBlock: React.FC = (props) => { +
+ +
+ {t('banner.info.eprelNotice.content1')} @@ -152,4 +155,6 @@ export const UpdateEnergyLabelMetaFieldsBlock: React.FC = (props) => { interface TProps { productId: string; + energyLabel: TEnergyLabel; + primaryLocale: string; } diff --git a/apps/sfy-eu-blocks-app/admin-block-extension/src/components/index.ts b/apps/sfy-eu-blocks-app/admin-block-extension/src/components/index.ts index 6120e36..cb4fd6e 100644 --- a/apps/sfy-eu-blocks-app/admin-block-extension/src/components/index.ts +++ b/apps/sfy-eu-blocks-app/admin-block-extension/src/components/index.ts @@ -1,4 +1,5 @@ export * from './BannerBlock'; +export * from './EnergyLabelPreview'; export * from './FormTextField'; export * from './LoadEnergyLabelSection'; export * from './UpdateEnergyLabelMetaFieldsSection'; diff --git a/apps/sfy-eu-blocks-app/admin-block-extension/src/environment/config/core-api.config.ts b/apps/sfy-eu-blocks-app/admin-block-extension/src/environment/config/core-api.config.ts index fbbe94d..f31b16d 100644 --- a/apps/sfy-eu-blocks-app/admin-block-extension/src/environment/config/core-api.config.ts +++ b/apps/sfy-eu-blocks-app/admin-block-extension/src/environment/config/core-api.config.ts @@ -1,3 +1,3 @@ export const coreApiConfig = { - baseUrl: 'https://rica-neither-reducing-legends.trycloudflare.com' // TODO: Find way to sync url from api-core-node + baseUrl: 'https://excess-dear-samsung-blonde.trycloudflare.com' // TODO: Find way to sync url from api-core-node }; diff --git a/apps/sfy-eu-blocks-app/admin-block-extension/src/environment/extension/i18n.ts b/apps/sfy-eu-blocks-app/admin-block-extension/src/environment/extension/i18n.ts index 3d03e23..4cf26bc 100644 --- a/apps/sfy-eu-blocks-app/admin-block-extension/src/environment/extension/i18n.ts +++ b/apps/sfy-eu-blocks-app/admin-block-extension/src/environment/extension/i18n.ts @@ -28,4 +28,5 @@ type TTranslationOptions = { 'banner.error.notFound': { registrationNumber: string }; 'banner.success.energyLabelFound': { productName: string }; 'banner.success.energyLabelUpdated': { productName: string }; + 'banner.warning.noDatasheetForLocale.content': { locale: string }; }; diff --git a/apps/sfy-eu-blocks-app/admin-block-extension/src/index.tsx b/apps/sfy-eu-blocks-app/admin-block-extension/src/index.tsx index 9818fdc..9f3b763 100644 --- a/apps/sfy-eu-blocks-app/admin-block-extension/src/index.tsx +++ b/apps/sfy-eu-blocks-app/admin-block-extension/src/index.tsx @@ -15,7 +15,8 @@ import { $banner, $energyLabel, applyEnergyLabelToUpdateMetafieldForm, - getEnergyLabelFormMetafields + getEnergyLabelFormMetafields, + getShopLocales } from './lib'; const queryClient = new QueryClient(); @@ -39,6 +40,21 @@ export default reactExtension(appConfig.target, async (api) => { return ; } + const shopLocalesResult = await getShopLocales(); + if (shopLocalesResult.isErr()) { + return ( + + ); + } + const primaryLocale = + shopLocalesResult.value.shopLocales.find((local) => local.primary)?.locale.toUpperCase() ?? + 'EN'; + const energyLabelResult = await getEnergyLabelFormMetafields(productId); if (energyLabelResult.isErr()) { return ( @@ -59,13 +75,13 @@ export default reactExtension(appConfig.target, async (api) => { return ( - + ); }); const Extension: React.FC = (props) => { - const { productId } = props; + const { productId, primaryLocale } = props; const banner = useGlobalState($banner); const energyLabel = useGlobalState($energyLabel); @@ -85,7 +101,11 @@ const Extension: React.FC = (props) => { )} {energyLabel != null ? ( - + ) : ( )} @@ -96,4 +116,5 @@ const Extension: React.FC = (props) => { interface TProps { productId: string; + primaryLocale: string; } diff --git a/apps/sfy-eu-blocks-app/admin-block-extension/src/lib/forms/update-energy-label-metafield.form.ts b/apps/sfy-eu-blocks-app/admin-block-extension/src/lib/forms/update-energy-label-metafield.form.ts index 0d10315..c692127 100644 --- a/apps/sfy-eu-blocks-app/admin-block-extension/src/lib/forms/update-energy-label-metafield.form.ts +++ b/apps/sfy-eu-blocks-app/admin-block-extension/src/lib/forms/update-energy-label-metafield.form.ts @@ -42,8 +42,11 @@ export const $updateEnergyLabelMetafieldForm = createForm({ validator: vValidator( v.pipe( v.string(), - v.url(() => t('validation.labelUrl.invalidFormat')) - // TODO: needs to end with .pdf + v.url(() => t('validation.labelUrl.invalidFormat')), + v.custom( + (url) => typeof url === 'string' && url.endsWith('.pdf'), + () => t('validation.labelUrl.mustEndWithPdf') + ) ) ) } diff --git a/apps/sfy-eu-blocks-app/admin-block-extension/src/lib/graphql/get-shop-locales.query.ts b/apps/sfy-eu-blocks-app/admin-block-extension/src/lib/graphql/get-shop-locales.query.ts new file mode 100644 index 0000000..b768191 --- /dev/null +++ b/apps/sfy-eu-blocks-app/admin-block-extension/src/lib/graphql/get-shop-locales.query.ts @@ -0,0 +1,24 @@ +import { gql } from 'feature-fetch'; + +import { q } from '../../environment'; + +export async function getShopLocales() { + return await q<{}, TGetShopLocalesQueryResponseData>( + gql` + query shopInfo { + shopLocales { + locale + primary + } + } + `, + {} + ); +} + +export interface TGetShopLocalesQueryResponseData { + shopLocales: { + locale: string; + primary: boolean; + }[]; +} diff --git a/apps/sfy-eu-blocks-app/admin-block-extension/src/lib/graphql/index.ts b/apps/sfy-eu-blocks-app/admin-block-extension/src/lib/graphql/index.ts index 6140e83..8ca3f39 100644 --- a/apps/sfy-eu-blocks-app/admin-block-extension/src/lib/graphql/index.ts +++ b/apps/sfy-eu-blocks-app/admin-block-extension/src/lib/graphql/index.ts @@ -1,3 +1,4 @@ export * from './delete-metafield.mutation'; export * from './get-metafield.query'; +export * from './get-shop-locales.query'; export * from './update-metafield.mutation'; diff --git a/apps/sfy-eu-blocks-app/app/shopify.app.toml b/apps/sfy-eu-blocks-app/app/shopify.app.toml index 394d898..7186363 100644 --- a/apps/sfy-eu-blocks-app/app/shopify.app.toml +++ b/apps/sfy-eu-blocks-app/app/shopify.app.toml @@ -8,7 +8,7 @@ extension_directories = [ ] name = "eu-blocks" handle = "eu-blocks" -application_url = "https://jpg-conjunction-prefers-chip.trycloudflare.com" +application_url = "https://agreed-merchant-mason-wallet.trycloudflare.com" embedded = true [build] @@ -18,13 +18,13 @@ include_config_on_deploy = true [access_scopes] # Learn more at https://shopify.dev/docs/apps/tools/cli/configuration#access_scopes -scopes = "read_files,read_products,write_files,write_products" +scopes = "read_files,read_locales,read_products,write_files,write_products" [auth] redirect_urls = [ - "https://jpg-conjunction-prefers-chip.trycloudflare.com/auth/callback", - "https://jpg-conjunction-prefers-chip.trycloudflare.com/auth/shopify/callback", - "https://jpg-conjunction-prefers-chip.trycloudflare.com/api/auth/callback" + "https://agreed-merchant-mason-wallet.trycloudflare.com/auth/callback", + "https://agreed-merchant-mason-wallet.trycloudflare.com/auth/shopify/callback", + "https://agreed-merchant-mason-wallet.trycloudflare.com/api/auth/callback" ] [webhooks] diff --git a/packages/api-energy-label/src/app/routes/v1.efficiency-class/index.tsx b/packages/api-energy-label/src/app/routes/v1.efficiency-class/index.tsx index d8a2e3f..8910e68 100644 --- a/packages/api-energy-label/src/app/routes/v1.efficiency-class/index.tsx +++ b/packages/api-energy-label/src/app/routes/v1.efficiency-class/index.tsx @@ -4,7 +4,7 @@ import { vValidator } from 'validation-adapters/valibot'; import { openApiRouter } from '../../router'; import { EfficiencyArrowLg, EfficiencyArrowSm } from './components'; -openApiRouter.get('/efficiency-class/arrow', { +openApiRouter.get('/efficiency-class/arrow.svg', { queryValidator: vValidator( v.object({ efficiencyClass: v.picklist(['A', 'B', 'C', 'D', 'E', 'F', 'G']), diff --git a/packages/types/src/api/.openapi/core-api-v1.ts b/packages/types/src/api/.openapi/core-api-v1.ts index e10164e..f6cab15 100644 --- a/packages/types/src/api/.openapi/core-api-v1.ts +++ b/packages/types/src/api/.openapi/core-api-v1.ts @@ -412,7 +412,7 @@ export interface paths { patch?: never; trace?: never; }; - "/v1/energy-label/efficiency-class/arrow": { + "/v1/energy-label/efficiency-class/arrow.svg": { parameters: { query?: never; header?: never; diff --git a/packages/types/src/api/.openapi/energy-label-api-v1.ts b/packages/types/src/api/.openapi/energy-label-api-v1.ts index 234cd5b..3a6f0d0 100644 --- a/packages/types/src/api/.openapi/energy-label-api-v1.ts +++ b/packages/types/src/api/.openapi/energy-label-api-v1.ts @@ -303,7 +303,7 @@ export interface paths { patch?: never; trace?: never; }; - "/efficiency-class/arrow": { + "/efficiency-class/arrow.svg": { parameters: { query?: never; header?: never; diff --git a/packages/types/src/api/resources/core-api_v1.yaml b/packages/types/src/api/resources/core-api_v1.yaml index 10a2d98..fd7ff70 100644 --- a/packages/types/src/api/resources/core-api_v1.yaml +++ b/packages/types/src/api/resources/core-api_v1.yaml @@ -53,8 +53,8 @@ paths: $ref: './energy-label-api_v1.yaml#/paths/~1product~1{registrationNumber}~1sheet' /v1/energy-label/product/{registrationNumber}/label: $ref: './energy-label-api_v1.yaml#/paths/~1product~1{registrationNumber}~1label' - /v1/energy-label/efficiency-class/arrow: - $ref: './energy-label-api_v1.yaml#/paths/~1efficiency-class~1arrow' + /v1/energy-label/efficiency-class/arrow.svg: + $ref: './energy-label-api_v1.yaml#/paths/~1efficiency-class~1arrow.svg' components: schemas: diff --git a/packages/types/src/api/resources/energy-label-api_v1.yaml b/packages/types/src/api/resources/energy-label-api_v1.yaml index 795ea6b..0c35797 100644 --- a/packages/types/src/api/resources/energy-label-api_v1.yaml +++ b/packages/types/src/api/resources/energy-label-api_v1.yaml @@ -182,7 +182,7 @@ paths: '500': $ref: './common.yaml#/components/responses/InternalServerError' - /efficiency-class/arrow: + /efficiency-class/arrow.svg: get: summary: Get energy efficiency class arrow as SVG description: Retrieves an SVG of the energy efficiency class arrow for a given energy class.