diff --git a/pwa/package-lock.json b/pwa/package-lock.json index 900f2005..f51ba7c2 100644 --- a/pwa/package-lock.json +++ b/pwa/package-lock.json @@ -11,6 +11,8 @@ "@conduction/components": "2.2.18", "@conduction/theme": "1.0.51", "@fortawesome/fontawesome-svg-core": "^6.1.1", + "@fortawesome/free-brands-svg-icons": "6.4.2", + "@fortawesome/free-regular-svg-icons": "6.4.2", "@fortawesome/free-solid-svg-icons": "^6.4.0", "@fortawesome/react-fontawesome": "^0.1.18", "@nl-design-system-unstable/amsterdam-design-tokens": "^1.0.0-alpha.107", @@ -2312,6 +2314,48 @@ "node": ">=6" } }, + "node_modules/@fortawesome/free-brands-svg-icons": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.4.2.tgz", + "integrity": "sha512-LKOwJX0I7+mR/cvvf6qIiqcERbdnY+24zgpUSouySml+5w8B4BJOx8EhDR/FTKAu06W12fmUIcv6lzPSwYKGGg==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.4.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-brands-svg-icons/node_modules/@fortawesome/fontawesome-common-types": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.2.tgz", + "integrity": "sha512-1DgP7f+XQIJbLFCTX1V2QnxVmpLdKdzzo2k8EmvDOePfchaIGQ9eCHj2up3/jNEbZuBqel5OxiaOJf37TWauRA==", + "hasInstallScript": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-regular-svg-icons": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.4.2.tgz", + "integrity": "sha512-0+sIUWnkgTVVXVAPQmW4vxb9ZTHv0WstOa3rBx9iPxrrrDH6bNLsDYuwXF9b6fGm+iR7DKQvQshUH/FJm3ed9Q==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.4.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-regular-svg-icons/node_modules/@fortawesome/fontawesome-common-types": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.2.tgz", + "integrity": "sha512-1DgP7f+XQIJbLFCTX1V2QnxVmpLdKdzzo2k8EmvDOePfchaIGQ9eCHj2up3/jNEbZuBqel5OxiaOJf37TWauRA==", + "hasInstallScript": true, + "engines": { + "node": ">=6" + } + }, "node_modules/@fortawesome/free-solid-svg-icons": { "version": "6.4.0", "hasInstallScript": true, diff --git a/pwa/package.json b/pwa/package.json index 3c012350..6ce3ee06 100644 --- a/pwa/package.json +++ b/pwa/package.json @@ -4,9 +4,7 @@ "private": true, "description": "Product Website Template", "author": "Conduction", - "keywords": [ - "gatsby" - ], + "keywords": ["gatsby"], "scripts": { "develop": "gatsby develop", "start": "gatsby develop", @@ -27,6 +25,8 @@ "@conduction/components": "2.2.18", "@conduction/theme": "1.0.51", "@fortawesome/fontawesome-svg-core": "^6.1.1", + "@fortawesome/free-brands-svg-icons": "6.4.2", + "@fortawesome/free-regular-svg-icons": "6.4.2", "@fortawesome/free-solid-svg-icons": "^6.4.0", "@fortawesome/react-fontawesome": "^0.1.18", "@nl-design-system-unstable/amsterdam-design-tokens": "^1.0.0-alpha.107", diff --git a/pwa/src/apiService/apiService.ts b/pwa/src/apiService/apiService.ts index 3382edf8..2a4b8b6a 100644 --- a/pwa/src/apiService/apiService.ts +++ b/pwa/src/apiService/apiService.ts @@ -1,8 +1,11 @@ -import axios, { AxiosInstance, AxiosResponse } from "axios"; import toast from "react-hot-toast"; +import axios, { AxiosInstance, AxiosResponse } from "axios"; +import { removeFileNameFromUrl } from "../services/FileNameFromUrl"; +import { DEFAULT_FOOTER_CONTENT_URL } from "../templates/templateParts/footer/FooterTemplate"; // Resources import OpenWoo from "./resources/openWoo"; +import FooterContent from "./resources/footerContent"; interface PromiseMessage { loading?: string; @@ -28,9 +31,22 @@ export default class APIService { }); } + public get FooterContentClient(): AxiosInstance { + return axios.create({ + baseURL: removeFileNameFromUrl( + process.env.GATSBY_FOOTER_CONTENT !== undefined && process.env.GATSBY_FOOTER_CONTENT.length !== 0 + ? process.env.GATSBY_FOOTER_CONTENT + : DEFAULT_FOOTER_CONTENT_URL, + ), + }); + } + public get OpenWoo(): OpenWoo { return new OpenWoo(this.BaseClient, this.Send); } + public get FooterContent(): FooterContent { + return new FooterContent(this.FooterContentClient, this.Send); + } // Send method public Send: TSendFunction = (instance, method, endpoint, payload, promiseMessage) => { diff --git a/pwa/src/apiService/resources/footerContent.ts b/pwa/src/apiService/resources/footerContent.ts new file mode 100644 index 00000000..7e0f1e74 --- /dev/null +++ b/pwa/src/apiService/resources/footerContent.ts @@ -0,0 +1,18 @@ +import { TSendFunction } from "../apiService"; +import { AxiosInstance } from "axios"; + +export default class FooterContent { + private _instance: AxiosInstance; + private _send: TSendFunction; + + constructor(_instance: AxiosInstance, send: TSendFunction) { + this._instance = _instance; + this._send = send; + } + + public getContent = async (fileName: string): Promise => { + const { data } = await this._send(this._instance, "GET", fileName); + + return data; + }; +} diff --git a/pwa/src/hooks/footerContent.ts b/pwa/src/hooks/footerContent.ts new file mode 100644 index 00000000..71a92ef0 --- /dev/null +++ b/pwa/src/hooks/footerContent.ts @@ -0,0 +1,25 @@ +import * as React from "react"; +import { useQuery } from "react-query"; +import APIService from "../apiService/apiService"; +import APIContext from "../apiService/apiContext"; +import { getFileNameFromUrl } from "../services/FileNameFromUrl"; +import { DEFAULT_FOOTER_CONTENT_URL } from "../templates/templateParts/footer/FooterTemplate"; + +export const useFooterContent = () => { + const API: APIService | null = React.useContext(APIContext); + + const fileName = getFileNameFromUrl( + process.env.GATSBY_FOOTER_CONTENT !== undefined && process.env.GATSBY_FOOTER_CONTENT.length !== 0 + ? process.env.GATSBY_FOOTER_CONTENT + : DEFAULT_FOOTER_CONTENT_URL, + ); + + const getContent = () => + useQuery(["contents", fileName], () => API?.FooterContent.getContent(fileName), { + onError: (error) => { + console.warn(error.message); + }, + }); + + return { getContent }; +}; diff --git a/pwa/src/layout/Layout.tsx b/pwa/src/layout/Layout.tsx index 186edb18..33bd2ae7 100644 --- a/pwa/src/layout/Layout.tsx +++ b/pwa/src/layout/Layout.tsx @@ -8,6 +8,10 @@ import { Head } from "./Head"; import { Content } from "../Content"; import { Document, Surface } from "@utrecht/component-library-react/dist/css-module"; import { Toaster } from "react-hot-toast"; +import { fas } from "@fortawesome/free-solid-svg-icons"; +import { fab } from "@fortawesome/free-brands-svg-icons"; +import { far } from "@fortawesome/free-regular-svg-icons"; +import { IconPack, library } from "@fortawesome/fontawesome-svg-core"; interface LayoutProps { children: React.ReactNode; @@ -19,6 +23,8 @@ const Layout: React.FC = ({ children, pageContext, location }) => { const [API, setAPI] = React.useState(React.useContext(APIContext)); const [globalContext, setGlobalContext] = React.useState(defaultGlobalContext); + library.add(fas, fab as IconPack, far as IconPack); + React.useEffect(() => { setAPI(new APIService()); }, [pageContext]); diff --git a/pwa/src/services/FileNameFromUrl.ts b/pwa/src/services/FileNameFromUrl.ts new file mode 100644 index 00000000..d85105c5 --- /dev/null +++ b/pwa/src/services/FileNameFromUrl.ts @@ -0,0 +1,9 @@ +export const getFileNameFromUrl = (url: string) => { + const finalSlashIndex = url.lastIndexOf("/"); + return url.substring(finalSlashIndex + 1); +}; + +export const removeFileNameFromUrl = (url: string) => { + const finalSlashIndex = url.lastIndexOf("/"); + return url.replace(`/${url.substring(finalSlashIndex + 1)}`, ""); +}; diff --git a/pwa/src/templates/templateParts/footer/FooterContent.json b/pwa/src/templates/templateParts/footer/FooterContent.json new file mode 100644 index 00000000..375aa19c --- /dev/null +++ b/pwa/src/templates/templateParts/footer/FooterContent.json @@ -0,0 +1,24 @@ +[ + { + "title": "Contact", + "items": [ + { + "ariaLabel": "Phone number", + "value": "+31 (0)85 3036840", + "link": "tel:+31 (0)85 3036840", + "icon": { "prefix": "fas", "icon": "phone", "placement": "left" } + }, + { + "ariaLabel": "Email address", + "value": "info@conduction.nl", + "link": "mailto:info@conduction.nl", + "icon": { "prefix": "fas", "icon": "envelope", "placement": "left" } + }, + { + "label": "Address", + "ariaLabel": "Address", + "value": "Lauriergracht 14h" + } + ] + } +] diff --git a/pwa/src/templates/templateParts/footer/FooterTemplate.module.css b/pwa/src/templates/templateParts/footer/FooterTemplate.module.css index 4315112c..a8edee5f 100644 --- a/pwa/src/templates/templateParts/footer/FooterTemplate.module.css +++ b/pwa/src/templates/templateParts/footer/FooterTemplate.module.css @@ -67,7 +67,7 @@ .dynamicSectionContent { display: flex; flex-direction: column; - margin-block-end: 8px; + margin-block-end: 12px; } .logoAndConduction { @@ -77,6 +77,13 @@ align-items: flex-end; } +.iconLeft { + margin-inline-end: var(--utrecht-icon-gap); +} +.iconRight { + margin-inline-start: var(--utrecht-icon-gap); +} + @media only screen and (max-width: 992px) { /* Tablet */ .contentGrid { diff --git a/pwa/src/templates/templateParts/footer/FooterTemplate.tsx b/pwa/src/templates/templateParts/footer/FooterTemplate.tsx index 3dd44467..a4ffceb9 100644 --- a/pwa/src/templates/templateParts/footer/FooterTemplate.tsx +++ b/pwa/src/templates/templateParts/footer/FooterTemplate.tsx @@ -1,19 +1,54 @@ import * as React from "react"; import * as styles from "./FooterTemplate.module.css"; -import { PageFooter, Link, Heading3 } from "@utrecht/component-library-react/dist/css-module"; +import parse from "html-react-parser"; +import { PageFooter, Link, Heading3, Icon } from "@utrecht/component-library-react/dist/css-module"; import { navigate } from "gatsby-link"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faCode, faHeart } from "@fortawesome/free-solid-svg-icons"; import { useTranslation } from "react-i18next"; +import { IconPrefix, IconName } from "@fortawesome/fontawesome-svg-core"; +import { useFooterContent } from "../../../hooks/footerContent"; + +export const DEFAULT_FOOTER_CONTENT_URL = + "https://raw.githubusercontent.com/ConductionNL/woo-website-template/main/pwa/src/templates/templateParts/footer/FooterContent.json"; type TDynamicContentItem = { title: string; - items: { label: string; value: string; link?: string }[]; + items: { + value: string; + ariaLabel: string; + link?: string; + markdownLink?: string; + label?: string; + icon?: { + icon: IconName; + prefix: IconPrefix; + placement: "left" | "right"; + }; + customIcon?: { + icon: string; + placement: "left" | "right"; + }; + }[]; }; export const FooterTemplate: React.FC = () => { const [footerContent, setFooterContent] = React.useState([]); + const _useFooterContent = useFooterContent(); + const getFooterContent = _useFooterContent.getContent(); + + // For production + React.useEffect(() => { + setFooterContent(getFooterContent.data); + }, [getFooterContent]); + + // For development + // React.useEffect(() => { + // const data = require("./FooterContent.json"); + // setFooterContent1(data); + // }, []); + React.useEffect(() => { if (!process.env.GATSBY_FOOTER_CONTENT) return; @@ -51,35 +86,18 @@ const DynamicSection: React.FC<{ content: TDynamicContentItem }> = ({ content }) {content.items.map((item, idx) => (
- {t(item.label)} + {item.label && {t(item.label)}} {/* External Link */} - {item.link && item.link.includes("http") && ( - - {item.value} - - )} + {item.link && item.link.includes("http") && } {/* Internal Link */} - {item.link && !item.link.includes("http") && ( - navigate(item.link ?? "")} - tabIndex={0} - aria-label={`${t(item.label)}, ${t(item.value)}`} - role="button" - > - {item.value} - - )} + {item.link && !item.link.includes("http") && } + + {/* Internal Link Github/Markdown link */} + {item.markdownLink && } {/* No Link */} - {!item.link && {item.value}} + {!item.link && !item.markdownLink && }
))} @@ -140,3 +158,129 @@ const WithLoveByConduction: React.FC = () => { ); }; + +interface LinkComponentProps { + item: any; +} + +const ExternalLink: React.FC = ({ item }) => { + const { t } = useTranslation(); + + return ( + + {item.customIcon && item.customIcon.placement === "left" && ( + {parse(item.customIcon.icon)} + )} + + {item.icon && item.icon.placement === "left" && ( + + )} + + {t(item.value)} + + {item.icon && item.icon.placement === "right" && ( + + )} + + {item.customIcon && item.customIcon.placement === "right" && ( + {parse(item.customIcon.icon)} + )} + + ); +}; + +const InternalLink: React.FC = ({ item }) => { + const { t } = useTranslation(); + + return ( + navigate(item.link ?? "")} + tabIndex={0} + aria-label={`${t(item.ariaLabel)}, ${t(item.value)}`} + role="button" + > + {item.icon && item.icon.placement === "left" && ( + + )} + + {item.customIcon && item.customIcon.placement === "left" && ( + {parse(item.customIcon.icon)} + )} + + {t(item.value)} + + {item.icon && item.icon.placement === "right" && ( + + )} + + {item.customIcon && item.customIcon.placement === "right" && ( + {parse(item.customIcon.icon)} + )} + + ); +}; + +const MarkdownLink: React.FC = ({ item }) => { + const { t } = useTranslation(); + + return ( + navigate(`/github/${item.value.replaceAll(" ", "_")}/?link=${item.markdownLink}`)} + tabIndex={0} + aria-label={`${t(item.ariaLabel)}, ${t(item.markdownLink)}`} + role="button" + > + {item.icon && item.icon.placement === "left" && ( + + )} + + {item.customIcon && item.customIcon.placement === "left" && ( + {parse(item.customIcon.icon)} + )} + + {t(item.value)} + + {item.icon && item.icon.placement === "right" && ( + + )} + + {item.customIcon && item.customIcon.placement === "right" && ( + {parse(item.customIcon.icon)} + )} + + ); +}; + +const NoLink: React.FC = ({ item }) => { + const { t } = useTranslation(); + + return ( + + {item.customIcon && item.customIcon.placement === "left" && ( + {parse(item.customIcon.icon)} + )} + + {item.icon && item.icon.placement === "left" && ( + + )} + + {t(item.value)} + + {item.icon && item.icon.placement === "right" && ( + + )} + + {item.customIcon && item.customIcon.placement === "right" && ( + {parse(item.customIcon.icon)} + )} + + ); +}; diff --git a/pwa/static/.env.development b/pwa/static/.env.development index 6032211b..9f30f387 100644 --- a/pwa/static/.env.development +++ b/pwa/static/.env.development @@ -18,7 +18,7 @@ GATSBY_JUMBOTRON_IMAGE_URL=https://www.conduction.nl/wp-content/uploads/2021/07/ # Footer GATSBY_FOOTER_LOGO_URL="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pgo8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDIwMDEwOTA0Ly9FTiIKICAgICJodHRwOi8vd3d3LnczLm9yZy9UUi8yMDAxL1JFQy1TVkctMjAwMTA5MDQvRFREL3N2ZzEwLmR0ZCI+CjxzdmcgdmVyc2lvbj0iMS4wIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgICAgd2lkdGg9IjcxLjAwMDAwMHB0IiBoZWlnaHQ9IjcwLjAwMDAwMHB0IiB2aWV3Qm94PSIwIDAgNzEuMDAwMDAwIDcwLjAwMDAwMCIKICAgICBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCBtZWV0Ij4KCiAgICA8ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLjAwMDAwMCw3MC4wMDAwMDApIHNjYWxlKDAuMTAwMDAwLC0wLjEwMDAwMCkiCiAgICAgICBmaWxsPSIjNDM3NkZDIiBzdHJva2U9Im5vbmUiPgogICAgICAgIDxwYXRoIGQ9Ik0yMTggNTkyIGwtMTM3IC03NyAwIC0xNjUgMCAtMTY1IDEzMSAtNzQgYzcyIC00MSAxMzcgLTc0IDE0MyAtNzQgNgowIDcwIDMzIDE0MyA3NCBsMTMxIDc0IDEgMTY1IDAgMTY1IC02OCAzNyBjLTM3IDIxIC05OSA1NiAtMTM3IDc3IGwtNzEgNDAKLTEzNiAtNzd6IG0yOTUgLTg1IGw1NyAtMzIgMCAtMTI1IDAgLTEyNSAtMTA2IC02MCBjLTU4IC0zMyAtMTA4IC02MCAtMTExCi01OSAtMyAwIC01MiAyNyAtMTA5IDYwIGwtMTAzIDU5IDAgMTI1IDAgMTI1IDEwNyA2MSAxMDcgNjIgNTAgLTI5IGMyNyAtMTYKNzYgLTQ0IDEwOCAtNjJ6Ii8+CiAgICAgICAgPHBhdGggZD0iTTI3OCA0NzIgbC02OCAtMzcgMCAtODUgMCAtODUgNzMgLTM5IDczIC0zOSA2MiAzNCBjMzQgMTggNjIgMzcgNjIKNDEgMCA0IC0xNCAxNSAtMzEgMjMgLTI4IDE1IC0zMyAxNSAtNTQgMSAtMzEgLTIwIC00NiAtMjAgLTg0IDIgLTI3IDE1IC0zMQoyMiAtMzEgNjIgMCA0MCA0IDQ3IDMxIDYzIDM4IDIxIDUzIDIxIDg0IDEgMjEgLTE0IDI2IC0xNCA1NCAxIDE3IDggMzEgMTkgMzEKMjMgMCA4IC0xMTMgNzIgLTEyNiA3MiAtNSAwIC0zOSAtMTcgLTc2IC0zOHoiLz4KICAgIDwvZz4KPC9zdmc+Cg==" GATSBY_FOOTER_LOGO_HREF=https://conduction.nl/ -GATSBY_FOOTER_CONTENT=[{"title":"Contact","items":[{"label":"Phone number","value":"+31 (0)85 3036840","link":"tel:+31 (0)85 3036840"},{"label":"Email address","value":"info@conduction.nl","link":"mailto:info@conduction.nl"},{"label":"Address","value":"Lauriergracht 14h"}]}] +GATSBY_FOOTER_CONTENT="https://raw.githubusercontent.com/ConductionNL/woo-website-template/main/pwa/src/templates/templateParts/footer/FooterContent.json" #OIDN GATSBY_OIDN_NUMBER=""