diff --git a/.env.template b/.env.template index 96797e7..4d40434 100644 --- a/.env.template +++ b/.env.template @@ -1,2 +1,3 @@ REACT_APP_KONTENT_PROJECT_ID= -# REACT_APP_KONTENT_GRAPHQL_ENDPOINT= \ No newline at end of file +# REACT_APP_KONTENT_GRAPHQL_ENDPOINT= +# REACT_APP_GA_ANALYTICS_TOKEN= \ No newline at end of file diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 93748a0..8d0d37b 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -23,6 +23,8 @@ jobs: cache: 'npm' - run: npm ci - run: npm run build --if-present + env: + REACT_APP_GA_ANALYTICS_TOKEN: ${{ secrets.REACT_APP_GA_ANALYTICS_TOKEN }} - name: Deploy 🚀 if: github.ref == 'refs/heads/main' uses: JamesIves/github-pages-deploy-action@3.6.2 diff --git a/.gitignore b/.gitignore index e31e74e..e6bedf0 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ # misc .DS_Store +.env .env.local .env.development.local .env.test.local diff --git a/README.md b/README.md index 1aa3f79..d9906d2 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ This optional section allows you to create your own copy of the project in Konte > ```sh > npm i -g @kentico/kontent-backup-manager@3.0.1 > # or - > yarn global add @kentico/kontent-backup-manager + > yarn global add @kentico/kontent-backup-manager@3.0.1 > > kbm --action=restore --projectId= --apiKey= --zipFilename=kontent-backup > ``` @@ -67,10 +67,11 @@ This optional section allows you to create your own copy of the project in Konte > By default, the content is loaded from a shared Kentico Kontent project. If you want to use your own clone of the project so that you can customize it and experiment with Kontent, continue to the next section. -| Variable | Required | Description | -| :--------------------------------: | :------: | :----------------------- | -| REACT_APP_KONTENT_PROJECT_ID | NO | Project identification | -| REACT_APP_KONTENT_GRAPHQL_ENDPOINT | NO | Kontent GraphQL endpoint | +| Variable | Required | Description | +| :--------------------------------: | :------: | :--------------------------------------------------------------------------------------- | +| REACT_APP_KONTENT_PROJECT_ID | NO | Project identification | +| REACT_APP_KONTENT_GRAPHQL_ENDPOINT | NO | Kontent GraphQL endpoint | +| REACT_APP_GA_ANALYTICS_TOKEN | NO | If you want to inject [Google analytics](https://developers.google.com/analytics) script | ## Content editing development @@ -547,6 +548,10 @@ query PostsQuery($persona: String) { --- +## Tracking + +The package is including tracking header to the requests to Kontent, which helps to identify the adoption of the source plugin and helps to analyze what happened in case of error. If you think that tracking should be optional feel free to raise the feature or pull request. + ## Learn More > This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). diff --git a/package-lock.json b/package-lock.json index 3d38d96..af89365 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "kontent-sample-app-graphql-react", + "name": "@kentico/kontent-sample-app-graphql-react", "version": "0.1.0", "lockfileVersion": 1, "requires": true, @@ -11897,6 +11897,11 @@ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz", "integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==" }, + "react-ga": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/react-ga/-/react-ga-3.3.0.tgz", + "integrity": "sha512-o8RScHj6Lb8cwy3GMrVH6NJvL+y0zpJvKtc0+wmH7Bt23rszJmnqEQxRbyrqUzk9DTJIHoP42bfO5rswC9SWBQ==" + }, "react-helmet-async": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/react-helmet-async/-/react-helmet-async-1.0.9.tgz", diff --git a/package.json b/package.json index e4c7398..3ca9e94 100644 --- a/package.json +++ b/package.json @@ -1,31 +1,7 @@ { - "name": "kontent-sample-app-graphql-react", + "name": "@kentico/kontent-sample-app-graphql-react", "version": "0.1.0", - "private": true, - "dependencies": { - "@apollo/client": "^3.4.17", - "@kentico/kontent-delivery": "^10.4.1", - "@material-ui/core": "^4.11.4", - "@material-ui/icons": "^4.11.2", - "@testing-library/jest-dom": "^5.11.4", - "@testing-library/react": "^11.1.0", - "@testing-library/user-event": "^12.1.10", - "clsx": "^1.1.1", - "graphql": "^15.5.0", - "html-react-parser": "^1.2.6", - "lodash.camelcase": "^4.3.0", - "lodash.get": "^4.4.2", - "lodash.upperfirst": "^4.3.1", - "material-ui-image": "^3.3.2", - "prop-types": "^15.7.2", - "react": "^17.0.2", - "react-dom": "^17.0.2", - "react-helmet-async": "^1.0.9", - "react-router-dom": "^5.2.0", - "react-scripts": "4.0.3", - "rxjs": "^7.0.0", - "web-vitals": "^1.0.1" - }, + "homepage": "https://kentico.github.io/kontent-sample-app-graphql-react/", "scripts": { "start": "react-scripts start", "build": "react-scripts build", @@ -49,5 +25,30 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "dependencies": { + "@apollo/client": "^3.4.17", + "@kentico/kontent-delivery": "^10.4.1", + "@material-ui/core": "^4.11.4", + "@material-ui/icons": "^4.11.2", + "@testing-library/jest-dom": "^5.11.4", + "@testing-library/react": "^11.1.0", + "@testing-library/user-event": "^12.1.10", + "clsx": "^1.1.1", + "graphql": "^15.5.0", + "html-react-parser": "^1.2.6", + "lodash.camelcase": "^4.3.0", + "lodash.get": "^4.4.2", + "lodash.upperfirst": "^4.3.1", + "material-ui-image": "^3.3.2", + "prop-types": "^15.7.2", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "react-ga": "^3.3.0", + "react-helmet-async": "^1.0.9", + "react-router-dom": "^5.2.0", + "react-scripts": "4.0.3", + "rxjs": "^7.0.0", + "web-vitals": "^1.0.1" } -} +} \ No newline at end of file diff --git a/src/App.js b/src/App.js index 83a90b2..bdac567 100644 --- a/src/App.js +++ b/src/App.js @@ -1,9 +1,11 @@ -import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; +import { Router, Switch, Route } from "react-router-dom"; import React, { useState } from "react"; +import ReactGA from 'react-ga'; +import { createBrowserHistory } from 'history'; import { gql, useQuery } from "@apollo/client"; import get from "lodash.get"; import Post from "./Post"; -import { getUrlSlug } from "./utils"; +import { getUrlFromMappingByPathName } from "./utils"; import LandingPage from "./LandingPage"; import ListingPage from "./ListingPage"; import SimplePage from "./SimplePage"; @@ -18,7 +20,7 @@ import GraphQLLoader from "./components/GraphQLLoader"; import getSeo from "./utils/getSeo"; import { getListingPaginationAndFilter } from "./utils/queryString"; -export default function App() { +export default function App(props) { const homePageQuery = gql` query HomePageQuery($codename: String!) { post_All { @@ -165,7 +167,7 @@ export default function App() { }); return mappings.reduce((result, item) => { - result[getUrlSlug(item.slug)] = { + result[[].concat(item.slug).join("/")] = { navigationCodename: item.navigationCodename, navigationType: item.navigationType, contentCodename: item.contentCodename, @@ -211,8 +213,16 @@ export default function App() { return ; } + const history = createBrowserHistory(); + if (props.initializeAnalytics) { + history.listen(location => { + ReactGA.set({ page: location.pathname }); + ReactGA.pageview(location.pathname); + }); + } + return ( - + @@ -220,7 +230,7 @@ export default function App() { ); function renderPage({ location }) { - const navigationItem = mappings[getUrlSlug(location.pathname)]; + const navigationItem = getUrlFromMappingByPathName(mappings, location.pathname); if (!navigationItem) { if (process.env.NODE_ENV === "development") { diff --git a/src/components/RichText.js b/src/components/RichText.js index 2d3b316..743c20e 100644 --- a/src/components/RichText.js +++ b/src/components/RichText.js @@ -1,7 +1,7 @@ import { makeStyles, Typography, useTheme } from "@material-ui/core"; import get from "lodash.get"; import { Image, Link } from "."; -import { getUrlFromMapping } from "../utils"; +import { getUrlFromMappingByCodename } from "../utils"; import RichTextComponent from "./RichTextComponent"; const useStyles = makeStyles((theme) => ({ @@ -84,7 +84,7 @@ function RichText(props) { ); }} resolveLink={(link, mappings, domNode, domToReact) => { - const url = getUrlFromMapping(mappings, link._system_.codename); + const url = getUrlFromMappingByCodename(mappings, link._system_.codename); if (url) { return ( diff --git a/src/index.js b/src/index.js index 1534bdd..823b5db 100644 --- a/src/index.js +++ b/src/index.js @@ -1,13 +1,14 @@ import React from 'react'; import ReactDOM from 'react-dom'; +import ReactGA from 'react-ga'; import App from './App'; import { HelmetProvider } from 'react-helmet-async'; import { ApolloClient, ApolloProvider, InMemoryCache } from '@apollo/client'; - +import { name, version } from "../package.json"; const GQL_ENDPOINT = process.env.REACT_APP_KONTENT_GRAPHQL_ENDPOINT || "https://graphql.kontent.ai"; const PROJECT_ID = process.env.REACT_APP_KONTENT_PROJECT_ID || "ad25961e-f934-01dc-e1fa-f4dd41b84df2"; - +const GA_TOKEN = process.env.REACT_APP_GA_ANALYTICS_TOKEN; const client = new ApolloClient({ cache: new InMemoryCache({ @@ -21,13 +22,18 @@ const client = new ApolloClient({ // } }), uri: `${GQL_ENDPOINT}/${PROJECT_ID}`, + headers: { + 'X-KC-SOURCE': `${name};${version}` + } }); +GA_TOKEN && ReactGA.initialize(GA_TOKEN); + ReactDOM.render( - + , diff --git a/src/utils/getUrlFromMapping.js b/src/utils/getUrlFromMapping.js index 1a60f83..de26fe1 100644 --- a/src/utils/getUrlFromMapping.js +++ b/src/utils/getUrlFromMapping.js @@ -1,3 +1,17 @@ -export default function getUrlFromMapping(mappings, codename) { +export function getUrlFromMappingByCodename(mappings, codename) { return Object.keys(mappings).find(key => mappings[key].navigationCodename === codename); } + +export function getUrlFromMappingByPathName(mappings, pathname) { + debugger; + + let unifiedPath = pathname; + + if (pathname.startsWith(process.env.PUBLIC_URL)) { + unifiedPath = pathname.replace(process.env.PUBLIC_URL, ""); + } + + unifiedPath = unifiedPath.startsWith("/") ? unifiedPath.substring(1) : unifiedPath; + + return mappings[unifiedPath]; +} \ No newline at end of file diff --git a/src/utils/getUrlSlug.js b/src/utils/getUrlSlug.js index c2014e6..9c614b8 100644 --- a/src/utils/getUrlSlug.js +++ b/src/utils/getUrlSlug.js @@ -2,5 +2,9 @@ export default function getUrlSlug(slugPartsArrayOrString) { const slugParts = [].concat(slugPartsArrayOrString); const slug = slugParts.join("/"); - return slug.startsWith("/") ? slug : `/${slug}` + if (!slug || slug === "/") { + return process.env.PUBLIC_URL || "/" + } + + return process.env.PUBLIC_URL + (slug.startsWith("/") ? slug : `/${slug}`) } diff --git a/src/utils/index.js b/src/utils/index.js index 25cfb0c..03c0132 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -1,10 +1,11 @@ -import getUrlFromMapping from "./getUrlFromMapping"; +import { getUrlFromMappingByCodename, getUrlFromMappingByPathName } from "./getUrlFromMapping"; import kontentImageLoader from "./kontentImageLoader"; import srcIsKontentAsset from "./srcIsKontentAsset"; import getUrlSlug from "./getUrlSlug"; export { - getUrlFromMapping, + getUrlFromMappingByCodename, + getUrlFromMappingByPathName, kontentImageLoader, srcIsKontentAsset, getUrlSlug,