')
373 | .append(i.clone())
374 | .remove()
375 | .html()
376 | .replace(/type="password"/i, 'type="text"')
377 | .replace(/type=password/i, 'type=text')
378 | );
379 |
380 | if (i.attr('id') != '')
381 | x.attr('id', i.attr('id') + '-polyfill-field');
382 |
383 | if (i.attr('name') != '')
384 | x.attr('name', i.attr('name') + '-polyfill-field');
385 |
386 | x.addClass('polyfill-placeholder')
387 | .val(x.attr('placeholder')).insertAfter(i);
388 |
389 | if (i.val() == '')
390 | i.hide();
391 | else
392 | x.hide();
393 |
394 | i
395 | .on('blur', function(event) {
396 |
397 | event.preventDefault();
398 |
399 | var x = i.parent().find('input[name=' + i.attr('name') + '-polyfill-field]');
400 |
401 | if (i.val() == '') {
402 |
403 | i.hide();
404 | x.show();
405 |
406 | }
407 |
408 | });
409 |
410 | x
411 | .on('focus', function(event) {
412 |
413 | event.preventDefault();
414 |
415 | var i = x.parent().find('input[name=' + x.attr('name').replace('-polyfill-field', '') + ']');
416 |
417 | x.hide();
418 |
419 | i
420 | .show()
421 | .focus();
422 |
423 | })
424 | .on('keypress', function(event) {
425 |
426 | event.preventDefault();
427 | x.val('');
428 |
429 | });
430 |
431 | });
432 |
433 | // Events.
434 | $this
435 | .on('submit', function() {
436 |
437 | $this.find('input[type=text],input[type=password],textarea')
438 | .each(function(event) {
439 |
440 | var i = $(this);
441 |
442 | if (i.attr('name').match(/-polyfill-field$/))
443 | i.attr('name', '');
444 |
445 | if (i.val() == i.attr('placeholder')) {
446 |
447 | i.removeClass('polyfill-placeholder');
448 | i.val('');
449 |
450 | }
451 |
452 | });
453 |
454 | })
455 | .on('reset', function(event) {
456 |
457 | event.preventDefault();
458 |
459 | $this.find('select')
460 | .val($('option:first').val());
461 |
462 | $this.find('input,textarea')
463 | .each(function() {
464 |
465 | var i = $(this),
466 | x;
467 |
468 | i.removeClass('polyfill-placeholder');
469 |
470 | switch (this.type) {
471 |
472 | case 'submit':
473 | case 'reset':
474 | break;
475 |
476 | case 'password':
477 | i.val(i.attr('defaultValue'));
478 |
479 | x = i.parent().find('input[name=' + i.attr('name') + '-polyfill-field]');
480 |
481 | if (i.val() == '') {
482 | i.hide();
483 | x.show();
484 | }
485 | else {
486 | i.show();
487 | x.hide();
488 | }
489 |
490 | break;
491 |
492 | case 'checkbox':
493 | case 'radio':
494 | i.attr('checked', i.attr('defaultValue'));
495 | break;
496 |
497 | case 'text':
498 | case 'textarea':
499 | i.val(i.attr('defaultValue'));
500 |
501 | if (i.val() == '') {
502 | i.addClass('polyfill-placeholder');
503 | i.val(i.attr('placeholder'));
504 | }
505 |
506 | break;
507 |
508 | default:
509 | i.val(i.attr('defaultValue'));
510 | break;
511 |
512 | }
513 | });
514 |
515 | });
516 |
517 | return $this;
518 |
519 | };
520 |
521 | /**
522 | * Moves elements to/from the first positions of their respective parents.
523 | * @param {jQuery} $elements Elements (or selector) to move.
524 | * @param {bool} condition If true, moves elements to the top. Otherwise, moves elements back to their original locations.
525 | */
526 | $.prioritize = function($elements, condition) {
527 |
528 | var key = '__prioritize';
529 |
530 | // Expand $elements if it's not already a jQuery object.
531 | if (typeof $elements != 'jQuery')
532 | $elements = $($elements);
533 |
534 | // Step through elements.
535 | $elements.each(function() {
536 |
537 | var $e = $(this), $p,
538 | $parent = $e.parent();
539 |
540 | // No parent? Bail.
541 | if ($parent.length == 0)
542 | return;
543 |
544 | // Not moved? Move it.
545 | if (!$e.data(key)) {
546 |
547 | // Condition is false? Bail.
548 | if (!condition)
549 | return;
550 |
551 | // Get placeholder (which will serve as our point of reference for when this element needs to move back).
552 | $p = $e.prev();
553 |
554 | // Couldn't find anything? Means this element's already at the top, so bail.
555 | if ($p.length == 0)
556 | return;
557 |
558 | // Move element to top of parent.
559 | $e.prependTo($parent);
560 |
561 | // Mark element as moved.
562 | $e.data(key, $p);
563 |
564 | }
565 |
566 | // Moved already?
567 | else {
568 |
569 | // Condition is true? Bail.
570 | if (condition)
571 | return;
572 |
573 | $p = $e.data(key);
574 |
575 | // Move element back to its original location (using our placeholder).
576 | $e.insertAfter($p);
577 |
578 | // Unmark element as moved.
579 | $e.removeData(key);
580 |
581 | }
582 |
583 | });
584 |
585 | };
586 |
587 | })(jQuery);
--------------------------------------------------------------------------------
/src/commonSlices/testSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 |
3 | const initialState = {
4 | tickets: [],
5 | test: "testing",
6 | isLoading: false,
7 | error: "",
8 | replyTicketError: "",
9 | searchTicketList: [],
10 | selectedTicket: {},
11 | replyMsg: "",
12 | cart: [],
13 | };
14 |
15 | const testSlice = createSlice({
16 | name: "testSlice",
17 | initialState,
18 | reducers: {
19 | fetchTicketLoading: (state) => {
20 | state.isLoading = true;
21 | },
22 | fetchTicketSuccess: (state, action) => {
23 | state.tickets = action.payload;
24 | state.searchTicketList = action.payload;
25 | state.isLoading = false;
26 | },
27 | fetchTicketFail: (state, { payload }) => {
28 | state.isLoading = false;
29 | state.error = payload;
30 | },
31 | },
32 | });
33 |
34 | const { reducer, actions } = testSlice;
35 |
36 | export const {
37 | fetchTicketLoading,
38 | fetchTicketSuccess,
39 | fetchTicketFail,
40 | fetchSingleTicketLoading,
41 | fetchSingleTicketSuccess,
42 | fetchSingleTicketFail,
43 |
44 | } = actions;
45 |
46 | export default reducer;
--------------------------------------------------------------------------------
/src/components/app.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Footer from './footer';
3 |
4 | export default class App extends Component {
5 | render() {
6 | return (
7 |
8 | {this.props.children}
9 |
10 |
11 | );
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/components/asset.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import { fetchAsset } from '../actions/index';
4 |
5 | class Asset extends Component {
6 | componentWillMount() {
7 | this.props.fetchAsset(this.props.assetId)
8 | }
9 | renderAsset() {
10 | return this.props.assets.map((asset) => {
11 | if (asset.sys.id == this.props.assetId) {
12 | return (
13 |

14 | );
15 | }
16 | });
17 | }
18 | render() {
19 | return (
20 |
21 | {this.renderAsset()}
22 |
23 | );
24 | }
25 | }
26 |
27 | function mapStateToProps(state) {
28 | return {
29 | assets: state.assets
30 | };
31 | }
32 |
33 | export default connect(mapStateToProps, { fetchAsset })(Asset)
34 |
--------------------------------------------------------------------------------
/src/components/call_to_action.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Markdown from 'react-remarkable';
3 |
4 | export default class CallToAction extends React.Component {
5 | constructor(props) {
6 | super(props);
7 | }
8 |
9 | render() {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | )
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/footer.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | class Footer extends Component {
4 | render() {
5 | return(
6 |
13 |
14 | );
15 | }
16 | }
17 |
18 | export default Footer;
19 |
--------------------------------------------------------------------------------
/src/components/header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class Header extends React.Component {
4 | constructor(props) {
5 | super(props);
6 | }
7 |
8 | render() {
9 | //If the component comes through with an image resolved use that, otherwise use the test image
10 | //NOTE: For locales with no fallback or localized images added, this can happen currently
11 | const imageURL = (this.props.fields.logo && this.props.fields.logo.fields.file) ? this.props.fields.logo.fields.file.url : this.props.defaultImageURL;
12 |
13 | return (
14 |
16 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | {this.props.fields.navigationLinks.map((navLink) => {
25 | return (
26 |
36 | )
37 | })}
38 |
39 |
40 | )
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/components/headline.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class Headline extends React.Component {
4 | constructor(props) {
5 | super(props);
6 | }
7 |
8 | render() {
9 | return (
10 |
11 |
{this.props.fields.headline}
12 | {this.props.fields.subHeadline}
13 |
14 | )
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/components/hero_image.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class HeroImage extends React.Component {
4 | constructor(props) {
5 | super(props);
6 | }
7 |
8 | render() {
9 | //If the component comes through with an image resolved use that, otherwise use the test image
10 | //NOTE: For locales with no fallback or localized images added, this can happen currently
11 | //const imageURL = (this.props.fields.image && this.props.fields.image.fields.file) ? this.props.fields.image.fields.file.url : this.props.defaultImageURL;
12 |
13 | let imageURL = '';
14 | if (this.props.fields.cloudinaryImage && this.props.fields.cloudinaryImage[0] && this.props.fields.cloudinaryImage[0].secure_url) {
15 | imageURL = this.props.fields.cloudinaryImage[0].secure_url + '?w=1600&q=80&fm=webp';
16 | } else {
17 | imageURL = (this.props.fields.image && this.props.fields.image.fields.file) ? this.props.fields.image.fields.file.url : this.props.defaultImageURL;
18 | imageURL = imageURL + '?w=1600&q=80&fm=jpg&fl=progressive';
19 | }
20 |
21 | return (
22 |
26 |
27 | {/* The following sprite is to help load the background image faster */}
28 |

29 |
30 | )
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/components/image_carousel.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Slider from 'react-slick';
3 |
4 | export default class ImageCarousel extends React.Component {
5 | constructor(props) {
6 | super(props);
7 | }
8 |
9 | render() {
10 | let images = []
11 | for (let i = 0; i < this.props.fields.images.length; i++) {
12 | images.push(`https:${this.props.fields.images[i].fields.image.fields.file.url}`)
13 | }
14 | console.log(images);
15 | var settings = {infinite: true, speed: 400, slidesToShow: 1, slidesToScroll: 1}
16 | return (
17 |
18 | {images.map((image) => (
19 |
20 |
21 |

22 |
23 | )
24 | )}
25 |
26 | )
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/components/image_with_caption.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class ImageWithCaption extends React.Component {
4 | constructor(props) {
5 | super(props);
6 | }
7 |
8 | render() {
9 | //If the component comes through with an image resolved use that, otherwise use the test image
10 | //NOTE: For locales with no fallback or localized images added, this can happen currently
11 | const imageURL = (this.props.fields.image && this.props.fields.image.fields.file) ? this.props.fields.image.fields.file.url : this.props.defaultImageURL;
12 |
13 | return (
14 |
15 |
16 |
17 |
18 |
19 |
20 |
22 |
23 |
24 | {/* {JSON.stringify(this.props.fields.name)} */}
25 |
26 |
{this.props.fields.name}
27 |
28 |
29 | )
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/components/locale_select.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const LocaleSelect = (props) => {
4 | if (!props.locales){
5 | return
;
6 | }
7 |
8 | //Make options from the current locale options
9 | let localeOptions = props.locales.map((code) =>
10 |
11 | );
12 |
13 | //Return JSX with select control for choosing locale options
14 | return (
15 |
16 |
17 |
18 |
25 |
26 |
27 |
28 |
29 |
30 | )
31 | }
32 |
33 | export default LocaleSelect;
34 |
--------------------------------------------------------------------------------
/src/components/paragraph_with_headline.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Markdown from 'react-remarkable';
3 |
4 | export default class ParagraphWithHeadline extends React.Component {
5 | constructor(props) {
6 | super(props);
7 | }
8 |
9 | render() {
10 | return (
11 |
15 |
16 |
{this.props.fields.headline}
17 |
18 |
19 | )
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/components/product_show.js:
--------------------------------------------------------------------------------
1 | // import React, { Component, PropTypes } from 'react';
2 | import React, { memo, useState, useEffect, useCallback } from "react";
3 | import { Link } from "react-router";
4 | import * as contentful from "contentful";
5 | import Section from "./section";
6 | import LocaleSelect from "./locale_select";
7 | import config from "./config";
8 | import ReactJson from "react-json-view";
9 |
10 | import marked from "marked";
11 |
12 | const Spinner = require("react-spinkit");
13 |
14 | const TEST_IMAGE_URL =
15 | "https://images.ctfassets.net/34zhepmq2vpx/4ClyFr0XGwcOiKUMyyiMKO/c47e029fa790bf3c01b8900bd6cacf87/TWD_Test_Image6.png";
16 |
17 | export default function ProductShow(props) {
18 | let options = {};
19 |
20 | let is_preview = "";
21 | let space_id = "";
22 | let access_token = "";
23 |
24 | options.space = space_id ? space_id : config.space_id;
25 | options.host = is_preview ? "preview.contentful.com" : undefined;
26 | options.accessToken = access_token
27 | ? access_token
28 | : is_preview
29 | ? config.preview_token
30 | : config.delivery_token;
31 | // hard code to start
32 | options.environment = config.environment ? config.environment : "master";
33 |
34 | const [productsByLocale, setProductsByLocale] = useState("");
35 | const [currentLocale, setCurrentLocale] = useState("");
36 | const [defaultLocale, setDefaultLocale] = useState("");
37 | const [locales, setLocales] = useState("");
38 |
39 | const [defaultImageURL, setDefaultImageURL] = useState("");
40 |
41 | const handleSelectLocale = (selectedCode) => {
42 | setCurrentLocale(selectedCode);
43 | };
44 |
45 | // get contentful space data
46 | const getCtflSpace = async () => {
47 | // contentful queries here
48 | const contentfulClient = contentful.createClient(options);
49 | //Get the locales setup for the space and do necessary processing
50 | await contentfulClient
51 | .getLocales()
52 | .then((data) => {
53 | //Store current locale codes in array to be populated in select dropdown on page
54 | const localeCodes = data.items.map((localeData) => localeData.code);
55 |
56 | //Filter to find the default locale to use later
57 | const defaultLocaleHolder = data.items.filter(
58 | (item) => item.default === true
59 | )[0].code;
60 |
61 | let productsByLocaleHolder = {}; //placeholder for productsByLocal
62 |
63 | //Get entries for each locale setup for the space
64 | localeCodes.map((localeCode) => {
65 | contentfulClient
66 | .getEntries({
67 | content_type: "landingPage",
68 | locale: localeCode,
69 | "fields.slug": props.location.pathname.split("/")[1],
70 | include: 10,
71 | })
72 | .then((data2) => {
73 | productsByLocaleHolder[localeCode] = data2.items[0];
74 | //If on the last object of the array, set the state after we get the entry
75 | if (localeCodes.indexOf(localeCode) == localeCodes.length - 1) {
76 | //Fetch a default image that can be used in case of errors retrieving others (e.g. fallbacks not defined and API doesn't return assets)
77 | //TODO: Handling this way for now since there hasn't been resolution whether this should fallback to the default locale or not. Can reassess when this is resolved.
78 | //Also set state
79 |
80 | setProductsByLocale(productsByLocaleHolder);
81 | setCurrentLocale(defaultLocaleHolder);
82 | setDefaultLocale(defaultLocaleHolder);
83 | setDefaultImageURL(TEST_IMAGE_URL);
84 | setLocales(localeCodes);
85 | }
86 | })
87 | .catch((erx) => {
88 | console.log("An Error Occured", erx);
89 | });
90 | });
91 | })
92 | .catch((ecflx) => {
93 | alert(
94 | "An Error Occured, Make sure you enable API key for 'demo' Enivironment on The Web App"
95 | );
96 | console.log("An Error Occured", ecflx);
97 | });
98 | };
99 |
100 | // this runs on component mount
101 | useEffect(() => {
102 | getCtflSpace();
103 |
104 | return () => {};
105 | }, []);
106 |
107 | // this runs if has productsByLocale changed
108 | useEffect(() => {
109 | // if currentLocal not in returned data, run again
110 | //must make sure productsByLocale is not empty first
111 | if (productsByLocale) {
112 | if (!productsByLocale[currentLocale]) {
113 | getCtflSpace();
114 | }
115 | }
116 |
117 | return () => {};
118 | }, [productsByLocale]);
119 |
120 | if (!productsByLocale[currentLocale]) {
121 | return (
122 |
123 |
124 | Loading...
125 |
126 |
127 | {productsByLocale ? (
128 | <>
129 | {" "}
130 |
131 |
132 |
YOU MAY REVIEW THE JSON DATA HERE
133 |
134 |
135 | {productsByLocale ? (
136 |
141 | ) : (
142 | ""
143 | )}
144 |
145 |
146 | >
147 | ) : (
148 | ""
149 | )}
150 |
151 | );
152 | }
153 |
154 | document.title = productsByLocale[currentLocale].fields.title;
155 |
156 | let sections = productsByLocale[currentLocale].fields.sections.map(
157 | (section, idx) => {
158 | if (section) {
159 | return (
160 |
169 | );
170 | } else {
171 | return "---";
172 | }
173 | }
174 | );
175 |
176 | return (
177 |
178 | {sections}
179 | {locales ? (
180 |
185 | ) : (
186 | ""
187 | )}
188 |
189 |
190 |
191 |
YOU MAY REVIEW THE JSON DATA HERE
192 |
193 |
194 | {productsByLocale ? (
195 |
200 | ) : (
201 | ""
202 | )}
203 |
204 |
205 |
206 | );
207 | }
208 |
--------------------------------------------------------------------------------
/src/components/product_show2.js:
--------------------------------------------------------------------------------
1 | // import React, { Component, PropTypes } from 'react';
2 | import React, { memo, useState, useEffect, useCallback } from "react";
3 | import { Link } from "react-router";
4 | import * as contentful from "contentful";
5 | import Section from "./section";
6 | import LocaleSelect from "./locale_select";
7 | import config from "./config";
8 | import ReactJson from "react-json-view";
9 |
10 | import marked from "marked";
11 |
12 | const Spinner = require("react-spinkit");
13 |
14 | const TEST_IMAGE_URL =
15 | "https://images.ctfassets.net/34zhepmq2vpx/4ClyFr0XGwcOiKUMyyiMKO/c47e029fa790bf3c01b8900bd6cacf87/TWD_Test_Image6.png";
16 |
17 | export default function ProductShow(props) {
18 | let options = {};
19 |
20 | let is_preview = "";
21 | let space_id = "";
22 | let access_token = "";
23 |
24 | options.space = space_id ? space_id : config.space_id;
25 | options.host = is_preview ? "preview.contentful.com" : undefined;
26 | options.accessToken = access_token
27 | ? access_token
28 | : is_preview
29 | ? config.preview_token
30 | : config.delivery_token;
31 | // hard code to start
32 | options.environment = config.environment ? config.environment : "master";
33 |
34 | const [productsByLocale, setProductsByLocale] = useState("");
35 | const [currentLocale, setCurrentLocale] = useState("");
36 | const [defaultLocale, setDefaultLocale] = useState("");
37 | const [locales, setLocales] = useState("");
38 |
39 | const [defaultImageURL, setDefaultImageURL] = useState("");
40 |
41 | const handleSelectLocale = (selectedCode) => {
42 | setCurrentLocale(selectedCode);
43 | };
44 |
45 | // get contentful space data
46 | const getCtflSpace = async () => {
47 | // contentful queries here
48 | const contentfulClient = contentful.createClient(options);
49 | //Get the locales setup for the space and do necessary processing
50 | await contentfulClient
51 | .getLocales()
52 | .then((data) => {
53 | //Store current locale codes in array to be populated in select dropdown on page
54 | const localeCodes = data.items.map((localeData) => localeData.code);
55 | // console.log("localeCodes", data.items)
56 |
57 | //Filter to find the default locale to use later
58 | const defaultLocaleHolder = data.items.filter(
59 | (item) => item.default === true
60 | )[0].code;
61 |
62 | let productsByLocaleHolder = {}; //placeholder for productsByLocal
63 |
64 | //Get entries for each locale setup for the space
65 | localeCodes.map((localeCode) => {
66 | contentfulClient
67 | .getEntries({
68 | content_type: "landingPage",
69 | locale: localeCode,
70 | "fields.slug": props.location.pathname.split("/")[1],
71 | include: 10,
72 | })
73 | .then((data2) => {
74 | productsByLocaleHolder[localeCode] = data2.items[0];
75 | //If on the last object of the array, set the state after we get the entry
76 | if (localeCodes.indexOf(localeCode) == localeCodes.length - 1) {
77 | //Fetch a default image that can be used in case of errors retrieving others (e.g. fallbacks not defined and API doesn't return assets)
78 | //TODO: Handling this way for now since there hasn't been resolution whether this should fallback to the default locale or not. Can reassess when this is resolved.
79 | //Also set state
80 |
81 | setProductsByLocale(productsByLocaleHolder);
82 | setCurrentLocale(defaultLocaleHolder);
83 | setDefaultLocale(defaultLocaleHolder);
84 | setDefaultImageURL(TEST_IMAGE_URL);
85 | setLocales(localeCodes);
86 | }
87 | })
88 | .catch((erx) => {
89 | alert("error1");
90 | console.log("An Error Occured", erx);
91 | });
92 | });
93 | })
94 | .catch((ecflx) => {
95 | alert(
96 | "An Error Occured, Make sure you enable API key for 'demo' Enivironment on The Web App"
97 | );
98 | console.log("An Error Occured", ecflx);
99 | });
100 | };
101 |
102 | // this runs on component mount
103 | useEffect(() => {
104 | getCtflSpace();
105 |
106 | return () => {};
107 | }, []);
108 |
109 | // this runs if has productsByLocale changed
110 | useEffect(() => {
111 | // if currentLocal not in returned data, run again
112 | //must make sure productsByLocale is not empty first
113 | if (productsByLocale) {
114 | if (!productsByLocale[currentLocale]) {
115 | getCtflSpace();
116 | }
117 | }
118 |
119 | return () => {};
120 | }, [productsByLocale]);
121 |
122 | if (!productsByLocale[currentLocale]) {
123 | return (
124 |
125 |
126 | Loading...
127 |
128 |
129 | {productsByLocale ? (
130 | <>
131 | {" "}
132 |
133 |
134 |
YOU MAY REVIEW THE JSON DATA HERE
135 |
136 |
137 | {productsByLocale ? (
138 |
143 | ) : (
144 | ""
145 | )}
146 |
147 |
148 | >
149 | ) : (
150 | ""
151 | )}
152 |
153 | );
154 | }
155 |
156 | document.title = productsByLocale[currentLocale].fields.title;
157 |
158 | let sections = productsByLocale[currentLocale].fields.sections.map(
159 | (section, idx) => (
160 |
161 |
170 |
171 | )
172 | );
173 | return (
174 |
175 | {sections}
176 |
181 |
182 |
183 |
184 |
YOU MAY REVIEW THE JSON DATA HERE
185 |
186 |
187 | {productsByLocale ? (
188 |
193 | ) : (
194 | ""
195 | )}
196 |
197 |
198 |
199 | );
200 | }
201 |
--------------------------------------------------------------------------------
/src/components/section.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Markdown from 'react-remarkable';
3 |
4 | import CallToAction from './call_to_action';
5 | import Header from './header';
6 | import Headline from './headline'
7 | import HeroImage from './hero_image';
8 | import ImageCarousel from './image_carousel'
9 | import ImageWithCaption from './image_with_caption';
10 | import ParagraphWithHeadline from './paragraph_with_headline'
11 | import SetOfTwo from './set_of_two'
12 | import SetOfThree from './set_of_three'
13 | import TextWithImage from './text_with_image'
14 |
15 | export default class Section extends React.Component {
16 | constructor(props) {
17 | super(props);
18 | }
19 |
20 | renderSection() {
21 | switch (this.props.sectionType) {
22 | case 'callToAction':
23 | return (
)
24 | case 'header':
25 | return (
)
26 | case 'headline':
27 | return (
)
28 | case 'heroImage':
29 | return (
)
30 | case 'imageCarousel':
31 | return (
)
32 | case 'imageWithCaption':
33 | return (
)
34 | case 'paragraphWithHeadline':
35 | return (
)
36 | case 'setOfTwo':
37 | return (
)
38 | case 'setOfThree':
39 | return (
)
40 | case 'textWithImage':
41 | return (
)
42 | default:
43 | console.log("Section type not found: " + this.props.sectionType);
44 | return (
Illegal Section Type
)
45 | }
46 | }
47 |
48 | render() {
49 | return (
50 |
51 | {this.renderSection()}
52 |
53 | )
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/components/set_of_three.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import CallToAction from './call_to_action';
4 | import Headline from './headline'
5 | import ImageCarousel from './image_carousel'
6 | import ImageWithCaption from './image_with_caption';
7 | import ParagraphWithHeadline from './paragraph_with_headline'
8 | import TextWithImage from './text_with_image'
9 |
10 | export default class SetOfThree extends React.Component {
11 | constructor(props) {
12 | super(props);
13 | }
14 |
15 | render() {
16 | return (
17 |
18 | {this.props.fields.items.map((item, idx) => {
19 | switch (item.sys.contentType.sys.id) {
20 | case 'callToAction':
21 | return (
)
22 | case 'headline':
23 | return (
)
24 | case 'imageWithCaption':
25 | return (
)
26 | case 'paragraphWithHeadline':
27 | return (
)
28 | case 'textWithImage':
29 | return (
)
30 | // case 'imageCarousel':
31 | // return (
32 | // item.fields.images.map((image, idx) => {
33 | // return (
34 | //

35 | // )
36 | // })
37 | // )
38 | }
39 | })}
40 |
41 | )
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/components/set_of_two.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import CallToAction from './call_to_action';
4 | import Headline from './headline'
5 | import ImageCarousel from './image_carousel'
6 | import ImageWithCaption from './image_with_caption';
7 | import ParagraphWithHeadline from './paragraph_with_headline'
8 | import TextWithImage from './text_with_image'
9 |
10 | export default class SetOfTwo extends React.Component {
11 | constructor(props) {
12 | super(props);
13 | }
14 |
15 | render() {
16 | return (
17 |
18 | {this.props.fields.items.map((item, idx) => {
19 | switch (item.sys.contentType.sys.id) {
20 | case 'callToAction':
21 | return (
)
22 | case 'headline':
23 | return (
)
24 | case 'imageWithCaption':
25 | return (
)
26 | case 'paragraphWithHeadline':
27 | return (
)
28 | case 'textWithImage':
29 | return (
)
30 | // case 'imageCarousel':
31 | // return (
32 | // item.fields.images.map((image, idx) => {
33 | // return (
34 | //

35 | // )
36 | // })
37 | // )
38 | }
39 | })}
40 |
41 | )
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/components/text_with_image.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class TextWithImage extends React.Component {
4 | constructor(props) {
5 | super(props);
6 | }
7 |
8 | render() {
9 | //If the component comes through with an image resolved use that, otherwise use the test image
10 | //NOTE: For locales with no fallback or localized images added, this can happen currently
11 | const imageURL = (this.props.fields.image && this.props.fields.image.fields.file) ? this.props.fields.image.fields.file.url : this.props.defaultImageURL;
12 |
13 | return (
14 |
15 |
{this.props.fields.text}
16 |

19 |
20 | )
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
15 | @tailwind base;
16 | @tailwind components;
17 | @tailwind utilities;
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import "./index.css";
4 | import "./assets/css/main.css";
5 | // import "./assets/css/theme.css"
6 | import "./assets/css/font-awesome.min.css";
7 |
8 | import "react-app-polyfill/ie9";
9 | import "react-app-polyfill/ie11";
10 | import "react-app-polyfill/stable";
11 |
12 | import App from "./App";
13 | import reportWebVitals from "./reportWebVitals";
14 |
15 | import { Provider } from "react-redux";
16 |
17 | import store from "./reducers";
18 | import { PersistGate } from "redux-persist/integration/react";
19 | import { persistStore } from "redux-persist";
20 |
21 | let persistor = persistStore(store);
22 | ReactDOM.render(
23 |
24 |
25 |
26 |
27 |
28 |
29 | Contentful Learning Services
30 |
31 |
32 |
,
33 | document.getElementById("root")
34 | );
35 |
36 | // If you want to start measuring performance in your app, pass a function
37 | // to log results (for example: reportWebVitals(console.log))
38 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
39 | reportWebVitals();
40 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/reducers/assets_reducer.js:
--------------------------------------------------------------------------------
1 | import { FETCH_ASSET } from '../actions/index';
2 |
3 | export default function(state = [], action) {
4 | switch(action.type) {
5 | case FETCH_ASSET:
6 | return [ action.payload.data, ...state];
7 | default:
8 | return state;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from "redux";
2 | import { persistStore, persistReducer } from "redux-persist";
3 | import { configureStore } from "@reduxjs/toolkit";
4 | import storage from "redux-persist/lib/storage"; // defaults to localStorage for web
5 | import thunk from "redux-thunk";
6 | import { encryptTransform } from "redux-persist-transform-encrypt";
7 |
8 | import testSlice from "../commonSlices/testSlice";
9 |
10 |
11 |
12 |
13 |
14 | const reducers = combineReducers({
15 | test: testSlice,
16 | });
17 |
18 | // const encryptor = createEncryptor({
19 | // secretKey: "99xx45ghty",
20 | // });
21 | const persistConfig = {
22 | key: "root",
23 | storage,
24 | transforms: [
25 | encryptTransform({
26 | secretKey: "my-super-secret-key",
27 | onError: function (error) {
28 | // Handle the error.
29 | },
30 | }),
31 | ],
32 | };
33 | const persistedReducer = persistReducer(persistConfig, reducers);
34 | const store = configureStore({
35 | reducer: persistedReducer,
36 | devTools: process.env.NODE_ENV !== "production",
37 | middleware: [thunk],
38 | });
39 |
40 | export default store;
--------------------------------------------------------------------------------
/src/reducers/products_reducer.js:
--------------------------------------------------------------------------------
1 | import { FETCH_PRODUCTS, FETCH_PRODUCT } from '../actions/index';
2 |
3 | const INITIAL_STATE = { all: [], product: null };
4 |
5 | export default function(state = INITIAL_STATE, action) {
6 | switch(action.type) {
7 | case FETCH_PRODUCTS:
8 | return { ...state, all: action.payload.data.items };
9 | case FETCH_PRODUCT:
10 | return { ...state, product: action.payload.data };
11 | default:
12 | return state;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/src/routes.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Route, Switch } from "react-router-dom";
3 |
4 | import App from "./components/app";
5 | import ProductShow from "./components/product_show";
6 |
7 | export default function Routes() {
8 | return (
9 |
10 |
11 |
12 |
13 | );
14 | }
15 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | purge: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'],
3 | darkMode: false, // or 'media' or 'class'
4 | theme: {
5 | extend: {},
6 | },
7 | variants: {
8 | extend: {},
9 | },
10 | plugins: [],
11 | }
12 |
--------------------------------------------------------------------------------
/winning-demo-content-model-simple.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentful/The-Learning-Demo/5f990cfc6e8b9a1ec67d2777564174056dfb24e8/winning-demo-content-model-simple.png
--------------------------------------------------------------------------------
/winning-demo-content-model.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentful/The-Learning-Demo/5f990cfc6e8b9a1ec67d2777564174056dfb24e8/winning-demo-content-model.png
--------------------------------------------------------------------------------