diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 0000000..446cece
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,10 @@
+const config = {
+ parser: 'babel-eslint',
+ extends: ['@magento'],
+ rules: {
+ 'no-undef': 'off',
+ 'no-useless-escape': 'off'
+ }
+};
+
+module.exports = config;
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1e1513c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+.idea
+.vscode
+coverage
+node_modules
+storybook-dist
+test-results
+dist
+.DS_Store
+.env
+build-stats.json
+npm-debug.log
+lastCachedGraphQLSchema.json
+test-report.xml
+test-results.json
+yarn-error.log
diff --git a/README.md b/README.md
index acc58c3..85fa07b 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,82 @@
-# mageworx-seo-veniapwa
\ No newline at end of file
+# MageWorx SEO Suite Ultimate extension for Magento Venia PWA
+This add-on integrates [SEO Suite Ultimate extension for Magento 2](https://www.mageworx.com/magento2-extensions/seo-tools-services.html) using [MageWorx SeoBase GraphQl extension](https://github.com/mageworx/MageWorx_SeoBaseGraphQl) with [Magento 2 Venia PWA storefront](https://magento.github.io/pwa-studio/venia-pwa-concept/).
+
+## Features
+- Canonical URLs to eliminate duplicate content
+- Alternate URLs
+- Meta robots
+- Rich snippets
+- Seller & Website markup
+
+## Upload the extension
+1. Create directory `@mageworx/seo-veniapwa` in the root of your project
+2. Copy this project to `@mageworx/seo-veniapwa`
+3. Run `yarn add file:./@mageworx/seo-veniapwa` in the root of your project
+4. Open `local-intercept.js` in the root of your project and put this code into `function localIntercept`. Pay attention, `function localIntercept` must have `targets` as parameter (you can see example of `local-intercept.js` in `@mageworx/seo-veniapwa/documentation`).
+```
+/* MageWorx seo-veniapwa veniapwa start */
+const seoTargetables = Targetables.using(targets);
+
+// product
+const ProductDetails_seo = seoTargetables.reactComponent(
+ '@magento/venia-ui/lib/components/ProductFullDetail/productFullDetail.js'
+);
+const SeoProductDetails = ProductDetails_seo.addImport("{Seo} from '../../../../../../@mageworx/seo-veniapwa/src/components/Seo'");
+ProductDetails_seo.insertAfterJSX(
+ '',
+ `<${SeoProductDetails} seoData={productDetails.seoAttributes}/>`
+);
+
+// category
+const CategoryContent_seo = seoTargetables.reactComponent(
+ '@magento/venia-ui/lib/RootComponents/Category/categoryContent.js'
+);
+const SeoCategoryContent = CategoryContent_seo.addImport("{Seo} from '../../../../../../@mageworx/seo-veniapwa/src/components/Seo'");
+CategoryContent_seo.insertAfterJSX(
+ '',
+ `<${SeoCategoryContent} seoData={talonProps.seoAttributes} />`
+);
+
+// cms page
+const CmsPage_seo = seoTargetables.reactComponent(
+ '@magento/venia-ui/lib/RootComponents/CMS/cms.js'
+);
+const SeoCmsPage = CmsPage_seo.addImport("{Seo} from '../../../../../../@mageworx/seo-veniapwa/src/components/Seo'");
+CmsPage_seo.surroundJSX(
+ '',
+ `
`
+);
+CmsPage_seo.insertAfterJSX(
+ '
',
+ `<${SeoCmsPage} seoData={talonProps.seoAttributes} />`
+);
+/* MageWorx seo-veniapwa veniapwa end */
+```
+5. Check that your `local-intercept` has this code before `module.exports`, if don't have you should add them (you can see example of `local-intercept.js` in `@mageworx/seo-veniapwa/documentation`)
+```
+const { Targetables } = require('@magento/pwa-buildpack');
+```
+6. Let's run your project
+```
+yarn watch
+```
+
+## Urls config
+You can change or add your custom urls for hreflang in `@mageworx/seo-veniapwa/src/hreflangs.config.js`, for exmaple:
+```
+const hreflangs_config = {
+ ...
+ {type: "store", code: "default", url: "https://magento-store.com/"},
+ {type: "store", code: "toys", url: "https://magento-toys.com/"},
+ {type: "lang", code: "de-DE", url: "https://magento-store.com/de/"},
+ {type: "lang", code: "en-US", url: "https://magento-store.com/en/"},
+ ...
+};
+```
+On frontend it will be look like:
+```
+
+
+```
+
+
diff --git a/babel.config.json b/babel.config.json
new file mode 100644
index 0000000..6f51179
--- /dev/null
+++ b/babel.config.json
@@ -0,0 +1,10 @@
+{
+ "presets": [
+ "@babel/preset-react",
+ "@babel/preset-env"
+ ],
+ "plugins": [
+ "@babel/plugin-transform-react-jsx",
+ "@babel/plugin-proposal-class-properties"
+ ]
+}
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..3269945
--- /dev/null
+++ b/package.json
@@ -0,0 +1,51 @@
+{
+ "name": "@mageworx/seo-veniapwa",
+ "author": "MageWorx",
+ "version": "1.0.0",
+ "main": "src/index.js",
+ "pwa-studio": {
+ "targets": {
+ "intercept": "src/intercept.js"
+ }
+ },
+ "scripts": {
+ "format": "prettier --ignore-path .gitignore \"src/**/*.+(ts|js|tsx)\" --write",
+ "lint": "eslint --ignore-path .gitignore 'src/**/{*.js,package.json}'",
+ "prepare": "install-peers"
+ },
+ "lint-staged": {
+ "./src/**/*.{ts,js,jsx,tsx}": [
+ "yarn lint --fix",
+ "yarn format"
+ ]
+ },
+ "peerDependencies": {
+ "@apollo/client": "~3.1.2",
+ "@magento/peregrine": "~7.0.0",
+ "@magento/pwa-buildpack": ">=6.0.0",
+ "@magento/venia-ui": "~4.0.0",
+ "react": "~16.9.0",
+ "react-dom": "^16.12.0",
+ "graphql-tag": "~2.10.1",
+ "webpack": "~4.38.0"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.11.6",
+ "@babel/plugin-proposal-class-properties": "^7.12.1",
+ "@babel/plugin-syntax-class-properties": "^7.12.1",
+ "babel-eslint": "~10.0.1",
+ "babel-preset-env": "^1.7.0",
+ "babel-preset-react": "^6.24.1",
+ "eslint": "^7.32.0",
+ "eslint-config-prettier": "^6.15.0",
+ "eslint-plugin-jsx-a11y": "^6.0.3",
+ "eslint-plugin-package-json": "^0.1.4",
+ "eslint-plugin-react": "^7.9.1",
+ "eslint-plugin-react-hooks": "^1.6.0",
+ "identity-obj-proxy": "^3.0.0",
+ "install-peers-cli": "^2.2.0",
+ "lint-staged": "^10.0.8",
+ "prettier": "^1.9.2",
+ "prettier-check": "^2.0.0"
+ }
+}
diff --git a/prettier.config.js b/prettier.config.js
new file mode 100644
index 0000000..7d36ec7
--- /dev/null
+++ b/prettier.config.js
@@ -0,0 +1,7 @@
+const config = {
+ singleQuote: true,
+ tabWidth: 4,
+ trailingComma: 'none'
+};
+
+module.exports = config;
diff --git a/src/components/Seo/Canonical.js b/src/components/Seo/Canonical.js
new file mode 100644
index 0000000..e69d2bf
--- /dev/null
+++ b/src/components/Seo/Canonical.js
@@ -0,0 +1,34 @@
+import React from 'react';
+import { Helmet } from 'react-helmet-async';
+
+import {findCodeFromConfig, getCurrentHostname} from "./features";
+
+const Canonical = props => {
+ const {canonical} = props;
+ if (!canonical) return null;
+
+ let canonical_result
+ if (canonical.url) {
+ if (canonical.url.match(/https?:\/\//i)) {
+ canonical_result =
;
+ }
+ else if (canonical.code) {
+ let elem_from_config = findCodeFromConfig(canonical.code, "store", true);
+ if (elem_from_config) {
+ canonical_result =
;
+ }
+ else return null;
+ }
+ else {
+ canonical_result =
;
+ }
+ return (
+
+ {canonical_result}
+
+ );
+ }
+ return null;
+};
+
+export default Canonical;
diff --git a/src/components/Seo/Hreflangs.js b/src/components/Seo/Hreflangs.js
new file mode 100644
index 0000000..85324b6
--- /dev/null
+++ b/src/components/Seo/Hreflangs.js
@@ -0,0 +1,26 @@
+import React from 'react';
+import { Helmet } from 'react-helmet-async';
+
+import {findCodeFromConfig} from "./features";
+
+const Hreflangs = props => {
+ const {mw_hreflangs} = props;
+ if (!mw_hreflangs || !mw_hreflangs.items) return null;
+
+ let hreflangs_result = mw_hreflangs.items.map((item, itemNum) => {
+ let elem_from_config = findCodeFromConfig(item.code, "lang",true);
+ if (elem_from_config) {
+ let url = elem_from_config + item.url;
+ return
;
+ }
+ else return null;
+ })
+ return (
+
+ {hreflangs_result}
+
+ );
+ return null;
+};
+
+export default Hreflangs;
diff --git a/src/components/Seo/Markup.js b/src/components/Seo/Markup.js
new file mode 100644
index 0000000..901720f
--- /dev/null
+++ b/src/components/Seo/Markup.js
@@ -0,0 +1,100 @@
+import React from 'react';
+import { Helmet } from 'react-helmet-async';
+
+import {getCurrentHostname} from "./features";
+
+const changeAllUrlsToLocal = (str) => {
+ str = str.replaceAll(/\\\//g, "/");
+ let resultUrl = str;
+
+ let currentHost = getCurrentHostname();
+ // for meta content
+ resultUrl = resultUrl.replaceAll(/^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/g, `${currentHost}$2"`);
+ // for script content (only url and image) (:?"image":")]
+ resultUrl = resultUrl.replaceAll(/(:?"url":")https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)"/g, `$1${currentHost}$3"`);
+ resultUrl = resultUrl.replaceAll(/(:?"image":")https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)"/g, `$1${currentHost}$3"`);
+ return resultUrl;
+}
+
+const getMetaArrayFromStr = (str) => {
+ return str.match(/
/gi);
+}
+
+const getScriptsArrayFromStr = (str) => {
+ return str.match(/
.+?<\/script>/gi);
+}
+
+const getContentAndAttributesOfMeta = (str) => {
+ const metaData = {
+ content: "",
+ property: "",
+ }
+ let content = str.match(//i);
+ let property = str.match(//i);
+ if (content && content.length>1) {
+ metaData.content = changeAllUrlsToLocal(content[1]);
+ }
+ if (property && property.length>1) {
+ metaData.property = property[1];
+ }
+ return metaData;
+}
+
+const getContentAndAttributesOfScript = (str) => {
+ const scriptData = {
+ content: "",
+ type: "",
+ }
+ let content = str.match(/(.+?)<\/script>/i);
+ let type = str.match(/.+?<\/script>/i);
+ if (content && content.length>1) {
+ scriptData.content = changeAllUrlsToLocal(content[1]);
+ }
+ if (type && type.length>1) {
+ scriptData.type = type[1];
+ }
+ return scriptData;
+}
+
+const getMetaResultOfJsxFromStr = (str) => {
+ let metaArray = getMetaArrayFromStr(str);
+ let result = metaArray.map((script, num) => {
+ let attributes = getContentAndAttributesOfMeta(script);
+ const {content, property} = attributes;
+ return
+ });
+ return result;
+}
+
+const getScriptsResultOfJsxFromStr = (str) => {
+ let scriptsArray = getScriptsArrayFromStr(str);
+ let result = scriptsArray.map((script, num) => {
+ let attributes = getContentAndAttributesOfScript(script);
+ const {type, content} = attributes;
+ return
+ });
+ return result;
+}
+
+const Markup = props => {
+ const {markup} = props;
+ if (!markup) return null;
+
+ let markup_result = [];
+ if (markup.rich_snippets) {
+ if (markup.rich_snippets.product) markup_result = markup_result.concat(getScriptsResultOfJsxFromStr(markup.rich_snippets.product));
+ if (markup.rich_snippets.seller) markup_result = markup_result.concat(getScriptsResultOfJsxFromStr(markup.rich_snippets.seller));
+ if (markup.rich_snippets.website) markup_result = markup_result.concat(getScriptsResultOfJsxFromStr(markup.rich_snippets.website));
+ if (markup.rich_snippets.webpage) markup_result = markup_result.concat(getScriptsResultOfJsxFromStr(markup.rich_snippets.webpage));
+ }
+ if (markup.social_markup) {
+ markup_result = markup_result.concat(getMetaResultOfJsxFromStr(markup.social_markup));
+ }
+ return (
+
+ {markup_result}
+
+ )
+};
+
+export default Markup;
diff --git a/src/components/Seo/Seo.js b/src/components/Seo/Seo.js
new file mode 100644
index 0000000..86a1342
--- /dev/null
+++ b/src/components/Seo/Seo.js
@@ -0,0 +1,41 @@
+import React from 'react';
+import { Helmet } from 'react-helmet-async';
+
+import Hreflangs from "./Hreflangs";
+import Markup from "./Markup";
+import Canonical from "./Canonical";
+
+const Seo = props => {
+ const {seoData} = props;
+ if (!seoData) return null;
+
+ const {
+ meta_robots,
+ mw_canonical_url,
+ mw_hreflangs,
+ mw_seo_markup
+ } = seoData;
+
+ let canonical;
+ let meta_robots_JSX;
+ let hreflangs;
+ let markup;
+ if (seoData) {
+ meta_robots_JSX = meta_robots && ;
+ canonical =
+ hreflangs = ;
+ markup =
+ }
+
+ return (
+ <>
+
+ {meta_robots_JSX}
+
+ {hreflangs}
+ {markup}
+ {canonical}
+ >
+ )
+};
+export default Seo;
diff --git a/src/components/Seo/features/index.js b/src/components/Seo/features/index.js
new file mode 100644
index 0000000..6e4be54
--- /dev/null
+++ b/src/components/Seo/features/index.js
@@ -0,0 +1,31 @@
+import urls_config from "../../../urls.congif";
+
+export const getCurrentHostname = (isNeedSlash) => {
+ let currentHostname = window.location.protocol + "//" + window.location.hostname;
+ if (window.location.port.length > 0) currentHostname += ":" + window.location.port;
+ if (isNeedSlash) currentHostname += "/";
+ return currentHostname;
+}
+
+export const findCodeFromConfig = (code, type, isNeedSlash) => {
+ const elem = urls_config.find((element) => {
+ if (element.type === type) {
+ if (element.code === code) {
+ return true;
+ }
+ }
+ })
+ if (elem) {
+ let url = elem.url;
+ if (isNeedSlash) {
+ // "http://demo.com" => "http://demo.com/"
+ if (url[url.length - 1] !== "/") url += "/";
+ }
+ else {
+ // "http://demo.com/" => "http://demo.com"
+ if (url[url.length - 1] === "/") url = url.substring(0, url.length - 1);
+ }
+ return url
+ }
+ else return null;
+}
diff --git a/src/components/Seo/index.js b/src/components/Seo/index.js
new file mode 100644
index 0000000..631c902
--- /dev/null
+++ b/src/components/Seo/index.js
@@ -0,0 +1 @@
+export { default as Seo } from "./Seo";
diff --git a/src/documentation/local-intercept.js b/src/documentation/local-intercept.js
new file mode 100755
index 0000000..d262823
--- /dev/null
+++ b/src/documentation/local-intercept.js
@@ -0,0 +1,48 @@
+const { Targetables } = require('@magento/pwa-buildpack');
+
+function localIntercept(targets) {
+ /* MageWorx seo-veniapwa start */
+ const seoTargetables = Targetables.using(targets);
+
+ // product
+ const ProductDetails_seo = seoTargetables.reactComponent(
+ '@magento/venia-ui/lib/components/ProductFullDetail/productFullDetail.js'
+ );
+ const SeoProductDetails = ProductDetails_seo.addImport("{Seo} from '../../../../../../@mageworx/seo-veniapwa/src/components/Seo'");
+ ProductDetails_seo.insertAfterJSX(
+ '',
+ `<${SeoProductDetails} seoData={productDetails.seoAttributes}/>`
+ );
+
+ // category
+ const CategoryContent_seo = seoTargetables.reactComponent(
+ '@magento/venia-ui/lib/RootComponents/Category/categoryContent.js'
+ );
+ const SeoCategoryContent = CategoryContent_seo.addImport("{Seo} from '../../../../../../@mageworx/seo-veniapwa/src/components/Seo'");
+ CategoryContent_seo.insertAfterJSX(
+ '',
+ `<${SeoCategoryContent} seoData={talonProps.seoAttributes} />`
+ );
+
+ // cms page
+ const CmsPage_seo = seoTargetables.reactComponent(
+ '@magento/venia-ui/lib/RootComponents/CMS/cms.js'
+ );
+ const SeoCmsPage = CmsPage_seo.addImport("{Seo} from '../../../../../../@mageworx/seo-veniapwa/src/components/Seo'");
+ CmsPage_seo.surroundJSX(
+ '',
+ ``
+ );
+ CmsPage_seo.insertAfterJSX(
+ '',
+ `<${SeoCmsPage} seoData={talonProps.seoAttributes} />`
+ );
+ CmsPage_seo.insertAfterJSX(
+ '',
+ `<${SeoCmsPage} seoData={talonProps.seoAttributes} />`
+ );
+ /* MageWorx seo-veniapwa end */
+}
+
+module.exports = localIntercept;
+
diff --git a/src/hooks/CMS/useCmsPageSeo.js b/src/hooks/CMS/useCmsPageSeo.js
new file mode 100644
index 0000000..1493b71
--- /dev/null
+++ b/src/hooks/CMS/useCmsPageSeo.js
@@ -0,0 +1,56 @@
+import {useEffect, useMemo} from "react";
+import { useQuery } from "@apollo/client";
+import gql from "graphql-tag";
+
+const GET_CMS_SEO = gql`
+ query GetCmsPageSeo($id: Int!) {
+ cmsPage(id: $id) {
+ mw_canonical_url {
+ url
+ code
+ }
+ meta_robots
+ mw_hreflangs {
+ items {
+ url
+ code
+ }
+ }
+ mw_seo_markup {
+ social_markup
+ rich_snippets {
+ website
+ seller
+ webpage
+ }
+ }
+ }
+ }
+`;
+
+const useCmsPageSeo = props => {
+ const { id } = props;
+
+ const { loading, error, data } = useQuery(GET_CMS_SEO, {
+ fetchPolicy: 'cache-and-network',
+ nextFetchPolicy: 'cache-first',
+ variables: {
+ id: Number(id)
+ }
+ });
+
+ const seoAttributes = useMemo(() => {
+ if (!data || !data.cmsPage) {
+ return null;
+ }
+ return data.cmsPage;
+ }, [data]);
+
+ return {
+ error,
+ loading,
+ seoAttributes
+ };
+};
+
+export default useCmsPageSeo;
diff --git a/src/hooks/Category/useCategorySeo.js b/src/hooks/Category/useCategorySeo.js
new file mode 100644
index 0000000..1eb4cd2
--- /dev/null
+++ b/src/hooks/Category/useCategorySeo.js
@@ -0,0 +1,56 @@
+import { useMemo } from "react";
+import { useQuery } from "@apollo/client";
+import gql from "graphql-tag";
+
+const GET_CATEGORY_SEO = gql`
+ query getCategorySeo($id: Int!) {
+ category(id: $id) {
+ id
+ mw_canonical_url {
+ url
+ code
+ }
+ meta_robots
+ mw_hreflangs {
+ items {
+ url
+ code
+ }
+ }
+ mw_seo_markup {
+ social_markup
+ rich_snippets {
+ website
+ seller
+ }
+ }
+ }
+ }
+`;
+
+const useProductAttachments = (props) => {
+ const { id } = props;
+
+ const { error, loading, data } = useQuery(GET_CATEGORY_SEO, {
+ fetchPolicy: "cache-and-network",
+ nextFetchPolicy: "cache-first",
+ variables: {
+ id
+ }
+ });
+
+ const seoAttributes = useMemo(() => {
+ if (!data || !data.category) {
+ return null;
+ }
+ return data.category;
+ }, [data]);
+
+ return {
+ error,
+ loading,
+ seoAttributes
+ };
+};
+
+export default useProductAttachments;
diff --git a/src/hooks/Product/useProductSeo.js b/src/hooks/Product/useProductSeo.js
new file mode 100644
index 0000000..b8a1f7a
--- /dev/null
+++ b/src/hooks/Product/useProductSeo.js
@@ -0,0 +1,72 @@
+import { useMemo } from "react";
+import { useQuery } from "@apollo/client";
+import gql from "graphql-tag";
+
+const GET_PRODUCT_SEO = gql`
+ query getProductSeo($urlKey: String!) {
+ products(filter: { url_key: { eq: $urlKey } }) {
+ items {
+ url_key
+ mw_canonical_url {
+ url
+ code
+ }
+ meta_robots
+ mw_hreflangs {
+ items {
+ url
+ code
+ }
+ }
+ mw_seo_markup {
+ social_markup
+ rich_snippets {
+ website
+ seller
+ product
+ }
+ }
+ }
+ }
+ }
+`;
+
+const useProductSeo = (props) => {
+ const { urlKey } = props;
+
+ const { error, loading, data } = useQuery(GET_PRODUCT_SEO, {
+ fetchPolicy: "cache-and-network",
+ nextFetchPolicy: "cache-first",
+ variables: {
+ urlKey: urlKey
+ }
+ });
+
+ const seoAttributes = useMemo(() => {
+ if (!data) {
+ // The product isn't in the cache and we don't have a response from GraphQL yet.
+ return null;
+ }
+
+ // Note: if a product is out of stock _and_ the backend specifies not to
+ // display OOS items, the items array will be empty.
+
+ // Only return the product that we queried for.
+ const product = data.products.items.find(
+ item => item.url_key === urlKey
+ );
+
+ if (!product) {
+ return null;
+ }
+ return product;
+ }, [data, urlKey]);
+
+ return {
+ error,
+ loading,
+ seoAttributes
+ };
+};
+
+export default useProductSeo;
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 0000000..3321a04
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1,7 @@
+/**
+ * Custom index for the extension attention this file should be not delete!
+ * It is use as main file but this can be empty by default only need to exits.
+ *
+ * A project index.js should contain default exports like:
+ * export { default } from './components/main';
+ */
diff --git a/src/intercept.js b/src/intercept.js
new file mode 100644
index 0000000..47dcee8
--- /dev/null
+++ b/src/intercept.js
@@ -0,0 +1,36 @@
+/**
+ * Custom intercept file for the extension
+ * By default you can only use target of @magento/pwa-buildpack.
+ */
+
+module.exports = targets => {
+ // For extends productFullDetail component in local-intercept
+ const { Targetables } = require('@magento/pwa-buildpack');
+ const targetables = Targetables.using(targets);
+ targetables.setSpecialFeatures('esModules','cssModules');
+
+ const peregrineTargets = targets.of("@magento/peregrine");
+ const talonsTarget = peregrineTargets.talons;
+
+ // product
+ talonsTarget.tap((talonWrapperConfig) => {
+ talonWrapperConfig.ProductFullDetail.useProductFullDetail.wrapWith(
+ "@mageworx/seo-veniapwa/src/targets/Product/wrapUseProductFullDetails"
+ );
+ });
+
+ // category
+ talonsTarget.tap((talonWrapperConfig) => {
+ talonWrapperConfig.Cms.useCmsPage.wrapWith(
+ "@mageworx/seo-veniapwa/src/targets/CMS/wrapUseCmsPage"
+ );
+ });
+
+ // cms
+ talonsTarget.tap((talonWrapperConfig) => {
+ talonWrapperConfig.RootComponents.Category.useCategoryContent.wrapWith(
+ "@mageworx/seo-veniapwa/src/targets/Category/wrapUseCategoryContent"
+ );
+ });
+
+};
diff --git a/src/targets/CMS/wrapUseCmsPage.js b/src/targets/CMS/wrapUseCmsPage.js
new file mode 100644
index 0000000..bd4ac0f
--- /dev/null
+++ b/src/targets/CMS/wrapUseCmsPage.js
@@ -0,0 +1,23 @@
+import useCmsPageSeo from "../../hooks/CMS/useCmsPageSeo";
+
+const wrapUseCmsPage = (original) => {
+ return function useCmsPage(props, ...restArgs) {
+ const { id } = props;
+
+ const seoQueryResult = useCmsPageSeo({
+ id
+ });
+
+ const { ...defaultReturnData } = original(
+ props,
+ ...restArgs
+ );
+
+ return {
+ ...defaultReturnData,
+ seoAttributes: seoQueryResult.seoAttributes,
+ };
+ };
+};
+
+export default wrapUseCmsPage;
diff --git a/src/targets/Category/wrapUseCategoryContent.js b/src/targets/Category/wrapUseCategoryContent.js
new file mode 100644
index 0000000..7a0f3dd
--- /dev/null
+++ b/src/targets/Category/wrapUseCategoryContent.js
@@ -0,0 +1,23 @@
+import useCategorySeo from "../../hooks/Category/useCategorySeo";
+
+const wrapUseCategoryContent = (original) => {
+ return function useCategoryContent(props, ...restArgs) {
+ const { categoryId } = props;
+
+ const seoQueryResult = useCategorySeo({
+ id: categoryId
+ });
+
+ const { ...defaultReturnData } = original(
+ props,
+ ...restArgs
+ );
+
+ return {
+ ...defaultReturnData,
+ seoAttributes: seoQueryResult.seoAttributes,
+ };
+ };
+};
+
+export default wrapUseCategoryContent;
diff --git a/src/targets/Product/wrapUseProductFullDetails.js b/src/targets/Product/wrapUseProductFullDetails.js
new file mode 100644
index 0000000..0c4ac83
--- /dev/null
+++ b/src/targets/Product/wrapUseProductFullDetails.js
@@ -0,0 +1,26 @@
+import useProductSeo from "../../hooks/Product/useProductSeo";
+
+const wrapUseProductFullDetails = (original) => {
+ return function useProductFullDetails(props, ...restArgs) {
+ const { product } = props;
+
+ const seoQueryResult = useProductSeo({
+ urlKey: product.url_key
+ });
+
+ const { productDetails, ...defaultReturnData } = original(
+ props,
+ ...restArgs
+ );
+
+ return {
+ ...defaultReturnData,
+ productDetails: {
+ ...productDetails,
+ seoAttributes: seoQueryResult.seoAttributes,
+ }
+ };
+ };
+};
+
+export default wrapUseProductFullDetails;
diff --git a/src/urls.congif.js b/src/urls.congif.js
new file mode 100644
index 0000000..b92b624
--- /dev/null
+++ b/src/urls.congif.js
@@ -0,0 +1,14 @@
+/*
+ EXAMPLE:
+ const hreflangs_config = [
+ {type: "store", code: "default", url: "https://magento-store.com/"},
+ {type: "store", code: "toys", url: "https://magento-toys.com/"},
+ {type: "lang", code: "de-DE", url: "https://magento-store.com/de/"},
+ {type: "lang", code: "en-US", url: "https://magento-store.com/en/"},
+ ];
+ */
+
+const urls_config = [];
+
+module.exports = urls_config;
+