├── .env.example ├── .eslintrc.json ├── .github ├── CODEOWNERS └── workflows │ └── integrate.yml ├── .gitignore ├── .nvmrc ├── .prettierrc ├── LICENSE ├── README.md ├── netlify.toml ├── package-lock.json ├── package.json ├── public ├── Images │ ├── facebook-icon.png │ └── twitter-icon.png └── index.html ├── src ├── App.css ├── App.tsx ├── Client.tsx ├── Components │ ├── Banner.tsx │ ├── BrewerStoreContainer.tsx │ ├── BrewerStoreListing.tsx │ ├── CheckboxFilter.tsx │ ├── CoffeeStoreContainer.tsx │ ├── CoffeeStoreListing.tsx │ ├── Footer.tsx │ ├── Header.tsx │ ├── LatestArticles.tsx │ ├── LinkButton.tsx │ ├── LowerCaseUrlLink.tsx │ ├── MessageBox.tsx │ ├── Metadata.tsx │ ├── OurStory.tsx │ ├── RichText.tsx │ ├── SpinnerLayout.tsx │ └── TasteOurCoffee.tsx ├── Fonts │ ├── Core-icons.eot │ ├── Core-icons.svg │ ├── Core-icons.ttf │ ├── Core-icons.woff │ ├── WorkSans-Bold.woff2 │ ├── WorkSans-Medium.woff2 │ └── WorkSans-Regular.woff2 ├── Images │ ├── Admin │ │ ├── checkbox-checked.svg │ │ ├── checkbox-unchecked.svg │ │ ├── error.svg │ │ ├── info.svg │ │ ├── kk-logo.svg │ │ ├── radio-button-checked.svg │ │ ├── radio-button.svg │ │ └── warning.svg │ └── icon-magnifier.png ├── Localization │ ├── en-US.json │ └── es-ES.json ├── LocalizedApp.tsx ├── Models │ ├── content-types │ │ ├── about_us.ts │ │ ├── accessory.ts │ │ ├── article.ts │ │ ├── brewer.ts │ │ ├── cafe.ts │ │ ├── coffee.ts │ │ ├── fact_about_us.ts │ │ ├── grinder.ts │ │ ├── hero_unit.ts │ │ ├── home.ts │ │ ├── hosted_video.ts │ │ ├── index.ts │ │ ├── office.ts │ │ └── tweet.ts │ ├── index.ts │ ├── project │ │ ├── assetFolders.ts │ │ ├── collections.ts │ │ ├── contentTypes.ts │ │ ├── index.ts │ │ ├── languages.ts │ │ ├── roles.ts │ │ ├── taxonomies.ts │ │ ├── webhooks.ts │ │ └── workflows.ts │ └── taxonomies │ │ ├── index.ts │ │ ├── manufacturer.ts │ │ ├── personas.ts │ │ ├── processing.ts │ │ ├── product_status.ts │ │ └── sitemap_538125f.ts ├── Pages │ ├── About.tsx │ ├── Admin │ │ ├── Configuration.css │ │ └── Configuration.tsx │ ├── Article.tsx │ ├── Articles.tsx │ ├── Brewer.tsx │ ├── Cafes.tsx │ ├── Coffee.tsx │ ├── Contacts.tsx │ ├── Home.tsx │ ├── NotFound.tsx │ └── Store.tsx ├── Utilities │ ├── CafeListing.ts │ ├── CheckboxFilter.ts │ ├── ContentLinks.ts │ ├── LanguageCodes.ts │ ├── LanugageLink.ts │ ├── LocalizationLoader.ts │ ├── SelectedProject.ts │ └── StoreListing.tsx ├── ViewModels │ └── CafeModel.ts ├── const.ts ├── custom.d.ts ├── index.css ├── index.tsx └── react-app-env.d.ts └── tsconfig.json /.env.example: -------------------------------------------------------------------------------- 1 | REACT_APP_ENVIRONMENT_ID=your_environment_id 2 | REACT_APP_PREVIEW_API_KEY=your_api_key -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["react-app", "react-app/jest"], 3 | "overrides": [ 4 | { 5 | "files": ["**/*.ts?(x)"], 6 | "rules": { 7 | "@typescript-eslint/explicit-function-return-type": "error" 8 | } 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Users referenced in this file will automatically be requested as reviewers for PRs that modify the given paths. 2 | # See https://help.github.com/articles/about-code-owners/ 3 | 4 | * @IvanKiral @Kontent-ai/developer-relations 5 | -------------------------------------------------------------------------------- /.github/workflows/integrate.yml: -------------------------------------------------------------------------------- 1 | name: Integrate 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | id: nvm 16 | - name: Use Node.js 17 | uses: actions/setup-node@v2 18 | with: 19 | node-version-file: '.nvmrc' 20 | - run: npm ci 21 | - run: npm run lint 22 | - run: npm run test 23 | - run: npm run build 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # production 7 | build 8 | 9 | # misc 10 | .DS_Store 11 | npm-debug.log 12 | 13 | # IDE 14 | .vs 15 | 16 | # dotenv environment variables file 17 | .env 18 | 19 | ### VisualStudioCode ### 20 | .vscode/* 21 | !.vscode/settings.json 22 | !.vscode/tasks.json 23 | !.vscode/launch.json 24 | !.vscode/extensions.json 25 | !.vscode/*.code-snippets 26 | 27 | # Local History for Visual Studio Code 28 | .history/ 29 | 30 | # Built Visual Studio Code Extensions 31 | *.vsix 32 | 33 | ### VisualStudioCode Patch ### 34 | # Ignore all local history of files 35 | .history 36 | .ionide 37 | 38 | # Support for Project snippet scope 39 | .vscode/*.code-snippets 40 | 41 | # Ignore code-workspaces 42 | *.code-workspace 43 | 44 | ### WebStorm ### 45 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 46 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 47 | 48 | # User-specific stuff 49 | .idea/**/workspace.xml 50 | .idea/**/tasks.xml 51 | .idea/**/usage.statistics.xml 52 | .idea/**/dictionaries 53 | .idea/**/shelf 54 | 55 | # AWS User-specific 56 | .idea/**/aws.xml 57 | 58 | # Generated files 59 | .idea/**/contentModel.xml 60 | 61 | # Sensitive or high-churn files 62 | .idea/**/dataSources/ 63 | .idea/**/dataSources.ids 64 | .idea/**/dataSources.local.xml 65 | .idea/**/sqlDataSources.xml 66 | .idea/**/dynamic.xml 67 | .idea/**/uiDesigner.xml 68 | .idea/**/dbnavigator.xml 69 | 70 | # Gradle 71 | .idea/**/gradle.xml 72 | .idea/**/libraries 73 | 74 | # Gradle and Maven with auto-import 75 | # When using Gradle or Maven with auto-import, you should exclude module files, 76 | # since they will be recreated, and may cause churn. Uncomment if using 77 | # auto-import. 78 | # .idea/artifacts 79 | # .idea/compiler.xml 80 | # .idea/jarRepositories.xml 81 | # .idea/modules.xml 82 | # .idea/*.iml 83 | # .idea/modules 84 | # *.iml 85 | # *.ipr 86 | 87 | # CMake 88 | cmake-build-*/ 89 | 90 | # Mongo Explorer plugin 91 | .idea/**/mongoSettings.xml 92 | 93 | # File-based project format 94 | *.iws 95 | 96 | # IntelliJ 97 | out/ 98 | 99 | # mpeltonen/sbt-idea plugin 100 | .idea_modules/ 101 | 102 | # JIRA plugin 103 | atlassian-ide-plugin.xml 104 | 105 | # Cursive Clojure plugin 106 | .idea/replstate.xml 107 | 108 | # SonarLint plugin 109 | .idea/sonarlint/ 110 | 111 | # Crashlytics plugin (for Android Studio and IntelliJ) 112 | com_crashlytics_export_strings.xml 113 | crashlytics.properties 114 | crashlytics-build.properties 115 | fabric.properties 116 | 117 | # Editor-based Rest Client 118 | .idea/httpRequests 119 | 120 | # Android studio 3.1+ serialized cache file 121 | .idea/caches/build_file_checksums.ser 122 | 123 | ### WebStorm Patch ### 124 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 125 | 126 | # *.iml 127 | # modules.xml 128 | # .idea/misc.xml 129 | # *.ipr 130 | 131 | # Sonarlint plugin 132 | # https://plugins.jetbrains.com/plugin/7973-sonarlint 133 | .idea/**/sonarlint/ 134 | 135 | # SonarQube Plugin 136 | # https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin 137 | .idea/**/sonarIssues.xml 138 | 139 | # Markdown Navigator plugin 140 | # https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced 141 | .idea/**/markdown-navigator.xml 142 | .idea/**/markdown-navigator-enh.xml 143 | .idea/**/markdown-navigator/ 144 | 145 | # Cache file creation bug 146 | # See https://youtrack.jetbrains.com/issue/JBR-2257 147 | .idea/$CACHE_FILE$ 148 | 149 | # CodeStream plugin 150 | # https://plugins.jetbrains.com/plugin/12206-codestream 151 | .idea/codestream.xml 152 | 153 | # End of https://www.toptal.com/developers/gitignore/api/webstorm,visualstudiocode 154 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/* -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "endOfLine": "crlf" 4 | } 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Kontent s.r.o. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | # https://docs.netlify.com/configure-builds/file-based-configuration/#sample-netlify-toml-file 2 | 3 | # The following redirect is intended for use with most SPAs 4 | # that handle routing internally. 5 | [[redirects]] 6 | from = "/*" 7 | to = "/index.html" 8 | status = 200 -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@kontent-ai/sample-app-react", 3 | "version": "2.1.1", 4 | "devDependencies": { 5 | "@testing-library/jest-dom": "^5.16.3", 6 | "@testing-library/react": "^12.1.4", 7 | "@testing-library/user-event": "^13.5.0", 8 | "@types/jest": "^27.4.1", 9 | "@types/node": "^16.11.26", 10 | "@types/react": "^17.0.43", 11 | "@types/react-dom": "^17.0.14", 12 | "@types/react-helmet": "^6.1.5", 13 | "@types/validator": "^13.7.2", 14 | "@types/webpack-env": "^1.16.3", 15 | "cpy-cli": "^4.0.0", 16 | "cross-env": "^7.0.2", 17 | "lint-staged": "^10.2.7", 18 | "prettier": "^2.0.5", 19 | "react-scripts": "^5.0.1", 20 | "typescript": "^4.8.4" 21 | }, 22 | "dependencies": { 23 | "@kontent-ai/delivery-sdk": "^14.0.1", 24 | "@kontent-ai/react-components": "0.3.0", 25 | "@simply007org/react-spinners": "0.0.3", 26 | "qs": "^6.9.4", 27 | "react": "^18.2.0", 28 | "react-dom": "^18.2.0", 29 | "react-helmet": "^6.0.0", 30 | "react-intl": "^6.4.0", 31 | "react-router-dom": "6.10.0", 32 | "universal-cookie": "^4.0.4", 33 | "validator": "^13.7.0" 34 | }, 35 | "scripts": { 36 | "lint": "eslint & prettier . --check", 37 | "start": "react-scripts start", 38 | "build": "cross-env CI=true react-scripts build && cpy build/index.html build --rename=200.html", 39 | "test-watch": "react-scripts test --env=jsdom", 40 | "test": "cross-env CI=true react-scripts test --env=jsdom --passWithNoTests" 41 | }, 42 | "browserslist": [ 43 | ">0.3%", 44 | "not ie 11", 45 | "not dead", 46 | "not op_mini all" 47 | ], 48 | "engines": { 49 | "npm": "8 || 9", 50 | "node": "18" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /public/Images/facebook-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kontent-ai/sample-app-react/b2821f997767040d16480ad67383d5baf9290347/public/Images/facebook-icon.png -------------------------------------------------------------------------------- /public/Images/twitter-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kontent-ai/sample-app-react/b2821f997767040d16480ad67383d5baf9290347/public/Images/twitter-icon.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Dancing Goat 8 | 9 | 28 | 29 | 30 | 31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './App.css'; 3 | import { Navigate, Route, Routes } from 'react-router-dom'; 4 | import SpinnerLayout from './Components/SpinnerLayout'; 5 | import Metadata from './Components/Metadata'; 6 | import qs from 'qs'; 7 | import Header from './Components/Header'; 8 | import Footer from './Components/Footer'; 9 | import Home from './Pages/Home'; 10 | import Store from './Pages/Store'; 11 | import Articles from './Pages/Articles'; 12 | import Article from './Pages/Article'; 13 | import About from './Pages/About'; 14 | import { useIntl } from 'react-intl'; 15 | import Cafes from './Pages/Cafes'; 16 | import Contact from './Pages/Contacts'; 17 | import Coffee from './Pages/Coffee'; 18 | import Brewer from './Pages/Brewer'; 19 | import { SetLanguageType } from './LocalizedApp'; 20 | import { NotFound } from './Pages/NotFound'; 21 | import { 22 | getEnvironmentIdFromCookies, 23 | getEnvironmentIdFromEnvironment, 24 | } from './Client'; 25 | import { projectConfigurationPath } from './const'; 26 | 27 | interface AppProps { 28 | changeLanguage: SetLanguageType; 29 | } 30 | 31 | const App: React.FC = ({ changeLanguage }) => { 32 | const { formatMessage } = useIntl(); 33 | 34 | if (getEnvironmentIdFromEnvironment() === null) { 35 | return ( 36 |
37 | Your environmentId given in your environment variables is not a valid 38 | GUID. 39 |
40 | ); 41 | } 42 | 43 | if ( 44 | getEnvironmentIdFromEnvironment() === undefined && 45 | !getEnvironmentIdFromCookies() 46 | ) { 47 | return ; 48 | } 49 | 50 | // slice(1) removes the `?` at the beginning of `location.search` 51 | const infoMessage = qs.parse(window.location.search.slice(1)).infoMessage; 52 | return ( 53 | 54 |
55 | 56 |
57 | 58 | } /> 59 | } /> 60 | } /> 61 | } /> 62 | } /> 63 | } /> 64 | } /> 65 | } /> 66 | } 69 | /> 70 | } /> 71 | } /> 72 | 73 |
74 |
75 |
76 | ); 77 | }; 78 | 79 | export default App; 80 | -------------------------------------------------------------------------------- /src/Client.tsx: -------------------------------------------------------------------------------- 1 | import Cookies from 'universal-cookie'; 2 | import { 3 | camelCasePropertyNameResolver, 4 | DeliveryClient, 5 | } from '@kontent-ai/delivery-sdk'; 6 | import packageInfo from '../package.json'; 7 | import { selectedEnvironmentCookieName } from './const'; 8 | import validator from 'validator'; 9 | import { 10 | createContext, 11 | FC, 12 | PropsWithChildren, 13 | useCallback, 14 | useContext, 15 | useMemo, 16 | useState, 17 | } from 'react'; 18 | 19 | const sourceTrackingHeaderName = 'X-KC-SOURCE'; 20 | 21 | const previewApiKey = process.env.REACT_APP_PREVIEW_API_KEY || ''; 22 | 23 | const cookies = new Cookies(document.cookie); 24 | 25 | const getEnvironmentIdFromEnvironment = (): string | null | undefined => { 26 | const environmentIdFromEnv = process.env.REACT_APP_ENVIRONMENT_ID; 27 | 28 | if (environmentIdFromEnv && !validator.isUUID(environmentIdFromEnv)) { 29 | console.error( 30 | `Your environmentId (${environmentIdFromEnv}) given in your environment variables is not a valid GUID.` 31 | ); 32 | return null; 33 | } 34 | 35 | return environmentIdFromEnv; 36 | }; 37 | 38 | const getEnvironmentIdFromCookies = (): string | null => { 39 | const environmentIdFromCookie = cookies.get(selectedEnvironmentCookieName); 40 | 41 | if (environmentIdFromCookie && !validator.isUUID(environmentIdFromCookie)) { 42 | console.error( 43 | `Your environmentId (${environmentIdFromCookie}) from cookies is not a valid GUID.` 44 | ); 45 | return null; 46 | } 47 | 48 | return environmentIdFromCookie; 49 | }; 50 | 51 | const currentEnvironmentId = 52 | getEnvironmentIdFromEnvironment() ?? getEnvironmentIdFromCookies() ?? ''; 53 | 54 | const isPreview = (): boolean => previewApiKey !== ''; 55 | 56 | type GlobalHeadersType = { 57 | header: string; 58 | value: string; 59 | }; 60 | 61 | const createClient = (newEnvironmentId: string): DeliveryClient => 62 | new DeliveryClient({ 63 | environmentId: newEnvironmentId, 64 | previewApiKey: previewApiKey, 65 | defaultQueryConfig: { 66 | usePreviewMode: isPreview(), 67 | }, 68 | globalHeaders: (_queryConfig): GlobalHeadersType[] => [ 69 | { 70 | header: sourceTrackingHeaderName, 71 | value: `${packageInfo.name};${packageInfo.version}`, 72 | }, 73 | ], 74 | propertyNameResolver: camelCasePropertyNameResolver, 75 | }); 76 | 77 | const Client = createClient(currentEnvironmentId); 78 | 79 | type ClientState = [DeliveryClient, (environmentId: string) => void]; 80 | 81 | const ClientContext = createContext(undefined as never); 82 | 83 | export const useClient = (): ClientState => useContext(ClientContext); 84 | 85 | export const ClientProvider: FC = ({ 86 | children, 87 | }: PropsWithChildren) => { 88 | const [client, setClient] = useState(Client); 89 | 90 | const updateClient = useCallback((newEnvironmentId: string) => { 91 | setClient(createClient(newEnvironmentId)); 92 | 93 | cookies.set(selectedEnvironmentCookieName, newEnvironmentId, { 94 | path: '/', 95 | sameSite: 'none', 96 | secure: true, 97 | }); 98 | }, []); 99 | 100 | return ( 101 | [client, updateClient], [client, updateClient])} 103 | > 104 | {children} 105 | 106 | ); 107 | }; 108 | 109 | export { 110 | createClient, 111 | getEnvironmentIdFromEnvironment, 112 | getEnvironmentIdFromCookies, 113 | }; 114 | -------------------------------------------------------------------------------- /src/Components/Banner.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { HeroUnit } from '../Models/content-types/hero_unit'; 3 | import RichText from './RichText'; 4 | 5 | interface BannerProps { 6 | heroUnit: HeroUnit; 7 | } 8 | 9 | const Banner: React.FC = (props) => { 10 | const heroUnit = props.heroUnit.elements; 11 | const images = heroUnit.image && heroUnit.image.value; 12 | const imageUrl = images && images.length && images[0].url; 13 | 14 | return ( 15 |
21 |

22 | {heroUnit.title && heroUnit.title.value} 23 |

24 | {heroUnit.marketingMessage && ( 25 | 26 | )} 27 |
28 | ); 29 | }; 30 | 31 | export default Banner; 32 | -------------------------------------------------------------------------------- /src/Components/BrewerStoreContainer.tsx: -------------------------------------------------------------------------------- 1 | import { spinnerService } from '@simply007org/react-spinners'; 2 | import React, { useEffect } from 'react'; 3 | import { useState } from 'react'; 4 | import { matchesTaxonomy } from '../Utilities/CheckboxFilter'; 5 | import { 6 | defaultLanguage, 7 | initLanguageCodeObjectWithArray, 8 | } from '../Utilities/LanguageCodes'; 9 | import BrewerStoreListing from './BrewerStoreListing'; 10 | import CheckboxFilter from './CheckboxFilter'; 11 | import { ITaxonomyTerms } from '@kontent-ai/delivery-sdk'; 12 | import { useIntl } from 'react-intl'; 13 | import { Brewer } from '../Models/content-types/brewer'; 14 | import { contentTypes } from '../Models/project/contentTypes'; 15 | import { useClient } from '../Client'; 16 | 17 | interface filterType { 18 | [index: string]: string[]; 19 | manufacturers: string[]; 20 | priceRanges: string[]; 21 | productStatuses: string[]; 22 | } 23 | 24 | const BrewerStoreContainer: React.FC = () => { 25 | const { formatMessage } = useIntl(); 26 | 27 | const [brewers, setBrewers] = useState( 28 | initLanguageCodeObjectWithArray() 29 | ); 30 | const { locale: language } = useIntl(); 31 | 32 | const [manufacturers, setManufacturers] = useState([]); 33 | const priceRanges = [ 34 | { min: 0, max: 50 }, 35 | { min: 50, max: 250 }, 36 | { min: 250, max: 5000 }, 37 | ]; 38 | const [productStatuses, setProductStatuses] = useState([]); 39 | 40 | const [filter, setFilter] = useState({ 41 | manufacturers: [], 42 | priceRanges: [], 43 | productStatuses: [], 44 | }); 45 | 46 | const [Client] = useClient(); 47 | 48 | useEffect(() => { 49 | spinnerService.show('apiSpinner'); 50 | 51 | const query = Client.items() 52 | .type(contentTypes.brewer.codename) 53 | .orderByAscending('elements.product_name'); 54 | 55 | if (language) { 56 | query.languageParameter(language); 57 | } 58 | 59 | query.toPromise().then((response) => { 60 | const currentLanguage = language || defaultLanguage; 61 | 62 | spinnerService.hide('apiSpinner'); 63 | setBrewers((data) => ({ 64 | ...data, 65 | [currentLanguage]: response.data.items as Brewer[], 66 | })); 67 | }); 68 | }, [language, Client]); 69 | 70 | useEffect(() => { 71 | Client.taxonomy('manufacturer') 72 | .toPromise() 73 | .then((response) => { 74 | setManufacturers(response.data.taxonomy.terms); 75 | }); 76 | }, [Client]); 77 | 78 | useEffect(() => { 79 | Client.taxonomy('product_status') 80 | .toPromise() 81 | .then((response) => { 82 | setProductStatuses(response.data.taxonomy.terms); 83 | }); 84 | }, [Client]); 85 | 86 | const matches = (brewer: Brewer): boolean => 87 | matchesTaxonomy(brewer, filter.manufacturers, 'manufacturer') && 88 | matchesPriceRanges(brewer) && 89 | matchesTaxonomy(brewer, filter.productStatuses, 'productStatus'); 90 | 91 | const matchesPriceRanges = (brewer: Brewer): boolean => { 92 | if (filter.priceRanges.length === 0) { 93 | return true; 94 | } 95 | const price = brewer.elements.price.value!!; 96 | const ranges = filter.priceRanges.map((priceRange) => ({ 97 | min: +priceRange.split('-')[0], 98 | max: +priceRange.split('-')[1], 99 | })); 100 | 101 | return ranges.some( 102 | (priceRange) => priceRange.min <= price && price <= priceRange.max 103 | ); 104 | }; 105 | 106 | const formatPrice = (price: number, language: string): string => { 107 | return price.toLocaleString(language, { 108 | style: 'currency', 109 | currency: 'USD', 110 | maximumFractionDigits: 2, 111 | }); 112 | }; 113 | 114 | const toggleFilter = (filterName: string, filterValue: string): void => { 115 | setFilter((filter) => ({ 116 | ...filter, 117 | [filterName]: filter[filterName].includes(filterValue) 118 | ? filter[filterName].filter((x: string) => x !== filterValue) 119 | : [...filter[filterName], filterValue], 120 | })); 121 | }; 122 | 123 | return ( 124 |
125 |
126 | 172 | 174 | matches(brewer) 175 | )} 176 | /> 177 |
178 |
179 | ); 180 | }; 181 | 182 | export default BrewerStoreContainer; 183 | -------------------------------------------------------------------------------- /src/Components/BrewerStoreListing.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import { resolveContentLink } from '../Utilities/ContentLinks'; 4 | import { formatPrice, renderProductStatus } from '../Utilities/StoreListing'; 5 | import { useIntl } from 'react-intl'; 6 | import { Brewer } from '../Models/content-types/brewer'; 7 | 8 | interface BrewerStoreListingProps { 9 | brewers: Brewer[]; 10 | } 11 | 12 | const BrewerStoreListing: React.FC = ({ brewers }) => { 13 | const { locale: language, formatMessage } = useIntl(); 14 | 15 | const brewersComponents = brewers.map((brewer) => { 16 | const price = 17 | brewer.elements.price.value !== null 18 | ? formatPrice(brewer.elements.price.value, language) 19 | : formatMessage({ id: 'BrewerStoreListing.noPriceValue' }); 20 | 21 | const name = 22 | brewer.elements.productName.value.trim().length > 0 23 | ? brewer.elements.productName.value 24 | : formatMessage({ id: 'BrewerStoreListing.noNameValue' }); 25 | 26 | const imageLink = 27 | brewer.elements.image.value[0] !== undefined ? ( 28 | {name} 29 | ) : ( 30 |
31 | {formatMessage({ id: 'BrewerStoreListing.noTeaserValue' })} 32 |
33 | ); 34 | 35 | const status = renderProductStatus(brewer.elements.productStatus); 36 | const link = resolveContentLink( 37 | { type: 'brewer', urlSlug: brewer.elements.urlPattern.value }, 38 | language 39 | ); 40 | 41 | return ( 42 |
43 |
44 | 45 |

{name}

46 | {status} 47 |
{imageLink}
48 |
49 | {price} 50 |
51 | 52 |
53 |
54 | ); 55 | }); 56 | 57 | return ( 58 |
59 | {brewersComponents} 60 |
61 | ); 62 | }; 63 | 64 | export default BrewerStoreListing; 65 | -------------------------------------------------------------------------------- /src/Components/CheckboxFilter.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | interface OptionsType { 4 | id: string; 5 | checked: boolean; 6 | label: string; 7 | onChange: (event: React.ChangeEvent) => void; 8 | } 9 | 10 | interface CheckboxFilterProps { 11 | options: OptionsType[]; 12 | } 13 | 14 | const CheckboxFilter: React.FC = ({ options }) => { 15 | return ( 16 | <> 17 | {options.map(({ id, checked, label, onChange }) => ( 18 | 19 | 25 | 26 | 27 | ))} 28 | 29 | ); 30 | }; 31 | 32 | export default CheckboxFilter; 33 | -------------------------------------------------------------------------------- /src/Components/CoffeeStoreContainer.tsx: -------------------------------------------------------------------------------- 1 | import { spinnerService } from '@simply007org/react-spinners'; 2 | import React, { useEffect, useState } from 'react'; 3 | import { matchesTaxonomy } from '../Utilities/CheckboxFilter'; 4 | import { 5 | defaultLanguage, 6 | initLanguageCodeObjectWithArray, 7 | } from '../Utilities/LanguageCodes'; 8 | import CheckboxFilter from './CheckboxFilter'; 9 | import CoffeeStoreListing from './CoffeeStoreListing'; 10 | import { ITaxonomyTerms } from '@kontent-ai/delivery-sdk'; 11 | import { useIntl } from 'react-intl'; 12 | import { Coffee } from '../Models/content-types/coffee'; 13 | import { contentTypes } from '../Models/project/contentTypes'; 14 | import { useClient } from '../Client'; 15 | 16 | interface filterType { 17 | [index: string]: string[]; 18 | processings: string[]; 19 | productStatuses: string[]; 20 | } 21 | 22 | const CoffeeStoreContainer: React.FC = () => { 23 | const [coffees, setCoffees] = useState( 24 | initLanguageCodeObjectWithArray() 25 | ); 26 | const [processings, setProcessings] = useState([]); 27 | const [productStatuses, setProductStatuses] = useState([]); 28 | const { locale: language, formatMessage } = useIntl(); 29 | 30 | const [filter, setFilter] = useState({ 31 | processings: [], 32 | productStatuses: [], 33 | }); 34 | 35 | const [Client] = useClient(); 36 | 37 | useEffect(() => { 38 | spinnerService.show('apiSpinner'); 39 | 40 | const query = Client.items() 41 | .type(contentTypes.coffee.codename) 42 | .orderByAscending('elements.product_name'); 43 | 44 | if (language) { 45 | query.languageParameter(language); 46 | } 47 | 48 | query.toPromise().then((response) => { 49 | const currentLanguage = language || defaultLanguage; 50 | 51 | spinnerService.hide('apiSpinner'); 52 | setCoffees((data) => ({ 53 | ...data, 54 | [currentLanguage]: response.data.items as Coffee[], 55 | })); 56 | }); 57 | }, [language, Client]); 58 | 59 | useEffect(() => { 60 | Client.taxonomy('processing') 61 | .toPromise() 62 | .then((response) => { 63 | setProcessings(response.data.taxonomy.terms); 64 | }); 65 | }, [Client]); 66 | 67 | useEffect(() => { 68 | Client.taxonomy('product_status') 69 | .toPromise() 70 | .then((response) => { 71 | setProductStatuses(response.data.taxonomy.terms); 72 | }); 73 | }, [Client]); 74 | 75 | const matches = (coffee: Coffee): boolean => 76 | matchesTaxonomy(coffee, filter.processings, 'processing') && 77 | matchesTaxonomy(coffee, filter.productStatuses, 'productStatus'); 78 | 79 | const toggleFilter = (filterName: string, filterValue: string): void => { 80 | setFilter((filter) => ({ 81 | ...filter, 82 | [filterName]: filter[filterName].includes(filterValue) 83 | ? filter[filterName].filter((x: string) => x !== filterValue) 84 | : [...filter[filterName], filterValue], 85 | })); 86 | }; 87 | 88 | return ( 89 |
90 |
91 | 123 | 125 | matches(coffee) 126 | )} 127 | /> 128 |
129 |
130 | ); 131 | }; 132 | 133 | export default CoffeeStoreContainer; 134 | -------------------------------------------------------------------------------- /src/Components/CoffeeStoreListing.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import { resolveContentLink } from '../Utilities/ContentLinks'; 4 | import { formatPrice, renderProductStatus } from '../Utilities/StoreListing'; 5 | import { useIntl } from 'react-intl'; 6 | import { Coffee } from '../Models/content-types/coffee'; 7 | 8 | interface CoffeeStoreListingProps { 9 | coffees: Coffee[]; 10 | } 11 | 12 | const CoffeeStoreListing: React.FC = ({ coffees }) => { 13 | const { locale: language, formatMessage } = useIntl(); 14 | const coffeesComponents = coffees.map((coffee) => { 15 | const price = 16 | coffee.elements.price.value !== null 17 | ? formatPrice(coffee.elements.price.value, language) 18 | : formatMessage({ id: 'CoffeeStoreListing.noPriceValue' }); 19 | 20 | const name = 21 | coffee.elements.productName.value.trim().length > 0 22 | ? coffee.elements.productName.value 23 | : formatMessage({ id: 'CoffeeStoreListing.noNameValue' }); 24 | 25 | const imageLink = 26 | coffee.elements.image.value[0] !== undefined ? ( 27 | {name} 33 | ) : ( 34 |
38 | {formatMessage({ id: 'CoffeeStoreListing.noTeaserValue' })} 39 |
40 | ); 41 | 42 | const status = renderProductStatus(coffee.elements.productStatus); 43 | const link = resolveContentLink( 44 | { type: 'coffee', urlSlug: coffee.elements.urlPattern.value }, 45 | language 46 | ); 47 | 48 | return ( 49 |
50 |
51 | 52 |

{name}

53 | {status} 54 |
{imageLink}
55 |
56 | {price} 57 |
58 | 59 |
60 |
61 | ); 62 | }); 63 | 64 | return ( 65 |
66 | {coffeesComponents} 67 |
68 | ); 69 | }; 70 | 71 | export default CoffeeStoreListing; 72 | -------------------------------------------------------------------------------- /src/Components/Footer.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Link from '../Components/LowerCaseUrlLink'; 3 | import { useIntl } from 'react-intl'; 4 | 5 | const Footer: React.FC = () => { 6 | const { formatMessage } = useIntl(); 7 | return ( 8 |
9 |
10 |
11 |
12 |
13 |
14 |

15 | {formatMessage({ id: 'Footer.contact' })} 16 |

17 |

18 | (+0) 000-000-0000 19 |
20 | 21 | dancinggoat@localhost.local 22 | 23 |
24 |
Dancing Goat Ltd 25 |
62 E Lake St Chicago, 26 |
{formatMessage({ id: 'Footer.cityStateZip' })} 27 |

28 |
29 |
30 |

{formatMessage({ id: 'Footer.followUs' })}

31 | 36 | Follow us on Facebook 42 | 43 | 48 | Follow us on Twitter 54 | 55 |
56 |
57 |
58 |
59 |
60 |
61 | Copyright © 2016 Dancing Goat.{' '} 62 | {formatMessage({ id: 'Footer.allRightsReserved' })} 63 |
64 |
65 |
66 | ); 67 | }; 68 | export default Footer; 69 | -------------------------------------------------------------------------------- /src/Components/Header.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Link from './LowerCaseUrlLink'; 3 | import { englishCode, spanishCode } from '../Utilities/LanguageCodes'; 4 | import MessageBox from './MessageBox'; 5 | import { ParsedQs } from 'qs'; 6 | import { useIntl } from 'react-intl'; 7 | import { SetLanguageType } from '../LocalizedApp'; 8 | 9 | interface HeaderProps { 10 | message?: string | ParsedQs | string[] | ParsedQs[] | undefined; 11 | changeLanguage: SetLanguageType; 12 | } 13 | 14 | const Header: React.FC = (props) => { 15 | const messageBox = props.message && ; 16 | const { locale: language, formatMessage } = useIntl(); 17 | return ( 18 |
19 |
20 |
21 | 63 | 95 |
96 |
97 | {messageBox} 98 |
99 |
100 |
101 |

102 | 103 | Dancing Goat 104 | 105 |

106 |
107 |
108 |
109 |
110 | ); 111 | }; 112 | 113 | export default Header; 114 | -------------------------------------------------------------------------------- /src/Components/LatestArticles.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Link from '../Components/LowerCaseUrlLink'; 3 | import { FormattedDate, useIntl } from 'react-intl'; 4 | import { Article } from '../Models/content-types/article'; 5 | 6 | interface LatestArticlesProps { 7 | articles: Article[]; 8 | } 9 | 10 | const LatestArticles: React.FC = (props) => { 11 | const { locale: language, formatMessage } = useIntl(); 12 | if (props.articles.length === 0) { 13 | return
; 14 | } 15 | 16 | const otherArticles = props.articles 17 | .slice(1) 18 | .map((article: Article, index: number) => { 19 | const title = 20 | article.elements.title.value.trim().length > 0 21 | ? article.elements.title.value 22 | : formatMessage({ id: 'LatestArticles.noTitleValue' }); 23 | 24 | const imageLink = 25 | article.elements.teaserImage.value[0] !== undefined ? ( 26 | {'Article 32 | ) : ( 33 |
34 | {formatMessage({ id: 'LatestArticles.noTeaserValue' })} 35 |
36 | ); 37 | 38 | const summary = 39 | article.elements.summary.value.trim().length > 0 40 | ? article.elements.summary.value 41 | : formatMessage({ id: 'LatestArticles.noSummaryValue' }); 42 | 43 | const link = `/${language.toLowerCase()}/articles/${article.system.id}`; 44 | 45 | return ( 46 |
47 |
48 | {imageLink} 49 |
50 | 55 |
56 |
57 |

58 | {title} 59 |

60 |

{summary}

61 |
62 |
63 |
64 | ); 65 | }); 66 | 67 | const { system, elements: articleElements } = props.articles[0]; 68 | 69 | const title = 70 | articleElements.title.value.trim().length > 0 71 | ? articleElements.title.value 72 | : formatMessage({ id: 'LatestArticles.noTitleValue' }); 73 | 74 | const imageLink = 75 | articleElements.teaserImage.value[0] !== undefined ? ( 76 | {'Article 82 | ) : ( 83 |
84 | {formatMessage({ id: 'LatestArticles.noTeaserValue' })} 85 |
86 | ); 87 | 88 | const summary = 89 | articleElements.summary.value.trim().length > 0 90 | ? articleElements.summary.value 91 | : formatMessage({ id: 'noSummaryValue' }); 92 | 93 | const link = `/${language.toLowerCase()}/articles/${system.id}`; 94 | const tabTitle = formatMessage({ id: 'LatestArticles.title' }); 95 | 96 | return ( 97 |
98 |

{tabTitle}

99 |
100 |
101 | {imageLink} 102 |
103 |
104 |
105 | 110 |
111 |
112 |

113 | {title} 114 |

115 |

{summary}

116 |
117 |
118 |
119 | {otherArticles} 120 |
121 | ); 122 | }; 123 | 124 | export default LatestArticles; 125 | -------------------------------------------------------------------------------- /src/Components/LinkButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Link from '../Components/LowerCaseUrlLink'; 4 | 5 | interface LinkButtonProps { 6 | link: string; 7 | text: string; 8 | } 9 | 10 | const LinkButton: React.FC = (props) => { 11 | return ( 12 |
13 |
14 | 15 | {props.text} 16 | 17 |
18 |
19 | ); 20 | }; 21 | 22 | export default LinkButton; 23 | -------------------------------------------------------------------------------- /src/Components/LowerCaseUrlLink.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | 4 | interface LoweCaseUrlLinkProps { 5 | to: string; 6 | className?: string; 7 | target?: string; 8 | children: React.ReactNode; 9 | } 10 | 11 | const LowerCaseUrlLink: React.FC = (props) => { 12 | if (/^https?:\/\//.test(props.to) || /^mailto:/.test(props.to)) { 13 | return ( 14 | 15 | {props.children} 16 | 17 | ); 18 | } 19 | return ; 20 | }; 21 | 22 | export default LowerCaseUrlLink; 23 | -------------------------------------------------------------------------------- /src/Components/MessageBox.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import infoImage from '../Images/Admin/info.svg'; 4 | import { ParsedQs } from 'qs'; 5 | 6 | interface MessageBoxProps { 7 | message: string | ParsedQs | string[] | ParsedQs[] | undefined; 8 | } 9 | 10 | const MessageBox: React.FC = (props) => { 11 | if (!props.message) { 12 | return null; 13 | } 14 | 15 | return ( 16 |
17 |
18 | 19 | 20 | 21 | 24 | 27 | 28 | 29 |
22 | info icon 23 | 25 |

{props.message}

26 |
30 |
31 |
32 | ); 33 | }; 34 | 35 | export default MessageBox; 36 | -------------------------------------------------------------------------------- /src/Components/Metadata.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Helmet } from 'react-helmet'; 3 | import { Elements } from '@kontent-ai/delivery-sdk'; 4 | 5 | interface MetaDataProps { 6 | title?: Elements.TextElement; 7 | description?: Elements.TextElement; 8 | ogTitle?: Elements.TextElement; 9 | ogDescription?: Elements.TextElement; 10 | ogImage?: Elements.AssetsElement; 11 | twitterTitle?: Elements.TextElement; 12 | twitterSite?: Elements.TextElement; 13 | twitterImage?: Elements.AssetsElement; 14 | twitterCreator?: Elements.TextElement; 15 | twitterDescription?: Elements.TextElement; 16 | } 17 | 18 | const Metadata: React.FC = (props) => { 19 | return ( 20 | 21 | {props.title?.value.trim().length === 0 ? ( 22 | Dancing Goat 23 | ) : ( 24 | {props.title?.value.trim()} 25 | )} 26 | 27 | {props.description?.value.trim().length === 0 ? null : ( 28 | 29 | )} 30 | 31 | {props.ogTitle?.value.trim().length === 0 32 | ? null 33 | : [ 34 | , 39 | , 40 | , 45 | ]} 46 | 47 | {props.ogImage?.value[0].url ? ( 48 | 49 | ) : null} 50 | 51 | {props.ogDescription?.value.trim().length === 0 ? null : ( 52 | 53 | )} 54 | 55 | {props.twitterTitle?.value.trim().length === 0 56 | ? null 57 | : [ 58 | , 63 | , 68 | ]} 69 | 70 | {props.twitterSite?.value.trim().length === 0 ? null : ( 71 | 72 | )} 73 | 74 | {props.twitterCreator?.value.trim().length === 0 ? null : ( 75 | 79 | )} 80 | 81 | {props.twitterDescription?.value.trim().length === 0 ? null : ( 82 | 86 | )} 87 | 88 | {props.twitterImage?.value[0].url ? ( 89 | 90 | ) : null} 91 | 92 | ); 93 | }; 94 | 95 | export default Metadata; 96 | -------------------------------------------------------------------------------- /src/Components/OurStory.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { FactAboutUs } from '../Models/content-types/fact_about_us'; 3 | import RichText from './RichText'; 4 | 5 | interface OurStoryProps { 6 | fact: FactAboutUs; 7 | } 8 | 9 | const OurStory: React.FC = (props) => { 10 | const fact = props.fact.elements; 11 | const images = fact.image && fact.image.value; 12 | const imageUrl = images && images.length && images[0].url; 13 | 14 | return ( 15 |
16 |

{fact.title && fact.title.value}

17 |
18 |
24 | {fact.description && } 25 |
26 |
27 |
28 | ); 29 | }; 30 | 31 | export default OurStory; 32 | -------------------------------------------------------------------------------- /src/Components/RichText.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | DomElementOptionsType, 3 | ResolversType, 4 | RichTextElement, 5 | } from '@kontent-ai/react-components'; 6 | import React from 'react'; 7 | import { Link } from 'react-router-dom'; 8 | import { resolveContentLink } from '../Utilities/ContentLinks'; 9 | import { 10 | ElementModels, 11 | Elements, 12 | IContentItem, 13 | ILink, 14 | IRichTextImage, 15 | } from '@kontent-ai/delivery-sdk'; 16 | 17 | interface RichTextProps { 18 | element: Elements.RichTextElement; 19 | className?: string; 20 | } 21 | 22 | const RichText: React.FC = (props) => { 23 | const resolvers: ResolversType = { 24 | resolveLinkedItem: ( 25 | linkedItem: IContentItem | undefined, 26 | domOptions: DomElementOptionsType 27 | ) => { 28 | const contentItemType = linkedItem ? linkedItem.system.type : ''; 29 | 30 | switch (contentItemType) { 31 | case 'tweet': { 32 | let tweetLink = linkedItem?.elements.tweetLink.value; 33 | let tweetID = tweetLink.match('^.*twitter.com/.*/(\\d+)/?.*$')[1]; 34 | 35 | let selectedTheme = linkedItem?.elements.theme.value[0].codename; 36 | selectedTheme = selectedTheme ? selectedTheme : 'light'; 37 | 38 | setTimeout(() => { 39 | window.twttr.widgets.createTweet( 40 | tweetID, 41 | document.getElementById(`tweet${tweetID}`), 42 | { 43 | theme: selectedTheme, 44 | } 45 | ); 46 | }, 100); 47 | 48 | return
; 49 | } 50 | case 'hosted_video': { 51 | if ( 52 | linkedItem?.elements.videoHost.value.find( 53 | (item: ElementModels.MultipleChoiceOption) => 54 | item.codename === 'vimeo' 55 | ) 56 | ) { 57 | return ( 58 | 67 | ); 68 | } else if ( 69 | linkedItem?.elements.videoHost.value.find( 70 | (item: ElementModels.MultipleChoiceOption) => 71 | item.codename === 'youtube' 72 | ) 73 | ) { 74 | return ( 75 | 84 | ); 85 | } else { 86 | return
Content item not supported
; 87 | } 88 | } 89 | default: 90 | return
Content item not supported
; 91 | } 92 | }, 93 | resolveLink: (link: ILink, domOptions: DomElementOptionsType) => { 94 | const path = resolveContentLink(link); 95 | 96 | return ( 97 | 98 | {domOptions.domToReact(domOptions.domElement.children)} 99 | 100 | ); 101 | }, 102 | resolveImage: ( 103 | image: IRichTextImage, 104 | domOptions: DomElementOptionsType 105 | ) => { 106 | return ( 107 | {`${image.description 112 | ); 113 | }, 114 | resolveDomNode: undefined, 115 | }; 116 | 117 | return ( 118 |
119 | 120 |
121 | ); 122 | }; 123 | 124 | export default RichText; 125 | -------------------------------------------------------------------------------- /src/Components/SpinnerLayout.tsx: -------------------------------------------------------------------------------- 1 | import { Spinner } from '@simply007org/react-spinners'; 2 | import React from 'react'; 3 | 4 | interface SpinnerLayoutProps { 5 | children: React.ReactNode; 6 | } 7 | 8 | const SpinnerLayout: React.FC = ({ children }) => { 9 | return ( 10 |
11 | 12 |
13 |
14 |
15 | 16 | {children} 17 |
18 | ); 19 | }; 20 | 21 | export default SpinnerLayout; 22 | -------------------------------------------------------------------------------- /src/Components/TasteOurCoffee.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Link from '../Components/LowerCaseUrlLink'; 4 | import { useIntl } from 'react-intl'; 5 | import { Cafe } from '../Models/content-types/cafe'; 6 | 7 | interface TestOurCoffeeProps { 8 | cafes: Cafe[]; 9 | } 10 | 11 | const TasteOurCoffee: React.FC = (props) => { 12 | const { locale: language, formatMessage } = useIntl(); 13 | const cafes = props.cafes.map((cafe: Cafe, index: number) => { 14 | const name = cafe.system.name; 15 | const imageLink = cafe.elements.photo.value[0].url; 16 | 17 | return ( 18 |
19 |
20 | 21 |

{name}

22 | 23 | {name} 29 | 30 |
31 |
32 | ); 33 | }); 34 | 35 | return ( 36 |
37 |
38 |

39 | {formatMessage({ id: 'TasteOurCoffee.title' })} 40 |

41 |
42 | {cafes} 43 |
44 | ); 45 | }; 46 | 47 | export default TasteOurCoffee; 48 | -------------------------------------------------------------------------------- /src/Fonts/Core-icons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kontent-ai/sample-app-react/b2821f997767040d16480ad67383d5baf9290347/src/Fonts/Core-icons.eot -------------------------------------------------------------------------------- /src/Fonts/Core-icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kontent-ai/sample-app-react/b2821f997767040d16480ad67383d5baf9290347/src/Fonts/Core-icons.ttf -------------------------------------------------------------------------------- /src/Fonts/Core-icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kontent-ai/sample-app-react/b2821f997767040d16480ad67383d5baf9290347/src/Fonts/Core-icons.woff -------------------------------------------------------------------------------- /src/Fonts/WorkSans-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kontent-ai/sample-app-react/b2821f997767040d16480ad67383d5baf9290347/src/Fonts/WorkSans-Bold.woff2 -------------------------------------------------------------------------------- /src/Fonts/WorkSans-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kontent-ai/sample-app-react/b2821f997767040d16480ad67383d5baf9290347/src/Fonts/WorkSans-Medium.woff2 -------------------------------------------------------------------------------- /src/Fonts/WorkSans-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kontent-ai/sample-app-react/b2821f997767040d16480ad67383d5baf9290347/src/Fonts/WorkSans-Regular.woff2 -------------------------------------------------------------------------------- /src/Images/Admin/checkbox-checked.svg: -------------------------------------------------------------------------------- 1 | checkbox-checked -------------------------------------------------------------------------------- /src/Images/Admin/checkbox-unchecked.svg: -------------------------------------------------------------------------------- 1 | checkbox-unchecked -------------------------------------------------------------------------------- /src/Images/Admin/error.svg: -------------------------------------------------------------------------------- 1 | error 2 | -------------------------------------------------------------------------------- /src/Images/Admin/info.svg: -------------------------------------------------------------------------------- 1 | info 2 | -------------------------------------------------------------------------------- /src/Images/Admin/kk-logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Images/Admin/radio-button-checked.svg: -------------------------------------------------------------------------------- 1 | radio-button-checked -------------------------------------------------------------------------------- /src/Images/Admin/radio-button.svg: -------------------------------------------------------------------------------- 1 | radio-button -------------------------------------------------------------------------------- /src/Images/Admin/warning.svg: -------------------------------------------------------------------------------- 1 | warning -------------------------------------------------------------------------------- /src/Images/icon-magnifier.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kontent-ai/sample-app-react/b2821f997767040d16480ad67383d5baf9290347/src/Images/icon-magnifier.png -------------------------------------------------------------------------------- /src/Localization/en-US.json: -------------------------------------------------------------------------------- 1 | { 2 | "locale": "en-US", 3 | "LatestArticles.title": "Latest articles", 4 | "LatestArticles.noTitleValue": "(Article has no title)", 5 | "LatestArticles.noTeaserValue": "(Article has no teaser image)", 6 | "LatestArticles.noSummaryValue": "No summary filled", 7 | "About.noTitleValue": "(Fact has no title)", 8 | "About.noDescriptionValue": "No description filled", 9 | "About.noTeaserValue": "(Fact has no teaser image)", 10 | "Articles.noTitleValue": "(Article has no title)", 11 | "Articles.noTeaserValue": "(Article has no teaser image)", 12 | "Articles.noSummaryValue": "No summary filled", 13 | "Articles.noBodyCopyValue": "No body copy filled", 14 | "Home.moreArticles": "More articles", 15 | "Home.aboutLinkText": "Read the whole story", 16 | "Home.cafesLinkText": "Find out more", 17 | "TasteOurCoffee.title": "Taste Our Coffee", 18 | "Header.homeLinkTitle": "Home", 19 | "Header.storeLinkTitle": "Product catalog", 20 | "Header.articlesLinkTitle": "Articles", 21 | "Header.aboutLinkTitle": "About us", 22 | "Header.cafesLinkTitle": "Cafes", 23 | "Header.contactsLinkTitle": "Contact", 24 | "Store.coffeesLinkTitle": "Coffees", 25 | "Store.brewersLinkTitle": "Brewers", 26 | "CoffeeStoreContainer.coffeeProcessingTitle": "Coffee processing", 27 | "CoffeeStoreContainer.statusTitle": "Status", 28 | "BrewerStoreContainer.manufacturerTitle": "Manufacturer", 29 | "BrewerStoreContainer.priceTitle": "Price", 30 | "BrewerStoreContainer.statusTitle": "Status", 31 | "BrewerStoreListing.noPriceValue": "No price filled", 32 | "BrewerStoreListing.noNameValue": "No name filled", 33 | "BrewerStoreListing.noTeaserValue": "(Product has no teaser image)", 34 | "BrewerStoreListing.noDescriptionValue": "No description filled", 35 | "Cafes.ourCafesTitle": "Our cafes", 36 | "Cafes.partnerCafesTitle": "Other places where you can drink our coffee", 37 | "CoffeeStoreListing.noPriceValue": "No price filled", 38 | "CoffeeStoreListing.noNameValue": "No name filled", 39 | "CoffeeStoreListing.noTeaserValue": "(Product has no teaser image)", 40 | "CoffeeStoreListing.noDescriptionValue": "No description filled", 41 | "Contacts.roasteryTitle": "Roastery", 42 | "Contacts.ourCafesTitle": "Our cafes", 43 | "Contacts.mapTitle": "Drop in", 44 | "Footer.followUs": "Follow us", 45 | "Footer.followUsOnFacebook": "Follow us on Facebook", 46 | "Footer.followUsOnTwitter": "Follow us on Twitter", 47 | "Footer.contact": "Contact", 48 | "Footer.cityStateZip": "Illinois 60601, USA", 49 | "Footer.allRightsReserved": "All rights reserved.", 50 | "NotFound.message": "Sorry, we could not find what you are looking for.", 51 | "Route.about": "about-us" 52 | } 53 | -------------------------------------------------------------------------------- /src/Localization/es-ES.json: -------------------------------------------------------------------------------- 1 | { 2 | "locale": "es-ES", 3 | "LatestArticles.title": "Último artículo", 4 | "LatestArticles.noTitleValue": "(El artículo no tiene título)", 5 | "LatestArticles.noTeaserValue": "(El artículo no tiene imagen teaser)", 6 | "LatestArticles.noSummaryValue": "Sin resumen lleno", 7 | "About.noTitleValue": "(Hecho no tiene titulo)", 8 | "About.noDescriptionValue": "ninguna descripción llena", 9 | "About.noTeaserValue": "(hecho no tiene imagen teaser)", 10 | "Articles.noTitleValue": "(El artículo no tiene título)", 11 | "Articles.noTeaserValue": "(El artículo no tiene imagen teaser)", 12 | "Articles.noSummaryValue": "Sin resumen lleno", 13 | "Articles.noBodyCopyValue": "ninguna copia del cuerpo llena", 14 | "Home.moreArticles": "Más artículos", 15 | "Home.aboutLinkText": "Leer toda la historia", 16 | "Home.cafesLinkText": "Más cafés", 17 | "TasteOurCoffee.title": "Probar nuestro café", 18 | "Header.homeLinkTitle": "Inicio", 19 | "Header.storeLinkTitle": "Tienda", 20 | "Header.articlesLinkTitle": "Artículos", 21 | "Header.aboutLinkTitle": "Quiénes somos", 22 | "Header.cafesLinkTitle": "Cafés", 23 | "Header.contactsLinkTitle": "Contacto", 24 | "Store.coffeesLinkTitle": "Cafés", 25 | "Store.brewersLinkTitle": "Cerveceros", 26 | "CoffeeStoreContainer.coffeeProcessingTitle": "Procesamiento del café", 27 | "CoffeeStoreContainer.statusTitle": "Estado", 28 | "BrewerStoreContainer.manufacturerTitle": "Fabricante", 29 | "BrewerStoreContainer.priceTitle": "Precio", 30 | "BrewerStoreContainer.statusTitle": "Estado", 31 | "BrewerStoreListing.PriceValue": "Sin precio llenado", 32 | "BrewerStoreListing.NameValue": "Sin nombre lleno", 33 | "BrewerStoreListing.TeaserValue": "(El producto no tiene imagen teaser)", 34 | "BrewerStoreListing.DescriptionValue": "ninguna descripción llena", 35 | "Cafes.ourCafesTitle": "Nuestros cafés", 36 | "Cafes.partnerCafesTitle": "Otros lugares donde puedes tomar nuestro café", 37 | "CoffeeStoreListing.noPriceValue": "Sin precio llenado", 38 | "CoffeeStoreListing.noNameValue": "Sin nombre lleno", 39 | "CoffeeStoreListing.noTeaserValue": "(El producto no tiene imagen teaser)", 40 | "CoffeeStoreListing.noDescriptionValue": "ninguna descripción llena", 41 | "Contacts.roasteryTitle": "Roastery", 42 | "Contacts.ourCafesTitle": "Probar nuestro café", 43 | "Contacts.mapTitle": "Pase por nuestros cafés ", 44 | "Footer.followUs": "Síguenos", 45 | "Footer.followUsOnFacebook": "Síguenos en Facebook", 46 | "Footer.followUsOnTwitter": "Síganos en Twitter", 47 | "Footer.contact": "Contacto", 48 | "Footer.cityStateZip": "Illinois 60601, EE.UU.", 49 | "Footer.allRightsReserved": "Todos los derechos reservados.", 50 | "NotFound.message": "Lo sentimos, no pudimos encontrar lo que buscas.", 51 | "Route.about": "acerca-de" 52 | } 53 | -------------------------------------------------------------------------------- /src/LocalizedApp.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useMemo } from 'react'; 2 | import { Navigate, useLocation, useNavigate } from 'react-router-dom'; 3 | 4 | import App from './App'; 5 | import { 6 | languageCodes, 7 | languageCodesLowerCase, 8 | } from './Utilities/LanguageCodes'; 9 | import { localizationObject } from './Utilities/LocalizationLoader'; 10 | import { IntlProvider } from 'react-intl'; 11 | import Cookies from 'universal-cookie'; 12 | 13 | export type SetLanguageType = (newLanguage: string, newUrl?: string) => void; 14 | 15 | interface LocalizedAppProps { 16 | lang: string; 17 | } 18 | 19 | const LocalizedApp: React.FC = ({ lang }) => { 20 | const cookies = useMemo(() => new Cookies(document.cookie), []); 21 | const { pathname } = useLocation(); 22 | const navigate = useNavigate(); 23 | 24 | useEffect(() => { 25 | cookies.set('lang', lang, { path: '/' }); 26 | }, [lang, cookies]); 27 | 28 | const setLanguageCode: SetLanguageType = (newLanguage, newUrl) => { 29 | if (lang === newLanguage || languageCodes.indexOf(newLanguage) < 0) { 30 | return; 31 | } 32 | 33 | const urlParts = pathname.split('/'); 34 | const currentLanguage = pathname.split('/')[1]; 35 | if (languageCodesLowerCase.indexOf(currentLanguage) > -1) { 36 | urlParts[1] = newLanguage; 37 | } else { 38 | urlParts.splice(1, 0, newLanguage); 39 | } 40 | 41 | if (newUrl) { 42 | navigate(urlParts.splice(0, 2).join('/').toLowerCase() + newUrl); 43 | } else { 44 | navigate(urlParts.join('/').toLowerCase()); 45 | } 46 | }; 47 | 48 | if (pathname !== pathname.toLowerCase()) { 49 | return ; 50 | } 51 | 52 | return ( 53 |
54 | 55 | 56 | 57 |
58 | ); 59 | }; 60 | 61 | export default LocalizedApp; 62 | -------------------------------------------------------------------------------- /src/Models/content-types/about_us.ts: -------------------------------------------------------------------------------- 1 | import { IContentItem, Elements } from '@kontent-ai/delivery-sdk'; 2 | import { Sitemap } from '../taxonomies/sitemap_538125f'; 3 | import { FactAboutUs } from './fact_about_us'; 4 | 5 | /** 6 | * Generated by '@kontent-ai/model-generator@5.2.0' 7 | * 8 | * About us 9 | * Id: b2c14f2c-6467-460b-a70b-bca17972a33a 10 | * Codename: about_us 11 | */ 12 | export type AboutUs = IContentItem<{ 13 | /** 14 | * Facts (modular_content) 15 | * Required: false 16 | * Id: cff560dc-ed24-7036-cbb6-b7a1b61b196a 17 | * Codename: facts 18 | * 19 | * Assign all facts about our company which will be displayed on the about us page. 20 | */ 21 | facts: Elements.LinkedItemsElement; 22 | 23 | /** 24 | * URL pattern (url_slug) 25 | * Required: false 26 | * Id: 50cb9acf-45f5-a186-5c00-9a9e4a37d84a 27 | * Codename: url_pattern 28 | * 29 | * Provide a SEO-friendly URL. 30 | */ 31 | urlPattern: Elements.UrlSlugElement; 32 | 33 | /** 34 | * Sitemap (taxonomy) 35 | * Required: false 36 | * Id: 65911e49-7bb0-41ca-9d17-ab218135126d 37 | * Codename: sitemap 38 | */ 39 | sitemap: Elements.TaxonomyElement; 40 | 41 | /** 42 | * Meta title (text) 43 | * Required: false 44 | * Id: 09398b24-61ed-512e-5b5c-affd54a098e5 45 | * Codename: metadata__meta_title 46 | * From snippet: Metadata 47 | * Snippet codename: metadata 48 | * 49 | * Length: 30–60 characters 50 | */ 51 | metadataMetaTitle: Elements.TextElement; 52 | 53 | /** 54 | * Meta description (text) 55 | * Required: false 56 | * Id: 2e555cc1-1eae-520c-189e-28548904f529 57 | * Codename: metadata__meta_description 58 | * From snippet: Metadata 59 | * Snippet codename: metadata 60 | * 61 | * Length: 70–150 characters 62 | */ 63 | metadataMetaDescription: Elements.TextElement; 64 | 65 | /** 66 | * og:title (text) 67 | * Required: false 68 | * Id: 1db86c7a-e836-3c4e-01e6-4f704ad38ba5 69 | * Codename: metadata__og_title 70 | * From snippet: Metadata 71 | * Snippet codename: metadata 72 | * 73 | * Max. 60 characters 74 | */ 75 | metadataOgTitle: Elements.TextElement; 76 | 77 | /** 78 | * og:description (text) 79 | * Required: false 80 | * Id: 05987dc9-ebe3-7b61-b949-522eb42dbc0d 81 | * Codename: metadata__og_description 82 | * From snippet: Metadata 83 | * Snippet codename: metadata 84 | * 85 | * Max. 150 characters 86 | */ 87 | metadataOgDescription: Elements.TextElement; 88 | 89 | /** 90 | * og:image (asset) 91 | * Required: false 92 | * Id: ce6cda71-9d38-1d57-3ac3-ec9b2e286edd 93 | * Codename: metadata__og_image 94 | * From snippet: Metadata 95 | * Snippet codename: metadata 96 | * 97 | * Use images that are at least 1200 × 630 px for best display on high resolution devices. At the minimum, you should use images that are 600 × 315 px to display link page posts with larger images. Images may be up to 1 MB in size. 98 | */ 99 | metadataOgImage: Elements.AssetsElement; 100 | 101 | /** 102 | * twitter:site (text) 103 | * Required: false 104 | * Id: 34213fdf-0015-8f4f-e5e6-83c6842cff4a 105 | * Codename: metadata__twitter_site 106 | * From snippet: Metadata 107 | * Snippet codename: metadata 108 | */ 109 | metadataTwitterSite: Elements.TextElement; 110 | 111 | /** 112 | * twitter:creator (text) 113 | * Required: false 114 | * Id: 68f65095-c9b4-05d6-a473-2883c2f0c7af 115 | * Codename: metadata__twitter_creator 116 | * From snippet: Metadata 117 | * Snippet codename: metadata 118 | */ 119 | metadataTwitterCreator: Elements.TextElement; 120 | 121 | /** 122 | * twitter:title (text) 123 | * Required: false 124 | * Id: b208d3dc-bd8d-b1af-5ef0-747650730ba7 125 | * Codename: metadata__twitter_title 126 | * From snippet: Metadata 127 | * Snippet codename: metadata 128 | * 129 | * Max. 60 characters 130 | */ 131 | metadataTwitterTitle: Elements.TextElement; 132 | 133 | /** 134 | * twitter:description (text) 135 | * Required: false 136 | * Id: b7d1dd8b-a5d8-2ad8-2f57-49881363f6f7 137 | * Codename: metadata__twitter_description 138 | * From snippet: Metadata 139 | * Snippet codename: metadata 140 | * 141 | * A description that concisely summarizes the content as appropriate for presentation within a Tweet. You should not re-use the title as the description, or use this field to describe the general services provided by the website. Max. 150 characters. 142 | */ 143 | metadataTwitterDescription: Elements.TextElement; 144 | 145 | /** 146 | * twitter:image (asset) 147 | * Required: false 148 | * Id: 63793ba4-6004-a93c-68ca-52a1f0482bca 149 | * Codename: metadata__twitter_image 150 | * From snippet: Metadata 151 | * Snippet codename: metadata 152 | * 153 | * Twitter's recommendation: A URL to a unique image representing the content of the page. You should not use a generic image such as your website logo, author photo, or other image that spans multiple pages. Images for this Card should be at least 280 × 150 px. Images may be up to 1 MB in size.Let's go for 560 × 300 px. 154 | */ 155 | metadataTwitterImage: Elements.AssetsElement; 156 | }>; 157 | -------------------------------------------------------------------------------- /src/Models/content-types/accessory.ts: -------------------------------------------------------------------------------- 1 | import { IContentItem, Elements } from '@kontent-ai/delivery-sdk'; 2 | import { ProductStatus } from '../taxonomies/product_status'; 3 | import { Sitemap } from '../taxonomies/sitemap_538125f'; 4 | 5 | /** 6 | * Generated by '@kontent-ai/model-generator@5.2.0' 7 | * 8 | * Accessory 9 | * Id: d9748663-f567-4c51-a922-c24a1d6b935a 10 | * Codename: accessory 11 | */ 12 | export type Accessory = IContentItem<{ 13 | /** 14 | * Product name (text) 15 | * Required: false 16 | * Id: f9e2672c-5035-412e-3985-d6112b3781bd 17 | * Codename: product_name 18 | * 19 | * Include a product display name. 20 | */ 21 | productName: Elements.TextElement; 22 | 23 | /** 24 | * Price (number) 25 | * Required: false 26 | * Id: 51d63ac3-d40d-15ea-c219-be207714077c 27 | * Codename: price 28 | * 29 | * Include a product price. 30 | */ 31 | price: Elements.NumberElement; 32 | 33 | /** 34 | * Image (asset) 35 | * Required: false 36 | * Id: f0db12e6-86e4-8597-903b-c5984076d6b3 37 | * Codename: image 38 | * 39 | * Upload one product image; the recommended size is 300 × 300 px. 40 | */ 41 | image: Elements.AssetsElement; 42 | 43 | /** 44 | * Manufacturer (text) 45 | * Required: false 46 | * Id: ab75ff46-b629-5ce5-aac9-79ed8a7b869c 47 | * Codename: manufacturer 48 | * 49 | * Include a manufacturer's name. 50 | */ 51 | manufacturer: Elements.TextElement; 52 | 53 | /** 54 | * Product status (taxonomy) 55 | * Required: false 56 | * Id: ef13b1f4-b558-f707-35a4-86146dbe4518 57 | * Codename: product_status 58 | * 59 | * Add a product status if the product is included in a special offering. 60 | */ 61 | productStatus: Elements.TaxonomyElement; 62 | 63 | /** 64 | * Short description (rich_text) 65 | * Required: false 66 | * Id: 9740e2d0-87e8-52f5-ff4c-566fa00b1253 67 | * Codename: short_description 68 | * 69 | * Include a short description that fits within 160 characters. 70 | */ 71 | shortDescription: Elements.RichTextElement; 72 | 73 | /** 74 | * Long description (rich_text) 75 | * Required: false 76 | * Id: 1f961774-a589-4e21-9f8e-a8c4908ea476 77 | * Codename: long_description 78 | * 79 | * Include a full product description. 80 | */ 81 | longDescription: Elements.RichTextElement; 82 | 83 | /** 84 | * URL pattern (url_slug) 85 | * Required: false 86 | * Id: 69e4af48-f1ac-1146-e6c9-d20d55ca5792 87 | * Codename: url_pattern 88 | * 89 | * Provide a SEO-friendly URL. 90 | */ 91 | urlPattern: Elements.UrlSlugElement; 92 | 93 | /** 94 | * Sitemap (taxonomy) 95 | * Required: false 96 | * Id: d8ed1597-930c-4cf8-a34e-c5d5e82718ed 97 | * Codename: sitemap 98 | */ 99 | sitemap: Elements.TaxonomyElement; 100 | 101 | /** 102 | * Meta title (text) 103 | * Required: false 104 | * Id: 09398b24-61ed-512e-5b5c-affd54a098e5 105 | * Codename: metadata__meta_title 106 | * From snippet: Metadata 107 | * Snippet codename: metadata 108 | * 109 | * Length: 30–60 characters 110 | */ 111 | metadataMetaTitle: Elements.TextElement; 112 | 113 | /** 114 | * Meta description (text) 115 | * Required: false 116 | * Id: 2e555cc1-1eae-520c-189e-28548904f529 117 | * Codename: metadata__meta_description 118 | * From snippet: Metadata 119 | * Snippet codename: metadata 120 | * 121 | * Length: 70–150 characters 122 | */ 123 | metadataMetaDescription: Elements.TextElement; 124 | 125 | /** 126 | * og:title (text) 127 | * Required: false 128 | * Id: 1db86c7a-e836-3c4e-01e6-4f704ad38ba5 129 | * Codename: metadata__og_title 130 | * From snippet: Metadata 131 | * Snippet codename: metadata 132 | * 133 | * Max. 60 characters 134 | */ 135 | metadataOgTitle: Elements.TextElement; 136 | 137 | /** 138 | * og:description (text) 139 | * Required: false 140 | * Id: 05987dc9-ebe3-7b61-b949-522eb42dbc0d 141 | * Codename: metadata__og_description 142 | * From snippet: Metadata 143 | * Snippet codename: metadata 144 | * 145 | * Max. 150 characters 146 | */ 147 | metadataOgDescription: Elements.TextElement; 148 | 149 | /** 150 | * og:image (asset) 151 | * Required: false 152 | * Id: ce6cda71-9d38-1d57-3ac3-ec9b2e286edd 153 | * Codename: metadata__og_image 154 | * From snippet: Metadata 155 | * Snippet codename: metadata 156 | * 157 | * Use images that are at least 1200 × 630 px for best display on high resolution devices. At the minimum, you should use images that are 600 × 315 px to display link page posts with larger images. Images may be up to 1 MB in size. 158 | */ 159 | metadataOgImage: Elements.AssetsElement; 160 | 161 | /** 162 | * twitter:site (text) 163 | * Required: false 164 | * Id: 34213fdf-0015-8f4f-e5e6-83c6842cff4a 165 | * Codename: metadata__twitter_site 166 | * From snippet: Metadata 167 | * Snippet codename: metadata 168 | */ 169 | metadataTwitterSite: Elements.TextElement; 170 | 171 | /** 172 | * twitter:creator (text) 173 | * Required: false 174 | * Id: 68f65095-c9b4-05d6-a473-2883c2f0c7af 175 | * Codename: metadata__twitter_creator 176 | * From snippet: Metadata 177 | * Snippet codename: metadata 178 | */ 179 | metadataTwitterCreator: Elements.TextElement; 180 | 181 | /** 182 | * twitter:title (text) 183 | * Required: false 184 | * Id: b208d3dc-bd8d-b1af-5ef0-747650730ba7 185 | * Codename: metadata__twitter_title 186 | * From snippet: Metadata 187 | * Snippet codename: metadata 188 | * 189 | * Max. 60 characters 190 | */ 191 | metadataTwitterTitle: Elements.TextElement; 192 | 193 | /** 194 | * twitter:description (text) 195 | * Required: false 196 | * Id: b7d1dd8b-a5d8-2ad8-2f57-49881363f6f7 197 | * Codename: metadata__twitter_description 198 | * From snippet: Metadata 199 | * Snippet codename: metadata 200 | * 201 | * A description that concisely summarizes the content as appropriate for presentation within a Tweet. You should not re-use the title as the description, or use this field to describe the general services provided by the website. Max. 150 characters. 202 | */ 203 | metadataTwitterDescription: Elements.TextElement; 204 | 205 | /** 206 | * twitter:image (asset) 207 | * Required: false 208 | * Id: 63793ba4-6004-a93c-68ca-52a1f0482bca 209 | * Codename: metadata__twitter_image 210 | * From snippet: Metadata 211 | * Snippet codename: metadata 212 | * 213 | * Twitter's recommendation: A URL to a unique image representing the content of the page. You should not use a generic image such as your website logo, author photo, or other image that spans multiple pages. Images for this Card should be at least 280 × 150 px. Images may be up to 1 MB in size.Let's go for 560 × 300 px. 214 | */ 215 | metadataTwitterImage: Elements.AssetsElement; 216 | }>; 217 | -------------------------------------------------------------------------------- /src/Models/content-types/article.ts: -------------------------------------------------------------------------------- 1 | import { IContentItem, Elements } from '@kontent-ai/delivery-sdk'; 2 | import { Personas } from '../taxonomies/personas'; 3 | import { Sitemap } from '../taxonomies/sitemap_538125f'; 4 | 5 | /** 6 | * Generated by '@kontent-ai/model-generator@5.2.0' 7 | * 8 | * Article 9 | * Id: b7aa4a53-d9b1-48cf-b7a6-ed0b182c4b89 10 | * Codename: article 11 | */ 12 | export type Article = IContentItem<{ 13 | /** 14 | * Title (text) 15 | * Required: false 16 | * Id: 85d5efc6-f47e-2fde-a6f5-0950fe89ecd1 17 | * Codename: title 18 | * 19 | * The title should fit within 60 characters.Our voice and tone recommendations: — avoid coffee jargon. 20 | */ 21 | title: Elements.TextElement; 22 | 23 | /** 24 | * Teaser image (asset) 25 | * Required: false 26 | * Id: 62eb9881-e222-6b81-91d2-fdf052726414 27 | * Codename: teaser_image 28 | * 29 | * Upload an image at a resolution of at least 600 × 1200 px. 30 | */ 31 | teaserImage: Elements.AssetsElement; 32 | 33 | /** 34 | * Post date (date_time) 35 | * Required: false 36 | * Id: 4ae5f7a9-fe1f-1e8c-bfec-d321455139c4 37 | * Codename: post_date 38 | * 39 | * Provide a date that will appear on the live site as the date this article was posted live. This date will also influence the order of the articles. 40 | */ 41 | postDate: Elements.DateTimeElement; 42 | 43 | /** 44 | * Summary (text) 45 | * Required: false 46 | * Id: 90550cbe-7bff-40a9-2947-9c81489fe562 47 | * Codename: summary 48 | * 49 | * Provide a short summary of the text. It should be catchy and make the visitor want to read the whole article.The summary should fit within 160 characters. 50 | */ 51 | summary: Elements.TextElement; 52 | 53 | /** 54 | * Body Copy (rich_text) 55 | * Required: false 56 | * Id: 108ed7c0-fc8c-c0ec-d0b5-5a8071408b54 57 | * Codename: body_copy 58 | * 59 | * Keep the article structured with concise paragraphs complemented with headlines that will help the reader navigate through the article's content.Preferred glossary terms — coffee, brewing, grinder, drip, roast, filter. 60 | */ 61 | bodyCopy: Elements.RichTextElement; 62 | 63 | /** 64 | * Related articles (modular_content) 65 | * Required: false 66 | * Id: ee7c3687-b469-6c56-3ac6-c8dfdc8b58b5 67 | * Codename: related_articles 68 | * 69 | * Provide articles with related topics. 70 | */ 71 | relatedArticles: Elements.LinkedItemsElement; 72 | 73 | /** 74 | * Meta keywords (text) 75 | * Required: false 76 | * Id: 5efb2425-5987-a4a6-a2d3-b14712b56e73 77 | * Codename: meta_keywords 78 | * 79 | * Enter tags separated with a comma. Example: coffee, "coffee roast”, grinder 80 | */ 81 | metaKeywords: Elements.TextElement; 82 | 83 | /** 84 | * Personas (taxonomy) 85 | * Required: false 86 | * Id: 0a16b642-ac3e-584d-a45a-ba354a30b2bd 87 | * Codename: personas 88 | * 89 | * Provide all personas for which this article is relevant. 90 | */ 91 | personas: Elements.TaxonomyElement; 92 | 93 | /** 94 | * Meta description (text) 95 | * Required: false 96 | * Id: b9dc537c-2518-e4f5-8325-ce4fce26171e 97 | * Codename: meta_description 98 | * 99 | * Sum up the blog for SEO purposes. Limit for the meta description is 160 characters. 100 | */ 101 | metaDescription: Elements.TextElement; 102 | 103 | /** 104 | * URL pattern (url_slug) 105 | * Required: false 106 | * Id: f2ff5e3f-a9ca-4604-58b0-34a2ad6a7cf1 107 | * Codename: url_pattern 108 | * 109 | * Provide a SEO-friendly URL. 110 | */ 111 | urlPattern: Elements.UrlSlugElement; 112 | 113 | /** 114 | * Sitemap (taxonomy) 115 | * Required: false 116 | * Id: a4d7a146-7e39-4001-b462-c5a0c734f3f5 117 | * Codename: sitemap 118 | */ 119 | sitemap: Elements.TaxonomyElement; 120 | 121 | /** 122 | * Meta title (text) 123 | * Required: false 124 | * Id: 09398b24-61ed-512e-5b5c-affd54a098e5 125 | * Codename: metadata__meta_title 126 | * From snippet: Metadata 127 | * Snippet codename: metadata 128 | * 129 | * Length: 30–60 characters 130 | */ 131 | metadataMetaTitle: Elements.TextElement; 132 | 133 | /** 134 | * Meta description (text) 135 | * Required: false 136 | * Id: 2e555cc1-1eae-520c-189e-28548904f529 137 | * Codename: metadata__meta_description 138 | * From snippet: Metadata 139 | * Snippet codename: metadata 140 | * 141 | * Length: 70–150 characters 142 | */ 143 | metadataMetaDescription: Elements.TextElement; 144 | 145 | /** 146 | * og:title (text) 147 | * Required: false 148 | * Id: 1db86c7a-e836-3c4e-01e6-4f704ad38ba5 149 | * Codename: metadata__og_title 150 | * From snippet: Metadata 151 | * Snippet codename: metadata 152 | * 153 | * Max. 60 characters 154 | */ 155 | metadataOgTitle: Elements.TextElement; 156 | 157 | /** 158 | * og:description (text) 159 | * Required: false 160 | * Id: 05987dc9-ebe3-7b61-b949-522eb42dbc0d 161 | * Codename: metadata__og_description 162 | * From snippet: Metadata 163 | * Snippet codename: metadata 164 | * 165 | * Max. 150 characters 166 | */ 167 | metadataOgDescription: Elements.TextElement; 168 | 169 | /** 170 | * og:image (asset) 171 | * Required: false 172 | * Id: ce6cda71-9d38-1d57-3ac3-ec9b2e286edd 173 | * Codename: metadata__og_image 174 | * From snippet: Metadata 175 | * Snippet codename: metadata 176 | * 177 | * Use images that are at least 1200 × 630 px for best display on high resolution devices. At the minimum, you should use images that are 600 × 315 px to display link page posts with larger images. Images may be up to 1 MB in size. 178 | */ 179 | metadataOgImage: Elements.AssetsElement; 180 | 181 | /** 182 | * twitter:site (text) 183 | * Required: false 184 | * Id: 34213fdf-0015-8f4f-e5e6-83c6842cff4a 185 | * Codename: metadata__twitter_site 186 | * From snippet: Metadata 187 | * Snippet codename: metadata 188 | */ 189 | metadataTwitterSite: Elements.TextElement; 190 | 191 | /** 192 | * twitter:creator (text) 193 | * Required: false 194 | * Id: 68f65095-c9b4-05d6-a473-2883c2f0c7af 195 | * Codename: metadata__twitter_creator 196 | * From snippet: Metadata 197 | * Snippet codename: metadata 198 | */ 199 | metadataTwitterCreator: Elements.TextElement; 200 | 201 | /** 202 | * twitter:title (text) 203 | * Required: false 204 | * Id: b208d3dc-bd8d-b1af-5ef0-747650730ba7 205 | * Codename: metadata__twitter_title 206 | * From snippet: Metadata 207 | * Snippet codename: metadata 208 | * 209 | * Max. 60 characters 210 | */ 211 | metadataTwitterTitle: Elements.TextElement; 212 | 213 | /** 214 | * twitter:description (text) 215 | * Required: false 216 | * Id: b7d1dd8b-a5d8-2ad8-2f57-49881363f6f7 217 | * Codename: metadata__twitter_description 218 | * From snippet: Metadata 219 | * Snippet codename: metadata 220 | * 221 | * A description that concisely summarizes the content as appropriate for presentation within a Tweet. You should not re-use the title as the description, or use this field to describe the general services provided by the website. Max. 150 characters. 222 | */ 223 | metadataTwitterDescription: Elements.TextElement; 224 | 225 | /** 226 | * twitter:image (asset) 227 | * Required: false 228 | * Id: 63793ba4-6004-a93c-68ca-52a1f0482bca 229 | * Codename: metadata__twitter_image 230 | * From snippet: Metadata 231 | * Snippet codename: metadata 232 | * 233 | * Twitter's recommendation: A URL to a unique image representing the content of the page. You should not use a generic image such as your website logo, author photo, or other image that spans multiple pages. Images for this Card should be at least 280 × 150 px. Images may be up to 1 MB in size.Let's go for 560 × 300 px. 234 | */ 235 | metadataTwitterImage: Elements.AssetsElement; 236 | }>; 237 | -------------------------------------------------------------------------------- /src/Models/content-types/brewer.ts: -------------------------------------------------------------------------------- 1 | import { IContentItem, Elements } from '@kontent-ai/delivery-sdk'; 2 | import { ProductStatus } from '../taxonomies/product_status'; 3 | import { Manufacturer } from '../taxonomies/manufacturer'; 4 | import { Sitemap } from '../taxonomies/sitemap_538125f'; 5 | 6 | /** 7 | * Generated by '@kontent-ai/model-generator@5.2.0' 8 | * 9 | * Brewer 10 | * Id: 7bc932b3-ce2a-4aa7-954e-04cbcbd214fc 11 | * Codename: brewer 12 | */ 13 | export type Brewer = IContentItem<{ 14 | /** 15 | * Product name (text) 16 | * Required: false 17 | * Id: 01d6c6c7-92ff-a675-e413-861e5a9a4034 18 | * Codename: product_name 19 | * 20 | * Include a product display name. 21 | */ 22 | productName: Elements.TextElement; 23 | 24 | /** 25 | * Price (number) 26 | * Required: false 27 | * Id: 48ebe0fc-afe7-2b93-d351-0e352332bc52 28 | * Codename: price 29 | * 30 | * Include a product price. 31 | */ 32 | price: Elements.NumberElement; 33 | 34 | /** 35 | * Image (asset) 36 | * Required: false 37 | * Id: d0c1e9e7-7d5d-e61f-8564-56a8cb758cb8 38 | * Codename: image 39 | * 40 | * Upload one product image; the recommended size is 300 × 300 px. 41 | */ 42 | image: Elements.AssetsElement; 43 | 44 | /** 45 | * Product status (taxonomy) 46 | * Required: false 47 | * Id: 960d0011-ff3b-41ac-1447-8eac6ee66eaa 48 | * Codename: product_status 49 | * 50 | * Add a product status if the product is included in a special offering. 51 | */ 52 | productStatus: Elements.TaxonomyElement; 53 | 54 | /** 55 | * Short description (rich_text) 56 | * Required: false 57 | * Id: 8c235a7f-034e-f803-e612-9c47c8a49506 58 | * Codename: short_description 59 | * 60 | * Include a short description that fits within 160 characters. 61 | */ 62 | shortDescription: Elements.RichTextElement; 63 | 64 | /** 65 | * Long description (rich_text) 66 | * Required: false 67 | * Id: 14510bef-fd86-7d5d-992a-c1e091cbcb97 68 | * Codename: long_description 69 | * 70 | * Include a full product description. 71 | */ 72 | longDescription: Elements.RichTextElement; 73 | 74 | /** 75 | * URL pattern (url_slug) 76 | * Required: false 77 | * Id: 737c85be-f532-c8fe-a308-2a0f2c512f3e 78 | * Codename: url_pattern 79 | * 80 | * Provide a SEO-friendly URL. 81 | */ 82 | urlPattern: Elements.UrlSlugElement; 83 | 84 | /** 85 | * Manufacturer (taxonomy) 86 | * Required: false 87 | * Id: 53b6bddb-fe8a-a8b2-9765-343479bf9fc2 88 | * Codename: manufacturer 89 | */ 90 | manufacturer: Elements.TaxonomyElement; 91 | 92 | /** 93 | * Sitemap (taxonomy) 94 | * Required: false 95 | * Id: 7632f3bb-2e53-438d-933c-4e8b6c035cb1 96 | * Codename: sitemap 97 | */ 98 | sitemap: Elements.TaxonomyElement; 99 | 100 | /** 101 | * Meta title (text) 102 | * Required: false 103 | * Id: 09398b24-61ed-512e-5b5c-affd54a098e5 104 | * Codename: metadata__meta_title 105 | * From snippet: Metadata 106 | * Snippet codename: metadata 107 | * 108 | * Length: 30–60 characters 109 | */ 110 | metadataMetaTitle: Elements.TextElement; 111 | 112 | /** 113 | * Meta description (text) 114 | * Required: false 115 | * Id: 2e555cc1-1eae-520c-189e-28548904f529 116 | * Codename: metadata__meta_description 117 | * From snippet: Metadata 118 | * Snippet codename: metadata 119 | * 120 | * Length: 70–150 characters 121 | */ 122 | metadataMetaDescription: Elements.TextElement; 123 | 124 | /** 125 | * og:title (text) 126 | * Required: false 127 | * Id: 1db86c7a-e836-3c4e-01e6-4f704ad38ba5 128 | * Codename: metadata__og_title 129 | * From snippet: Metadata 130 | * Snippet codename: metadata 131 | * 132 | * Max. 60 characters 133 | */ 134 | metadataOgTitle: Elements.TextElement; 135 | 136 | /** 137 | * og:description (text) 138 | * Required: false 139 | * Id: 05987dc9-ebe3-7b61-b949-522eb42dbc0d 140 | * Codename: metadata__og_description 141 | * From snippet: Metadata 142 | * Snippet codename: metadata 143 | * 144 | * Max. 150 characters 145 | */ 146 | metadataOgDescription: Elements.TextElement; 147 | 148 | /** 149 | * og:image (asset) 150 | * Required: false 151 | * Id: ce6cda71-9d38-1d57-3ac3-ec9b2e286edd 152 | * Codename: metadata__og_image 153 | * From snippet: Metadata 154 | * Snippet codename: metadata 155 | * 156 | * Use images that are at least 1200 × 630 px for best display on high resolution devices. At the minimum, you should use images that are 600 × 315 px to display link page posts with larger images. Images may be up to 1 MB in size. 157 | */ 158 | metadataOgImage: Elements.AssetsElement; 159 | 160 | /** 161 | * twitter:site (text) 162 | * Required: false 163 | * Id: 34213fdf-0015-8f4f-e5e6-83c6842cff4a 164 | * Codename: metadata__twitter_site 165 | * From snippet: Metadata 166 | * Snippet codename: metadata 167 | */ 168 | metadataTwitterSite: Elements.TextElement; 169 | 170 | /** 171 | * twitter:creator (text) 172 | * Required: false 173 | * Id: 68f65095-c9b4-05d6-a473-2883c2f0c7af 174 | * Codename: metadata__twitter_creator 175 | * From snippet: Metadata 176 | * Snippet codename: metadata 177 | */ 178 | metadataTwitterCreator: Elements.TextElement; 179 | 180 | /** 181 | * twitter:title (text) 182 | * Required: false 183 | * Id: b208d3dc-bd8d-b1af-5ef0-747650730ba7 184 | * Codename: metadata__twitter_title 185 | * From snippet: Metadata 186 | * Snippet codename: metadata 187 | * 188 | * Max. 60 characters 189 | */ 190 | metadataTwitterTitle: Elements.TextElement; 191 | 192 | /** 193 | * twitter:description (text) 194 | * Required: false 195 | * Id: b7d1dd8b-a5d8-2ad8-2f57-49881363f6f7 196 | * Codename: metadata__twitter_description 197 | * From snippet: Metadata 198 | * Snippet codename: metadata 199 | * 200 | * A description that concisely summarizes the content as appropriate for presentation within a Tweet. You should not re-use the title as the description, or use this field to describe the general services provided by the website. Max. 150 characters. 201 | */ 202 | metadataTwitterDescription: Elements.TextElement; 203 | 204 | /** 205 | * twitter:image (asset) 206 | * Required: false 207 | * Id: 63793ba4-6004-a93c-68ca-52a1f0482bca 208 | * Codename: metadata__twitter_image 209 | * From snippet: Metadata 210 | * Snippet codename: metadata 211 | * 212 | * Twitter's recommendation: A URL to a unique image representing the content of the page. You should not use a generic image such as your website logo, author photo, or other image that spans multiple pages. Images for this Card should be at least 280 × 150 px. Images may be up to 1 MB in size.Let's go for 560 × 300 px. 213 | */ 214 | metadataTwitterImage: Elements.AssetsElement; 215 | }>; 216 | -------------------------------------------------------------------------------- /src/Models/content-types/cafe.ts: -------------------------------------------------------------------------------- 1 | import { IContentItem, Elements } from '@kontent-ai/delivery-sdk'; 2 | import { Sitemap } from '../taxonomies/sitemap_538125f'; 3 | 4 | /** 5 | * Generated by '@kontent-ai/model-generator@5.2.0' 6 | * 7 | * Cafe 8 | * Id: fe41ae5a-5fe2-420a-8560-f7d6d3533dc2 9 | * Codename: cafe 10 | */ 11 | export type Cafe = IContentItem<{ 12 | /** 13 | * Street (text) 14 | * Required: false 15 | * Id: 866afdba-d334-f01a-1d52-a9ca3f57cb4b 16 | * Codename: street 17 | */ 18 | street: Elements.TextElement; 19 | 20 | /** 21 | * City (text) 22 | * Required: false 23 | * Id: 339e6d4f-67c1-5f5e-6921-3b374eb96f5b 24 | * Codename: city 25 | */ 26 | city: Elements.TextElement; 27 | 28 | /** 29 | * Country (text) 30 | * Required: false 31 | * Id: 7531a08f-e148-8cc0-9d2d-155215502e08 32 | * Codename: country 33 | */ 34 | country: Elements.TextElement; 35 | 36 | /** 37 | * State (text) 38 | * Required: false 39 | * Id: a015b689-cad3-1ac9-04b4-73697525752d 40 | * Codename: state 41 | */ 42 | state: Elements.TextElement; 43 | 44 | /** 45 | * ZIP Code (text) 46 | * Required: false 47 | * Id: bb158ac2-41e1-5a7d-0826-bb8bf6744f0e 48 | * Codename: zip_code 49 | */ 50 | zipCode: Elements.TextElement; 51 | 52 | /** 53 | * Phone (text) 54 | * Required: false 55 | * Id: 1c71bc62-4b62-f307-37ef-0823776f8f73 56 | * Codename: phone 57 | */ 58 | phone: Elements.TextElement; 59 | 60 | /** 61 | * Email (text) 62 | * Required: false 63 | * Id: 6f726c77-36bd-8062-51df-056136e10d35 64 | * Codename: email 65 | */ 66 | email: Elements.TextElement; 67 | 68 | /** 69 | * Photo (asset) 70 | * Required: false 71 | * Id: 5769c0f4-66a8-4c73-3c19-c023bdfa123a 72 | * Codename: photo 73 | */ 74 | photo: Elements.AssetsElement; 75 | 76 | /** 77 | * Sitemap (taxonomy) 78 | * Required: false 79 | * Id: e82d0f49-5b15-45e1-9b1f-32ccc1be4941 80 | * Codename: sitemap 81 | */ 82 | sitemap: Elements.TaxonomyElement; 83 | }>; 84 | -------------------------------------------------------------------------------- /src/Models/content-types/coffee.ts: -------------------------------------------------------------------------------- 1 | import { IContentItem, Elements } from '@kontent-ai/delivery-sdk'; 2 | import { ProductStatus } from '../taxonomies/product_status'; 3 | import { Processing } from '../taxonomies/processing'; 4 | import { Sitemap } from '../taxonomies/sitemap_538125f'; 5 | 6 | /** 7 | * Generated by '@kontent-ai/model-generator@5.2.0' 8 | * 9 | * Coffee 10 | * Id: 929985ac-4aa5-436b-85a2-94c2d4fbbebd 11 | * Codename: coffee 12 | */ 13 | export type Coffee = IContentItem<{ 14 | /** 15 | * Product name (text) 16 | * Required: false 17 | * Id: edaec5c4-e653-9109-eb0d-fc40ccf3c810 18 | * Codename: product_name 19 | * 20 | * Include a product display name. 21 | */ 22 | productName: Elements.TextElement; 23 | 24 | /** 25 | * Price (number) 26 | * Required: false 27 | * Id: 624592dc-49b2-330a-7185-e1f2396ce90c 28 | * Codename: price 29 | * 30 | * Include a product price. 31 | */ 32 | price: Elements.NumberElement; 33 | 34 | /** 35 | * Image (asset) 36 | * Required: false 37 | * Id: 30ac3ccc-1e7f-1490-e1f6-915c64176a55 38 | * Codename: image 39 | * 40 | * Upload a product image; the recommended size is 300 × 300 px. 41 | */ 42 | image: Elements.AssetsElement; 43 | 44 | /** 45 | * Short description (rich_text) 46 | * Required: false 47 | * Id: b5a3263a-a1d7-92b7-865a-329f833285fa 48 | * Codename: short_description 49 | * 50 | * Include a short description that fits within 160 characters. 51 | */ 52 | shortDescription: Elements.RichTextElement; 53 | 54 | /** 55 | * Long description (rich_text) 56 | * Required: false 57 | * Id: d468a0aa-e0fa-0cae-41db-6e006bff2527 58 | * Codename: long_description 59 | * 60 | * Include a full product description. 61 | */ 62 | longDescription: Elements.RichTextElement; 63 | 64 | /** 65 | * Product status (taxonomy) 66 | * Required: false 67 | * Id: 1ee64175-fde7-fc1e-5259-511a31c326c3 68 | * Codename: product_status 69 | * 70 | * Add a product status if the product is included in a special offering. 71 | */ 72 | productStatus: Elements.TaxonomyElement; 73 | 74 | /** 75 | * Farm (text) 76 | * Required: false 77 | * Id: e5cf103f-9b84-1ab0-29f1-fb5a1657c6f7 78 | * Codename: farm 79 | * 80 | * Include the name of the coffee farm. 81 | */ 82 | farm: Elements.TextElement; 83 | 84 | /** 85 | * Country (text) 86 | * Required: false 87 | * Id: 6eec1918-378d-3b15-8b1a-19c5f0748321 88 | * Codename: country 89 | * 90 | * Include the coffee's country of origin. 91 | */ 92 | country: Elements.TextElement; 93 | 94 | /** 95 | * Variety (text) 96 | * Required: false 97 | * Id: 301c6712-962f-b05a-6f6e-2f0e1e959039 98 | * Codename: variety 99 | * 100 | * Include a coffee variety name. 101 | */ 102 | variety: Elements.TextElement; 103 | 104 | /** 105 | * Altitude (text) 106 | * Required: false 107 | * Id: 23a772c0-0b2b-588d-9849-e29068701f03 108 | * Codename: altitude 109 | * 110 | * Include the altitude at which the coffee is grown. Elevation affects the size, shape, and taste of coffee beans. 111 | */ 112 | altitude: Elements.TextElement; 113 | 114 | /** 115 | * URL pattern (url_slug) 116 | * Required: false 117 | * Id: 3af8ce38-c03d-063c-ea80-72684dfddf31 118 | * Codename: url_pattern 119 | * 120 | * Provide a SEO-friendly URL. 121 | */ 122 | urlPattern: Elements.UrlSlugElement; 123 | 124 | /** 125 | * Processing (taxonomy) 126 | * Required: false 127 | * Id: b63e3516-d5fa-fdac-a03a-2b027bf02a28 128 | * Codename: processing 129 | */ 130 | processing: Elements.TaxonomyElement; 131 | 132 | /** 133 | * Sitemap (taxonomy) 134 | * Required: false 135 | * Id: 99d7a378-e18f-41dd-b5bb-2a569c3d1de7 136 | * Codename: sitemap 137 | */ 138 | sitemap: Elements.TaxonomyElement; 139 | 140 | /** 141 | * Meta title (text) 142 | * Required: false 143 | * Id: 09398b24-61ed-512e-5b5c-affd54a098e5 144 | * Codename: metadata__meta_title 145 | * From snippet: Metadata 146 | * Snippet codename: metadata 147 | * 148 | * Length: 30–60 characters 149 | */ 150 | metadataMetaTitle: Elements.TextElement; 151 | 152 | /** 153 | * Meta description (text) 154 | * Required: false 155 | * Id: 2e555cc1-1eae-520c-189e-28548904f529 156 | * Codename: metadata__meta_description 157 | * From snippet: Metadata 158 | * Snippet codename: metadata 159 | * 160 | * Length: 70–150 characters 161 | */ 162 | metadataMetaDescription: Elements.TextElement; 163 | 164 | /** 165 | * og:title (text) 166 | * Required: false 167 | * Id: 1db86c7a-e836-3c4e-01e6-4f704ad38ba5 168 | * Codename: metadata__og_title 169 | * From snippet: Metadata 170 | * Snippet codename: metadata 171 | * 172 | * Max. 60 characters 173 | */ 174 | metadataOgTitle: Elements.TextElement; 175 | 176 | /** 177 | * og:description (text) 178 | * Required: false 179 | * Id: 05987dc9-ebe3-7b61-b949-522eb42dbc0d 180 | * Codename: metadata__og_description 181 | * From snippet: Metadata 182 | * Snippet codename: metadata 183 | * 184 | * Max. 150 characters 185 | */ 186 | metadataOgDescription: Elements.TextElement; 187 | 188 | /** 189 | * og:image (asset) 190 | * Required: false 191 | * Id: ce6cda71-9d38-1d57-3ac3-ec9b2e286edd 192 | * Codename: metadata__og_image 193 | * From snippet: Metadata 194 | * Snippet codename: metadata 195 | * 196 | * Use images that are at least 1200 × 630 px for best display on high resolution devices. At the minimum, you should use images that are 600 × 315 px to display link page posts with larger images. Images may be up to 1 MB in size. 197 | */ 198 | metadataOgImage: Elements.AssetsElement; 199 | 200 | /** 201 | * twitter:site (text) 202 | * Required: false 203 | * Id: 34213fdf-0015-8f4f-e5e6-83c6842cff4a 204 | * Codename: metadata__twitter_site 205 | * From snippet: Metadata 206 | * Snippet codename: metadata 207 | */ 208 | metadataTwitterSite: Elements.TextElement; 209 | 210 | /** 211 | * twitter:creator (text) 212 | * Required: false 213 | * Id: 68f65095-c9b4-05d6-a473-2883c2f0c7af 214 | * Codename: metadata__twitter_creator 215 | * From snippet: Metadata 216 | * Snippet codename: metadata 217 | */ 218 | metadataTwitterCreator: Elements.TextElement; 219 | 220 | /** 221 | * twitter:title (text) 222 | * Required: false 223 | * Id: b208d3dc-bd8d-b1af-5ef0-747650730ba7 224 | * Codename: metadata__twitter_title 225 | * From snippet: Metadata 226 | * Snippet codename: metadata 227 | * 228 | * Max. 60 characters 229 | */ 230 | metadataTwitterTitle: Elements.TextElement; 231 | 232 | /** 233 | * twitter:description (text) 234 | * Required: false 235 | * Id: b7d1dd8b-a5d8-2ad8-2f57-49881363f6f7 236 | * Codename: metadata__twitter_description 237 | * From snippet: Metadata 238 | * Snippet codename: metadata 239 | * 240 | * A description that concisely summarizes the content as appropriate for presentation within a Tweet. You should not re-use the title as the description, or use this field to describe the general services provided by the website. Max. 150 characters. 241 | */ 242 | metadataTwitterDescription: Elements.TextElement; 243 | 244 | /** 245 | * twitter:image (asset) 246 | * Required: false 247 | * Id: 63793ba4-6004-a93c-68ca-52a1f0482bca 248 | * Codename: metadata__twitter_image 249 | * From snippet: Metadata 250 | * Snippet codename: metadata 251 | * 252 | * Twitter's recommendation: A URL to a unique image representing the content of the page. You should not use a generic image such as your website logo, author photo, or other image that spans multiple pages. Images for this Card should be at least 280 × 150 px. Images may be up to 1 MB in size.Let's go for 560 × 300 px. 253 | */ 254 | metadataTwitterImage: Elements.AssetsElement; 255 | }>; 256 | -------------------------------------------------------------------------------- /src/Models/content-types/fact_about_us.ts: -------------------------------------------------------------------------------- 1 | import { IContentItem, Elements } from '@kontent-ai/delivery-sdk'; 2 | import { Sitemap } from '../taxonomies/sitemap_538125f'; 3 | 4 | /** 5 | * Generated by '@kontent-ai/model-generator@5.2.0' 6 | * 7 | * Fact about us 8 | * Id: b99ec220-0f2b-4658-a080-ff0afe92f6d1 9 | * Codename: fact_about_us 10 | */ 11 | export type FactAboutUs = IContentItem<{ 12 | /** 13 | * Title (text) 14 | * Required: false 15 | * Id: 2375ca8a-43d2-7282-162c-922a6fdeba3e 16 | * Codename: title 17 | * 18 | * Provide a title that fits within 60 characters. 19 | */ 20 | title: Elements.TextElement; 21 | 22 | /** 23 | * Description (rich_text) 24 | * Required: false 25 | * Id: 1b658e81-88c9-73d1-374d-7a60e3756ef7 26 | * Codename: description 27 | * 28 | * Include a main goal of our business. The limit is 80 characters. 29 | */ 30 | description: Elements.RichTextElement; 31 | 32 | /** 33 | * Image (asset) 34 | * Required: false 35 | * Id: 68d194b6-efad-6b25-89a4-2dfc75fed5a5 36 | * Codename: image 37 | * 38 | * Attach a teaser image; max. dimensions are 1280 × 600 px; allowed formats are *.jpg, *.png, *.gif. 39 | */ 40 | image: Elements.AssetsElement; 41 | 42 | /** 43 | * Sitemap (taxonomy) 44 | * Required: false 45 | * Id: 23a715f9-755c-4b9d-b6b3-e02fc1b09b83 46 | * Codename: sitemap 47 | */ 48 | sitemap: Elements.TaxonomyElement; 49 | }>; 50 | -------------------------------------------------------------------------------- /src/Models/content-types/grinder.ts: -------------------------------------------------------------------------------- 1 | import { IContentItem, Elements } from '@kontent-ai/delivery-sdk'; 2 | import { ProductStatus } from '../taxonomies/product_status'; 3 | import { Sitemap } from '../taxonomies/sitemap_538125f'; 4 | 5 | /** 6 | * Generated by '@kontent-ai/model-generator@5.2.0' 7 | * 8 | * Grinder 9 | * Id: da4f1cb1-8a55-43e5-9fcc-67ad331c8888 10 | * Codename: grinder 11 | */ 12 | export type Grinder = IContentItem<{ 13 | /** 14 | * Product name (text) 15 | * Required: false 16 | * Id: b7b9604e-c74e-79ea-2a0f-d74d6347e35a 17 | * Codename: product_name 18 | * 19 | * Include a product display name. 20 | */ 21 | productName: Elements.TextElement; 22 | 23 | /** 24 | * Price (number) 25 | * Required: false 26 | * Id: 1e14595d-7fe8-7765-6df6-fad6a6e4851f 27 | * Codename: price 28 | * 29 | * Include a product price. 30 | */ 31 | price: Elements.NumberElement; 32 | 33 | /** 34 | * Image (asset) 35 | * Required: false 36 | * Id: 7fe226b9-d21f-80ff-e680-6e6f44ce044b 37 | * Codename: image 38 | * 39 | * Upload one product image; the recommended size is 300 × 300 px. 40 | */ 41 | image: Elements.AssetsElement; 42 | 43 | /** 44 | * Manufacturer (text) 45 | * Required: false 46 | * Id: 2c389736-7dfa-b03e-8996-7eba451cfdb5 47 | * Codename: manufacturer 48 | * 49 | * Include a manufacturer's name. 50 | */ 51 | manufacturer: Elements.TextElement; 52 | 53 | /** 54 | * Product status (taxonomy) 55 | * Required: false 56 | * Id: 54120e88-82cc-2202-e72d-9cae0ced3f51 57 | * Codename: product_status 58 | * 59 | * Add a product status if the product is included in a special offering. 60 | */ 61 | productStatus: Elements.TaxonomyElement; 62 | 63 | /** 64 | * Short description (rich_text) 65 | * Required: false 66 | * Id: 23ee310f-9732-9193-ad5e-80f75bfc276d 67 | * Codename: short_description 68 | * 69 | * Include a short description that fits within 160 characters. 70 | */ 71 | shortDescription: Elements.RichTextElement; 72 | 73 | /** 74 | * Long description (rich_text) 75 | * Required: false 76 | * Id: 21e77286-e87b-d6e3-902d-13b5814b5e75 77 | * Codename: long_description 78 | * 79 | * Include a full product description. 80 | */ 81 | longDescription: Elements.RichTextElement; 82 | 83 | /** 84 | * URL pattern (url_slug) 85 | * Required: false 86 | * Id: bf6ad588-11e5-ba0d-2c18-ccd50064a32a 87 | * Codename: url_pattern 88 | * 89 | * Provide a SEO-friendly URL. 90 | */ 91 | urlPattern: Elements.UrlSlugElement; 92 | 93 | /** 94 | * Sitemap (taxonomy) 95 | * Required: false 96 | * Id: 02f0e374-6760-4b4d-99a8-60485572b764 97 | * Codename: sitemap 98 | */ 99 | sitemap: Elements.TaxonomyElement; 100 | 101 | /** 102 | * Meta title (text) 103 | * Required: false 104 | * Id: 09398b24-61ed-512e-5b5c-affd54a098e5 105 | * Codename: metadata__meta_title 106 | * From snippet: Metadata 107 | * Snippet codename: metadata 108 | * 109 | * Length: 30–60 characters 110 | */ 111 | metadataMetaTitle: Elements.TextElement; 112 | 113 | /** 114 | * Meta description (text) 115 | * Required: false 116 | * Id: 2e555cc1-1eae-520c-189e-28548904f529 117 | * Codename: metadata__meta_description 118 | * From snippet: Metadata 119 | * Snippet codename: metadata 120 | * 121 | * Length: 70–150 characters 122 | */ 123 | metadataMetaDescription: Elements.TextElement; 124 | 125 | /** 126 | * og:title (text) 127 | * Required: false 128 | * Id: 1db86c7a-e836-3c4e-01e6-4f704ad38ba5 129 | * Codename: metadata__og_title 130 | * From snippet: Metadata 131 | * Snippet codename: metadata 132 | * 133 | * Max. 60 characters 134 | */ 135 | metadataOgTitle: Elements.TextElement; 136 | 137 | /** 138 | * og:description (text) 139 | * Required: false 140 | * Id: 05987dc9-ebe3-7b61-b949-522eb42dbc0d 141 | * Codename: metadata__og_description 142 | * From snippet: Metadata 143 | * Snippet codename: metadata 144 | * 145 | * Max. 150 characters 146 | */ 147 | metadataOgDescription: Elements.TextElement; 148 | 149 | /** 150 | * og:image (asset) 151 | * Required: false 152 | * Id: ce6cda71-9d38-1d57-3ac3-ec9b2e286edd 153 | * Codename: metadata__og_image 154 | * From snippet: Metadata 155 | * Snippet codename: metadata 156 | * 157 | * Use images that are at least 1200 × 630 px for best display on high resolution devices. At the minimum, you should use images that are 600 × 315 px to display link page posts with larger images. Images may be up to 1 MB in size. 158 | */ 159 | metadataOgImage: Elements.AssetsElement; 160 | 161 | /** 162 | * twitter:site (text) 163 | * Required: false 164 | * Id: 34213fdf-0015-8f4f-e5e6-83c6842cff4a 165 | * Codename: metadata__twitter_site 166 | * From snippet: Metadata 167 | * Snippet codename: metadata 168 | */ 169 | metadataTwitterSite: Elements.TextElement; 170 | 171 | /** 172 | * twitter:creator (text) 173 | * Required: false 174 | * Id: 68f65095-c9b4-05d6-a473-2883c2f0c7af 175 | * Codename: metadata__twitter_creator 176 | * From snippet: Metadata 177 | * Snippet codename: metadata 178 | */ 179 | metadataTwitterCreator: Elements.TextElement; 180 | 181 | /** 182 | * twitter:title (text) 183 | * Required: false 184 | * Id: b208d3dc-bd8d-b1af-5ef0-747650730ba7 185 | * Codename: metadata__twitter_title 186 | * From snippet: Metadata 187 | * Snippet codename: metadata 188 | * 189 | * Max. 60 characters 190 | */ 191 | metadataTwitterTitle: Elements.TextElement; 192 | 193 | /** 194 | * twitter:description (text) 195 | * Required: false 196 | * Id: b7d1dd8b-a5d8-2ad8-2f57-49881363f6f7 197 | * Codename: metadata__twitter_description 198 | * From snippet: Metadata 199 | * Snippet codename: metadata 200 | * 201 | * A description that concisely summarizes the content as appropriate for presentation within a Tweet. You should not re-use the title as the description, or use this field to describe the general services provided by the website. Max. 150 characters. 202 | */ 203 | metadataTwitterDescription: Elements.TextElement; 204 | 205 | /** 206 | * twitter:image (asset) 207 | * Required: false 208 | * Id: 63793ba4-6004-a93c-68ca-52a1f0482bca 209 | * Codename: metadata__twitter_image 210 | * From snippet: Metadata 211 | * Snippet codename: metadata 212 | * 213 | * Twitter's recommendation: A URL to a unique image representing the content of the page. You should not use a generic image such as your website logo, author photo, or other image that spans multiple pages. Images for this Card should be at least 280 × 150 px. Images may be up to 1 MB in size.Let's go for 560 × 300 px. 214 | */ 215 | metadataTwitterImage: Elements.AssetsElement; 216 | }>; 217 | -------------------------------------------------------------------------------- /src/Models/content-types/hero_unit.ts: -------------------------------------------------------------------------------- 1 | import { IContentItem, Elements } from '@kontent-ai/delivery-sdk'; 2 | import { Sitemap } from '../taxonomies/sitemap_538125f'; 3 | 4 | /** 5 | * Generated by '@kontent-ai/model-generator@5.2.0' 6 | * 7 | * Hero Unit 8 | * Id: f4deeb7e-fe9b-49a2-a5f6-a51a9be6ac73 9 | * Codename: hero_unit 10 | */ 11 | export type HeroUnit = IContentItem<{ 12 | /** 13 | * Title (text) 14 | * Required: false 15 | * Id: 16ea3e64-4103-da81-eabd-af7efc2ab8a6 16 | * Codename: title 17 | * 18 | * Provide a title that fits within 60 characters. 19 | */ 20 | title: Elements.TextElement; 21 | 22 | /** 23 | * Image (asset) 24 | * Required: false 25 | * Id: 4aeed98e-58d1-ab13-232c-542bf268fe48 26 | * Codename: image 27 | * 28 | * Attach a teaser image; max. dimensions are 1280 × 600 px; allowed formats are *.jpg, *.png, *.gif. 29 | */ 30 | image: Elements.AssetsElement; 31 | 32 | /** 33 | * Marketing message (rich_text) 34 | * Required: false 35 | * Id: ecf4e55f-1ae0-f539-3516-5714a0f032e9 36 | * Codename: marketing_message 37 | * 38 | * Include a main goal of our business. The limit is 80 characters. 39 | */ 40 | marketingMessage: Elements.RichTextElement; 41 | 42 | /** 43 | * Sitemap (taxonomy) 44 | * Required: false 45 | * Id: 0f620d29-a4c7-4944-b7d2-be5de2733b6e 46 | * Codename: sitemap 47 | */ 48 | sitemap: Elements.TaxonomyElement; 49 | }>; 50 | -------------------------------------------------------------------------------- /src/Models/content-types/home.ts: -------------------------------------------------------------------------------- 1 | import { IContentItem, Elements } from '@kontent-ai/delivery-sdk'; 2 | import { Sitemap } from '../taxonomies/sitemap_538125f'; 3 | import { Article } from './article'; 4 | import { Cafe } from './cafe'; 5 | import { FactAboutUs } from './fact_about_us'; 6 | import { HeroUnit } from './hero_unit'; 7 | 8 | /** 9 | * Generated by '@kontent-ai/model-generator@5.2.0' 10 | * 11 | * Home 12 | * Id: a29399c3-5281-47ab-9916-acd4a6f887b7 13 | * Codename: home 14 | */ 15 | export type Home = IContentItem<{ 16 | /** 17 | * Hero unit (modular_content) 18 | * Required: false 19 | * Id: 2b15a8f3-2e5f-7d01-4d8e-5b22e222aa76 20 | * Codename: hero_unit 21 | * 22 | * Assign 1 Hero unit that has been prepared for a home page. 23 | */ 24 | heroUnit: Elements.LinkedItemsElement; 25 | 26 | /** 27 | * Articles (modular_content) 28 | * Required: false 29 | * Id: 222f3a69-a54f-3e92-83ac-05f8a08e667f 30 | * Codename: articles 31 | * 32 | * Assign all articles which should be displayed on the home page. 33 | */ 34 | articles: Elements.LinkedItemsElement
; 35 | 36 | /** 37 | * Our story (modular_content) 38 | * Required: false 39 | * Id: 617bccc0-4844-4beb-4ede-6247e954633a 40 | * Codename: our_story 41 | * 42 | * Assign 1 Fact about us which will be displayed on the home page. 43 | */ 44 | ourStory: Elements.LinkedItemsElement; 45 | 46 | /** 47 | * Cafes (modular_content) 48 | * Required: false 49 | * Id: 6356c948-0fd6-00d0-8fc1-e2484180ae7c 50 | * Codename: cafes 51 | * 52 | * Assign 4 Cafes which will be displayed on the home page. 53 | */ 54 | cafes: Elements.LinkedItemsElement; 55 | 56 | /** 57 | * Contact (rich_text) 58 | * Required: false 59 | * Id: ee854076-236b-5312-0ed5-8c3cd55ca9e0 60 | * Codename: contact 61 | * 62 | * Add Contact us information to be displayed on the home page. 63 | */ 64 | contact: Elements.RichTextElement; 65 | 66 | /** 67 | * URL pattern (url_slug) 68 | * Required: false 69 | * Id: dd70db4b-ee97-5ab4-b752-4f9d70389426 70 | * Codename: url_pattern 71 | * 72 | * Provide a SEO-friendly URL. 73 | */ 74 | urlPattern: Elements.UrlSlugElement; 75 | 76 | /** 77 | * Sitemap (taxonomy) 78 | * Required: false 79 | * Id: ea3bf3c2-7eb7-4c91-aff3-09f89c743710 80 | * Codename: sitemap 81 | */ 82 | sitemap: Elements.TaxonomyElement; 83 | 84 | /** 85 | * Meta title (text) 86 | * Required: false 87 | * Id: 09398b24-61ed-512e-5b5c-affd54a098e5 88 | * Codename: metadata__meta_title 89 | * From snippet: Metadata 90 | * Snippet codename: metadata 91 | * 92 | * Length: 30–60 characters 93 | */ 94 | metadataMetaTitle: Elements.TextElement; 95 | 96 | /** 97 | * Meta description (text) 98 | * Required: false 99 | * Id: 2e555cc1-1eae-520c-189e-28548904f529 100 | * Codename: metadata__meta_description 101 | * From snippet: Metadata 102 | * Snippet codename: metadata 103 | * 104 | * Length: 70–150 characters 105 | */ 106 | metadataMetaDescription: Elements.TextElement; 107 | 108 | /** 109 | * og:title (text) 110 | * Required: false 111 | * Id: 1db86c7a-e836-3c4e-01e6-4f704ad38ba5 112 | * Codename: metadata__og_title 113 | * From snippet: Metadata 114 | * Snippet codename: metadata 115 | * 116 | * Max. 60 characters 117 | */ 118 | metadataOgTitle: Elements.TextElement; 119 | 120 | /** 121 | * og:description (text) 122 | * Required: false 123 | * Id: 05987dc9-ebe3-7b61-b949-522eb42dbc0d 124 | * Codename: metadata__og_description 125 | * From snippet: Metadata 126 | * Snippet codename: metadata 127 | * 128 | * Max. 150 characters 129 | */ 130 | metadataOgDescription: Elements.TextElement; 131 | 132 | /** 133 | * og:image (asset) 134 | * Required: false 135 | * Id: ce6cda71-9d38-1d57-3ac3-ec9b2e286edd 136 | * Codename: metadata__og_image 137 | * From snippet: Metadata 138 | * Snippet codename: metadata 139 | * 140 | * Use images that are at least 1200 × 630 px for best display on high resolution devices. At the minimum, you should use images that are 600 × 315 px to display link page posts with larger images. Images may be up to 1 MB in size. 141 | */ 142 | metadataOgImage: Elements.AssetsElement; 143 | 144 | /** 145 | * twitter:site (text) 146 | * Required: false 147 | * Id: 34213fdf-0015-8f4f-e5e6-83c6842cff4a 148 | * Codename: metadata__twitter_site 149 | * From snippet: Metadata 150 | * Snippet codename: metadata 151 | */ 152 | metadataTwitterSite: Elements.TextElement; 153 | 154 | /** 155 | * twitter:creator (text) 156 | * Required: false 157 | * Id: 68f65095-c9b4-05d6-a473-2883c2f0c7af 158 | * Codename: metadata__twitter_creator 159 | * From snippet: Metadata 160 | * Snippet codename: metadata 161 | */ 162 | metadataTwitterCreator: Elements.TextElement; 163 | 164 | /** 165 | * twitter:title (text) 166 | * Required: false 167 | * Id: b208d3dc-bd8d-b1af-5ef0-747650730ba7 168 | * Codename: metadata__twitter_title 169 | * From snippet: Metadata 170 | * Snippet codename: metadata 171 | * 172 | * Max. 60 characters 173 | */ 174 | metadataTwitterTitle: Elements.TextElement; 175 | 176 | /** 177 | * twitter:description (text) 178 | * Required: false 179 | * Id: b7d1dd8b-a5d8-2ad8-2f57-49881363f6f7 180 | * Codename: metadata__twitter_description 181 | * From snippet: Metadata 182 | * Snippet codename: metadata 183 | * 184 | * A description that concisely summarizes the content as appropriate for presentation within a Tweet. You should not re-use the title as the description, or use this field to describe the general services provided by the website. Max. 150 characters. 185 | */ 186 | metadataTwitterDescription: Elements.TextElement; 187 | 188 | /** 189 | * twitter:image (asset) 190 | * Required: false 191 | * Id: 63793ba4-6004-a93c-68ca-52a1f0482bca 192 | * Codename: metadata__twitter_image 193 | * From snippet: Metadata 194 | * Snippet codename: metadata 195 | * 196 | * Twitter's recommendation: A URL to a unique image representing the content of the page. You should not use a generic image such as your website logo, author photo, or other image that spans multiple pages. Images for this Card should be at least 280 × 150 px. Images may be up to 1 MB in size.Let's go for 560 × 300 px. 197 | */ 198 | metadataTwitterImage: Elements.AssetsElement; 199 | }>; 200 | -------------------------------------------------------------------------------- /src/Models/content-types/hosted_video.ts: -------------------------------------------------------------------------------- 1 | import { IContentItem, Elements } from '@kontent-ai/delivery-sdk'; 2 | /** 3 | * Generated by '@kontent-ai/model-generator@5.2.0' 4 | * 5 | * Hosted video 6 | * Id: 269202ad-1d9d-47fd-b3e8-bdb05b3e3cf0 7 | * Codename: hosted_video 8 | */ 9 | export type HostedVideo = IContentItem<{ 10 | /** 11 | * Video ID (text) 12 | * Required: true 13 | * Id: 116a2441-6441-7124-c85b-46a4fef5dcb9 14 | * Codename: video_id 15 | */ 16 | videoId: Elements.TextElement; 17 | 18 | /** 19 | * Video host (multiple_choice) 20 | * Required: true 21 | * Id: 87924912-4861-aa84-176a-1eae7b22529b 22 | * Codename: video_host 23 | */ 24 | videoHost: Elements.MultipleChoiceElement; 25 | }>; 26 | -------------------------------------------------------------------------------- /src/Models/content-types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './hero_unit'; 2 | export * from './hosted_video'; 3 | export * from './coffee'; 4 | export * from './article'; 5 | export * from './grinder'; 6 | export * from './office'; 7 | export * from './tweet'; 8 | export * from './cafe'; 9 | export * from './home'; 10 | export * from './accessory'; 11 | export * from './brewer'; 12 | export * from './about_us'; 13 | export * from './fact_about_us'; 14 | -------------------------------------------------------------------------------- /src/Models/content-types/office.ts: -------------------------------------------------------------------------------- 1 | import { IContentItem, Elements } from '@kontent-ai/delivery-sdk'; 2 | import { Sitemap } from '../taxonomies/sitemap_538125f'; 3 | 4 | /** 5 | * Generated by '@kontent-ai/model-generator@5.2.0' 6 | * 7 | * Office 8 | * Id: e097306b-3893-4a42-9973-2525fad14d66 9 | * Codename: office 10 | */ 11 | export type Office = IContentItem<{ 12 | /** 13 | * Name (text) 14 | * Required: false 15 | * Id: bb81a11d-886c-2a32-e480-29f01cea667f 16 | * Codename: name 17 | */ 18 | name: Elements.TextElement; 19 | 20 | /** 21 | * Street (text) 22 | * Required: false 23 | * Id: f7eb7ab2-4e41-aca0-7e93-dbbbdca330eb 24 | * Codename: street 25 | */ 26 | street: Elements.TextElement; 27 | 28 | /** 29 | * City (text) 30 | * Required: false 31 | * Id: 95477abc-d6b4-a6b3-5b72-c92763da55bf 32 | * Codename: city 33 | */ 34 | city: Elements.TextElement; 35 | 36 | /** 37 | * Country (text) 38 | * Required: false 39 | * Id: 4fbc7779-652d-7716-2673-7419aaaceed1 40 | * Codename: country 41 | */ 42 | country: Elements.TextElement; 43 | 44 | /** 45 | * State (text) 46 | * Required: false 47 | * Id: 08df2f10-52b8-d451-fab1-b6da8ddb3fd2 48 | * Codename: state 49 | */ 50 | state: Elements.TextElement; 51 | 52 | /** 53 | * Zip code (text) 54 | * Required: false 55 | * Id: e7141da8-8792-a66d-d1c8-1fe704758393 56 | * Codename: zip_code 57 | */ 58 | zipCode: Elements.TextElement; 59 | 60 | /** 61 | * Phone (text) 62 | * Required: false 63 | * Id: 2ac708e2-cd0e-67b0-67f8-71725625dc6d 64 | * Codename: phone 65 | */ 66 | phone: Elements.TextElement; 67 | 68 | /** 69 | * Email (text) 70 | * Required: false 71 | * Id: 251dc38f-43a3-d924-a328-8708ecb00ef1 72 | * Codename: email 73 | */ 74 | email: Elements.TextElement; 75 | 76 | /** 77 | * Sitemap (taxonomy) 78 | * Required: false 79 | * Id: 93e978da-a9dd-43eb-a16a-ace55c1a5245 80 | * Codename: sitemap 81 | */ 82 | sitemap: Elements.TaxonomyElement; 83 | }>; 84 | -------------------------------------------------------------------------------- /src/Models/content-types/tweet.ts: -------------------------------------------------------------------------------- 1 | import { IContentItem, Elements } from '@kontent-ai/delivery-sdk'; 2 | /** 3 | * Generated by '@kontent-ai/model-generator@5.2.0' 4 | * 5 | * Tweet 6 | * Id: f808c142-4b62-43b0-8f4d-1cbf412580ab 7 | * Codename: tweet 8 | */ 9 | export type Tweet = IContentItem<{ 10 | /** 11 | * Tweet link (text) 12 | * Required: true 13 | * Id: 20bf9ba1-28fe-203c-5920-6f9610498fb9 14 | * Codename: tweet_link 15 | */ 16 | tweetLink: Elements.TextElement; 17 | 18 | /** 19 | * Theme (multiple_choice) 20 | * Required: false 21 | * Id: 779b27fd-5a4d-5e5f-66dc-b30931fcba92 22 | * Codename: theme 23 | */ 24 | theme: Elements.MultipleChoiceElement; 25 | 26 | /** 27 | * Display options (multiple_choice) 28 | * Required: false 29 | * Id: 8c6db6cf-1003-951e-5407-b2a19c15b4cd 30 | * Codename: display_options 31 | */ 32 | displayOptions: Elements.MultipleChoiceElement; 33 | }>; 34 | -------------------------------------------------------------------------------- /src/Models/index.ts: -------------------------------------------------------------------------------- 1 | export * from './project'; 2 | export * from './content-types'; 3 | export * from './taxonomies'; 4 | -------------------------------------------------------------------------------- /src/Models/project/assetFolders.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Generated by '@kontent-ai/model-generator@5.2.0' 3 | * 4 | * Project name: Sample Project 5 | * Environment: Production 6 | * Project Id: 159e78b3-53be-00bf-4a39-4f5211f2ace4 7 | */ 8 | export const assetFolders = { 9 | /** 10 | * Café locations 11 | */ 12 | caféLocations: { 13 | id: '958001d8-2228-4373-b966-5262b5b96f71', 14 | name: 'Café locations', 15 | externalId: undefined, 16 | folders: {}, 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /src/Models/project/collections.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Generated by '@kontent-ai/model-generator@5.2.0' 3 | * 4 | * Project name: Sample Project 5 | * Environment: Production 6 | * Project Id: 159e78b3-53be-00bf-4a39-4f5211f2ace4 7 | */ 8 | export const collections = { 9 | /** 10 | * Default 11 | */ 12 | default: { 13 | codename: 'default', 14 | id: '00000000-0000-0000-0000-000000000000', 15 | name: 'Default', 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /src/Models/project/index.ts: -------------------------------------------------------------------------------- 1 | export * from './languages'; 2 | export * from './collections'; 3 | export * from './contentTypes'; 4 | export * from './taxonomies'; 5 | export * from './workflows'; 6 | export * from './roles'; 7 | export * from './assetFolders'; 8 | export * from './webhooks'; 9 | -------------------------------------------------------------------------------- /src/Models/project/languages.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Generated by '@kontent-ai/model-generator@5.2.0' 3 | * 4 | * Project name: Sample Project 5 | * Environment: Production 6 | * Project Id: 159e78b3-53be-00bf-4a39-4f5211f2ace4 7 | */ 8 | export const languages = { 9 | /** 10 | * English (United States) 11 | */ 12 | enUS: { 13 | codename: 'en-US', 14 | id: '00000000-0000-0000-0000-000000000000', 15 | isActive: true, 16 | isDefault: true, 17 | fallbackLanguageId: '00000000-0000-0000-0000-000000000000', 18 | externalId: undefined, 19 | name: 'English (United States)', 20 | }, 21 | 22 | /** 23 | * Spanish (Spain) 24 | */ 25 | esES: { 26 | codename: 'es-ES', 27 | id: 'd1f95fde-af02-b3b5-bd9e-f232311ccab8', 28 | isActive: true, 29 | isDefault: false, 30 | fallbackLanguageId: '00000000-0000-0000-0000-000000000000', 31 | externalId: undefined, 32 | name: 'Spanish (Spain)', 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /src/Models/project/roles.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Generated by '@kontent-ai/model-generator@5.2.0' 3 | * 4 | * Project name: Sample Project 5 | * Environment: Production 6 | * Project Id: 159e78b3-53be-00bf-4a39-4f5211f2ace4 7 | */ 8 | export const roles = {}; 9 | -------------------------------------------------------------------------------- /src/Models/project/taxonomies.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Generated by '@kontent-ai/model-generator@5.2.0' 3 | * 4 | * Project name: Sample Project 5 | * Environment: Production 6 | * Project Id: 159e78b3-53be-00bf-4a39-4f5211f2ace4 7 | */ 8 | export const taxonomies = { 9 | /** 10 | * Processing 11 | */ 12 | processing: { 13 | codename: 'processing', 14 | id: 'd351400e-0290-87b2-1413-6c411d8ae5a4', 15 | externalId: undefined, 16 | name: 'Processing', 17 | terms: { 18 | wet__washed_: { 19 | codename: 'wet__washed_', 20 | id: 'a831d60b-ff0e-7df1-61d2-73e851a5deab', 21 | externalId: undefined, 22 | name: 'Wet (Washed)', 23 | terms: {}, 24 | }, 25 | dry__natural_: { 26 | codename: 'dry__natural_', 27 | id: 'ac34eaa7-8463-62b9-825e-66aff9e6b216', 28 | externalId: undefined, 29 | name: 'Dry (Natural)', 30 | terms: {}, 31 | }, 32 | semi_dry: { 33 | codename: 'semi_dry', 34 | id: '908321fa-776d-d441-6a72-76fba3959c1d', 35 | externalId: undefined, 36 | name: 'Semi-dry', 37 | terms: {}, 38 | }, 39 | }, 40 | }, 41 | 42 | /** 43 | * Product status 44 | */ 45 | product_status: { 46 | codename: 'product_status', 47 | id: '79b1c5b6-30bc-d076-a236-d9ec9f1ff01b', 48 | externalId: undefined, 49 | name: 'Product status', 50 | terms: { 51 | on_sale: { 52 | codename: 'on_sale', 53 | id: '6352c8bf-8024-9986-8373-35445e1f0d59', 54 | externalId: undefined, 55 | name: 'On sale', 56 | terms: {}, 57 | }, 58 | bestseller: { 59 | codename: 'bestseller', 60 | id: '8d808da3-29de-e608-5699-8565687dd474', 61 | externalId: undefined, 62 | name: 'Bestseller', 63 | terms: {}, 64 | }, 65 | }, 66 | }, 67 | 68 | /** 69 | * Sitemap 70 | */ 71 | sitemap_538125f: { 72 | codename: 'sitemap_538125f', 73 | id: '538125ff-c59c-4193-88ce-71a7016830ed', 74 | externalId: undefined, 75 | name: 'Sitemap', 76 | terms: { 77 | home: { 78 | codename: 'home', 79 | id: '796ad1bc-7d9f-426f-97ba-d16003c3f04c', 80 | externalId: undefined, 81 | name: 'Home', 82 | terms: {}, 83 | }, 84 | products: { 85 | codename: 'products', 86 | id: '6bb115ce-4f92-44c6-8643-d148f9f7028a', 87 | externalId: undefined, 88 | name: 'Products', 89 | terms: { 90 | coffee: { 91 | codename: 'coffee', 92 | id: '30453aab-633b-4e7c-b9b3-cda95163ad0f', 93 | externalId: undefined, 94 | name: 'Coffee', 95 | terms: {}, 96 | }, 97 | brewers: { 98 | codename: 'brewers', 99 | id: 'c66be017-bacc-403f-9f98-8ead623d687e', 100 | externalId: undefined, 101 | name: 'Brewers', 102 | terms: {}, 103 | }, 104 | accessories: { 105 | codename: 'accessories', 106 | id: 'baa49a0a-479a-4279-9fe0-f29393f330c5', 107 | externalId: undefined, 108 | name: 'Accessories', 109 | terms: {}, 110 | }, 111 | grinders: { 112 | codename: 'grinders', 113 | id: '82b3f64d-a41f-4f78-a410-c6c0e095b3c7', 114 | externalId: undefined, 115 | name: 'Grinders', 116 | terms: {}, 117 | }, 118 | }, 119 | }, 120 | cafes: { 121 | codename: 'cafes', 122 | id: '2ab0aad5-7609-4371-8d6e-cb4a917b2ad1', 123 | externalId: undefined, 124 | name: 'Cafes', 125 | terms: { 126 | north_america: { 127 | codename: 'north_america', 128 | id: 'f319b28e-c194-496a-b188-4f91cc010a67', 129 | externalId: undefined, 130 | name: 'North America', 131 | terms: {}, 132 | }, 133 | europe: { 134 | codename: 'europe', 135 | id: 'bbac5542-33b5-40ad-a3a9-2f2d37a40ab3', 136 | externalId: undefined, 137 | name: 'Europe', 138 | terms: {}, 139 | }, 140 | australia: { 141 | codename: 'australia', 142 | id: '405c6578-8233-4277-9826-6b5e74dc6f39', 143 | externalId: undefined, 144 | name: 'Australia', 145 | terms: {}, 146 | }, 147 | }, 148 | }, 149 | articles: { 150 | codename: 'articles', 151 | id: '7ca9b56c-6379-46dc-ae90-3df4b9abd217', 152 | externalId: undefined, 153 | name: 'Articles', 154 | terms: {}, 155 | }, 156 | offices: { 157 | codename: 'offices', 158 | id: '3f7bee52-08cc-4a12-8fe9-dbce05871ca0', 159 | externalId: undefined, 160 | name: 'Offices', 161 | terms: {}, 162 | }, 163 | about_us: { 164 | codename: 'about_us', 165 | id: 'bbe0ed98-bc68-4f9b-85cb-73cb28078b3c', 166 | externalId: undefined, 167 | name: 'About us', 168 | terms: {}, 169 | }, 170 | }, 171 | }, 172 | 173 | /** 174 | * Personas 175 | */ 176 | personas: { 177 | codename: 'personas', 178 | id: 'f30c7f72-e9ab-8832-2a57-62944a038809', 179 | externalId: undefined, 180 | name: 'Personas', 181 | terms: { 182 | coffee_expert: { 183 | codename: 'coffee_expert', 184 | id: '6693ca6e-79e0-57e4-000d-d23d5ce8f656', 185 | externalId: undefined, 186 | name: 'Coffee expert', 187 | terms: { 188 | barista: { 189 | codename: 'barista', 190 | id: '6a372f43-ccd7-e524-6308-c2094e7b6596', 191 | externalId: undefined, 192 | name: 'Barista', 193 | terms: {}, 194 | }, 195 | cafe_owner: { 196 | codename: 'cafe_owner', 197 | id: 'cdf2f3c6-89e3-5df1-f7de-7179460bd6b4', 198 | externalId: undefined, 199 | name: 'Cafe owner', 200 | terms: {}, 201 | }, 202 | }, 203 | }, 204 | coffee_enthusiast: { 205 | codename: 'coffee_enthusiast', 206 | id: 'ab2b73a3-473d-4232-0652-495598f5d670', 207 | externalId: undefined, 208 | name: 'Coffee enthusiast', 209 | terms: { 210 | coffee_lover: { 211 | codename: 'coffee_lover', 212 | id: '208a9095-1b92-10da-7627-75ae311935cf', 213 | externalId: undefined, 214 | name: 'Coffee lover', 215 | terms: {}, 216 | }, 217 | coffee_blogger: { 218 | codename: 'coffee_blogger', 219 | id: '4fa27320-c363-3ebe-5ab5-b531300f053f', 220 | externalId: undefined, 221 | name: 'Coffee blogger', 222 | terms: {}, 223 | }, 224 | }, 225 | }, 226 | }, 227 | }, 228 | 229 | /** 230 | * Manufacturer 231 | */ 232 | manufacturer: { 233 | codename: 'manufacturer', 234 | id: '4ce421e9-c403-eee8-fdc2-74f09392a749', 235 | externalId: undefined, 236 | name: 'Manufacturer', 237 | terms: { 238 | aerobie: { 239 | codename: 'aerobie', 240 | id: 'f04c8552-1b97-a49b-3944-79275622f471', 241 | externalId: undefined, 242 | name: 'Aerobie', 243 | terms: {}, 244 | }, 245 | chemex: { 246 | codename: 'chemex', 247 | id: '16d27bf1-e0f4-8646-0e54-1b71efc6947f', 248 | externalId: undefined, 249 | name: 'Chemex', 250 | terms: {}, 251 | }, 252 | espro: { 253 | codename: 'espro', 254 | id: 'b378225f-6dfc-e261-3848-dd030a6d7883', 255 | externalId: undefined, 256 | name: 'Espro', 257 | terms: {}, 258 | }, 259 | hario: { 260 | codename: 'hario', 261 | id: '6fde9724-5b72-8bc9-6da0-4f0573a54532', 262 | externalId: undefined, 263 | name: 'Hario', 264 | terms: {}, 265 | }, 266 | }, 267 | }, 268 | }; 269 | -------------------------------------------------------------------------------- /src/Models/project/webhooks.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Generated by '@kontent-ai/model-generator@5.2.0' 3 | * 4 | * Project name: Sample Project 5 | * Environment: Production 6 | * Project Id: 159e78b3-53be-00bf-4a39-4f5211f2ace4 7 | */ 8 | export const webhooks = {}; 9 | -------------------------------------------------------------------------------- /src/Models/project/workflows.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Generated by '@kontent-ai/model-generator@5.2.0' 3 | * 4 | * Project name: Sample Project 5 | * Environment: Production 6 | * Project Id: 159e78b3-53be-00bf-4a39-4f5211f2ace4 7 | */ 8 | export const workflows = { 9 | /** 10 | * Default 11 | * Archived step Id: 7a535a69-ad34-47f8-806a-def1fdf4d391 12 | * Published step Id: b4363ccd-8f21-45fd-a840-5843d7b7f008 13 | */ 14 | default: { 15 | codename: 'default', 16 | id: '00000000-0000-0000-0000-000000000000', 17 | name: 'Default', 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /src/Models/taxonomies/index.ts: -------------------------------------------------------------------------------- 1 | export * from './processing'; 2 | export * from './product_status'; 3 | export * from './sitemap_538125f'; 4 | export * from './personas'; 5 | export * from './manufacturer'; 6 | -------------------------------------------------------------------------------- /src/Models/taxonomies/manufacturer.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Generated by '@kontent-ai/model-generator@5.2.0' 3 | * 4 | * Manufacturer 5 | * Id: 4ce421e9-c403-eee8-fdc2-74f09392a749 6 | * Codename: manufacturer 7 | */ 8 | export type Manufacturer = 'aerobie' | 'chemex' | 'espro' | 'hario'; 9 | -------------------------------------------------------------------------------- /src/Models/taxonomies/personas.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Generated by '@kontent-ai/model-generator@5.2.0' 3 | * 4 | * Personas 5 | * Id: f30c7f72-e9ab-8832-2a57-62944a038809 6 | * Codename: personas 7 | */ 8 | export type Personas = 9 | | 'coffee_expert' 10 | | 'barista' 11 | | 'cafe_owner' 12 | | 'coffee_enthusiast' 13 | | 'coffee_lover' 14 | | 'coffee_blogger'; 15 | -------------------------------------------------------------------------------- /src/Models/taxonomies/processing.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Generated by '@kontent-ai/model-generator@5.2.0' 3 | * 4 | * Processing 5 | * Id: d351400e-0290-87b2-1413-6c411d8ae5a4 6 | * Codename: processing 7 | */ 8 | export type Processing = 'wet__washed_' | 'dry__natural_' | 'semi_dry'; 9 | -------------------------------------------------------------------------------- /src/Models/taxonomies/product_status.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Generated by '@kontent-ai/model-generator@5.2.0' 3 | * 4 | * Product status 5 | * Id: 79b1c5b6-30bc-d076-a236-d9ec9f1ff01b 6 | * Codename: product_status 7 | */ 8 | export type ProductStatus = 'on_sale' | 'bestseller'; 9 | -------------------------------------------------------------------------------- /src/Models/taxonomies/sitemap_538125f.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Generated by '@kontent-ai/model-generator@5.2.0' 3 | * 4 | * Sitemap 5 | * Id: 538125ff-c59c-4193-88ce-71a7016830ed 6 | * Codename: sitemap_538125f 7 | */ 8 | export type Sitemap = 9 | | 'home' 10 | | 'products' 11 | | 'coffee' 12 | | 'brewers' 13 | | 'accessories' 14 | | 'grinders' 15 | | 'cafes' 16 | | 'north_america' 17 | | 'europe' 18 | | 'australia' 19 | | 'articles' 20 | | 'offices' 21 | | 'about_us'; 22 | -------------------------------------------------------------------------------- /src/Pages/About.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { spinnerService } from '@simply007org/react-spinners'; 3 | import { useEffect, useState } from 'react'; 4 | import { useClient } from '../Client'; 5 | import Metadata from '../Components/Metadata'; 6 | import RichText from '../Components/RichText'; 7 | import { 8 | defaultLanguage, 9 | initLanguageCodeObject, 10 | } from '../Utilities/LanguageCodes'; 11 | import { useIntl } from 'react-intl'; 12 | import { AboutUs } from '../Models/content-types/about_us'; 13 | import { contentTypes } from '../Models/project/contentTypes'; 14 | import { FactAboutUs } from '../Models/content-types/fact_about_us'; 15 | 16 | interface AboutProps { 17 | urlSlug?: string; 18 | } 19 | 20 | const About: React.FC = ({ urlSlug }) => { 21 | const { locale: language, formatMessage } = useIntl(); 22 | const [facts, setFacts] = useState(initLanguageCodeObject(null)); 23 | const [metadata, setMetadata] = useState(initLanguageCodeObject()); 24 | const [Client] = useClient(); 25 | 26 | useEffect(() => { 27 | spinnerService.show('apiSpinner'); 28 | 29 | const query = Client.items() 30 | .type(contentTypes.about_us.codename) 31 | .elementsParameter([ 32 | 'facts', 33 | 'modular_content', 34 | 'title', 35 | 'description', 36 | 'image', 37 | ]); 38 | 39 | if (language) { 40 | query.languageParameter(language); 41 | } 42 | 43 | if (urlSlug) { 44 | query.equalsFilter('elements.url_pattern', urlSlug); 45 | } 46 | query.toPromise().then((response) => { 47 | const currentLanguage = language || defaultLanguage; 48 | 49 | spinnerService.hide('apiSpinner'); 50 | setFacts((data) => ({ 51 | ...data, 52 | [currentLanguage]: response.data.items[0] as AboutUs, 53 | })); 54 | }); 55 | }, [language, urlSlug, Client]); 56 | 57 | useEffect(() => { 58 | // do not use spinner for metadata fetch 59 | 60 | const query = Client.items() 61 | .type('about_us') 62 | .elementsParameter([ 63 | 'metadata__meta_title', 64 | 'metadata__meta_description', 65 | 'metadata__og_title', 66 | 'metadata__og_description', 67 | 'metadata__og_image', 68 | 'metadata__twitter_title', 69 | 'metadata__twitter_site', 70 | 'metadata__twitter_creator', 71 | 'metadata__twitter_description', 72 | 'metadata__twitter_image', 73 | ]); 74 | 75 | if (language) { 76 | query.languageParameter(language); 77 | } 78 | 79 | if (urlSlug) { 80 | query.equalsFilter('elements.url_pattern', urlSlug); 81 | } 82 | query.toPromise().then((response) => { 83 | const currentLanguage = language || defaultLanguage; 84 | 85 | setMetadata((data) => ({ 86 | ...data, 87 | [currentLanguage]: response.data.items[0], 88 | })); 89 | }); 90 | }, [language, urlSlug, Client]); 91 | 92 | const factsComponent = 93 | facts[language]?.elements.facts.linkedItems && 94 | facts[language]?.elements.facts.linkedItems.map( 95 | (fact: FactAboutUs, index: number) => { 96 | const title = 97 | fact.elements.title.value.trim().length > 0 98 | ? fact.elements.title.value 99 | : formatMessage({ id: 'About.noTitleValue' }); 100 | 101 | const descriptionElement = 102 | fact.elements.description.value !== '


' ? ( 103 | 107 | ) : ( 108 |

109 | {formatMessage({ id: 'About.noDescriptionValue' })} 110 |

111 | ); 112 | 113 | const imageLink = 114 | fact.elements.image.value[0] !== undefined ? ( 115 | {title} 121 | ) : ( 122 |
123 | {formatMessage({ id: 'About.noTeaserValue' })} 124 |
125 | ); 126 | 127 | if (index % 2 === 0) { 128 | return ( 129 |
130 |

{title}

131 |
{descriptionElement}
132 |
{imageLink}
133 |
134 | ); 135 | } 136 | 137 | return ( 138 |
139 |

{title}

140 |
{descriptionElement}
141 |
{imageLink}
142 |
143 | ); 144 | } 145 | ); 146 | 147 | const metaDataElements = metadata[language]?.elements || {}; 148 | 149 | return ( 150 |
151 | 163 | {factsComponent} 164 |
165 | ); 166 | }; 167 | 168 | export default About; 169 | -------------------------------------------------------------------------------- /src/Pages/Admin/Configuration.css: -------------------------------------------------------------------------------- 1 | /* taken from https://github.com/kontent-ai/sample-app-net build output*/ 2 | /* and prefixed to not affect rest of the site */ 3 | .configuration-page .button { 4 | font-family: 'Work Sans', arial, sans-serif; 5 | font-size: 12px; 6 | font-weight: 700; 7 | line-height: 16px; 8 | letter-spacing: 0.1ch; 9 | border: 0; 10 | border-radius: 5000px; 11 | padding: 12px 24px; 12 | cursor: pointer; 13 | text-transform: uppercase; 14 | } 15 | .configuration-page .button-primary { 16 | background: #6956cc; 17 | color: #fff; 18 | } 19 | .configuration-page .button-primary:hover, 20 | .configuration-page .button-primary:focus { 21 | background-color: #231f20; 22 | } 23 | .configuration-page .button-primary.active, 24 | .configuration-page .button-primary:active, 25 | .configuration-page .button-primary.disabled, 26 | .configuration-page .button-primary[disabled] { 27 | color: #8c8c8c; 28 | background: #dfdfdf; 29 | box-shadow: none; 30 | } 31 | .configuration-page .kk-container { 32 | margin: 0 auto; 33 | padding: 0 24px; 34 | max-width: 1048px; 35 | } 36 | .configuration-page .col { 37 | flex: 1; 38 | } 39 | @media (min-width: 900px) { 40 | .configuration-page .row-lg { 41 | display: flex; 42 | } 43 | .configuration-page .row-lg--align-start { 44 | align-items: flex-start; 45 | } 46 | .configuration-page .col + .col { 47 | margin-left: 24px; 48 | } 49 | } 50 | .configuration-page .hidden { 51 | position: absolute; 52 | width: 1px; 53 | height: 1px; 54 | padding: 0; 55 | margin: -1px; 56 | overflow: hidden; 57 | clip: rect(0, 0, 0, 0); 58 | white-space: nowrap; 59 | border: 0; 60 | } 61 | .configuration-page .user-message { 62 | margin-top: 24px; 63 | } 64 | .configuration-page .user-message td { 65 | padding: 0; 66 | } 67 | .configuration-page .user-message__img { 68 | height: 20px; 69 | width: 20px; 70 | margin-right: 8px; 71 | margin-top: 2px; 72 | } 73 | .configuration-page .user-message__img--error { 74 | background: url('../../Images/Admin/error.svg') no-repeat center center; 75 | } 76 | .configuration-page .user-message__img--info { 77 | background: url('../../Images/Admin/info.svg') no-repeat center center; 78 | } 79 | .configuration-page *, 80 | .configuration-page *::before, 81 | .configuration-page *::after { 82 | box-sizing: border-box; 83 | } 84 | .configuration-page html, 85 | .configuration-page body { 86 | color: #151515; 87 | margin: 0; 88 | padding: 0; 89 | } 90 | .configuration-page h1, 91 | .configuration-page h2, 92 | .configuration-page h3, 93 | .configuration-page h4, 94 | .configuration-page h5, 95 | .configuration-page h6 { 96 | margin: 0; 97 | } 98 | .configuration-page p { 99 | margin: 0; 100 | font-family: 'Work Sans', arial, sans-serif; 101 | font-size: 16px; 102 | font-weight: 400; 103 | line-height: 24px; 104 | } 105 | .configuration-page .headline-large { 106 | font-family: 'Work Sans', arial, sans-serif; 107 | font-size: 24px; 108 | font-weight: 700; 109 | line-height: 32px; 110 | } 111 | .configuration-page .headline-medium { 112 | font-family: 'Work Sans', arial, sans-serif; 113 | font-size: 16px; 114 | font-weight: 700; 115 | line-height: 24px; 116 | } 117 | .configuration-page .gradient-desk { 118 | background-image: linear-gradient(135deg, #f3f4f5, #d3dff3); 119 | min-height: 100vh; 120 | } 121 | .configuration-page .color-alert-text { 122 | color: #db0000; 123 | } 124 | .configuration-page .paper { 125 | background: #fff; 126 | border-radius: 16px; 127 | padding: 24px; 128 | } 129 | .configuration-page .margin-top-l { 130 | margin-top: 16px; 131 | } 132 | .configuration-page .margin-top-xl { 133 | margin-top: 24px; 134 | } 135 | .configuration-page .padding-bottom-xl { 136 | padding-bottom: 24px; 137 | } 138 | .configuration-page .logo-link { 139 | text-decoration: none; 140 | display: block; 141 | } 142 | .configuration-page .logo { 143 | height: 48px; 144 | margin: 32px 0 40px; 145 | } 146 | .configuration-page .project-id-form { 147 | display: flex; 148 | } 149 | .configuration-page .project-id-form__input { 150 | font-family: Inter, sans-serif; 151 | font-size: 14px; 152 | font-weight: 400; 153 | line-height: 20px; 154 | line-height: normal; 155 | border-radius: 20px; 156 | border: 1px solid #a8a8a8; 157 | padding: 0 16px; 158 | min-width: 20ch; 159 | width: 100%; 160 | max-width: 40ch; 161 | } 162 | @media (min-width: 900px) { 163 | .configuration-page .project-id-form__input { 164 | max-width: none; 165 | } 166 | } 167 | .configuration-page .project-id-form__input::placeholder { 168 | color: #6f6f6f; 169 | opacity: 1; 170 | } 171 | .configuration-page .project-id-form__input:hover { 172 | border-color: #141619; 173 | } 174 | .configuration-page .project-id-form__input:focus { 175 | border-color: #141619; 176 | outline: none; 177 | } 178 | .configuration-page .project-id-form__submit-button { 179 | margin-left: 8px; 180 | } 181 | -------------------------------------------------------------------------------- /src/Pages/Admin/Configuration.tsx: -------------------------------------------------------------------------------- 1 | import React, { ChangeEvent, FormEvent, useEffect, useState } from 'react'; 2 | import validator from 'validator'; 3 | import { createClient, useClient } from '../../Client'; 4 | import { 5 | defaultEnvironmentId, 6 | getSampleProjectItems, 7 | } from '../../Utilities/SelectedProject'; 8 | import KontentLogo from '../../Images/Admin/kk-logo.svg'; 9 | import './Configuration.css'; 10 | import { spinnerService } from '@simply007org/react-spinners'; 11 | import SpinnerLayout from '../../Components/SpinnerLayout'; 12 | import { useNavigate } from 'react-router-dom'; 13 | import { selectedEnvironmentCookieName } from '../../const'; 14 | import Cookies from 'universal-cookie'; 15 | 16 | type getWindowCenterPositionReturnType = { 17 | left: number; 18 | top: number; 19 | }; 20 | 21 | const getWindowCenterPosition = ( 22 | windowWidth: number, 23 | windowHeight: number 24 | ): getWindowCenterPositionReturnType => { 25 | const dualScreenLeft = 26 | window.screenLeft !== undefined ? window.screenLeft : window.screenX; 27 | const dualScreenTop = 28 | window.screenTop !== undefined ? window.screenTop : window.screenY; 29 | const screenWidth = window.innerWidth 30 | ? window.innerWidth 31 | : document.documentElement.clientWidth 32 | ? document.documentElement.clientWidth 33 | : window.screen.width; 34 | const screenHeight = window.innerHeight 35 | ? window.innerHeight 36 | : document.documentElement.clientHeight 37 | ? document.documentElement.clientHeight 38 | : window.screen.height; 39 | const left = screenWidth / 2 - windowWidth / 2 + dualScreenLeft; 40 | const top = screenHeight / 2 - windowHeight / 2 + dualScreenTop; 41 | return { left, top }; 42 | }; 43 | 44 | const Configuration: React.FC = () => { 45 | const cookies = new Cookies(document.cookie); 46 | const cookie = cookies.get(selectedEnvironmentCookieName); 47 | const navigate = useNavigate(); 48 | const [currentEnvironmentInputValue, setCurrentEnvironmentInputValue] = 49 | useState(cookie); 50 | 51 | const [, setClient] = useClient(); 52 | 53 | useEffect(() => { 54 | window.addEventListener('message', receiveMessage, false); 55 | 56 | // clean up the event every time the component is re-rendered 57 | return function cleanup() { 58 | window.removeEventListener('message', receiveMessage); 59 | }; 60 | }); 61 | 62 | const handleEnvironmentInputChange = ( 63 | event: ChangeEvent 64 | ): void => { 65 | setCurrentEnvironmentInputValue(event.target.value); 66 | }; 67 | 68 | const handleSetEnvironmentSubmit = ( 69 | event: React.FormEvent 70 | ): void => { 71 | event.preventDefault(); 72 | const newEnvironmentId = ( 73 | (event.target as HTMLFormElement)[0] as HTMLInputElement 74 | )?.value; 75 | 76 | setNewEnvironmentId(newEnvironmentId); 77 | }; 78 | 79 | const setNewEnvironmentId = ( 80 | newEnvironmentId: string, 81 | newlyGeneratedProject?: boolean 82 | ): void => { 83 | if (!validator.isUUID(newEnvironmentId)) { 84 | const message = `Selected envrionment ID (${newEnvironmentId}) is not a valid GUID`; 85 | console.error(message); 86 | alert(message); 87 | setCurrentEnvironmentInputValue(cookie); 88 | 89 | return; 90 | } 91 | 92 | setClient(newEnvironmentId); 93 | if (newlyGeneratedProject) { 94 | waitUntilProjectAccessible(newEnvironmentId); 95 | spinnerService.show('apiSpinner'); 96 | return; 97 | } 98 | 99 | redirectToHome(newEnvironmentId); 100 | }; 101 | 102 | const waitUntilProjectAccessible = async ( 103 | newEnvironmentId: string 104 | ): Promise => { 105 | const sampleProjectClientResult = await getSampleProjectItems(); 106 | 107 | setClient(newEnvironmentId); 108 | 109 | const intervalId = setInterval(() => { 110 | createClient(newEnvironmentId) 111 | .items() 112 | .elementsParameter(['id']) 113 | .depthParameter(0) 114 | .toPromise() 115 | .then((response) => { 116 | if ( 117 | response.data.items.length >= 118 | sampleProjectClientResult.data.items.length 119 | ) { 120 | spinnerService.hide('apiSpinner'); 121 | clearInterval(intervalId); 122 | redirectToHome(newEnvironmentId); 123 | } 124 | }); 125 | }, 2000); 126 | }; 127 | 128 | const redirectToHome = (newEnvironmentId: string): void => { 129 | const infoMessage = 130 | newEnvironmentId === defaultEnvironmentId 131 | ? `You've configured your app to with a environment ID of a shared Kontent.ai project.` 132 | : `You've configured your app with a environment ID "${newEnvironmentId}". You can edit its contents at https://kontent.ai/.`; 133 | navigate(`/?infoMessage=${infoMessage}`); 134 | }; 135 | 136 | const openKontentProjectSelector = ( 137 | event: FormEvent 138 | ): void => { 139 | event.preventDefault(); 140 | const windowWidth = 800; 141 | const windowHeight = 800; 142 | const { left, top } = getWindowCenterPosition(windowWidth, windowHeight); 143 | 144 | window.open( 145 | 'https://app.kontent.ai/sample-site-configuration', 146 | 'Kontent-ai', 147 | `status=no,width=${windowWidth},height=${windowHeight},resizable=yes,left= 148 | ${left},top=${top},toolbar=no,menubar=no,location=no,directories=no` 149 | ); 150 | }; 151 | 152 | const receiveMessage = (event: MessageEvent): void => { 153 | if (event.origin.toLowerCase() !== 'https://app.kontent.ai') return; 154 | 155 | if (!event.data.environmentGuid) { 156 | return; 157 | } 158 | 159 | setNewEnvironmentId( 160 | event.data.environmentGuid, 161 | event.data.newlyGeneratedProject 162 | ); 163 | }; 164 | 165 | return ( 166 | 167 |
168 |
169 |
170 | 171 | Kontent.ai logo 172 | 173 |
174 |
175 |
176 |

Sample Site—Configuration

177 |

178 | For your sample app to work, you should have a Kontent.ai 179 | project containing content. Your app should be then configured 180 | with its environment ID. You can either get it by signing in 181 | using your Kontent.ai credentials or by signing up for a trial. 182 | Later, it will be converted to a free plan. 183 |

184 |
185 |
186 |
187 |
188 |

Get a Environment ID

189 |

190 | You may wish to either select from existing projects or create a 191 | new sample project. The app will be configured with its 192 | environment ID. 193 |

194 |
195 | 200 |
201 |
202 |
203 |
204 |
205 |
206 |

207 | Set A Environment ID Manually 208 |

209 |

210 | Alternatively, you can configure your app manually by 211 | submitting a Environment ID below. 212 |

213 |
217 | 228 | 233 | 238 |
239 |
240 |
241 |

Use the Shared Project

242 |

243 | Alternatively, you may wish to use the shared project 244 | (environment ID "{defaultEnvironmentId} 245 | "). 246 |

247 |

248 | Note: You cannot edit content in the shared 249 | project. 250 |

251 | 256 | setNewEnvironmentId(defaultEnvironmentId) 257 | } 258 | /> 259 |
260 |
261 |
262 |
263 |
264 |
265 | ); 266 | }; 267 | 268 | export default Configuration; 269 | -------------------------------------------------------------------------------- /src/Pages/Article.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { spinnerService } from '@simply007org/react-spinners'; 3 | import { useEffect, useState } from 'react'; 4 | import { 5 | defaultLanguage, 6 | initLanguageCodeObject, 7 | } from '../Utilities/LanguageCodes'; 8 | import RichText from '../Components/RichText'; 9 | import Metadata from '../Components/Metadata'; 10 | import { useLocation, useNavigate, useParams } from 'react-router-dom'; 11 | import { useIntl } from 'react-intl'; 12 | import { Article as ArticleType } from '../Models/content-types/article'; 13 | import { contentTypes } from '../Models/project/contentTypes'; 14 | import { resolveChangeLanguageLink } from '../Utilities/LanugageLink'; 15 | import { useClient } from '../Client'; 16 | 17 | const Article: React.FC = () => { 18 | const { locale: language, formatDate, formatMessage } = useIntl(); 19 | const { articleId } = useParams(); 20 | const [article, setArticle] = useState(initLanguageCodeObject()); 21 | const navigate = useNavigate(); 22 | const { pathname } = useLocation(); 23 | const [Client] = useClient(); 24 | 25 | useEffect(() => { 26 | spinnerService.show('apiSpinner'); 27 | 28 | const query = Client.items() 29 | .type(contentTypes.article.codename) 30 | .equalsFilter('system.id', articleId!!) 31 | .elementsParameter([ 32 | 'title', 33 | 'teaser_image', 34 | 'post_date', 35 | 'body_copy', 36 | 'video_host', 37 | 'video_id', 38 | 'tweet_link', 39 | 'theme', 40 | 'display_options', 41 | 'metadata__meta_title', 42 | 'metadata__meta_description', 43 | 'metadata__og_title', 44 | 'metadata__og_description', 45 | 'metadata__og_image', 46 | 'metadata__twitter_title', 47 | 'metadata__twitter_site', 48 | 'metadata__twitter_creator', 49 | 'metadata__twitter_description', 50 | 'metadata__twitter_image', 51 | ]); 52 | 53 | if (language) { 54 | query.languageParameter(language); 55 | } 56 | 57 | query.toPromise().then((response) => { 58 | const currentLanguage = language || defaultLanguage; 59 | 60 | if (response.data.items[0].system.language !== language) { 61 | navigate( 62 | resolveChangeLanguageLink( 63 | pathname, 64 | response.data.items[0].system.language 65 | ), 66 | { replace: true } 67 | ); 68 | } 69 | 70 | spinnerService.hide('apiSpinner'); 71 | setArticle((data) => ({ 72 | ...data, 73 | [currentLanguage]: response.data.items[0] as ArticleType, 74 | })); 75 | }); 76 | }, [language, articleId, navigate, pathname, Client]); 77 | 78 | const currentArticle = article[language]; 79 | if (!currentArticle) { 80 | return
; 81 | } 82 | 83 | const makeFormatDate = (value: string): string => { 84 | return formatDate(value, { 85 | year: 'numeric', 86 | month: 'long', 87 | day: 'numeric', 88 | weekday: 'long', 89 | }); 90 | }; 91 | 92 | const title = 93 | currentArticle.elements.title.value.trim().length > 0 94 | ? currentArticle.elements.title.value 95 | : formatMessage({ id: 'Article.noTitleValue' }); 96 | 97 | const imageLink = 98 | currentArticle?.elements.teaserImage.value[0] !== undefined ? ( 99 | {title} 105 | ) : ( 106 |
107 | {formatMessage({ id: 'Article.noTeaserValue' })} 108 |
109 | ); 110 | 111 | const postDate = makeFormatDate(currentArticle.elements.postDate.value!); 112 | 113 | const bodyCopyElement = 114 | currentArticle.elements.bodyCopy.value !== '


' ? ( 115 | 119 | ) : ( 120 |

121 | {formatMessage({ id: 'Article.noBodyCopyValue' })} 122 |

123 | ); 124 | 125 | return ( 126 |
127 | 139 |
140 |

{title}

141 |
{postDate}
142 |
143 |
144 | {imageLink} 145 |
146 |
147 |
{bodyCopyElement}
148 |
149 |
150 | ); 151 | }; 152 | 153 | export default Article; 154 | -------------------------------------------------------------------------------- /src/Pages/Articles.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { spinnerService } from '@simply007org/react-spinners'; 3 | import { useEffect, useState } from 'react'; 4 | import { useClient } from '../Client'; 5 | import { 6 | defaultLanguage, 7 | initLanguageCodeObjectWithArray, 8 | } from '../Utilities/LanguageCodes'; 9 | import { Link } from 'react-router-dom'; 10 | import { useIntl } from 'react-intl'; 11 | import { Article as ArticleType } from '../Models/content-types/article'; 12 | import { contentTypes } from '../Models/project/contentTypes'; 13 | 14 | const Articles: React.FC = () => { 15 | const { locale: language, formatDate, formatMessage } = useIntl(); 16 | const [articles, setArticles] = useState( 17 | initLanguageCodeObjectWithArray() 18 | ); 19 | 20 | const [Client] = useClient(); 21 | 22 | useEffect(() => { 23 | spinnerService.show('apiSpinner'); 24 | 25 | const query = Client.items() 26 | .type(contentTypes.article.codename) 27 | .orderByDescending('elements.post_date') 28 | .limitParameter(10); 29 | 30 | if (language) { 31 | query.languageParameter(language); 32 | } 33 | 34 | query.toPromise().then((response) => { 35 | const currentLanguage = language || defaultLanguage; 36 | 37 | spinnerService.hide('apiSpinner'); 38 | setArticles((data) => ({ 39 | ...data, 40 | [currentLanguage]: response.data.items as ArticleType[], 41 | })); 42 | }); 43 | }, [language, Client]); 44 | 45 | const makeFormatDate = (value: string): string => { 46 | return formatDate(value, { 47 | month: 'long', 48 | day: 'numeric', 49 | }); 50 | }; 51 | 52 | let counter = 0; 53 | 54 | const articlesComponent = articles[language].reduce( 55 | (result: JSX.Element[], article: ArticleType, index: number) => { 56 | if (index % 4 === 0) { 57 | result.push(
); 58 | } 59 | 60 | const title = 61 | article.elements.title.value.trim().length > 0 62 | ? article.elements.title.value 63 | : formatMessage({ id: 'Articles.noTitleValue' }); 64 | 65 | const postDate = makeFormatDate(article.elements.postDate.value!!); 66 | 67 | const imageLink = 68 | article.elements.teaserImage.value[0] !== undefined ? ( 69 | {'Article 75 | ) : ( 76 |
77 | {formatMessage({ id: 'noTeaserValue' })} 78 |
79 | ); 80 | 81 | const summary = 82 | article.elements.summary.value.trim().length > 0 83 | ? article.elements.summary.value 84 | : formatMessage({ id: 'noSummaryValue' }); 85 | 86 | const link = article.system.id; 87 | 88 | result.push( 89 |
90 |
91 | {imageLink} 92 |
{postDate}
93 |
94 |

95 | {title} 96 |

97 |

{summary}

98 |
99 |
100 |
101 | ); 102 | 103 | return result; 104 | }, 105 | [] 106 | ); 107 | return
{articlesComponent}
; 108 | }; 109 | 110 | export default Articles; 111 | -------------------------------------------------------------------------------- /src/Pages/Brewer.tsx: -------------------------------------------------------------------------------- 1 | import { spinnerService } from '@simply007org/react-spinners'; 2 | import React, { useEffect, useState } from 'react'; 3 | import Metadata from '../Components/Metadata'; 4 | import RichText from '../Components/RichText'; 5 | import { 6 | defaultLanguage, 7 | initLanguageCodeObject, 8 | } from '../Utilities/LanguageCodes'; 9 | import { useLocation, useNavigate, useParams } from 'react-router-dom'; 10 | import { useIntl } from 'react-intl'; 11 | import { Brewer as BrewerType } from '../Models/content-types/brewer'; 12 | import { contentTypes } from '../Models/project/contentTypes'; 13 | import { resolveChangeLanguageLink } from '../Utilities/LanugageLink'; 14 | import { useClient } from '../Client'; 15 | 16 | const Brewer: React.FC = () => { 17 | const [brewer, setBrewer] = useState( 18 | initLanguageCodeObject(null) 19 | ); 20 | const { brewerSlug } = useParams(); 21 | const { locale: language, formatMessage } = useIntl(); 22 | const navigate = useNavigate(); 23 | const { pathname } = useLocation(); 24 | const [Client] = useClient(); 25 | 26 | useEffect(() => { 27 | spinnerService.show('apiSpinner'); 28 | 29 | const query = Client.items() 30 | .type(contentTypes.brewer.codename) 31 | .equalsFilter('elements.url_pattern', brewerSlug!!); 32 | 33 | if (language) { 34 | query.languageParameter(language); 35 | } 36 | 37 | query.toPromise().then((response) => { 38 | const currentLanguage = language || defaultLanguage; 39 | 40 | if (response.data.items[0].system.language !== language) { 41 | navigate( 42 | resolveChangeLanguageLink( 43 | pathname, 44 | response.data.items[0].system.language 45 | ), 46 | { replace: true } 47 | ); 48 | } 49 | 50 | spinnerService.hide('apiSpinner'); 51 | setBrewer((data) => ({ 52 | ...data, 53 | [currentLanguage]: response.data.items[0] as BrewerType, 54 | })); 55 | }); 56 | }, [language, brewerSlug, pathname, navigate, Client]); 57 | 58 | const brewerData = brewer[language || defaultLanguage]!; 59 | 60 | if (!brewerData) { 61 | return
; 62 | } 63 | 64 | const name = 65 | brewerData.elements.productName.value.trim().length > 0 66 | ? brewerData.elements.productName.value 67 | : formatMessage({ id: 'Brewer.noNameValue' }); 68 | 69 | const imageLink = 70 | brewerData.elements.image.value[0] !== undefined ? ( 71 | {name} 76 | ) : ( 77 |
78 | {formatMessage({ id: 'noTeaserValue' })} 79 |
80 | ); 81 | 82 | const descriptionElement = 83 | brewerData.elements.longDescription.value !== '


' ? ( 84 | 85 | ) : ( 86 |

{formatMessage({ id: 'noDescriptionValue' })}

87 | ); 88 | 89 | return ( 90 |
91 | 103 |
104 |
105 |
106 |
107 |

{name}

108 |
109 |
110 |
111 |
112 |
113 |
{imageLink}
114 |
{descriptionElement}
115 |
116 |
117 |
118 |
119 | ); 120 | }; 121 | 122 | export default Brewer; 123 | -------------------------------------------------------------------------------- /src/Pages/Cafes.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { spinnerService } from '@simply007org/react-spinners'; 3 | import { useEffect, useState } from 'react'; 4 | import { useClient } from '../Client'; 5 | import { createCafeModel } from '../Utilities/CafeListing'; 6 | import { 7 | defaultLanguage, 8 | ILanguageObjectWithArray, 9 | initLanguageCodeObjectWithArray, 10 | } from '../Utilities/LanguageCodes'; 11 | import { useIntl } from 'react-intl'; 12 | import { CafeModel } from '../ViewModels/CafeModel'; 13 | import { Cafe } from '../Models/content-types/cafe'; 14 | import { contentTypes } from '../Models/project/contentTypes'; 15 | 16 | const Cafes: React.FC = () => { 17 | const { formatMessage, locale: language } = useIntl(); 18 | const [cafes, setCafes] = useState>( 19 | initLanguageCodeObjectWithArray() 20 | ); 21 | const [Client] = useClient(); 22 | 23 | useEffect(() => { 24 | spinnerService.show('apiSpinner'); 25 | 26 | const query = Client.items() 27 | .type(contentTypes.cafe.codename) 28 | .orderByDescending('system.name'); 29 | 30 | if (language) { 31 | query.languageParameter(language); 32 | } 33 | 34 | query.toPromise().then((response) => { 35 | const currentLanguage = language || defaultLanguage; 36 | 37 | spinnerService.hide('apiSpinner'); 38 | setCafes((data) => ({ 39 | ...data, 40 | [currentLanguage]: response.data.items as Cafe[], 41 | })); 42 | }); 43 | }, [language, Client]); 44 | 45 | if (cafes[language].length === 0) { 46 | return
; 47 | } 48 | const companyCafes = cafes[language].filter( 49 | (cafe: Cafe) => cafe.elements.country.value === 'USA' 50 | ); 51 | const partnerCafes = cafes[language].filter( 52 | (cafe: Cafe) => cafe.elements.country.value !== 'USA' 53 | ); 54 | 55 | const companyCafeComponents = companyCafes.map((cafe: Cafe) => { 56 | const model = createCafeModel(cafe); 57 | 58 | return ( 59 |
60 |
64 |
72 |
73 |

{model.name}

74 |
75 | 76 | {model.street}, {model.city} 77 |
78 | {model.zipCode}, {model.countryWithState} 79 |
80 |
81 |

{model.phone}

82 |
83 |
84 |
85 | ); 86 | }); 87 | 88 | const partnerCafeModels = partnerCafes.map((cafe: Cafe) => 89 | createCafeModel(cafe) 90 | ); 91 | 92 | const partnerCafeLocations = partnerCafeModels 93 | .map((model: CafeModel) => model.location) 94 | .reduce((result: string[], location: string) => { 95 | if (result.indexOf(location) < 0) { 96 | result.push(location); 97 | } 98 | return result; 99 | }, []) 100 | .sort(); 101 | 102 | const partnerCafeComponents = partnerCafeLocations.map((location: string) => { 103 | const locationPartnerCafes = partnerCafeModels 104 | .filter((model: CafeModel) => model.location === location) 105 | .map((model: CafeModel, modelIndex: number) => { 106 | return ( 107 |

108 | {model.name}, {model.street}, {model.phone} 109 |

110 | ); 111 | }); 112 | 113 | return ( 114 |
115 |

{location}

116 | {locationPartnerCafes} 117 |
118 | ); 119 | }); 120 | 121 | return ( 122 |
123 |

{formatMessage({ id: 'Cafes.ourCafesTitle' })}

124 |
{companyCafeComponents}
125 |

{formatMessage({ id: 'Cafes.partnerCafesTitle' })}

126 |
{partnerCafeComponents}
127 |
128 | ); 129 | }; 130 | 131 | export default Cafes; 132 | -------------------------------------------------------------------------------- /src/Pages/Coffee.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { spinnerService } from '@simply007org/react-spinners'; 3 | import { useEffect, useState } from 'react'; 4 | import { useClient } from '../Client'; 5 | import Metadata from '../Components/Metadata'; 6 | import RichText from '../Components/RichText'; 7 | import { 8 | defaultLanguage, 9 | initLanguageCodeObject, 10 | } from '../Utilities/LanguageCodes'; 11 | import { useLocation, useNavigate, useParams } from 'react-router-dom'; 12 | import { useIntl } from 'react-intl'; 13 | import { Coffee as CoffeeType } from '../Models/content-types/coffee'; 14 | import { contentTypes } from '../Models/project/contentTypes'; 15 | import { resolveChangeLanguageLink } from '../Utilities/LanugageLink'; 16 | 17 | const Coffee: React.FC = () => { 18 | const [coffee, setCoffee] = useState(initLanguageCodeObject()); 19 | const { coffeeSlug } = useParams(); 20 | const { locale: language, formatMessage } = useIntl(); 21 | const navigate = useNavigate(); 22 | const { pathname } = useLocation(); 23 | const [Client] = useClient(); 24 | 25 | useEffect(() => { 26 | spinnerService.show('apiSpinner'); 27 | 28 | const query = Client.items() 29 | .type(contentTypes.coffee.codename) 30 | .equalsFilter('elements.url_pattern', coffeeSlug!!); 31 | 32 | if (language) { 33 | query.languageParameter(language); 34 | } 35 | 36 | query.toPromise().then((response) => { 37 | const currentLanguage = language || defaultLanguage; 38 | 39 | if (response.data.items[0].system.language !== language) { 40 | navigate( 41 | resolveChangeLanguageLink( 42 | pathname, 43 | response.data.items[0].system.language 44 | ), 45 | { replace: true } 46 | ); 47 | } 48 | 49 | spinnerService.hide('apiSpinner'); 50 | setCoffee((data) => ({ 51 | ...data, 52 | [currentLanguage]: response.data.items[0] as CoffeeType, 53 | })); 54 | }); 55 | }, [language, coffeeSlug, pathname, navigate, Client]); 56 | 57 | const coffeeData = coffee[language || defaultLanguage]; 58 | 59 | if (!coffeeData) { 60 | return
; 61 | } 62 | 63 | const name = 64 | coffeeData.elements.productName.value.trim().length > 0 65 | ? coffeeData.elements.productName.value 66 | : formatMessage({ id: 'Coffee.noNameValue' }); 67 | 68 | const imageLink = 69 | coffeeData.elements.image.value[0] !== undefined ? ( 70 | {name} 75 | ) : ( 76 |
77 | {formatMessage({ id: 'noTeaserValue' })} 78 |
79 | ); 80 | 81 | const descriptionElement = 82 | coffeeData.elements.longDescription.value !== '


' ? ( 83 | 84 | ) : ( 85 |

{formatMessage({ id: 'noDescriptionValue' })}

86 | ); 87 | 88 | const farm = 89 | coffeeData.elements.farm.value.trim().length > 0 90 | ? coffeeData.elements.farm.value 91 | : '\u00A0'; 92 | 93 | const variety = 94 | coffeeData.elements.variety.value.trim().length > 0 95 | ? coffeeData.elements.variety.value 96 | : '\u00A0'; 97 | 98 | const processing = 99 | coffeeData.elements.processing.value.length > 0 100 | ? coffeeData.elements.processing.value[0].name 101 | : '\u00A0'; 102 | const altitude = 103 | coffeeData.elements.altitude.value.trim().length > 0 104 | ? coffeeData.elements.altitude.value + ' feet' 105 | : '\u00A0'; 106 | 107 | return ( 108 |
109 | 121 |
122 |
123 |
124 |
125 |

{name}

126 |
127 |
128 |
129 |
130 |
131 |
{imageLink}
132 |
133 | {descriptionElement} 134 |
135 |

Parameters

136 |
137 |
Farm
138 |
{farm}
139 |
Variety
140 |
{variety}
141 |
Processing
142 |
{processing}
143 |
Altitude
144 |
{altitude}
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 | ); 153 | }; 154 | 155 | export default Coffee; 156 | -------------------------------------------------------------------------------- /src/Pages/Contacts.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { spinnerService } from '@simply007org/react-spinners'; 3 | import { useEffect, useState } from 'react'; 4 | import { createCafeModel } from '../Utilities/CafeListing'; 5 | import { 6 | defaultLanguage, 7 | initLanguageCodeObjectWithArray, 8 | } from '../Utilities/LanguageCodes'; 9 | import { useIntl } from 'react-intl'; 10 | import { Cafe } from '../Models/content-types/cafe'; 11 | import { contentTypes } from '../Models/project/contentTypes'; 12 | import { useClient } from '../Client'; 13 | 14 | const Contacts: React.FC = () => { 15 | const { locale: language, formatMessage } = useIntl(); 16 | const [companyCafes, setCompanyCafes] = useState( 17 | initLanguageCodeObjectWithArray() 18 | ); 19 | 20 | const [Client] = useClient(); 21 | 22 | useEffect(() => { 23 | spinnerService.show('apiSpinner'); 24 | 25 | const query = Client.items() 26 | .type(contentTypes.cafe.codename) 27 | .equalsFilter('elements.country', 'USA') 28 | .orderByDescending('system.name'); 29 | 30 | if (language) { 31 | query.languageParameter(language); 32 | } 33 | 34 | query.toPromise().then((response) => { 35 | const currentLanguage = language || defaultLanguage; 36 | 37 | spinnerService.hide('apiSpinner'); 38 | setCompanyCafes((data) => ({ 39 | ...data, 40 | [currentLanguage]: response.data.items as Cafe[], 41 | })); 42 | }); 43 | }, [language, Client]); 44 | 45 | if (companyCafes[language]?.length === 0) { 46 | return
; 47 | } 48 | 49 | const roastery = createCafeModel(companyCafes[language][0]); 50 | const roasteryComponent = ( 51 |
52 |

53 | {formatMessage({ id: 'Contacts.roasteryTitle' })} 54 |

55 |
    56 |
  • {roastery.phone}
  • 57 |
  • 58 | 59 | {roastery.email} 60 | 61 |
  • 62 |
  • 63 |

    64 | {roastery.dataAddress},
    65 | {roastery.zipCode}, {roastery.countryWithState} 66 |
    67 |

    68 |
  • 69 |
70 |
71 | ); 72 | 73 | const companyCafeComponents = companyCafes[language].map((cafe: Cafe) => { 74 | const model = createCafeModel(cafe); 75 | return ( 76 |
77 |
81 |
82 |

{model.name}

83 |
84 | 85 | {model.street}, {model.city} 86 |
87 | {model.zipCode}, {model.countryWithState} 88 |
89 |
90 |

{model.phone}

91 |
92 |
93 |
94 | ); 95 | }); 96 | return ( 97 |
98 | {roasteryComponent} 99 |
100 |

{formatMessage({ id: 'Contacts.ourCafesTitle' })}

101 |
{companyCafeComponents}
102 |
103 |
104 | ); 105 | }; 106 | 107 | export default Contacts; 108 | -------------------------------------------------------------------------------- /src/Pages/Home.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { spinnerService } from '@simply007org/react-spinners'; 3 | import { useEffect, useState } from 'react'; 4 | import Banner from '../Components/Banner'; 5 | import LatestArticles from '../Components/LatestArticles'; 6 | import LinkButton from '../Components/LinkButton'; 7 | import Metadata from '../Components/Metadata'; 8 | import OurStory from '../Components/OurStory'; 9 | import TasteOurCoffee from '../Components/TasteOurCoffee'; 10 | import { getAboutUsLink } from '../Utilities/ContentLinks'; 11 | import { 12 | defaultLanguage, 13 | initLanguageCodeObject, 14 | } from '../Utilities/LanguageCodes'; 15 | import { useIntl } from 'react-intl'; 16 | import { Home as HomeType } from '../Models/content-types/home'; 17 | import { contentTypes } from '../Models/project/contentTypes'; 18 | import { useClient } from '../Client'; 19 | 20 | const Home: React.FC = () => { 21 | const { locale: language, formatMessage } = useIntl(); 22 | const [homeData, setHomeData] = useState(initLanguageCodeObject()); 23 | const [Client] = useClient(); 24 | 25 | useEffect(() => { 26 | spinnerService.show('apiSpinner'); 27 | 28 | const query = Client.items().type(contentTypes.home.codename); 29 | if (language) { 30 | query.languageParameter(language); 31 | } 32 | 33 | query.toPromise().then((response) => { 34 | const currentLanguage = language || defaultLanguage; 35 | 36 | spinnerService.hide('apiSpinner'); 37 | setHomeData((data) => ({ 38 | ...data, 39 | [currentLanguage]: response.data.items[0] as HomeType, 40 | })); 41 | }); 42 | }, [language, Client]); 43 | 44 | const homeElements = homeData[language]?.elements; 45 | const aboutUsLink = getAboutUsLink(language); 46 | 47 | if (!homeElements) { 48 | return <> ; 49 | } 50 | 51 | if (!spinnerService.isShowing('apiSpinner')) { 52 | return ( 53 |
54 | 66 | {homeElements.heroUnit && 67 | homeElements.heroUnit.linkedItems && 68 | homeElements.heroUnit.linkedItems.length && ( 69 | 70 | )} 71 | {homeElements.articles && ( 72 | 73 | )} 74 | 78 | {homeElements.ourStory && 79 | homeElements.ourStory.linkedItems && 80 | homeElements.ourStory.linkedItems.length && ( 81 | <> 82 | 83 | 87 | 88 | )} 89 | {homeElements.cafes && homeElements.cafes.value && ( 90 | 91 | )} 92 | 96 |
97 | ); 98 | } 99 | 100 | return <>; 101 | }; 102 | 103 | export default Home; 104 | -------------------------------------------------------------------------------- /src/Pages/NotFound.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { FormattedMessage } from 'react-intl'; 3 | 4 | export const NotFound: React.FC = () => { 5 | return ( 6 |
7 |
8 |

404

9 |

10 | 11 |

12 |
13 |
14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /src/Pages/Store.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, Link, Routes } from 'react-router-dom'; 3 | import CoffeeStoreContainer from '../Components/CoffeeStoreContainer'; 4 | import BrewerStoreContainer from '../Components/BrewerStoreContainer'; 5 | import { useIntl } from 'react-intl'; 6 | 7 | const Store: React.FC = () => { 8 | const { formatMessage } = useIntl(); 9 | 10 | return ( 11 |
12 |
13 | 29 | 30 | 31 | } /> 32 | } /> 33 | } /> 34 | 35 |
36 |
37 | ); 38 | }; 39 | 40 | export default Store; 41 | -------------------------------------------------------------------------------- /src/Utilities/CafeListing.ts: -------------------------------------------------------------------------------- 1 | import { Cafe } from '../Models/content-types/cafe'; 2 | import { CafeModel } from '../ViewModels/CafeModel'; 3 | 4 | const createCafeModel = (cafe: Cafe): CafeModel => { 5 | const model = { 6 | name: cafe.system.name, 7 | email: cafe.elements.email.value, 8 | imageLink: 'url(' + cafe.elements.photo.value[0].url + ')', 9 | street: cafe.elements.street.value, 10 | city: cafe.elements.city.value, 11 | zipCode: cafe.elements.zipCode.value, 12 | country: cafe.elements.country.value, 13 | state: cafe.elements.state.value, 14 | phone: cafe.elements.phone.value, 15 | }; 16 | 17 | const addressModel = { 18 | dataAddress: model.city + ', ' + model.street, 19 | countryWithState: model.country + (model.state ? ', ' + model.state : ''), 20 | }; 21 | 22 | const locationModel = { 23 | location: model.city + ', ' + addressModel.countryWithState, 24 | }; 25 | 26 | return { ...model, ...addressModel, ...locationModel }; 27 | }; 28 | 29 | export { createCafeModel }; 30 | -------------------------------------------------------------------------------- /src/Utilities/CheckboxFilter.ts: -------------------------------------------------------------------------------- 1 | import { IContentItem, IContentItemElements } from '@kontent-ai/delivery-sdk'; 2 | 3 | const matchesTaxonomy = ( 4 | item: IContentItem, 5 | filter: string[], 6 | elementName: string 7 | ): boolean => { 8 | if (filter.length === 0) { 9 | return true; 10 | } 11 | const codenames = item.elements[elementName].value.map( 12 | (x: IContentItemElements) => x.codename 13 | ); 14 | return codenames.some((x: string) => filter.includes(x)); 15 | }; 16 | 17 | export { matchesTaxonomy }; 18 | -------------------------------------------------------------------------------- /src/Utilities/ContentLinks.ts: -------------------------------------------------------------------------------- 1 | import { englishCode, spanishCode } from './LanguageCodes'; 2 | 3 | interface linkType { 4 | linkId?: string; 5 | type: string; 6 | urlSlug: string; 7 | } 8 | 9 | export function getAboutUsLink(language: string): string { 10 | return !language || language.toLowerCase() === englishCode.toLowerCase() 11 | ? `about-us` 12 | : language && language.toLowerCase() === spanishCode.toLowerCase() 13 | ? `acerca-de` 14 | : ''; 15 | } 16 | 17 | export function resolveContentLink( 18 | link: linkType, 19 | language: string = englishCode 20 | ): string { 21 | let resultLink; 22 | switch (link.type) { 23 | case 'about_us': 24 | resultLink = `${link.urlSlug}`; 25 | break; 26 | 27 | case 'fact_about_us': 28 | resultLink = `${getAboutUsLink(language)}`; 29 | break; 30 | 31 | case 'article': 32 | resultLink = `articles/${link.linkId}`; 33 | break; 34 | 35 | case 'brewer': 36 | resultLink = `brewers/${link.urlSlug}`; 37 | break; 38 | 39 | case 'cafe': 40 | resultLink = 'cafes'; 41 | break; 42 | 43 | case 'coffee': 44 | resultLink = `coffees/${link.urlSlug}`; 45 | break; 46 | 47 | case 'office': 48 | resultLink = 'contacts'; 49 | break; 50 | 51 | case 'home': 52 | resultLink = ''; 53 | break; 54 | 55 | default: 56 | resultLink = ''; 57 | break; 58 | } 59 | 60 | return `/${language}/${resultLink}`.toLowerCase(); 61 | } 62 | -------------------------------------------------------------------------------- /src/Utilities/LanguageCodes.ts: -------------------------------------------------------------------------------- 1 | import { IContentItem } from '@kontent-ai/delivery-sdk'; 2 | 3 | const languageCodes = [ 4 | 'en-US', // default languages 5 | 'es-ES', 6 | ]; 7 | 8 | const englishCode = languageCodes[0]; 9 | const spanishCode = languageCodes[1]; 10 | 11 | const languageCodesLowerCase = languageCodes.map((code) => code.toLowerCase()); 12 | 13 | const defaultLanguage = languageCodes[0]; 14 | 15 | export interface ILanguageObject { 16 | [key: string]: TContentItem | null; 17 | } 18 | 19 | const initLanguageCodeObject = ( 20 | object: ILanguageObject | null = null 21 | ): ILanguageObject => { 22 | if (!object) { 23 | object = {}; 24 | } 25 | 26 | languageCodes.forEach((language) => { 27 | if (object) { 28 | object[language] = null; 29 | } 30 | }); 31 | 32 | return object; 33 | }; 34 | 35 | export interface ILanguageObjectWithArray { 36 | [key: string]: TContentItem[]; 37 | } 38 | 39 | const initLanguageCodeObjectWithArray = ( 40 | object: ILanguageObjectWithArray | null = null 41 | ): ILanguageObjectWithArray => { 42 | if (!object) { 43 | object = {}; 44 | } 45 | 46 | languageCodes.forEach((language) => { 47 | if (object) { 48 | object[language] = []; 49 | } 50 | }); 51 | 52 | return object; 53 | }; 54 | 55 | export { 56 | languageCodes, 57 | languageCodesLowerCase, 58 | defaultLanguage, 59 | initLanguageCodeObject, 60 | initLanguageCodeObjectWithArray, 61 | englishCode, 62 | spanishCode, 63 | }; 64 | export default languageCodes; 65 | -------------------------------------------------------------------------------- /src/Utilities/LanugageLink.ts: -------------------------------------------------------------------------------- 1 | export function resolveChangeLanguageLink( 2 | path: string, 3 | language: string 4 | ): string { 5 | const pathArray = path.split('/'); 6 | pathArray[1] = language.toLowerCase(); 7 | return pathArray.join('/'); 8 | } 9 | -------------------------------------------------------------------------------- /src/Utilities/LocalizationLoader.ts: -------------------------------------------------------------------------------- 1 | type localizationObjectType = { 2 | [key: string]: { 3 | [key: string]: string; 4 | }; 5 | }; 6 | 7 | const initLocalizationObject = (): localizationObjectType => { 8 | const localizations = require.context('../Localization', false, /\.json$/); 9 | 10 | const localizationObject: localizationObjectType = {}; 11 | localizations.keys().forEach((item: string) => { 12 | let localizationKey = item.replace(/\.\/(\w+-\w+)\.json$/, '$1'); 13 | const localization = require(`../Localization/${localizationKey}`); 14 | 15 | localizationObject[localizationKey] = localization; 16 | }); 17 | return localizationObject; 18 | }; 19 | 20 | export const localizationObject = initLocalizationObject(); 21 | -------------------------------------------------------------------------------- /src/Utilities/SelectedProject.ts: -------------------------------------------------------------------------------- 1 | import { createClient } from '../Client'; 2 | import { 3 | Contracts, 4 | IDeliveryNetworkResponse, 5 | Responses, 6 | } from '@kontent-ai/delivery-sdk'; 7 | 8 | const defaultEnvironmentId = '975bf280-fd91-488c-994c-2f04416e5ee3'; 9 | 10 | const getSampleProjectItems = (): Promise< 11 | IDeliveryNetworkResponse< 12 | Responses.IListContentItemsResponse, 13 | Contracts.IListContentItemsContract 14 | > 15 | > => { 16 | const client = createClient(defaultEnvironmentId); 17 | 18 | return client.items().elementsParameter(['id']).depthParameter(0).toPromise(); 19 | }; 20 | 21 | export { defaultEnvironmentId, getSampleProjectItems }; 22 | -------------------------------------------------------------------------------- /src/Utilities/StoreListing.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Elements } from '@kontent-ai/delivery-sdk'; 3 | 4 | const formatPrice = (price: number, language: string): string => { 5 | return price.toLocaleString(language, { 6 | style: 'currency', 7 | currency: 'USD', 8 | }); 9 | }; 10 | 11 | const renderProductStatus = ( 12 | productStatus: Elements.TaxonomyElement 13 | ): React.ReactNode => { 14 | if (productStatus.value.length === 0) { 15 | return ; 16 | } 17 | 18 | const text = productStatus.value.map((x) => x.name).join(', '); 19 | return {text}; 20 | }; 21 | 22 | export { formatPrice, renderProductStatus }; 23 | -------------------------------------------------------------------------------- /src/ViewModels/CafeModel.ts: -------------------------------------------------------------------------------- 1 | export type CafeModel = { 2 | name: string; 3 | email: string; 4 | imageLink: string; 5 | street: string; 6 | city: string; 7 | zipCode: string; 8 | country: string; 9 | state: string; 10 | phone: string; 11 | dataAddress: string; 12 | countryWithState: string; 13 | location: string; 14 | }; 15 | -------------------------------------------------------------------------------- /src/const.ts: -------------------------------------------------------------------------------- 1 | export const selectedEnvironmentCookieName = 'EnvironmentId'; 2 | export const projectConfigurationPath = '/Admin/Configuration'; 3 | -------------------------------------------------------------------------------- /src/custom.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svg' { 2 | const content: string; 3 | export default content; 4 | } 5 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './LocalizedApp'; 5 | import { 6 | BrowserRouter as Router, 7 | Navigate, 8 | Route, 9 | Routes, 10 | } from 'react-router-dom'; 11 | import Configuration from './Pages/Admin/Configuration'; 12 | import { projectConfigurationPath } from './const'; 13 | import languageCodes, { englishCode } from './Utilities/LanguageCodes'; 14 | import Cookies from 'universal-cookie'; 15 | import { ClientProvider } from './Client'; 16 | 17 | const cookies = new Cookies(document.cookie); 18 | const cookiesLang = cookies.get('lang'); 19 | const lang = languageCodes.includes(cookiesLang) ? cookiesLang : englishCode; 20 | 21 | ReactDOM.render( 22 | 23 | 24 | 25 | } /> 26 | 27 | {languageCodes.map((value) => ( 28 | } 32 | /> 33 | ))} 34 | 35 | } /> 36 | } 39 | /> 40 | 41 | 42 | , 43 | document.getElementById('root') 44 | ); 45 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | interface Window { 2 | twttr: any; 3 | } 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"] 20 | } 21 | --------------------------------------------------------------------------------