├── .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 |
127 |
128 | {formatMessage({ id: 'BrewerStoreContainer.manufacturerTitle' })}
129 |
130 | {
132 | return {
133 | id: manufacturer.codename,
134 | checked: filter.manufacturers.includes(manufacturer.codename),
135 | label: manufacturer.name,
136 | onChange: (event) =>
137 | toggleFilter('manufacturers', event.target.id),
138 | };
139 | })}
140 | />
141 | {formatMessage({ id: 'BrewerStoreContainer.priceTitle' })}
142 | {
144 | const priceRangeId = `${priceRange.min}-${priceRange.max}`;
145 | return {
146 | id: priceRangeId,
147 | checked: filter.priceRanges.includes(priceRangeId),
148 | label: `${formatPrice(
149 | priceRange.min,
150 | language
151 | )} - ${formatPrice(priceRange.max, language)}`,
152 | onChange: (event) =>
153 | toggleFilter('priceRanges', event.target.id),
154 | };
155 | })}
156 | />
157 | {formatMessage({ id: 'BrewerStoreContainer.statusTitle' })}
158 | {
160 | return {
161 | id: productStatus.codename,
162 | checked: filter.productStatuses.includes(
163 | productStatus.codename
164 | ),
165 | label: productStatus.name,
166 | onChange: (event) =>
167 | toggleFilter('productStatuses', event.target.id),
168 | };
169 | })}
170 | />
171 |
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 |
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 | {label}
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 |
92 |
93 | {formatMessage({
94 | id: 'CoffeeStoreContainer.coffeeProcessingTitle',
95 | })}
96 |
97 | {
99 | return {
100 | id: processing.codename,
101 | checked: filter.processings.includes(processing.codename),
102 | label: processing.name,
103 | onChange: (event) =>
104 | toggleFilter('processings', event.target.id),
105 | };
106 | })}
107 | />
108 | {formatMessage({ id: 'CoffeeStoreContainer.statusTitle' })}
109 | {
111 | return {
112 | id: productStatus.codename,
113 | checked: filter.productStatuses.includes(
114 | productStatus.codename
115 | ),
116 | label: productStatus.name,
117 | onChange: (event) =>
118 | toggleFilter('productStatuses', event.target.id),
119 | };
120 | })}
121 | />
122 |
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 |
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 |
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 |
22 |
23 |
24 |
25 | {formatMessage({ id: 'Header.homeLinkTitle' })}
26 |
27 |
28 |
29 |
30 | {formatMessage({ id: 'Header.storeLinkTitle' })}
31 |
32 |
33 |
34 |
35 | {formatMessage({ id: 'Header.articlesLinkTitle' })}
36 |
37 |
38 | {language.toLowerCase() === englishCode.toLowerCase() ? (
39 |
40 |
41 | {formatMessage({ id: 'Header.aboutLinkTitle' })}
42 |
43 |
44 | ) : language.toLowerCase() === spanishCode.toLowerCase() ? (
45 |
46 |
47 | {formatMessage({ id: 'Header.aboutLinkTitle' })}
48 |
49 |
50 | ) : null}
51 |
52 |
53 | {formatMessage({ id: 'Header.cafesLinkTitle' })}
54 |
55 |
56 |
57 |
58 | {formatMessage({ id: 'Header.contactsLinkTitle' })}
59 |
60 |
61 |
62 |
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 |
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 |
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 |
22 |
23 |
24 |
25 | {props.message}
26 |
27 |
28 |
29 |
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 | VIDEO
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 |
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 |
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 |
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 |
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 |
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 |
174 |
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 |
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 |
239 |
240 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
14 |
15 |
16 |
17 |
18 | {formatMessage({ id: 'Store.coffeesLinkTitle' })}
19 |
20 |
21 |
22 |
23 | {formatMessage({ id: 'Store.brewersLinkTitle' })}
24 |
25 |
26 |
27 |
28 |
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 |
--------------------------------------------------------------------------------