diff --git a/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/.env.local.sample b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/.env.local.sample new file mode 100644 index 0000000000..04519cd4e1 --- /dev/null +++ b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/.env.local.sample @@ -0,0 +1,25 @@ + +#create environment file name as .env.local +#and place following configuration data. + +CONTENTSTACK_API_KEY=YOUR_API_KEY +CONTENTSTACK_DELIVERY_TOKEN=YOUR_DELIVERY_TOKEN +CONTENTSTACK_ENVIRONMENT=YOUR_PUBLISHING_ENVIRONMENT + +# For live preview +CONTENTSTACK_MANAGEMENT_TOKEN= +CONTENTSTACK_API_HOST=api.contentstack.io +CONTENTSTACK_APP_HOST=app.contentstack.com +CONTENTSTACK_LIVE_PREVIEW=true +CONTENTSTACK_LIVE_EDIT_TAGS=false + +#site-map +NEXT_PUBLIC_HOSTED_URL=http://localhost:3000 +# Requires host url for sitemap. Localhost:3000 is set as default value + +# For Live preview default value is to true to disable live preview set CONTENTSTACK_LIVE_PREVIEW=false +# For live edit tags default value is set to false to enable live edit tag set CONTENTSTACK_LIVE_EDIT_TAGS=true +# For NA region add CONTENTSTACK_APP_HOST=app.contentstack.com +# For EU region add CONTENTSTACK_APP_HOST=eu-app.contentstack.com + +# For setting custom host add CONTENTSTACK_API_HOST=for(NA: api.contentstack.io, EU: eu-api.contentstack.com) diff --git a/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/.eslintrc.json b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/.eslintrc.json new file mode 100644 index 0000000000..afdeb5bba3 --- /dev/null +++ b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/.eslintrc.json @@ -0,0 +1,26 @@ +{ + "env": { + "browser": true, + "es2021": true + }, + "extends": [ + "eslint:recommended", + "plugin:react/recommended", + "extends: next/core-web-vitals" + ], + "parserOptions": { + "ecmaFeatures": { + "jsx": true + }, + "ecmaVersion": 12, + "sourceType": "module" + }, + "plugins": [ + "react" + ], + "rules": { + "react/prop-types":0, + "no-undef":"off", + "no-unused-vars": "off" + } +} diff --git a/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/.github/workflows/codeql-analysis.yml b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000000..61cd42c405 --- /dev/null +++ b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/.github/workflows/codeql-analysis.yml @@ -0,0 +1,70 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: '*' + pull_request: + # The branches below must be a subset of the branches above + branches: '*' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'javascript' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # ℹī¸ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/.github/workflows/sast-scan.yml b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/.github/workflows/sast-scan.yml new file mode 100644 index 0000000000..21f14859d3 --- /dev/null +++ b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/.github/workflows/sast-scan.yml @@ -0,0 +1,14 @@ +name: SAST Scan +on: + push: + branches: + - '*' + pull_request: + types: [opened, synchronize, reopened] +jobs: + security: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Horusec Scan + run: docker run -v /var/run/docker.sock:/var/run/docker.sock -v $(pwd):/src horuszup/horusec-cli:latest horusec start -p /src -P $(pwd) diff --git a/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/.github/workflows/sca-monitor.yml b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/.github/workflows/sca-monitor.yml new file mode 100644 index 0000000000..294eab3e1d --- /dev/null +++ b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/.github/workflows/sca-monitor.yml @@ -0,0 +1,13 @@ +name: Source Composition Analysis Monitor +on: push +jobs: + security: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Run Snyk to check for vulnerabilities + uses: snyk/actions/node@master + env: + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + with: + command: monitor diff --git a/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/.github/workflows/sca-scan.yml b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/.github/workflows/sca-scan.yml new file mode 100644 index 0000000000..525e2860cc --- /dev/null +++ b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/.github/workflows/sca-scan.yml @@ -0,0 +1,16 @@ +name: Source Composition Analysis Scan +on: + push: + branches: + - master + pull_request: + types: [opened, synchronize, reopened] +jobs: + security: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Run Snyk to check for vulnerabilities + uses: snyk/actions/node@master + env: + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} diff --git a/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/.github/workflows/secrets-scan.yml b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/.github/workflows/secrets-scan.yml new file mode 100644 index 0000000000..71d6503763 --- /dev/null +++ b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/.github/workflows/secrets-scan.yml @@ -0,0 +1,14 @@ +name: Secrets Scan +on: + push: + branches: + - '*' + pull_request: + types: [opened, synchronize, reopened] +jobs: + security: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Gittyleaks + uses: gupy-io/gittyleaks-action@v0.1 diff --git a/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/.gitignore b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/.gitignore new file mode 100644 index 0000000000..737d872109 --- /dev/null +++ b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/.gitignore @@ -0,0 +1,35 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo diff --git a/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/CODEOWNERS b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/CODEOWNERS new file mode 100644 index 0000000000..ba5226b9a3 --- /dev/null +++ b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/CODEOWNERS @@ -0,0 +1,2 @@ + +* @contentstack/security-admin @contentstack/ecosystem-admin diff --git a/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/LICENSE b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/LICENSE new file mode 100644 index 0000000000..2bba21a5aa --- /dev/null +++ b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Contentstack + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/README.md b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/README.md new file mode 100644 index 0000000000..a380173a88 --- /dev/null +++ b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/README.md @@ -0,0 +1,32 @@ +[![Contentstack Logo](/public/contentstack-readme-logo.png)](https://www.contentstack.com/) + + +# Build a Starter Website with Next.js and Contentstack + +About Contentstack: Contentstack is a headless CMS with an API-first approach that puts content at the centre. It is designed to simplify the process of publication by separating code from content. + +About this project: Next.js is a minimalistic framework for server-rendered React applications.This guide will help you create a starter website built on top of Next.js with minimal steps. + + + +![Contentstack-Nextjs-starter-app](/public/starter-app.png) + +## Live Demo + +You can check the [live demo](https://contentstack-nextjs-starter-app.vercel.app) to get first-hand experience of the website. + + +## Tutorial + +We have created an in-depth tutorial on how you can create a Next.js starter website using Contentstack's Node.js SDK and its fetch content from Contentstack. + +[Build Website using Next.js and Contentstack](https://www.contentstack.com/docs/developers/sample-apps/build-a-starter-website-using-next-js-and-contentstack/) + + +**More Resources** + +Read Contentstack [docs](https://www.contentstack.com/docs/) + +Region support [docs](https://www.contentstack.com/docs/developers/selecting-region-in-contentstack-starter-apps) + +Learn about [Next.js](https://learnnextjs.com/) diff --git a/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/SECURITY.md b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/SECURITY.md new file mode 100644 index 0000000000..b5fe070ed3 --- /dev/null +++ b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/SECURITY.md @@ -0,0 +1,27 @@ +## Security + +Contentstack takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations. + +If you believe you have found a security vulnerability in any Contentstack-owned repository, please report it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Send email to [security@contentstack.com](mailto:security@contentstack.com). + +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + + * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) + * Full paths of source file(s) related to the manifestation of the issue + * The location of the affected source code (tag/branch/commit or direct URL) + * Any special configuration required to reproduce the issue + * Step-by-step instructions to reproduce the issue + * Proof-of-concept or exploit code (if possible) + * Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +[https://www.contentstack.com/trust/](https://www.contentstack.com/trust/) diff --git a/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/components/about-section-bucket.tsx b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/components/about-section-bucket.tsx new file mode 100644 index 0000000000..2ef308c75c --- /dev/null +++ b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/components/about-section-bucket.tsx @@ -0,0 +1,82 @@ +import React from 'react'; +import parse from 'html-react-parser'; +import { Action,Image } from '../typescript/action'; + +type AdditionalParam = { + title_h2?: string; + title_h3?: string; + description?: string; +} + +type Bucket = { + title_h3: string; + description: string; + icon: Image; + $: AdditionalParam; + url: string; +} + +type BucketsList = { + title_h3: string; + description: string; + url: string; + call_to_action: Action; + icon: Image; + $: AdditionalParam; +} + +type BucketProps = { + title_h2: string; + buckets:[BucketsList]; + $: AdditionalParam; +} + +export default function AboutSectionBucket({ sectionWithBuckets }: {sectionWithBuckets:BucketProps}) { + function bucketContent(bucket: Bucket, index: number) { + return ( +
+ {bucket.icon && ( + art work + )} + +
+ {bucket.title_h3 && ( +

{bucket.title_h3}

+ )} + {typeof bucket.description === 'string' && ( +
{parse(bucket.description)}
+ )} +
+
+ ); + } + + return ( +
+
+ {sectionWithBuckets.title_h2 && ( +

+ {sectionWithBuckets.title_h2} +

+ )} +
+
+
+ {sectionWithBuckets?.buckets.map( + (bucket, index) => index < 2 && bucketContent(bucket, index) + )} +
+
+ {sectionWithBuckets.buckets.map( + (bucket, index) => index >= 2 && bucketContent(bucket, index) + )} +
+
+
+ ); +} diff --git a/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/components/archive-relative.tsx b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/components/archive-relative.tsx new file mode 100644 index 0000000000..682ec5b761 --- /dev/null +++ b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/components/archive-relative.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import Link from 'next/link'; +import parse from 'html-react-parser'; + +type AdditionalParam = { + title: string; + body: string; +} + +type Blog = { + url: string; + body: string; + title: string; + $: AdditionalParam; +} + +type BlogListProps = { + blogs: [Blog]; +} + +export default function ArchiveRelative({ blogs }: BlogListProps) { + return ( + <> + {blogs?.map((blog, idx) => ( + + +
+

{blog.title}

+ {typeof blog.body === 'string' && ( +
{parse(blog.body.slice(0, 80))}
+ )} +
+
+ + ))} + + ); +} diff --git a/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/components/blog-banner.tsx b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/components/blog-banner.tsx new file mode 100644 index 0000000000..4cd98313ec --- /dev/null +++ b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/components/blog-banner.tsx @@ -0,0 +1,42 @@ +import React from 'react'; + +type AdditionalParam = { + banner_title:string; + banner_description: string; + title: {}; + title_h2: string; + body: string; + date: string; +} + +type BannerProps = { + banner_title:string; + banner_description: string; + bg_color: string; + $: AdditionalParam; +} + +export default function BlogBanner({ blogBanner }: {blogBanner : BannerProps}) { + return ( +
+
+ {blogBanner.banner_title && ( +

+ {blogBanner.banner_title} +

+ )} + + {blogBanner.banner_description && ( +

+ {blogBanner.banner_description} +

+ )} +
+
+ ); +} diff --git a/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/components/blog-list.tsx b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/components/blog-list.tsx new file mode 100644 index 0000000000..cec2042e5f --- /dev/null +++ b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/components/blog-list.tsx @@ -0,0 +1,82 @@ +import React from 'react'; +import moment from 'moment'; +import parse from 'html-react-parser'; +import Link from 'next/link'; +import { Image } from "../typescript/action"; + +type AdditionalParam = { + banner_title:string; + banner_description: string; + title: {}; + title_h2: string; + body: string; + date: string; +} + +type Author = { + title: string; + $: AdditionalParam; +} + + +type BloglistProps = { + body: string; + url: string; + featured_image: Image; + title: string; + date: string; + author: [Author]; + $: AdditionalParam; +} + +function BlogList({ bloglist }: { bloglist: BloglistProps }) { + let body: string = bloglist.body && bloglist.body.substr(0, 300); + const stringLength = body.lastIndexOf(' '); + body = `${body.substr(0, Math.min(body.length, stringLength))}...`; + return ( +
+ {bloglist.featured_image && ( + + + blog img + + + )} +
+ {bloglist.title && ( + + +

{bloglist.title}

+
+ + )} +

+ + {moment(bloglist.date).format('ddd, MMM D YYYY')} + + ,{" "} + + {bloglist.author[0].title} + +

+
{parse(body)}
+ {bloglist.url ? ( + + + {'Read more -->'} + + + ) : ( + '' + )} +
+
+ ); +} + +export default BlogList; \ No newline at end of file diff --git a/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/components/blog-section.tsx b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/components/blog-section.tsx new file mode 100644 index 0000000000..4938e11e17 --- /dev/null +++ b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/components/blog-section.tsx @@ -0,0 +1,88 @@ +import React from 'react'; +import Link from 'next/link'; +import parse from 'html-react-parser'; +import { Image } from "../typescript/action"; + +type AdditionalParam = { + banner_title:string; + banner_description: string; + title: {}; + title_h2: string; + body: string; + date: string; +} + +type Article = { + href: string; + title: string; + $: AdditionalParam; +} + +type FeaturedBlog = { + title: string; + featured_image: Image; + body: string; + url: string; + $: AdditionalParam; +} + +type FeaturedBlogData = { + title_h2: string; + view_articles: Article; + featured_blogs: [FeaturedBlog] + $: AdditionalParam; +} + +type FeaturedBlogProps = { + fromBlog: FeaturedBlogData; + } + +export default function BlogSection(props: FeaturedBlogProps) { + + const fromBlog = props.fromBlog; + + return ( +
+
+ {fromBlog.title_h2 && ( +

{fromBlog.title_h2}

+ )} + {fromBlog.view_articles && ( + + + {fromBlog.view_articles.title} + + + )} +
+
+ {fromBlog.featured_blogs.map((blog, index) => ( +
+ {blog.featured_image && ( + {blog.featured_image.filename} + )} +
+ {blog.title &&

{blog.title}

} + {typeof blog.body === 'string' && ( +
{parse(blog.body.slice(0, 300))}
+ )} + {blog.url && ( + + {'Read More -->'} + + )} +
+
+ ))} +
+
+ ); +} diff --git a/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/components/card-section.tsx b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/components/card-section.tsx new file mode 100644 index 0000000000..08fe35b9e5 --- /dev/null +++ b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/components/card-section.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import Link from 'next/link'; +import { Action } from "../typescript/action"; + +type AdditionalParam = { + title_h3: string; + description: string; + } + +type Card = { + title_h3: string; + description: string; + call_to_action: Action; + $: AdditionalParam; + } + +type CardProps = { + cards: [Card] + } + +export default function CardSection({ cards }: CardProps) { + return ( +
+ {cards?.map((card, index) => ( +
+ {card.title_h3 &&

{card.title_h3}

} + {card.description &&

{card.description}

} +
+ {card.call_to_action.title && card.call_to_action.href && ( + + {card.call_to_action.title} + + )} +
+
+ ))} +
+ ); +} diff --git a/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/components/devtools.tsx b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/components/devtools.tsx new file mode 100644 index 0000000000..9c2a07aef1 --- /dev/null +++ b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/components/devtools.tsx @@ -0,0 +1,118 @@ +import React, { useState, useEffect } from 'react'; +import dynamic from 'next/dynamic'; +import Tooltip from './tool-tip'; + +const DynamicReactJson = dynamic(import('react-json-view'), { ssr: false }); + +function filterObject(inputObject: any) { + const unWantedProps = [ + '_version', + 'ACL', + '_owner', + '_in_progress', + 'created_at', + 'created_by', + 'updated_at', + 'updated_by', + 'publish_details', + ]; + for (const key in inputObject) { + unWantedProps.includes(key) && delete inputObject[key]; + if (typeof inputObject[key] !== 'object') { + continue; + } + inputObject[key] = filterObject(inputObject[key]); + } + return inputObject; +} + +const DevTools = ({ response }: any) => { + const filteredJson = filterObject(response); + const [forceUpdate, setForceUpdate] = useState(0); + + function copyObject(object: any) { + navigator.clipboard.writeText(object); + setForceUpdate(1); + } + + useEffect(() => { + if (forceUpdate !== 0) { + setTimeout(() => setForceUpdate(0), 300); + } + }, [forceUpdate]); + + return ( + + ); +}; +export default DevTools; diff --git a/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/components/footer.tsx b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/components/footer.tsx new file mode 100644 index 0000000000..c72d5e66ce --- /dev/null +++ b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/components/footer.tsx @@ -0,0 +1,124 @@ +import React, { useState, useEffect } from 'react'; +import Link from 'next/link'; +import parse from 'html-react-parser'; +import { onEntryChange } from '../contentstack-sdk'; +import { getFooterRes } from '../helper'; +import Skeleton from 'react-loading-skeleton'; +import { FooterProps, Entry, Links } from "../typescript/layout"; + +export default function Footer({ footer, entries }: {footer: FooterProps, entries: Entry}) { + + const [getFooter, setFooter] = useState(footer); + + function buildNavigation(ent: Entry, ft: FooterProps) { + let newFooter = { ...ft }; + if (ent.length !== newFooter.navigation.link.length) { + ent.forEach((entry) => { + const fFound = newFooter?.navigation.link.find( + (nlink: Links) => nlink.title === entry.title + ); + if (!fFound) { + newFooter.navigation.link?.push({ + title: entry.title, + href: entry.url, + $: entry.$, + }); + } + }); + } + return newFooter; + } + + async function fetchData() { + try { + if (footer && entries) { + const footerRes = await getFooterRes(); + const newfooter = buildNavigation(entries, footerRes); + setFooter(newfooter); + } + } catch (error) { + console.error(error); + } + } + + useEffect(() => { + onEntryChange(() => fetchData()); + }, [footer]); + + const footerData = getFooter ? getFooter : undefined; + + return ( + + ); +} \ No newline at end of file diff --git a/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/components/header.tsx b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/components/header.tsx new file mode 100644 index 0000000000..5f07e458e9 --- /dev/null +++ b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/components/header.tsx @@ -0,0 +1,125 @@ +import React, { useState, useEffect } from 'react'; +import Link from 'next/link'; +import { useRouter } from 'next/router'; +import parse from 'html-react-parser'; +import Tooltip from './tool-tip'; +import { onEntryChange } from '../contentstack-sdk'; +import { getHeaderRes } from '../helper'; +import Skeleton from 'react-loading-skeleton'; +import { HeaderProps, Entry, NavLinks } from "../typescript/layout"; + +export default function Header({ header, entries }: {header: HeaderProps, entries: Entry}) { + + const router = useRouter(); + const [getHeader, setHeader] = useState(header); + + function buildNavigation(ent: Entry, hd: HeaderProps) { + let newHeader={...hd}; + if (ent.length!== newHeader.navigation_menu.length) { + ent.forEach((entry) => { + const hFound = newHeader?.navigation_menu.find( + (navLink: NavLinks) => navLink.label === entry.title + ); + if (!hFound) { + newHeader.navigation_menu?.push({ + label: entry.title, + page_reference: [ + { title: entry.title, url: entry.url, $: entry.$ }, + ], + $:{} + }); + } + }); + } + return newHeader + } + + async function fetchData() { + try { + if (header && entries) { + const headerRes = await getHeaderRes(); + const newHeader = buildNavigation(entries,headerRes) + setHeader(newHeader); + } + } catch (error) { + console.error(error); + } + } + + useEffect(() => { + if (header && entries) { + onEntryChange(() => fetchData()); + } + }, [header]); + const headerData = getHeader ? getHeader : undefined; + + return ( +
+
+ {headerData?.notification_bar.show_announcement ? ( + typeof headerData.notification_bar.announcement_text === 'string' && ( +
+ {parse(headerData.notification_bar.announcement_text)} +
+ ) + ) : ( + + )} +
+
+
+ {headerData ? ( + + + {headerData.title} + + + ) : ( + + )} +
+ + + + +
+ + + JSON Preview icon + + +
+
+
+ ); +} \ No newline at end of file diff --git a/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/components/hero-banner.tsx b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/components/hero-banner.tsx new file mode 100644 index 0000000000..4ac9f2ab0a --- /dev/null +++ b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/components/hero-banner.tsx @@ -0,0 +1,80 @@ +import React from 'react'; +import Link from 'next/link'; +import { Image, Action } from "../typescript/action"; + +type AdditionalParam = { + banner_title: string; + banner_description: string; +} + +type Banner = { + bg_color: string; + text_color: string; + banner_title: string; + banner_description: string; + call_to_action: Action; + banner_image: Image; + $: AdditionalParam; +} + +type BannerProps = { + banner: Banner; +} + +export default function HeroBanner(props: BannerProps) { + + const banner = props.banner; + + return ( +
+
+ {banner.banner_title && ( +

+ {banner.banner_title} +

+ )} + {banner.banner_description ? ( +

+ {banner?.banner_description} +

+ ) : ( + '' + )} + {banner.call_to_action.title && banner.call_to_action.href ? ( + + + {banner?.call_to_action.title} + + + ) : ( + '' + )} +
+ {banner.banner_image ? ( + {banner.banner_image.filename} + ) : ( + '' + )} +
+ ); +} diff --git a/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/components/layout.tsx b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/components/layout.tsx new file mode 100644 index 0000000000..86931fbcd4 --- /dev/null +++ b/packages/contentstack-bootstrap/contentstack-nextjs-starter-app/components/layout.tsx @@ -0,0 +1,74 @@ +import React, { useState, useEffect } from 'react'; +import Header from './header'; +import Footer from './footer'; +import DevTools from './devtools'; +import { HeaderProps, FooterProps, PageProps, Posts, ChilderenProps, Entry, NavLinks, Links } from "../typescript/layout"; + +export default function Layout({ + header, + footer, + page, + blogPost, + blogList, + entries, + children, +}: { header: HeaderProps, footer: FooterProps, page: PageProps, blogPost: Posts, blogList: Posts, entries: Entry, children: ChilderenProps }) { + + const [getLayout, setLayout] = useState({ header, footer }); + const jsonObj: any = { header, footer }; + page && (jsonObj.page = page); + blogPost && (jsonObj.blog_post = blogPost); + blogList && (jsonObj.blog_post = blogList); + + function buildNavigation(ent: Entry, hd: HeaderProps, ft: FooterProps) { + let newHeader = { ...hd }; + let newFooter = { ...ft }; + if (ent.length !== newHeader.navigation_menu.length) { + ent.forEach((entry) => { + const hFound = newHeader?.navigation_menu.find( + (navLink: NavLinks) => navLink.label === entry.title + ); + if (!hFound) { + newHeader.navigation_menu?.push({ + label: entry.title, + page_reference: [ + { title: entry.title, url: entry.url, $: entry.$ }, + ], + $: {}, + }); + } + const fFound = newFooter?.navigation.link.find( + (nlink: Links) => nlink.title === entry.title + ); + if (!fFound) { + newFooter.navigation.link?.push({ + title: entry.title, + href: entry.url, + $: entry.$, + }); + } + }); + } + return [newHeader, newFooter]; + } + + useEffect(() => { + if (footer && header && entries) { + const [newHeader, newFooter] = buildNavigation(entries, header, footer); + setLayout({ header: newHeader, footer: newFooter }); + } + }, [header, footer]); + + return ( + <> + {header ?
: ''} +
+ <> + {children} + {Object.keys(jsonObj).length && } + +
+ {footer ?