Showing preview only (271K chars total). Download the full file or copy to clipboard to get everything.
Repository: strapi/starters-and-templates
Branch: main
Commit: 7ab5f0b7e292
Files: 204
Total size: 214.7 KB
Directory structure:
gitextract_ipi637vq/
├── .gitignore
├── LICENSE.txt
├── README.md
├── lerna.json
├── package.json
└── packages/
├── starters/
│ ├── .gitkeep
│ ├── gatsby-blog/
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── starter/
│ │ │ ├── .eslintrc.js
│ │ │ ├── .gitignore
│ │ │ ├── README.md
│ │ │ ├── gatsby-browser.js
│ │ │ ├── gatsby-config.js
│ │ │ ├── gatsby-node.js
│ │ │ ├── package.json
│ │ │ ├── postcss.config.js
│ │ │ ├── src/
│ │ │ │ ├── components/
│ │ │ │ │ ├── article-card.js
│ │ │ │ │ ├── articles-grid.js
│ │ │ │ │ ├── block-media.js
│ │ │ │ │ ├── block-quote.js
│ │ │ │ │ ├── block-rich-text.js
│ │ │ │ │ ├── block-slider.js
│ │ │ │ │ ├── blocks-renderer.js
│ │ │ │ │ ├── footer.js
│ │ │ │ │ ├── headings.js
│ │ │ │ │ ├── layout.js
│ │ │ │ │ ├── navbar.js
│ │ │ │ │ └── seo.js
│ │ │ │ ├── pages/
│ │ │ │ │ ├── about.js
│ │ │ │ │ └── index.js
│ │ │ │ ├── styles/
│ │ │ │ │ └── global.css
│ │ │ │ └── templates/
│ │ │ │ └── article-post.js
│ │ │ └── tailwind.config.js
│ │ └── starter.json
│ ├── gatsby-corporate/
│ │ └── README.md
│ ├── next-blog/
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── starter/
│ │ │ ├── .eslintrc
│ │ │ ├── .prettierrc
│ │ │ ├── assets/
│ │ │ │ └── css/
│ │ │ │ └── style.css
│ │ │ ├── components/
│ │ │ │ ├── articles.js
│ │ │ │ ├── card.js
│ │ │ │ ├── image.js
│ │ │ │ ├── layout.js
│ │ │ │ ├── nav.js
│ │ │ │ └── seo.js
│ │ │ ├── lib/
│ │ │ │ ├── api.js
│ │ │ │ └── media.js
│ │ │ ├── next.config.js
│ │ │ ├── package.json
│ │ │ └── pages/
│ │ │ ├── _app.js
│ │ │ ├── _document.js
│ │ │ ├── article/
│ │ │ │ └── [slug].js
│ │ │ ├── category/
│ │ │ │ └── [slug].js
│ │ │ └── index.js
│ │ └── starter.json
│ └── next-corporate/
│ ├── README.md
│ ├── package.json
│ ├── starter/
│ │ ├── .eslintrc
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── components/
│ │ │ ├── elements/
│ │ │ │ ├── button-link.js
│ │ │ │ ├── button.js
│ │ │ │ ├── custom-link.js
│ │ │ │ ├── footer.js
│ │ │ │ ├── image.js
│ │ │ │ ├── loader.js
│ │ │ │ ├── mobile-nav-menu.js
│ │ │ │ ├── navbar.js
│ │ │ │ ├── notification-banner.js
│ │ │ │ ├── seo.js
│ │ │ │ └── video.js
│ │ │ ├── icons/
│ │ │ │ └── world.js
│ │ │ ├── layout.js
│ │ │ ├── locale-switch.js
│ │ │ ├── sections/
│ │ │ │ ├── bottom-actions.js
│ │ │ │ ├── feature-columns-group.js
│ │ │ │ ├── feature-rows-group.js
│ │ │ │ ├── hero.js
│ │ │ │ ├── large-video.js
│ │ │ │ ├── lead-form.js
│ │ │ │ ├── pricing.js
│ │ │ │ ├── rich-text.js
│ │ │ │ └── testimonials-group.js
│ │ │ └── sections.js
│ │ ├── jsconfig.json
│ │ ├── next.config.js
│ │ ├── package.json
│ │ ├── pages/
│ │ │ ├── [[...slug]].js
│ │ │ ├── _app.js
│ │ │ ├── _document.js
│ │ │ └── api/
│ │ │ ├── exit-preview.js
│ │ │ └── preview.js
│ │ ├── postcss.config.js
│ │ ├── public/
│ │ │ └── .gitkeep
│ │ ├── styles/
│ │ │ └── index.css
│ │ ├── tailwind.config.js
│ │ └── utils/
│ │ ├── api.js
│ │ ├── button.js
│ │ ├── hooks.js
│ │ ├── localize.js
│ │ ├── media.js
│ │ ├── parse-cookies.js
│ │ └── types.js
│ └── starter.json
└── templates/
├── .gitkeep
├── blog/
│ ├── README.md
│ ├── package.json
│ ├── template/
│ │ ├── data/
│ │ │ └── data.json
│ │ └── src/
│ │ ├── api/
│ │ │ ├── .gitkeep
│ │ │ ├── about/
│ │ │ │ ├── content-types/
│ │ │ │ │ └── about/
│ │ │ │ │ └── schema.json
│ │ │ │ ├── controllers/
│ │ │ │ │ └── about.js
│ │ │ │ ├── routes/
│ │ │ │ │ └── about.js
│ │ │ │ └── services/
│ │ │ │ └── about.js
│ │ │ ├── article/
│ │ │ │ ├── content-types/
│ │ │ │ │ └── article/
│ │ │ │ │ └── schema.json
│ │ │ │ ├── controllers/
│ │ │ │ │ └── article.js
│ │ │ │ ├── routes/
│ │ │ │ │ └── article.js
│ │ │ │ └── services/
│ │ │ │ └── article.js
│ │ │ ├── author/
│ │ │ │ ├── content-types/
│ │ │ │ │ └── author/
│ │ │ │ │ └── schema.json
│ │ │ │ ├── controllers/
│ │ │ │ │ └── author.js
│ │ │ │ ├── routes/
│ │ │ │ │ └── author.js
│ │ │ │ └── services/
│ │ │ │ └── author.js
│ │ │ ├── category/
│ │ │ │ ├── content-types/
│ │ │ │ │ └── category/
│ │ │ │ │ └── schema.json
│ │ │ │ ├── controllers/
│ │ │ │ │ └── category.js
│ │ │ │ ├── routes/
│ │ │ │ │ └── category.js
│ │ │ │ └── services/
│ │ │ │ └── category.js
│ │ │ └── global/
│ │ │ ├── content-types/
│ │ │ │ └── global/
│ │ │ │ └── schema.json
│ │ │ ├── controllers/
│ │ │ │ └── global.js
│ │ │ ├── routes/
│ │ │ │ └── global.js
│ │ │ └── services/
│ │ │ └── global.js
│ │ ├── bootstrap.js
│ │ ├── components/
│ │ │ └── shared/
│ │ │ ├── media.json
│ │ │ ├── quote.json
│ │ │ ├── rich-text.json
│ │ │ ├── seo.json
│ │ │ └── slider.json
│ │ ├── extensions/
│ │ │ └── .gitkeep
│ │ └── index.js
│ └── template.json
├── corporate/
│ ├── README.md
│ ├── package.json
│ ├── template/
│ │ ├── data/
│ │ │ ├── data.js
│ │ │ ├── en/
│ │ │ │ ├── global.json
│ │ │ │ ├── index.js
│ │ │ │ └── pages.json
│ │ │ ├── fr/
│ │ │ │ ├── global.json
│ │ │ │ ├── index.js
│ │ │ │ └── pages.json
│ │ │ └── lead-form-submissions.json
│ │ └── src/
│ │ ├── api/
│ │ │ ├── .gitkeep
│ │ │ ├── global/
│ │ │ │ ├── content-types/
│ │ │ │ │ └── global/
│ │ │ │ │ └── schema.json
│ │ │ │ ├── controllers/
│ │ │ │ │ └── global.js
│ │ │ │ ├── routes/
│ │ │ │ │ └── global.js
│ │ │ │ └── services/
│ │ │ │ └── global.js
│ │ │ ├── lead-form-submission/
│ │ │ │ ├── content-types/
│ │ │ │ │ └── lead-form-submission/
│ │ │ │ │ └── schema.json
│ │ │ │ ├── controllers/
│ │ │ │ │ └── lead-form-submission.js
│ │ │ │ ├── routes/
│ │ │ │ │ └── lead-form-submission.js
│ │ │ │ └── services/
│ │ │ │ └── lead-form-submission.js
│ │ │ └── page/
│ │ │ ├── content-types/
│ │ │ │ └── page/
│ │ │ │ └── schema.json
│ │ │ ├── controllers/
│ │ │ │ └── page.js
│ │ │ ├── routes/
│ │ │ │ └── page.js
│ │ │ └── services/
│ │ │ └── page.js
│ │ ├── bootstrap.js
│ │ ├── components/
│ │ │ ├── elements/
│ │ │ │ ├── feature-column.json
│ │ │ │ ├── feature-row.json
│ │ │ │ ├── feature.json
│ │ │ │ ├── footer-section.json
│ │ │ │ ├── logos.json
│ │ │ │ ├── notification-banner.json
│ │ │ │ ├── plan.json
│ │ │ │ └── testimonial.json
│ │ │ ├── layout/
│ │ │ │ ├── footer.json
│ │ │ │ └── navbar.json
│ │ │ ├── links/
│ │ │ │ ├── button-link.json
│ │ │ │ ├── button.json
│ │ │ │ └── link.json
│ │ │ ├── meta/
│ │ │ │ └── metadata.json
│ │ │ └── sections/
│ │ │ ├── bottom-actions.json
│ │ │ ├── feature-columns-group.json
│ │ │ ├── feature-rows-group.json
│ │ │ ├── hero.json
│ │ │ ├── large-video.json
│ │ │ ├── lead-form.json
│ │ │ ├── pricing.json
│ │ │ ├── rich-text.json
│ │ │ └── testimonials-group.json
│ │ └── index.js
│ └── template.json
└── ecommerce/
├── README.md
├── package.json
├── template/
│ ├── data/
│ │ └── data.js
│ └── src/
│ ├── api/
│ │ ├── .gitkeep
│ │ ├── category/
│ │ │ ├── content-types/
│ │ │ │ └── category/
│ │ │ │ └── schema.json
│ │ │ ├── controllers/
│ │ │ │ └── category.js
│ │ │ ├── routes/
│ │ │ │ └── category.js
│ │ │ └── services/
│ │ │ └── category.js
│ │ └── product/
│ │ ├── content-types/
│ │ │ └── product/
│ │ │ └── schema.json
│ │ ├── controllers/
│ │ │ └── product.js
│ │ ├── routes/
│ │ │ └── product.js
│ │ └── services/
│ │ └── product.js
│ ├── bootstrap.js
│ ├── components/
│ │ └── custom/
│ │ └── custom-field.json
│ └── index.js
└── template.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# Created by https://www.toptal.com/developers/gitignore/api/node,macos,windows,visualstudiocode
# Edit at https://www.toptal.com/developers/gitignore?templates=node,macos,windows,visualstudiocode
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
.env.production
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
### VisualStudioCode ###
.vscode/*
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
### Windows ###
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# End of https://www.toptal.com/developers/gitignore/api/node,macos,windows,visualstudiocode
# Template development directory
template-dev/
================================================
FILE: LICENSE.txt
================================================
MIT License
Copyright (c) 2021 Strapi
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.
================================================
FILE: README.md
================================================
> [!WARNING]
> This repository is only compatible with Strapi v4 version<br/>
> The most recent information can be found at https://docs.strapi.io/
# Strapi starters & templates
Monorepo for all official Strapi v4 starters and templates.
### Starters
> As you may have [noticed](https://github.com/strapi/starters-and-templates/issues?q=is%3Aissue+is%3Aopen+install), a lot of the Starters are out of date and given constraints in bandwidth and other priorities, we've decided to sunset all Starters and only actively maintain a single Next.js Starter.
- [Gatsby Blog](./packages/starters/gatsby-blog)
- [Next Blog](./packages/starters/next-blog)
- [Next Corporate Site](./packages/starters/next-corporate)
### Templates
- [Blog](./packages/templates/blog)
- [Corporate](./packages/templates/corporate)
- [Ecommerce](./packages/templates/ecommerce)
================================================
FILE: lerna.json
================================================
{
"packages": [
"packages/templates/*",
"packages/starters/*"
],
"npmClient": "yarn",
"useWorkspaces": true,
"version": "independent"
}
================================================
FILE: package.json
================================================
{
"name": "@strapi/starters-and-templates",
"version": "1.0.0",
"private": true,
"description": "All Strapi templates",
"repository": "https://github.com/strapi/starters-and-templates.git",
"license": "MIT",
"author": {
"name": "Strapi team",
"email": "hi@strapi.io",
"url": "https://strapi.io"
},
"maintainers": [
{
"name": "Strapi team",
"email": "hi@strapi.io",
"url": "https://strapi.io"
}
],
"workspaces": [
"packages/**/*"
],
"devDependencies": {
"lerna": "^4.0.0"
}
}
================================================
FILE: packages/starters/.gitkeep
================================================
================================================
FILE: packages/starters/gatsby-blog/README.md
================================================
# Strapi Starter Gatsby Blog
Gatsby starter for creating a blog with Strapi.
This starter allows you to try Strapi with Gatsby with the example of a simple blog. It is fully customizable and due to the fact that it is open source, fully open to contributions. So do not hesitate to add new features and report bugs!
This starter uses the [Strapi blog template](https://github.com/strapi/starters-and-templates/tree/main/packages/templates/blog)
## Getting started
Use our `create-strapi-starter` CLI to create your project.
```sh
# Using Yarn
yarn create strapi-starter my-project gatsby-blog
# Or using NPM
npx create-strapi-starter my-project gatsby-blog
```
The CLI will create a monorepo, install dependencies, and run your project automatically.
The Gatsby frontend server will run here => [http://localhost:3000](http://localhost:3000)
The Strapi backend server will run here => [http://localhost:1337](http://localhost:1337)
You will however need to manually create a full access [API token](https://docs.strapi.io/developer-docs/latest/setup-deployment-guides/configurations/optional/api-tokens.html) in Strapi. Once it's created, save it as `STRAPI_TOKEN` in your environment variables.
## Deploying to production
You will need to deploy the `frontend` and `backend` projects separately. Here are the docs to deploy each one:
- [Deploy Strapi](https://strapi.io/documentation/developer-docs/latest/setup-deployment-guides/deployment.html#hosting-provider-guides)
- [Deploy Gatsby](https://www.gatsbyjs.com/docs/deploying-and-hosting/)
Enjoy this starter!
================================================
FILE: packages/starters/gatsby-blog/package.json
================================================
{
"name": "@strapi/starter-gatsby-blog",
"version": "1.0.7",
"description": "Strapi blog starter with Gatsby",
"keywords": [
"strapi",
"starter",
"gatsby",
"blog"
],
"homepage": "https://github.com/strapi/starters-and-templates/tree/main/packages/starters/gatsby-blog#readme",
"bugs": {
"url": "https://github.com/strapi/starters-and-templates/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/strapi/starters-and-templates.git"
},
"license": "MIT",
"author": {
"name": "Strapi team",
"email": "hi@strapi.io",
"url": "https://strapi.io"
},
"maintainers": [
{
"name": "Strapi team",
"email": "hi@strapi.io",
"url": "https://strapi.io"
}
]
}
================================================
FILE: packages/starters/gatsby-blog/starter/.eslintrc.js
================================================
module.exports = {
globals: {
__PATH_PREFIX__: true,
},
extends: ["react-app", "prettier"],
plugins: ["prettier"],
rules: {
"prettier/prettier": [
"error",
{
printWidth: 80,
singleQuote: false,
trailingComma: "es5",
semi: false,
tabWidth: 2,
},
],
},
}
================================================
FILE: packages/starters/gatsby-blog/starter/.gitignore
================================================
node_modules/
.cache/
public
.env
.env.production
.env.local
.env.development
================================================
FILE: packages/starters/gatsby-blog/starter/README.md
================================================
<p align="center">
<a href="https://www.gatsbyjs.com/?utm_source=starter&utm_medium=readme&utm_campaign=minimal-starter">
<img alt="Gatsby" src="https://www.gatsbyjs.com/Gatsby-Monogram.svg" width="60" />
</a>
</p>
<h1 align="center">
Gatsby minimal starter
</h1>
## 🚀 Quick start
1. **Create a Gatsby site.**
Use the Gatsby CLI to create a new site, specifying the minimal starter.
```shell
# create a new Gatsby site using the minimal starter
npm init gatsby
```
2. **Start developing.**
Navigate into your new site’s directory and start it up.
```shell
cd my-gatsby-site/
npm run develop
```
3. **Open the code and start customizing!**
Your site is now running at http://localhost:8000!
Edit `src/pages/index.js` to see your site update in real-time!
4. **Learn more**
- [Documentation](https://www.gatsbyjs.com/docs/?utm_source=starter&utm_medium=readme&utm_campaign=minimal-starter)
- [Tutorials](https://www.gatsbyjs.com/tutorial/?utm_source=starter&utm_medium=readme&utm_campaign=minimal-starter)
- [Guides](https://www.gatsbyjs.com/tutorial/?utm_source=starter&utm_medium=readme&utm_campaign=minimal-starter)
- [API Reference](https://www.gatsbyjs.com/docs/api-reference/?utm_source=starter&utm_medium=readme&utm_campaign=minimal-starter)
- [Plugin Library](https://www.gatsbyjs.com/plugins?utm_source=starter&utm_medium=readme&utm_campaign=minimal-starter)
- [Cheat Sheet](https://www.gatsbyjs.com/docs/cheat-sheet/?utm_source=starter&utm_medium=readme&utm_campaign=minimal-starter)
## 🚀 Quick start (Gatsby Cloud)
Deploy this starter with one click on [Gatsby Cloud](https://www.gatsbyjs.com/cloud/):
[<img src="https://www.gatsbyjs.com/deploynow.svg" alt="Deploy to Gatsby Cloud">](https://www.gatsbyjs.com/dashboard/deploynow?url=https://github.com/gatsbyjs/gatsby-starter-minimal)
================================================
FILE: packages/starters/gatsby-blog/starter/gatsby-browser.js
================================================
import "./src/styles/global.css"
================================================
FILE: packages/starters/gatsby-blog/starter/gatsby-config.js
================================================
require("dotenv").config({
path: `.env.${process.env.NODE_ENV}`,
})
module.exports = {
plugins: [
"gatsby-plugin-gatsby-cloud",
"gatsby-plugin-postcss",
{
resolve: "gatsby-source-strapi",
options: {
apiURL: process.env.STRAPI_API_URL || "http://localhost:1337",
accessToken: process.env.STRAPI_TOKEN,
collectionTypes: [
{
singularName: "article",
queryParams: {
publicationState:
process.env.GATSBY_IS_PREVIEW === "true" ? "preview" : "live",
populate: {
cover: "*",
blocks: {
populate: "*",
},
},
},
},
{
singularName: "author",
},
{
singularName: "category",
},
],
singleTypes: [
{
singularName: "about",
queryParams: {
populate: {
blocks: {
populate: "*",
},
},
},
},
{
singularName: "global",
queryParams: {
populate: {
favicon: "*",
defaultSeo: {
populate: "*",
},
},
},
},
],
},
},
"gatsby-plugin-image",
"gatsby-plugin-sharp",
"gatsby-transformer-sharp",
"gatsby-transformer-remark",
],
}
================================================
FILE: packages/starters/gatsby-blog/starter/gatsby-node.js
================================================
const path = require("path")
exports.createPages = async ({ graphql, actions, reporter }) => {
const { createPage } = actions
// Define a template for blog post
const articlePost = path.resolve("./src/templates/article-post.js")
const result = await graphql(
`
{
allStrapiArticle {
nodes {
title
slug
}
}
}
`
)
if (result.errors) {
reporter.panicOnBuild(
`There was an error loading your Strapi articles`,
result.errors
)
return
}
const articles = result.data.allStrapiArticle.nodes
if (articles.length > 0) {
articles.forEach((article) => {
createPage({
path: `/article/${article.slug}`,
component: articlePost,
context: {
slug: article.slug,
},
})
})
}
}
================================================
FILE: packages/starters/gatsby-blog/starter/package.json
================================================
{
"name": "my-gatsby-blog",
"version": "1.0.7",
"private": true,
"description": "Strapi Gatsby Blog",
"author": "Strapi team",
"keywords": [
"gatsby"
],
"scripts": {
"develop": "gatsby develop",
"start": "gatsby develop",
"build": "gatsby build",
"serve": "gatsby serve",
"clean": "gatsby clean",
"lint": "eslint .",
"lint:fix": "eslint . --fix"
},
"dependencies": {
"gatsby": "^4.6.1",
"gatsby-plugin-gatsby-cloud": "^4.7.0",
"gatsby-plugin-image": "^2.6.0",
"gatsby-plugin-postcss": "^5.6.0",
"gatsby-plugin-sharp": "^4.6.0",
"gatsby-source-strapi": "^2.0.0",
"gatsby-transformer-remark": "^5.6.0",
"gatsby-transformer-sharp": "^4.6.0",
"postcss": "^8.4.6",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-helmet": "^6.1.0",
"react-slick": "^0.28.1",
"slick-carousel": "^1.8.1"
},
"devDependencies": {
"@tailwindcss/line-clamp": "^0.3.1",
"@tailwindcss/typography": "^0.5.1",
"autoprefixer": "^10.4.2",
"eslint": "^8.8.0",
"eslint-config-prettier": "^8.3.0",
"eslint-config-react-app": "^7.0.0",
"eslint-plugin-prettier": "^4.0.0",
"prettier": "^2.5.1",
"prettier-plugin-tailwindcss": "^0.1.4",
"tailwindcss": "^3.0.18"
}
}
================================================
FILE: packages/starters/gatsby-blog/starter/postcss.config.js
================================================
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
================================================
FILE: packages/starters/gatsby-blog/starter/src/components/article-card.js
================================================
import React from "react"
import { Link, graphql } from "gatsby"
import { GatsbyImage, getImage } from "gatsby-plugin-image"
const ArticleCard = ({ article }) => {
return (
<Link
to={`/article/${article.slug}`}
className="overflow-hidden rounded-lg bg-white shadow-sm transition-shadow hover:shadow-md"
>
<GatsbyImage
image={getImage(article.cover?.localFile)}
alt={article.cover?.alternativeText}
/>
<div className="px-4 py-4">
<h3 className="font-bold text-neutral-700">{article.title}</h3>
<p className="line-clamp-2 mt-2 text-neutral-500">
{article.description}
</p>
</div>
</Link>
)
}
export const query = graphql`
fragment ArticleCard on STRAPI_ARTICLE {
id
slug
title
description
cover {
alternativeText
localFile {
childImageSharp {
gatsbyImageData(aspectRatio: 1.77)
}
}
}
}
`
export default ArticleCard
================================================
FILE: packages/starters/gatsby-blog/starter/src/components/articles-grid.js
================================================
import React from "react"
import ArticleCard from "./article-card"
const ArticlesGrid = ({ articles }) => {
return (
<div className="container mt-12 grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
{articles.map((article) => (
<ArticleCard article={article} />
))}
</div>
)
}
export default ArticlesGrid
================================================
FILE: packages/starters/gatsby-blog/starter/src/components/block-media.js
================================================
import React from "react"
import { GatsbyImage, getImage } from "gatsby-plugin-image"
const BlockMedia = ({ data }) => {
const isVideo = data.file.mime.startsWith("video")
return (
<div className="py-8">
{isVideo ? (
<p>TODO video</p>
) : (
<GatsbyImage
image={getImage(data.file.localFile)}
alt={data.file.alternativeText}
/>
)}
</div>
)
}
export default BlockMedia
================================================
FILE: packages/starters/gatsby-blog/starter/src/components/block-quote.js
================================================
import React from "react"
const BlockQuote = ({ data }) => {
return (
<div className="py-6">
<blockquote className="container max-w-xl border-l-4 border-neutral-700 py-2 pl-6 text-neutral-700">
<p className="text-5xl font-medium italic">{data.quoteBody}</p>
<cite className="mt-4 block font-bold uppercase not-italic">
{data.title}
</cite>
</blockquote>
</div>
)
}
export default BlockQuote
================================================
FILE: packages/starters/gatsby-blog/starter/src/components/block-rich-text.js
================================================
import React from "react"
const BlockRichText = ({ data }) => {
return (
<div className="prose mx-auto py-8">
<div
dangerouslySetInnerHTML={{
__html: data.richTextBody.data.childMarkdownRemark.html,
}}
/>
</div>
)
}
export default BlockRichText
================================================
FILE: packages/starters/gatsby-blog/starter/src/components/block-slider.js
================================================
import React from "react"
import { GatsbyImage, getImage } from "gatsby-plugin-image"
import Slider from "react-slick"
import "slick-carousel/slick/slick.css"
import "slick-carousel/slick/slick-theme.css"
const BlockSlider = ({ data }) => {
return (
<div className="container max-w-3xl py-8">
<Slider
dots={true}
infinite={true}
speed={300}
slidesToShow={1}
slidesToScroll={1}
arrows={true}
swipe={true}
>
{data.files.map((file) => (
<GatsbyImage
key={file.id}
image={getImage(file.localFile)}
alt={file.alternativeText}
/>
))}
</Slider>
</div>
)
}
export default BlockSlider
================================================
FILE: packages/starters/gatsby-blog/starter/src/components/blocks-renderer.js
================================================
import React from "react"
import { graphql } from "gatsby"
import BlockRichText from "./block-rich-text"
import BlockMedia from "./block-media"
import BlockQuote from "./block-quote"
import BlockSlider from "./block-slider"
const componentsMap = {
STRAPI__COMPONENT_SHARED_RICH_TEXT: BlockRichText,
STRAPI__COMPONENT_SHARED_MEDIA: BlockMedia,
STRAPI__COMPONENT_SHARED_QUOTE: BlockQuote,
STRAPI__COMPONENT_SHARED_SLIDER: BlockSlider,
}
const Block = ({ block }) => {
const Component = componentsMap[block.__typename]
if (!Component) {
return null
}
return <Component data={block} />
}
const BlocksRenderer = ({ blocks }) => {
return (
<div>
{blocks.map((block, index) => (
<Block key={`${index}${block.__typename}`} block={block} />
))}
</div>
)
}
export const query = graphql`
fragment Blocks on STRAPI__COMPONENT_SHARED_MEDIASTRAPI__COMPONENT_SHARED_QUOTESTRAPI__COMPONENT_SHARED_RICH_TEXTSTRAPI__COMPONENT_SHARED_SLIDERUnion {
__typename
... on STRAPI__COMPONENT_SHARED_RICH_TEXT {
richTextBody: body {
__typename
data {
id
childMarkdownRemark {
html
}
}
}
}
... on STRAPI__COMPONENT_SHARED_MEDIA {
file {
mime
localFile {
childImageSharp {
gatsbyImageData
}
}
}
}
... on STRAPI__COMPONENT_SHARED_QUOTE {
title
quoteBody: body
}
... on STRAPI__COMPONENT_SHARED_SLIDER {
files {
id
mime
localFile {
childImageSharp {
gatsbyImageData
}
}
}
}
}
`
export default BlocksRenderer
================================================
FILE: packages/starters/gatsby-blog/starter/src/components/footer.js
================================================
import React from "react"
const Footer = () => {
const currentYear = new Date().getFullYear()
return (
<footer className="mt-16 bg-neutral-100 py-8 text-neutral-700">
<div className="container">
<p>Copyright {currentYear}</p>
</div>
</footer>
)
}
export default Footer
================================================
FILE: packages/starters/gatsby-blog/starter/src/components/headings.js
================================================
import React from "react"
const Headings = ({ title, description }) => {
return (
<header className="container mt-8">
<h1 className="text-6xl font-bold text-neutral-700">{title}</h1>
{description && (
<p className="mt-4 text-2xl text-neutral-500">{description}</p>
)}
</header>
)
}
export default Headings
================================================
FILE: packages/starters/gatsby-blog/starter/src/components/layout.js
================================================
import React from "react"
import Footer from "./footer"
import Navbar from "./navbar"
const Layout = ({ children }) => {
return (
<div className="flex min-h-screen flex-col justify-between bg-neutral-50 text-neutral-900">
<div>
<Navbar />
{children}
</div>
<Footer />
</div>
)
}
export default Layout
================================================
FILE: packages/starters/gatsby-blog/starter/src/components/navbar.js
================================================
import { Link } from "gatsby"
import React from "react"
const Navbar = () => {
return (
<header className="bg-primary-200">
<nav className="container flex flex-row items-baseline justify-between py-6">
<Link to="/" className="text-xl font-medium">
Blog
</Link>
<div className="flex flex-row items-baseline justify-end">
<Link className="font-medium" to="/about">
About
</Link>
</div>
</nav>
</header>
)
}
export default Navbar
================================================
FILE: packages/starters/gatsby-blog/starter/src/components/seo.js
================================================
import React from "react"
import { Helmet } from "react-helmet"
import { useStaticQuery, graphql } from "gatsby"
const Seo = ({ seo = {} }) => {
const { strapiGlobal } = useStaticQuery(graphql`
query {
strapiGlobal {
siteName
favicon {
localFile {
url
}
}
defaultSeo {
metaTitle
metaDescription
shareImage {
localFile {
url
}
}
}
}
}
`)
const { siteName, defaultSeo, favicon } = strapiGlobal
// Merge default and page-specific SEO values
const fullSeo = { ...defaultSeo, ...seo }
// Add site name to title
fullSeo.metaTitle = `${fullSeo.metaTitle} | ${siteName}`
const getMetaTags = () => {
const tags = []
if (fullSeo.metaTitle) {
tags.push(
{
property: "og:title",
content: fullSeo.metaTitle,
},
{
name: "twitter:title",
content: fullSeo.metaTitle,
}
)
}
if (fullSeo.metaDescription) {
tags.push(
{
name: "description",
content: fullSeo.metaDescription,
},
{
property: "og:description",
content: fullSeo.metaDescription,
},
{
name: "twitter:description",
content: fullSeo.metaDescription,
}
)
}
if (fullSeo.shareImage) {
const imageUrl = fullSeo.shareImage.localFile.url
tags.push(
{
name: "image",
content: imageUrl,
},
{
property: "og:image",
content: imageUrl,
},
{
name: "twitter:image",
content: imageUrl,
}
)
}
if (fullSeo.article) {
tags.push({
property: "og:type",
content: "article",
})
}
tags.push({ name: "twitter:card", content: "summary_large_image" })
return tags
}
const metaTags = getMetaTags()
return (
<Helmet
title={fullSeo.metaTitle}
link={[
{
rel: "icon",
href: favicon.localFile.url,
},
]}
meta={metaTags}
/>
)
}
export default Seo
================================================
FILE: packages/starters/gatsby-blog/starter/src/pages/about.js
================================================
import React from "react"
import { useStaticQuery, graphql } from "gatsby"
import Layout from "../components/layout"
import Seo from "../components/seo"
import BlocksRenderer from "../components/blocks-renderer"
import Headings from "../components/headings"
const AboutPage = () => {
const { strapiAbout } = useStaticQuery(graphql`
query {
strapiAbout {
title
blocks {
...Blocks
}
}
}
`)
const { title, blocks } = strapiAbout
const seo = {
metaTitle: title,
metaDescription: title,
}
return (
<Layout>
<Seo seo={seo} />
<Headings title={strapiAbout.title} />
<BlocksRenderer blocks={blocks} />
</Layout>
)
}
export default AboutPage
================================================
FILE: packages/starters/gatsby-blog/starter/src/pages/index.js
================================================
import React from "react"
import { useStaticQuery, graphql } from "gatsby"
import Layout from "../components/layout"
import ArticlesGrid from "../components/articles-grid"
import Seo from "../components/seo"
import Headings from "../components/headings"
const IndexPage = () => {
const { allStrapiArticle, strapiGlobal } = useStaticQuery(graphql`
query {
allStrapiArticle {
nodes {
...ArticleCard
}
}
strapiGlobal {
siteName
siteDescription
}
}
`)
return (
<Layout>
<Seo seo={{ metaTitle: "Home" }} />
<Headings
title={strapiGlobal.siteName}
description={strapiGlobal.siteDescription}
/>
<main>
<ArticlesGrid articles={allStrapiArticle.nodes} />
</main>
</Layout>
)
}
export default IndexPage
================================================
FILE: packages/starters/gatsby-blog/starter/src/styles/global.css
================================================
@tailwind base;
@tailwind components;
@tailwind utilities;
================================================
FILE: packages/starters/gatsby-blog/starter/src/templates/article-post.js
================================================
import React from "react"
import { graphql } from "gatsby"
import { GatsbyImage, getImage } from "gatsby-plugin-image"
import Layout from "../components/layout"
import BlocksRenderer from "../components/blocks-renderer"
import Seo from "../components/seo"
const ArticlePage = ({ data }) => {
const article = data.strapiArticle
const seo = {
metaTitle: article.title,
metaDescription: article.description,
shareImage: article.cover,
}
return (
<Layout as="article">
<Seo seo={seo} />
<header className="container max-w-4xl py-8">
<h1 className="text-6xl font-bold text-neutral-700">{article.title}</h1>
<p className="mt-4 text-2xl text-neutral-500">{article.description}</p>
<GatsbyImage
image={getImage(article?.cover?.localFile)}
alt={article?.cover?.alternativeText}
className="mt-6"
/>
</header>
<main className="mt-8">
<BlocksRenderer blocks={article.blocks || []} />
</main>
</Layout>
)
}
export const pageQuery = graphql`
query ($slug: String) {
strapiArticle(slug: { eq: $slug }) {
id
slug
title
description
blocks {
...Blocks
}
cover {
alternativeText
localFile {
url
childImageSharp {
gatsbyImageData
}
}
}
}
}
`
export default ArticlePage
================================================
FILE: packages/starters/gatsby-blog/starter/tailwind.config.js
================================================
const colors = require("tailwindcss/colors")
module.exports = {
content: ["./src/**/*.{js,jsx,ts,tsx}"],
theme: {
extend: {
colors: {
neutral: colors.neutral,
primary: colors.sky,
},
},
container: {
center: true,
padding: {
DEFAULT: "1rem",
xs: "1rem",
sm: "2rem",
xl: "5rem",
"2xl": "6rem",
},
},
},
plugins: [
require("@tailwindcss/line-clamp"),
require("@tailwindcss/typography"),
],
}
================================================
FILE: packages/starters/gatsby-blog/starter.json
================================================
{
"template": {
"name": "@strapi/template-blog",
"version": "^2.0.0"
}
}
================================================
FILE: packages/starters/gatsby-corporate/README.md
================================================
# Placeholder for Strapi Gatsby corporate starter
The Strapi team will build this starter soon.
In the meantime, you can check out our other Strapi v4 starters:
- [Gatsby Blog](https://github.com/strapi/starters-and-templates/tree/main/packages/starters/gatsby-blog)
- [Next Blog](https://github.com/strapi/starters-and-templates/tree/main/packages/starters/next-blog)
- [Next Corporate Site](https://github.com/strapi/starters-and-templates/tree/main/packages/starters/next-corporate)
================================================
FILE: packages/starters/next-blog/README.md
================================================
# Strapi Starter Next Blog
Next starter for creating a blog with Strapi.

This starter allows you to try Strapi with Next with the example of a simple blog. It is fully customizable and due to the fact that it is open source, fully open to contributions. So do not hesitate to add new features and report bugs!
This starter uses the [Strapi blog template](https://github.com/strapi/strapi-template-blog)
Check out all of our starters [here](https://strapi.io/starters)
## Features
- 2 Content types: Article, Category
- 2 Created articles
- 3 Created categories
- Responsive design using UIkit
Pages:
- "/" to display every articles
- "/article/:id" to display one article
- "/category/:id" display articles depending on the category
## Getting started
Use our `create-strapi-starter` CLI to create your project.
```sh
# Using Yarn
yarn create strapi-starter my-project next-blog
# Or using NPM
npx create-strapi-starter my-project next-blog
```
The CLI will create a monorepo, install dependencies, and run your project automatically.
The Next frontend server will run here => [http://localhost:3000](http://localhost:3000)
The Strapi backend server will run here => [http://localhost:1337](http://localhost:1337)
## Deploying to production
You will need to deploy the `frontend` and `backend` projects separately. Here are the docs to deploy each one:
- [Deploy Strapi](https://strapi.io/documentation/developer-docs/latest/setup-deployment-guides/deployment.html#hosting-provider-guides)
- [Deploy Next](https://nextjs.org/docs/deployment)
Don't forget to setup the environment variables on your production app:
For the frontend the following environment variable is required:
- `NEXT_PUBLIC_STRAPI_API_URL`: URL of your Strapi backend, without trailing slash
Enjoy this starter!
================================================
FILE: packages/starters/next-blog/package.json
================================================
{
"name": "@strapi/starter-next-blog",
"version": "1.0.4",
"description": "Strapi blog starter with Next.js",
"keywords": [
"strapi",
"starter",
"nextjs",
"blog"
],
"homepage": "https://github.com/strapi/starters-and-templates/tree/main/packages/starters/next-blog#readme",
"bugs": {
"url": "https://github.com/strapi/starters-and-templates/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/strapi/starters-and-templates.git"
},
"license": "MIT",
"author": {
"name": "Strapi team",
"email": "hi@strapi.io",
"url": "https://strapi.io"
},
"maintainers": [
{
"name": "Strapi team",
"email": "hi@strapi.io",
"url": "https://strapi.io"
}
]
}
================================================
FILE: packages/starters/next-blog/starter/.eslintrc
================================================
{
"extends": [
"next",
"prettier"
],
"plugins": [
"prettier"
],
"rules": {
"prettier/prettier": "error"
}
}
================================================
FILE: packages/starters/next-blog/starter/.prettierrc
================================================
{
"printWidth": 80,
"singleQuote": false,
"trailingComma": "es5",
"semi": false,
"tabWidth": 2
}
================================================
FILE: packages/starters/next-blog/starter/assets/css/style.css
================================================
a {
text-decoration: none;
}
h1 {
font-family: Staatliches;
font-size: 120px;
}
#category {
font-family: Staatliches;
font-weight: 500;
}
#title {
letter-spacing: 0.4px;
font-size: 22px;
font-size: 1.375rem;
line-height: 1.13636;
}
#banner {
margin: 20px;
height: 800px;
}
#editor {
font-size: 16px;
font-size: 1rem;
line-height: 1.75;
}
.uk-navbar-container {
background: #fff !important;
font-family: Staatliches;
}
img:hover {
opacity: 1;
transition: opacity 0.25s cubic-bezier(0.39, 0.575, 0.565, 1);
}
================================================
FILE: packages/starters/next-blog/starter/components/articles.js
================================================
import React from "react"
import Card from "./card"
const Articles = ({ articles }) => {
const leftArticlesCount = Math.ceil(articles.length / 5)
const leftArticles = articles.slice(0, leftArticlesCount)
const rightArticles = articles.slice(leftArticlesCount, articles.length)
return (
<div>
<div className="uk-child-width-1-2@s" data-uk-grid="true">
<div>
{leftArticles.map((article, i) => {
return (
<Card
article={article}
key={`article__left__${article.attributes.slug}`}
/>
)
})}
</div>
<div>
<div className="uk-child-width-1-2@m uk-grid-match" data-uk-grid>
{rightArticles.map((article, i) => {
return (
<Card
article={article}
key={`article__left__${article.attributes.slug}`}
/>
)
})}
</div>
</div>
</div>
</div>
)
}
export default Articles
================================================
FILE: packages/starters/next-blog/starter/components/card.js
================================================
import React from "react"
import Link from "next/link"
import NextImage from "./image"
const Card = ({ article }) => {
return (
<Link href={`/article/${article.attributes.slug}`}>
<a className="uk-link-reset">
<div className="uk-card uk-card-muted">
<div className="uk-card-media-top">
<NextImage image={article.attributes.image} />
</div>
<div className="uk-card-body">
<p id="category" className="uk-text-uppercase">
{article.attributes.category.name}
</p>
<p id="title" className="uk-text-large">
{article.attributes.title}
</p>
</div>
</div>
</a>
</Link>
)
}
export default Card
================================================
FILE: packages/starters/next-blog/starter/components/image.js
================================================
import { getStrapiMedia } from "../lib/media"
import NextImage from "next/image"
const Image = ({ image, style }) => {
const { url, alternativeText, width, height } = image.data.attributes
// const loader = () => {
// return getStrapiMedia(image)
// }
return (
<NextImage
// loader={loader}
layout="responsive"
width={width || "100%"}
height={height || "100%"}
objectFit="contain"
src={getStrapiMedia(image)}
alt={alternativeText || ""}
/>
)
}
export default Image
================================================
FILE: packages/starters/next-blog/starter/components/layout.js
================================================
import Nav from "./nav"
const Layout = ({ children, categories, seo }) => (
<>
<Nav categories={categories} />
{children}
</>
)
export default Layout
================================================
FILE: packages/starters/next-blog/starter/components/nav.js
================================================
import React from "react"
import Link from "next/link"
const Nav = ({ categories }) => {
return (
<div>
<nav className="uk-navbar-container" data-uk-navbar>
<div className="uk-navbar-left">
<ul className="uk-navbar-nav">
<li>
<Link href="/">
<a>Strapi Blog</a>
</Link>
</li>
</ul>
</div>
<div className="uk-navbar-right">
<ul className="uk-navbar-nav">
{categories.map((category) => {
return (
<li key={category.id}>
<Link href={`/category/${category.attributes.slug}`}>
<a className="uk-link-reset">{category.attributes.name}</a>
</Link>
</li>
)
})}
</ul>
</div>
</nav>
</div>
)
}
export default Nav
================================================
FILE: packages/starters/next-blog/starter/components/seo.js
================================================
import Head from "next/head"
import { useContext } from "react"
import { GlobalContext } from "../pages/_app"
import { getStrapiMedia } from "../lib/media"
const Seo = ({ seo }) => {
const { defaultSeo, siteName } = useContext(GlobalContext)
const seoWithDefaults = {
...defaultSeo,
...seo,
}
const fullSeo = {
...seoWithDefaults,
// Add title suffix
metaTitle: `${seoWithDefaults.metaTitle} | ${siteName}`,
// Get full image URL
shareImage: getStrapiMedia(seoWithDefaults.shareImage),
}
return (
<Head>
{fullSeo.metaTitle && (
<>
<title>{fullSeo.metaTitle}</title>
<meta property="og:title" content={fullSeo.metaTitle} />
<meta name="twitter:title" content={fullSeo.metaTitle} />
</>
)}
{fullSeo.metaDescription && (
<>
<meta name="description" content={fullSeo.metaDescription} />
<meta property="og:description" content={fullSeo.metaDescription} />
<meta name="twitter:description" content={fullSeo.metaDescription} />
</>
)}
{fullSeo.shareImage && (
<>
<meta property="og:image" content={fullSeo.shareImage} />
<meta name="twitter:image" content={fullSeo.shareImage} />
<meta name="image" content={fullSeo.shareImage} />
</>
)}
{fullSeo.article && <meta property="og:type" content="article" />}
<meta name="twitter:card" content="summary_large_image" />
</Head>
)
}
export default Seo
================================================
FILE: packages/starters/next-blog/starter/lib/api.js
================================================
import qs from "qs"
/**
* Get full Strapi URL from path
* @param {string} path Path of the URL
* @returns {string} Full Strapi URL
*/
export function getStrapiURL(path = "") {
return `${
process.env.NEXT_PUBLIC_STRAPI_API_URL || "http://localhost:1337"
}${path}`
}
/**
* Helper to make GET requests to Strapi API endpoints
* @param {string} path Path of the API route
* @param {Object} urlParamsObject URL params object, will be stringified
* @param {Object} options Options passed to fetch
* @returns Parsed API call response
*/
export async function fetchAPI(path, urlParamsObject = {}, options = {}) {
// Merge default and user options
const mergedOptions = {
headers: {
"Content-Type": "application/json",
},
...options,
}
// Build request URL
const queryString = qs.stringify(urlParamsObject)
const requestUrl = `${getStrapiURL(
`/api${path}${queryString ? `?${queryString}` : ""}`
)}`
// Trigger API call
const response = await fetch(requestUrl, mergedOptions)
// Handle response
if (!response.ok) {
console.error(response.statusText)
throw new Error(`An error occurred please try again`)
}
const data = await response.json()
return data
}
================================================
FILE: packages/starters/next-blog/starter/lib/media.js
================================================
import { getStrapiURL } from "./api"
export function getStrapiMedia(media) {
const { url } = media.data.attributes
const imageUrl = url.startsWith("/") ? getStrapiURL(url) : url
return imageUrl
}
================================================
FILE: packages/starters/next-blog/starter/next.config.js
================================================
/**
* @type {import('next').NextConfig}
*/
const nextConfig = {
images: {
loader: "default",
domains: ["localhost"],
},
}
module.exports = nextConfig
================================================
FILE: packages/starters/next-blog/starter/package.json
================================================
{
"name": "my-next-blog",
"version": "1.0.3",
"private": true,
"scripts": {
"develop": "next dev",
"dev": "next dev",
"build": "next build",
"start": "next start",
"deploy": "next build && next export",
"lint": "next lint",
"lint:fix": "next lint --fix"
},
"dependencies": {
"moment": "^2.24.0",
"next": "^11.0.0",
"qs": "^6.10.1",
"react": "17.0.0",
"react-dom": "17.0.0",
"react-markdown": "^4.2.2",
"react-moment": "^0.9.6"
},
"license": "MIT",
"devDependencies": {
"eslint": "^7.30.0",
"eslint-config-next": "^11.0.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^3.4.0",
"prettier": "^2.3.1"
}
}
================================================
FILE: packages/starters/next-blog/starter/pages/_app.js
================================================
import App from "next/app"
import Head from "next/head"
import "../assets/css/style.css"
import { createContext } from "react"
import { fetchAPI } from "../lib/api"
import { getStrapiMedia } from "../lib/media"
// Store Strapi Global object in context
export const GlobalContext = createContext({})
const MyApp = ({ Component, pageProps }) => {
const { global } = pageProps
return (
<>
<Head>
<link
rel="shortcut icon"
href={getStrapiMedia(global.attributes.favicon)}
/>
</Head>
<GlobalContext.Provider value={global.attributes}>
<Component {...pageProps} />
</GlobalContext.Provider>
</>
)
}
// getInitialProps disables automatic static optimization for pages that don't
// have getStaticProps. So article, category and home pages still get SSG.
// Hopefully we can replace this with getStaticProps once this issue is fixed:
// https://github.com/vercel/next.js/discussions/10949
MyApp.getInitialProps = async (ctx) => {
// Calls page's `getInitialProps` and fills `appProps.pageProps`
const appProps = await App.getInitialProps(ctx)
// Fetch global site settings from Strapi
const globalRes = await fetchAPI("/global", {
populate: {
favicon: "*",
defaultSeo: {
populate: "*",
},
},
})
// Pass the data to our page via props
return { ...appProps, pageProps: { global: globalRes.data } }
}
export default MyApp
================================================
FILE: packages/starters/next-blog/starter/pages/_document.js
================================================
import Document, { Html, Head, Main, NextScript } from "next/document"
class MyDocument extends Document {
render() {
return (
<Html>
<Head>
{/* eslint-disable-next-line */}
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Staatliches"
/>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/uikit@3.2.3/dist/css/uikit.min.css"
/>
<script
async
src="https://cdnjs.cloudflare.com/ajax/libs/uikit/3.2.0/js/uikit.min.js"
/>
<script
async
src="https://cdn.jsdelivr.net/npm/uikit@3.2.3/dist/js/uikit-icons.min.js"
/>
<script
async
src="https://cdnjs.cloudflare.com/ajax/libs/uikit/3.2.0/js/uikit.js"
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument
================================================
FILE: packages/starters/next-blog/starter/pages/article/[slug].js
================================================
import ReactMarkdown from "react-markdown"
import Moment from "react-moment"
import { fetchAPI } from "../../lib/api"
import Layout from "../../components/layout"
import NextImage from "../../components/image"
import Seo from "../../components/seo"
import { getStrapiMedia } from "../../lib/media"
const Article = ({ article, categories }) => {
const imageUrl = getStrapiMedia(article.attributes.image)
const seo = {
metaTitle: article.attributes.title,
metaDescription: article.attributes.description,
shareImage: article.attributes.image,
article: true,
}
return (
<Layout categories={categories.data}>
<Seo seo={seo} />
<div
id="banner"
className="uk-height-medium uk-flex uk-flex-center uk-flex-middle uk-background-cover uk-light uk-padding uk-margin"
data-src={imageUrl}
data-srcset={imageUrl}
data-uk-img
>
<h1>{article.attributes.title}</h1>
</div>
<div className="uk-section">
<div className="uk-container uk-container-small">
<ReactMarkdown
source={article.attributes.content}
escapeHtml={false}
/>
<hr className="uk-divider-small" />
<div className="uk-grid-small uk-flex-left" data-uk-grid="true">
<div>
{article.attributes.author.picture && (
<NextImage image={article.attributes.author.picture} />
)}
</div>
<div className="uk-width-expand">
<p className="uk-margin-remove-bottom">
By {article.attributes.author.name}
</p>
<p className="uk-text-meta uk-margin-remove-top">
<Moment format="MMM Do YYYY">
{article.attributes.published_at}
</Moment>
</p>
</div>
</div>
</div>
</div>
</Layout>
)
}
export async function getStaticPaths() {
const articlesRes = await fetchAPI("/articles", { fields: ["slug"] })
return {
paths: articlesRes.data.map((article) => ({
params: {
slug: article.attributes.slug,
},
})),
fallback: false,
}
}
export async function getStaticProps({ params }) {
const articlesRes = await fetchAPI("/articles", {
filters: {
slug: params.slug,
},
populate: "*",
})
const categoriesRes = await fetchAPI("/categories")
return {
props: { article: articlesRes.data[0], categories: categoriesRes },
revalidate: 1,
}
}
export default Article
================================================
FILE: packages/starters/next-blog/starter/pages/category/[slug].js
================================================
import Articles from "../../components/articles"
import { fetchAPI } from "../../lib/api"
import Layout from "../../components/layout"
import Seo from "../../components/seo"
const Category = ({ category, categories }) => {
const seo = {
metaTitle: category.attributes.name,
metaDescription: `All ${category.attributes.name} articles`,
}
return (
<Layout categories={categories.data}>
<Seo seo={seo} />
<div className="uk-section">
<div className="uk-container uk-container-large">
<h1>{category.attributes.name}</h1>
<Articles articles={category.attributes.articles.data} />
</div>
</div>
</Layout>
)
}
export async function getStaticPaths() {
const categoriesRes = await fetchAPI("/categories", { fields: ["slug"] })
return {
paths: categoriesRes.data.map((category) => ({
params: {
slug: category.attributes.slug,
},
})),
fallback: false,
}
}
export async function getStaticProps({ params }) {
const matchingCategories = await fetchAPI("/categories", {
filters: { slug: params.slug },
populate: {
articles: {
populate: "*",
},
},
})
const allCategories = await fetchAPI("/categories")
return {
props: {
category: matchingCategories.data[0],
categories: allCategories,
},
revalidate: 1,
}
}
export default Category
================================================
FILE: packages/starters/next-blog/starter/pages/index.js
================================================
import React from "react"
import Articles from "../components/articles"
import Layout from "../components/layout"
import Seo from "../components/seo"
import { fetchAPI } from "../lib/api"
const Home = ({ articles, categories, homepage }) => {
return (
<Layout categories={categories}>
<Seo seo={homepage.attributes.seo} />
<div className="uk-section">
<div className="uk-container uk-container-large">
<h1>{homepage.attributes.hero.title}</h1>
<Articles articles={articles} />
</div>
</div>
</Layout>
)
}
export async function getStaticProps() {
// Run API calls in parallel
const [articlesRes, categoriesRes, homepageRes] = await Promise.all([
fetchAPI("/articles", { populate: "*" }),
fetchAPI("/categories", { populate: "*" }),
fetchAPI("/homepage", {
populate: {
hero: "*",
seo: { populate: "*" },
},
}),
])
return {
props: {
articles: articlesRes.data,
categories: categoriesRes.data,
homepage: homepageRes.data,
},
revalidate: 1,
}
}
export default Home
================================================
FILE: packages/starters/next-blog/starter.json
================================================
{
"template": {
"name": "@strapi/template-blog",
"version": "^1.0.0"
}
}
================================================
FILE: packages/starters/next-corporate/README.md
================================================
# Strapi Starter Next Corporate Site
Next starter for creating a corporate site with Strapi.
[View the live demo](https://strapi-starter-next-corporate.vercel.app/) • [Read the blog post](https://strapi.io/blog/strapi-starter-next-corporate-site)

This starter is designed for flexibility. Using it, you'll be able to manage your website content entirely in Strapi, and get a Next app automatically generated. Marketing teams will be able to create pages and design their layout without help from developers.
This starter features:
- Pages creation within Strapi, no code necessary
- Fully flexible page structure: design the pages you want using UI Sections
- 8 UI Sections out of the box: Hero, RichText, LargeVideo, Testimonials, Pricing, BottomActions, FeatureRows, FeatureColumns
- Easy to theme with Tailwind
- Static site generation with Next
- An integrated Preview Mode, to view your pages on a private URL before publishing them
- Content in multiple languages using i18n
This starter uses the [Strapi corporate template](https://github.com/strapi/strapi-template-corporate)
Check out all of our starters [here](https://strapi.io/starters)
## Getting started
Use our `create-strapi-starter` CLI to create your project.
```sh
# Using Yarn
yarn create strapi-starter my-site next-corporate
# Or using NPM
npx create-strapi-starter my-site next-corporate
```
The CLI will create a monorepo, install dependencies, and run your project automatically.
The Next frontend server will run here => [http://localhost:3000](http://localhost:3000)
The Strapi backend server will run here => [http://localhost:1337](http://localhost:1337)
## Preview Mode
You can turn preview mode on with a URL like this:
`http://localhost:3000/api/preview?secret=<preview-secret>&slug=<slug>`
`<preview-secret>` is the secret token defined in your .env config,
`<slug>` is the slug you entered in Strapi for your page.
While preview mode is on you can access `draft` pages just like you would `published` pages.
For example [http://localhost:3000/secret](http://localhost:3000/secret) would be available in preview mode.
A banner will remain under the navigation to let you know preview mode is on and it will also allow you to turn it off.
## Customize your corporate site
To edit this website, you'll need to run both the frontend and the backend in your development environment.
### Adding Sections
We have built sections for you, but you will likely want to add more to fit your needs. Follow these steps:
- Create a new component in Strapi in the "sections" category
- In the Content-Types Builder, open the Pages collection and check your new section on the `contentSections` field.
- Create a React component that takes a `data` prop in `/frontend/components/sections`
- To link your Strapi section to this React component, open `/frontend/components/sections.js`, and add an entry to the `sectionComponents` object.
### Custom theme
We use Tailwind CSS for styling. To modify your page's look, you can edit the theme in `/front/tailwind.config.js`. Read the [Tailwind docs](https://v1.tailwindcss.com/docs/theme) to view all the changes you can make. For example, you can change the primary color like this:
```js
const { colors } = require(`tailwindcss/defaultTheme`);
module.exports = {
theme: {
extend: {
colors: {
primary: colors.green,
},
},
},
};
```
## Deploying to production
You will need to deploy the `frontend` and `backend` projects separately. Here are the docs to deploy each one:
- [Deploy Strapi](https://strapi.io/documentation/v3.x/admin-panel/deploy.html#deployment)
- [Deploy Next.js](https://nextjs.org/docs/deployment)
Don't forget to set up your environment variables on your production apps.
Here are the required ones for the frontend:
- `NEXT_PUBLIC_STRAPI_API_URL`: URL of your Strapi backend, without trailing slash
- `PREVIEW_SECRET`: a random string used to protect your preview pages
And for the backend:
- `FRONTEND_URL`: URL of your frontend, without trailing slash
- `FRONTEND_PREVIEW_SECRET`: token of Next.js preview mode defined on the frontend
Have fun using this starter!
================================================
FILE: packages/starters/next-corporate/package.json
================================================
{
"name": "@strapi/starter-next-corporate",
"version": "1.0.4",
"description": "Strapi corporate starter with Next.js",
"keywords": [
"strapi",
"starter",
"nextjs",
"corporate"
],
"homepage": "https://github.com/strapi/starters-and-templates/tree/main/packages/starters/next-corporate#readme",
"bugs": {
"url": "https://github.com/strapi/starters-and-templates/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/strapi/starters-and-templates.git"
},
"license": "MIT",
"author": {
"name": "Strapi team",
"email": "hi@strapi.io",
"url": "https://strapi.io"
},
"maintainers": [
{
"name": "Strapi team",
"email": "hi@strapi.io",
"url": "https://strapi.io"
}
]
}
================================================
FILE: packages/starters/next-corporate/starter/.eslintrc
================================================
{
"extends": [
"next",
"prettier"
],
"plugins": [
"prettier"
],
"rules": {
"prettier/prettier": [
"error",
{
"printWidth": 80,
"singleQuote": false,
"trailingComma": "es5",
"semi": false,
"tabWidth": 2
}
]
}
}
================================================
FILE: packages/starters/next-corporate/starter/.gitignore
================================================
.vscode
# 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
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
================================================
FILE: packages/starters/next-corporate/starter/README.md
================================================
# Next frontend
This frontend relies on Next's [Static Generation](https://nextjs.org/docs/basic-features/pages) using [Strapi](https://strapi.io/) as the data source. Make sure Strapi is running in parallel when you run this app.
## Routes
**pages/[[...slug]].js**
This file generates all the app's route. First, it fetches all the pages entries in Strapi. Then, it creates one route per page found. These routes can look like this:
* yoursite.com
* yoursite.com/page
* yoursite.com/page/nested/route
Notice that the path of the page can be several layers deep, or it can be the root of the site. This is possible thanks to Next's [optional catch-all routes](https://nextjs.org/docs/routing/dynamic-routes#optional-catch-all-routes).
To see how to build these nested routes, see [the Strapi project's Readme](../backend/README.md).
## Available Scripts
In the project directory, you can run:
**`yarn dev`**
Runs the app in the development mode.
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.
You will also see any errors in the console.
**`yarn build`**
Builds the app for production to the `.next` folder.<br>
It correctly bundles React in production mode and optimizes the build for the best performance.
**`yarn start`**
Starts the application in production mode.
The application should be compiled with \`next build\` first.
See the section in Next docs about [deployment](https://nextjs.org/docs/deployment) for more
information.
================================================
FILE: packages/starters/next-corporate/starter/components/elements/button-link.js
================================================
import classNames from "classnames"
import PropTypes from "prop-types"
import { buttonLinkPropTypes } from "utils/types"
import CustomLink from "./custom-link"
const ButtonContent = ({ button, appearance, compact }) => {
return (
<div
className={classNames(
// Common classes
"block w-full lg:w-auto text-center uppercase tracking-wide font-semibold text-base md:text-sm border-2 rounded-md",
// Full-size button
{
"px-8 py-4": compact === false,
},
// Compact button
{
"px-6 py-2": compact === true,
},
// Specific to when the button is fully dark
{
"bg-primary-600 text-white border-primary-600": appearance === "dark",
},
// Specific to when the button is dark outlines
{
"text-primary-600 border-primary-600": appearance === "dark-outline",
},
// Specific to when the button is fully white
{
"bg-white text-primary-600 border-white": appearance === "white",
},
// Specific to when the button is white outlines
{
"text-white border-white": appearance === "white-outline",
}
)}
>
{button.text}
</div>
)
}
const ButtonLink = ({ button, appearance, compact = false }) => {
return (
<CustomLink link={button}>
<ButtonContent
button={button}
appearance={appearance}
compact={compact}
/>
</CustomLink>
)
}
ButtonLink.propTypes = {
button: buttonLinkPropTypes,
appearance: PropTypes.oneOf([
"dark",
"white-outline",
"white",
"dark-outline",
]),
compact: PropTypes.bool,
}
export default ButtonLink
================================================
FILE: packages/starters/next-corporate/starter/components/elements/button.js
================================================
import classNames from "classnames"
import PropTypes from "prop-types"
import { buttonLinkPropTypes } from "utils/types"
import Loader from "./loader"
const Button = ({
button,
appearance,
compact = false,
handleClick,
loading = false,
type,
}) => {
return (
<button link={button} onClick={handleClick} type={type}>
<div
className={classNames(
// Common classes
"flex w-full justify-center lg:w-auto text-center uppercase tracking-wide font-semibold text-base md:text-sm border-2 rounded-md",
// Full-size button
{
"px-8 py-4": compact === false,
},
// Compact button
{
"px-6 py-2": compact === true,
},
// Specific to when the button is fully dark
{
"bg-primary-600 text-white border-primary-600":
appearance === "dark",
},
// Specific to when the button is dark outlines
{
"text-primary-600 border-primary-600":
appearance === "dark-outline",
},
// Specific to when the button is fully white
{
"bg-white text-primary-600 border-white": appearance === "white",
},
// Specific to when the button is white outlines
{
"text-white border-white": appearance === "white-outline",
}
)}
>
{loading && <Loader />}
{button.text}
</div>
</button>
)
}
Button.propTypes = {
button: buttonLinkPropTypes,
appearance: PropTypes.oneOf([
"dark",
"white-outline",
"white",
"dark-outline",
]),
compact: PropTypes.bool,
}
export default Button
================================================
FILE: packages/starters/next-corporate/starter/components/elements/custom-link.js
================================================
import Link from "next/link"
import PropTypes from "prop-types"
import { linkPropTypes } from "utils/types"
const CustomLink = ({ link, children }) => {
const isInternalLink = link.url.startsWith("/")
// For internal links, use the Next.js Link component
if (isInternalLink) {
return (
<Link href={link.url}>
<a>{children}</a>
</Link>
)
}
// Plain <a> tags for external links
if (link.newTab) {
return (
<a href={link.url} target="_blank" rel="noopener noreferrer">
{children}
</a>
)
}
return (
<a href={link.url} target="_self">
{children}
</a>
)
}
CustomLink.propTypes = {
link: linkPropTypes,
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
]).isRequired,
}
export default CustomLink
================================================
FILE: packages/starters/next-corporate/starter/components/elements/footer.js
================================================
import PropTypes from "prop-types"
import { linkPropTypes, mediaPropTypes } from "utils/types"
import NextImage from "./image"
import CustomLink from "./custom-link"
const Footer = ({ footer }) => {
return (
<footer className="pt-12 bg-gray-100">
<div className="container flex flex-col lg:flex-row lg:justify-between">
<div>
{footer.logo && (
<NextImage width="120" height="33" media={footer.logo} />
)}
</div>
<nav className="flex flex-wrap flex-row lg:gap-20 items-start lg:justify-end mb-10">
{footer.columns.map((footerColumn) => (
<div
key={footerColumn.id}
className="mt-10 lg:mt-0 w-6/12 lg:w-auto"
>
<p className="uppercase tracking-wide font-semibold">
{footerColumn.title}
</p>
<ul className="mt-2">
{footerColumn.links.map((link) => (
<li
key={link.id}
className="text-gray-700 py-1 px-1 -mx-1 hover:text-gray-900"
>
<CustomLink link={link}>{link.text}</CustomLink>
</li>
))}
</ul>
</div>
))}
</nav>
</div>
<div className="text-sm bg-gray-200 py-6 text-gray-700">
<div className="container">{footer.smallText}</div>
</div>
</footer>
)
}
Footer.propTypes = {
footer: PropTypes.shape({
logo: mediaPropTypes.isRequired,
columns: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
.isRequired,
title: PropTypes.string.isRequired,
links: PropTypes.arrayOf(linkPropTypes),
})
),
smallText: PropTypes.string.isRequired,
}),
}
export default Footer
================================================
FILE: packages/starters/next-corporate/starter/components/elements/image.js
================================================
import { getStrapiMedia } from "utils/media"
import Image from "next/image"
import PropTypes from "prop-types"
import { mediaPropTypes } from "utils/types"
const NextImage = ({ media, ...props }) => {
const { url, alternativeText, width, height } = media.data.attributes
const loader = ({ src, width }) => {
return getStrapiMedia(src)
}
// The image has a fixed width and height
if (props.width && props.height) {
return (
<Image loader={loader} src={url} alt={alternativeText || ""} {...props} />
)
}
// The image is responsive
return (
<Image
loader={loader}
layout="responsive"
width={width || "100%"}
height={height || "100%"}
objectFit="contain"
src={url}
alt={alternativeText || ""}
/>
)
}
Image.propTypes = {
media: mediaPropTypes.isRequired,
className: PropTypes.string,
}
export default NextImage
================================================
FILE: packages/starters/next-corporate/starter/components/elements/loader.js
================================================
import React from "react"
const Loader = () => {
return (
<svg
viewBox="0 0 38 38"
className="animate-spin h-5 w-5 stroke-current text-black-600 mr-2"
xmlns="http://www.w3.org/2000/svg"
>
<g fill="none" fillRule="evenodd">
<g transform="translate(1 1)" strokeWidth="2">
<circle strokeOpacity=".5" cx="18" cy="18" r="18" />
<path d="M36 18c0-9.94-8.06-18-18-18">
<animateTransform
attributeName="transform"
type="rotate"
from="0 18 18"
to="360 18 18"
dur="1s"
repeatCount="indefinite"
/>
</path>
</g>
</g>
</svg>
)
}
export default Loader
================================================
FILE: packages/starters/next-corporate/starter/components/elements/mobile-nav-menu.js
================================================
import PropTypes from "prop-types"
import { MdClose, MdChevronRight } from "react-icons/md"
import { mediaPropTypes, linkPropTypes, buttonLinkPropTypes } from "utils/types"
import { useLockBodyScroll } from "utils/hooks"
import { getButtonAppearance } from "utils/button"
import ButtonLink from "./button-link"
import NextImage from "./image"
import CustomLink from "./custom-link"
const MobileNavMenu = ({ navbar, closeSelf }) => {
// Prevent window scroll while mobile nav menu is open
useLockBodyScroll()
return (
<div className="w-screen h-screen fixed top-0 left-0 overflow-y-scroll bg-white z-10 pb-6">
<div className="container h-full flex flex-col justify-between">
{/* Top section */}
<div className="flex flex-row justify-between py-2 items-center">
{/* Company logo */}
<NextImage width="120" height="33" media={navbar.logo} />
{/* Close button */}
<button onClick={closeSelf} className="py-1 px-1">
<MdClose className="h-8 w-auto" />
</button>
</div>
{/* Bottom section */}
<div className="flex flex-col justify-end w-9/12 mx-auto">
<ul className="flex flex-col list-none gap-6 items-baseline text-xl mb-10">
{navbar.links.map((navLink) => (
<li key={navLink.id} className="block w-full">
<CustomLink link={navLink}>
<div className="hover:text-gray-900 py-6 flex flex-row justify-between items-center">
<span>{navLink.text}</span>
<MdChevronRight className="h-8 w-auto" />
</div>
</CustomLink>
</li>
))}
</ul>
<ButtonLink
button={navbar.button}
appearance={getButtonAppearance(navbar.button.type, "light")}
/>
</div>
</div>
</div>
)
}
MobileNavMenu.propTypes = {
navbar: PropTypes.shape({
logo: mediaPropTypes,
links: PropTypes.arrayOf(linkPropTypes),
button: buttonLinkPropTypes,
}),
closeSelf: PropTypes.func,
}
export default MobileNavMenu
================================================
FILE: packages/starters/next-corporate/starter/components/elements/navbar.js
================================================
import { useState } from "react"
import PropTypes from "prop-types"
import Link from "next/link"
import { useRouter } from "next/router"
import { getButtonAppearance } from "utils/button"
import { mediaPropTypes, linkPropTypes, buttonLinkPropTypes } from "utils/types"
import { MdMenu } from "react-icons/md"
import MobileNavMenu from "./mobile-nav-menu"
import ButtonLink from "./button-link"
import NextImage from "./image"
import CustomLink from "./custom-link"
import LocaleSwitch from "../locale-switch"
const Navbar = ({ navbar, pageContext }) => {
const router = useRouter()
const [mobileMenuIsShown, setMobileMenuIsShown] = useState(false)
return (
<>
{/* The actual navbar */}
<nav className="border-gray-200 border-b-2 py-6 sm:py-2">
<div className="container flex flex-row items-center justify-between">
{/* Content aligned to the left */}
<div className="flex flex-row items-center">
<Link href="/">
<a className="h-8 w-32">
<NextImage width="120" height="33" media={navbar.logo} />
</a>
</Link>
{/* List of links on desktop */}
<ul className="hidden list-none md:flex flex-row gap-4 items-baseline ml-10">
{navbar.links.map((navLink) => (
<li key={navLink.id}>
<CustomLink link={navLink} locale={router.locale}>
<div className="hover:text-gray-900 px-2 py-1">
{navLink.text}
</div>
</CustomLink>
</li>
))}
</ul>
</div>
<div className="flex">
{/* Locale Switch Mobile */}
{pageContext.localizedPaths && (
<div className="md:hidden">
<LocaleSwitch pageContext={pageContext} />
</div>
)}
{/* Hamburger menu on mobile */}
<button
onClick={() => setMobileMenuIsShown(true)}
className="p-1 block md:hidden"
>
<MdMenu className="h-8 w-auto" />
</button>
{/* CTA button on desktop */}
{navbar.button && (
<div className="hidden md:block">
<ButtonLink
button={navbar.button}
appearance={getButtonAppearance(navbar.button.type, "light")}
compact
/>
</div>
)}
{/* Locale Switch Desktop */}
{pageContext.localizedPaths && (
<div className="hidden md:block">
<LocaleSwitch pageContext={pageContext} />
</div>
)}
</div>
</div>
</nav>
{/* Mobile navigation menu panel */}
{mobileMenuIsShown && (
<MobileNavMenu
navbar={navbar}
closeSelf={() => setMobileMenuIsShown(false)}
/>
)}
</>
)
}
Navbar.propTypes = {
navbar: PropTypes.shape({
logo: PropTypes.shape({
image: mediaPropTypes,
url: PropTypes.string,
}),
links: PropTypes.arrayOf(linkPropTypes),
button: buttonLinkPropTypes,
}),
initialLocale: PropTypes.string,
}
export default Navbar
================================================
FILE: packages/starters/next-corporate/starter/components/elements/notification-banner.js
================================================
import Markdown from "react-markdown"
import classNames from "classnames"
import { MdClose } from "react-icons/md"
const NotificationBanner = ({ data: { text, type }, closeSelf }) => {
return (
<div
className={classNames(
// Common classes
"text-white px-2 py-2",
{
// Apply theme based on notification type
"bg-blue-600": type === "info",
"bg-orange-600": type === "warning",
"bg-red-600": type === "alert",
}
)}
>
<div className="container flex flex-row justify-between items-center ">
<div className="rich-text-banner flex-1">
<Markdown>{text}</Markdown>
</div>
<button onClick={closeSelf} className="px-1 py-1 flex-shrink-0">
<MdClose className="h-6 w-auto" color="#fff" />
</button>
</div>
</div>
)
}
export default NotificationBanner
================================================
FILE: packages/starters/next-corporate/starter/components/elements/seo.js
================================================
import { NextSeo } from "next-seo"
import PropTypes from "prop-types"
import { getStrapiMedia } from "utils/media"
import { mediaPropTypes } from "utils/types"
const Seo = ({ metadata }) => {
// Prevent errors if no metadata was set
if (!metadata) return null
return (
<NextSeo
title={metadata.metaTitle}
description={metadata.metaDescription}
openGraph={{
// Title and description are mandatory
title: metadata.metaTitle,
description: metadata.metaDescription,
// Only include OG image if we have it
// Careful: if you disable image optimization in Strapi, this will break
...(metadata.shareImage && {
images: Object.values(
metadata.shareImage.data.attributes.formats
).map((image) => {
return {
url: getStrapiMedia(image.url),
width: image.width,
height: image.height,
}
}),
}),
}}
// Only included Twitter data if we have it
twitter={{
...(metadata.twitterCardType && { cardType: metadata.twitterCardType }),
// Handle is the twitter username of the content creator
...(metadata.twitterUsername && { handle: metadata.twitterUsername }),
}}
/>
)
}
Seo.propTypes = {
metadata: PropTypes.shape({
metaTitle: PropTypes.string.isRequired,
metaDescription: PropTypes.string.isRequired,
shareImage: mediaPropTypes,
twitterCardType: PropTypes.string,
twitterUsername: PropTypes.string,
}),
}
export default Seo
================================================
FILE: packages/starters/next-corporate/starter/components/elements/video.js
================================================
import PropTypes from "prop-types"
import { getStrapiMedia } from "utils/media"
import { mediaPropTypes } from "utils/types"
const Video = ({
media,
poster,
className,
controls = true,
autoPlay = false,
}) => {
const fullVideoUrl = getStrapiMedia(media.url)
const fullPosterUrl = getStrapiMedia(poster?.url)
return (
<video
className={className}
poster={fullPosterUrl}
controls={controls}
autoPlay={autoPlay}
>
<source src={fullVideoUrl} type={media.mime} />
</video>
)
}
Video.propTypes = {
media: mediaPropTypes.isRequired,
poster: mediaPropTypes,
className: PropTypes.string,
controls: PropTypes.bool,
autoPlay: PropTypes.bool,
}
export default Video
================================================
FILE: packages/starters/next-corporate/starter/components/icons/world.js
================================================
import React from "react"
const WorldIcon = () => {
return (
<div className="w-4 h-4 mr-2 ">
<svg
className="fill-current text-primary-600"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 490 490"
>
<path d="M490 245C490 109.915 380.089 0 245 0S0 109.915 0 245c0 135.085 109.911 245 245 245s245-109.915 245-245zM263.846 380.696c11.096.548 21.781 1.361 31.996 2.43-11.076 17.053-22.316 31.807-31.996 43.387v-45.817zm0-38.711v-81.591h80.86c-4.208 31.012-14.804 60.245-27.914 86.267-16.352-2.171-34.078-3.82-52.946-4.676zm0-119.946v-80.36c19.587-.4 39.945-1.888 60.783-4.753 11.631 24.771 20.063 53.417 21.66 85.113h-82.443zm0-118.966v-51.29c11.357 11.26 26.119 27.703 40.158 48.699-13.635 1.373-27.106 2.33-40.158 2.591zm-37.692-.673c-16.318-.805-31.757-2.205-46.078-4.046 16.965-24.888 34.774-43.291 46.078-53.774v57.82zm0 38.825v80.815h-89.986c1.658-32.662 10.587-62.206 22.834-87.553a619.247 619.247 0 0067.152 6.738zm0 119.17v81.003c-19.581.4-39.939 1.851-60.768 4.712-12.983-25.886-23.46-54.922-27.638-85.716h88.406zm0 119.628v55.272c-11.131-12.632-25.648-30.714-39.886-52.708 13.541-1.356 26.922-2.304 39.886-2.564zm111.038 8.844c18.508 3.238 34.063 6.762 45.881 9.837-23.907 21.36-52.737 37.222-84.641 45.727 12.172-15.038 25.844-33.717 38.76-55.564zm18.791-35.812c12.927-28.029 23.045-59.309 26.832-92.66h68.71c-2.959 39.704-17.194 76.236-39.557 106.571-10.45-3.268-29.932-8.767-55.985-13.911zm28.019-131.016c-1.427-34.133-9.499-64.953-20.987-91.989a444.866 444.866 0 0044.872-12.453c23.245 29.466 38.694 65.279 43.062 104.441h-66.947zM345.135 94.558a341.42 341.42 0 00-30.991-44.13c23.562 8.348 45.226 20.672 64.008 36.41-11.037 3.104-22.05 5.585-33.017 7.72zm-206.091-2.866c-9.782-1.944-18.654-3.915-26.181-5.776a207.72 207.72 0 0152.147-31.524c-8.691 10.813-17.559 23.385-25.966 37.3zM120.62 127.3c-12.104 27.687-20.691 59.444-22.168 94.739H39.051c4.376-39.236 19.87-75.112 43.187-104.609 8.84 2.654 21.899 6.232 38.382 9.87zM99.638 260.395c3.796 33.422 13.951 64.76 26.918 92.833-16.081 3.579-32.278 8.175-48.492 13.78-22.381-30.344-36.629-66.891-39.589-106.614h61.163zm45.423 128.115c12.212 20.697 25.116 38.605 36.803 53.259-28.391-9.075-54.009-24.2-75.627-43.689 12.978-3.859 25.933-7.066 38.824-9.57z" />
</svg>
</div>
)
}
export default WorldIcon
================================================
FILE: packages/starters/next-corporate/starter/components/layout.js
================================================
import { useState } from "react"
import Navbar from "./elements/navbar"
import Footer from "./elements/footer"
import NotificationBanner from "./elements/notification-banner"
const Layout = ({ children, global, pageContext }) => {
const { navbar, footer, notificationBanner } = global.attributes
const [bannerIsShown, setBannerIsShown] = useState(true)
return (
<div className="flex flex-col justify-between min-h-screen">
{/* Aligned to the top */}
<div className="flex-1">
{notificationBanner && bannerIsShown && (
<NotificationBanner
data={notificationBanner}
closeSelf={() => setBannerIsShown(false)}
/>
)}
<Navbar navbar={navbar} pageContext={pageContext} />
<div>{children}</div>
</div>
{/* Aligned to the bottom */}
<Footer footer={footer} />
</div>
)
}
export default Layout
================================================
FILE: packages/starters/next-corporate/starter/components/locale-switch.js
================================================
import { useEffect, useState, useRef } from "react"
import { useRouter } from "next/router"
import PropTypes from "prop-types"
import Link from "next/link"
import Cookies from "js-cookie"
import { MdExpandMore } from "react-icons/md"
import WorldIcon from "./icons/world"
import { useOnClickOutside } from "../utils/hooks"
const LocaleSwitch = ({ pageContext }) => {
const isMounted = useRef(false)
const select = useRef()
const router = useRouter()
const [locale, setLocale] = useState()
const [showing, setShowing] = useState(false)
const handleLocaleChange = async (selectedLocale) => {
// Persist the user's language preference
// https://nextjs.org/docs/advanced-features/i18n-routing#leveraging-the-next_locale-cookie
Cookies.set("NEXT_LOCALE", selectedLocale)
setLocale(selectedLocale)
}
const handleLocaleChangeRef = useRef(handleLocaleChange)
useOnClickOutside(select, () => setShowing(false))
useEffect(() => {
const localeCookie = Cookies.get("NEXT_LOCALE")
if (!localeCookie) {
handleLocaleChangeRef.current(router.locale)
}
const checkLocaleMismatch = async () => {
if (
!isMounted.current &&
localeCookie &&
localeCookie !== pageContext.locale
) {
// Redirect to locale page if locale mismatch
const localePage = getLocalizedPage(localeCookie, pageContext)
router.push(
`${localizePath({ ...pageContext, ...localePage })}`,
`${localizePath({ ...pageContext, ...localePage })}`,
{ locale: localePage.locale }
)
}
setShowing(false)
}
setLocale(localeCookie || router.locale)
checkLocaleMismatch()
return () => {
isMounted.current = true
}
}, [locale, router, pageContext])
return (
<div ref={select} className="relative ml-4 ">
<button
type="button"
className="hover:bg-primary-50 hover:text-primary-600 focus:bg-primary-50 focus:text-primary-600 focus:outline-none flex items-center justify-between px-2 py-2 cursor-pointer h-full rounded-md w-20"
onClick={() => setShowing(!showing)}
>
<WorldIcon />
<span className="capitalize">{locale}</span>
<MdExpandMore className="ml-1 text-primary-600" />
</button>
<div
className={`w-full bg-white p-1 mt-1 shadow-lg rounded-md ${
showing ? "absolute" : "hidden"
}`}
>
{pageContext.localizedPaths &&
pageContext.localizedPaths.map(({ href, locale }) => {
return (
<Link
href={href}
key={locale}
locale={locale}
role="option"
passHref
>
<p
onClick={() => handleLocaleChange(locale)}
className="capitalize hover:bg-primary-50 hover:text-primary-600 cursor-pointer p-2 rounded-md text-center hover:text-primary-600"
>
{locale}
</p>
</Link>
)
})}
</div>
</div>
)
}
LocaleSwitch.propTypes = {
initialLocale: PropTypes.string,
}
export default LocaleSwitch
================================================
FILE: packages/starters/next-corporate/starter/components/sections/bottom-actions.js
================================================
import ButtonLink from "@/components/elements/button-link"
import { getButtonAppearance } from "utils/button"
const BottomActions = ({ data }) => {
return (
<section className="bg-primary-800 py-20 text-center">
<h2 className="title text-white mb-10">{data.title}</h2>
{/* Buttons row */}
<div className="container flex flex-row justify-center flex-wrap gap-4">
{data.buttons.map((button) => (
<ButtonLink
button={button}
appearance={getButtonAppearance(button.type, "dark")}
key={button.id}
/>
))}
</div>
</section>
)
}
export default BottomActions
================================================
FILE: packages/starters/next-corporate/starter/components/sections/feature-columns-group.js
================================================
import NextImage from "../elements/image"
const FeatureColumnsGroup = ({ data }) => {
return (
<div className="container flex flex-col lg:flex-row lg:flex-wrap gap-12 align-top py-12">
{data.features.map((feature) => (
<div className="flex-1 text-lg" key={feature.id}>
<div className="w-10 h-10">
<NextImage media={feature.icon} />
</div>
<h3 className="font-bold mt-4 mb-4">{feature.title}</h3>
<p>{feature.description}</p>
</div>
))}
</div>
)
}
export default FeatureColumnsGroup
================================================
FILE: packages/starters/next-corporate/starter/components/sections/feature-rows-group.js
================================================
import classNames from "classnames"
import NextImage from "../elements/image"
import Video from "../elements/video"
import CustomLink from "../elements/custom-link"
const FeatureRowsGroup = ({ data }) => {
return (
<div className="container flex flex-col gap-12 py-12">
{data.features.map((feature, index) => (
<div
className={classNames(
// Common classes
"flex flex-col justify-start md:justify-between md:items-center gap-10",
{
"lg:flex-row": index % 2 === 0,
"lg:flex-row-reverse": index % 2 === 1,
}
)}
key={feature.id}
>
{/* Text section */}
<div className="w-full lg:w-6/12 lg:pr-6 text-lg">
<h3 className="title">{feature.title}</h3>
<p className="my-6">{feature.description}</p>
<CustomLink link={feature.link}>
<div className="text-blue-600 with-arrow hover:underline">
{feature.link.text}
</div>
</CustomLink>
</div>
{/* Media section */}
<div className="w-full sm:9/12 lg:w-4/12 max-h-full">
{/* Images */}
{feature.media.data.attributes.mime.startsWith("image") && (
<div className="w-full h-auto">
<NextImage media={feature.media} />
</div>
)}
{/* Videos */}
{feature.media.data.attributes.mime.startsWith("video") && (
<Video
media={feature.media}
className="w-full h-auto"
autoPlay
controls={false}
/>
)}
</div>
</div>
))}
</div>
)
}
export default FeatureRowsGroup
================================================
FILE: packages/starters/next-corporate/starter/components/sections/hero.js
================================================
import Markdown from "react-markdown"
import { getButtonAppearance } from "utils/button"
import ButtonLink from "../elements/button-link"
import NextImage from "../elements/image"
const Hero = ({ data }) => {
return (
<main className="container flex flex-col md:flex-row items-center justify-between py-12">
{/* Left column for content */}
<div className="flex-1 sm:pr-8">
{/* Hero section label */}
<p className="uppercase tracking-wide font-semibold">{data.label}</p>
{/* Big title */}
<h1 className="title mt-2 sm:mt-0 mb-4 sm:mb-2">{data.title}</h1>
{/* Description paragraph */}
<p className="text-xl mb-6">{data.description}</p>
{/* Buttons row */}
<div className="flex flex-row flex-wrap gap-4">
{data.buttons.map((button) => (
<ButtonLink
button={button}
appearance={getButtonAppearance(button.type, "light")}
key={button.id}
/>
))}
</div>
{/* Small rich text */}
<div className="text-base md:text-sm mt-4 sm:mt-3 rich-text-hero">
<Markdown>{data.smallTextWithLink}</Markdown>
</div>
</div>
{/* Right column for the image */}
<div className="flex-shrink-0 w-full md:w-6/12 mt-6 md:mt-0">
<NextImage media={data.picture} />
</div>
</main>
)
}
export default Hero
================================================
FILE: packages/starters/next-corporate/starter/components/sections/large-video.js
================================================
import Video from "../elements/video"
const LargeVideo = ({ data }) => {
return (
<section className="container flex flex-col align-middle text-center pt-12 pb-16">
<h2 className="title mb-6">{data.title}</h2>
<p className="text-lg mb-10">{data.description}</p>
{/* Video wrapper */}
<div className="w-full lg:w-9/12 mx-auto overflow-hidden shadow-2xl">
<Video
media={data.video}
poster={data.poster}
className="w-full max-h-full"
/>
</div>
</section>
)
}
export default LargeVideo
================================================
FILE: packages/starters/next-corporate/starter/components/sections/lead-form.js
================================================
import { useState } from "react"
import { fetchAPI } from "utils/api"
import * as yup from "yup"
import { Formik, Form, Field } from "formik"
import Button from "../elements/button"
const LeadForm = ({ data }) => {
const [loading, setLoading] = useState(false)
const LeadSchema = yup.object().shape({
email: yup.string().email().required(),
})
return (
<div className="py-10 text-center">
<h1 className="text-3xl mb-10 font-bold mb-2">{data.title}</h1>
<div className="flex flex-col items-center">
<Formik
initialValues={{ email: "" }}
validationSchema={LeadSchema}
onSubmit={async (values, { setSubmitting, setErrors }) => {
setLoading(true)
try {
setErrors({ api: null })
await fetchAPI(
"/lead-form-submissions",
{},
{
method: "POST",
body: JSON.stringify({
data: {
email: values.email,
location: data.location,
},
}),
}
)
} catch (err) {
setErrors({ api: err.message })
}
setLoading(false)
setSubmitting(false)
}}
>
{({ errors, touched, isSubmitting }) => (
<div>
<Form className="flex flex-col md:flex-row gap-4">
<Field
className="text-base focus:outline-none py-4 md:py-0 px-4 border-2 rounded-md"
type="email"
name="email"
placeholder={data.emailPlaceholder}
/>
<Button
type="submit"
button={data.submitButton}
disabled={isSubmitting}
loading={loading}
/>
</Form>
<p className="text-red-500 h-12 text-sm mt-1 ml-2 text-left">
{(errors.email && touched.email && errors.email) || errors.api}
</p>
</div>
)}
</Formik>
</div>
</div>
)
}
export default LeadForm
================================================
FILE: packages/starters/next-corporate/starter/components/sections/pricing.js
================================================
import { MdCheckBox } from "react-icons/md"
import classNames from "classnames"
const Pricing = ({ data }) => {
return (
<div className="container py-12">
<h1 className="text-4xl text-center">{data.title}</h1>
<div className="flex flex-col lg:flex-row gap-4 lg:justify-center mt-6">
{data.plans.map((plan) => (
<div
className={classNames(
// Common classes
"rounded-md border-2 py-4 px-4 flex-1 md:w-lg",
// Normal plan
{
"bg-gray-100 text-gray-900 border-gray-300":
!plan.isRecommended,
},
// Recommended plan
{
"bg-primary-100 text-primary-900 border-primary-300":
plan.isRecommended,
}
)}
key={plan.id}
>
<h2 className="text-2xl">{plan.name}</h2>
<p
className={classNames("mt-4 text-lg", {
"text-primary-700": plan.isRecommended,
"text-gray-700": !plan.isRecommended,
})}
>
{plan.description}
</p>
<p className="text-3xl mt-4">
{plan.price === 0 ? "Free " : `$${plan.price} `}
<span className="text-base font-medium">{plan.pricePeriod}</span>
</p>
<ul className="mt-4 flex flex-col gap-3">
{plan.features.map((feature) => (
<li
className="flex flex-row justify-between items-center"
key={feature.id}
>
<span>{feature.name}</span>
<MdCheckBox className="h-6 w-auto text-gray-900" />
</li>
))}
</ul>
</div>
))}
</div>
</div>
)
}
export default Pricing
================================================
FILE: packages/starters/next-corporate/starter/components/sections/rich-text.js
================================================
import PropTypes from "prop-types"
import Markdown from "react-markdown"
const RichText = ({ data }) => {
return (
<div className="prose prose-lg container py-12">
<Markdown>{data.content}</Markdown>
</div>
)
}
RichText.propTypes = {
data: PropTypes.shape({
content: PropTypes.string,
}),
}
export default RichText
================================================
FILE: packages/starters/next-corporate/starter/components/sections/testimonials-group.js
================================================
import classNames from "classnames"
import { useState } from "react"
import NextImage from "../elements/image"
import CustomLink from "../elements/custom-link"
const TestimonialsGroup = ({ data }) => {
// Only show one testimonial at a time
const [selectedTestimonialIndex, setSelectedTestimonialIndex] = useState(0)
const selectedTestimonial = data.testimonials[selectedTestimonialIndex]
return (
<section className="text-center text-lg bg-gray-200 pt-12 pb-16">
<h2 className="title mb-4">{data.title}</h2>
<p className="text-gray-700 mb-4">{data.description}</p>
<CustomLink link={data.link}>
<span className="with-arrow text-blue-700 hover:underline">
{data.link.text}
</span>
</CustomLink>
{/* Current testimonial card */}
<div className="max-w-5xl w-8/12 sm:w-8/12 bg-white shadow-md sm:shadow-xl mx-auto flex flex-col sm:flex-row mt-10 text-left">
<div className="w-full md:w-4/12 flex-shrink-0">
<NextImage media={selectedTestimonial.picture} />
</div>
<div className="px-4 py-4 sm:px-12 sm:pt-12 sm:pb-4 flex flex-col justify-between">
<div>
<NextImage
width="120"
height="33"
media={selectedTestimonial.logo}
/>
<p className="italic mb-6">
"{selectedTestimonial.text}"
</p>
<p className="font-bold text-base sm:text-sm">
{selectedTestimonial.authorName}
</p>
<p className="text-base sm:text-sm">
{selectedTestimonial.authorTitle}
</p>
</div>
<CustomLink
link={{
url: selectedTestimonial.link,
text: "",
newTab: false,
id: 0,
}}
>
<span className="uppercase tracking-wide text-blue-700 hover:underline with-arrow sm:self-end mt-6 sm:mt-0">
Read story
</span>
</CustomLink>
</div>
</div>
{/* Change selected testimonial (only if there is more than one) */}
{data.testimonials.length > 1 && (
<div className="flex flex-row gap-4 mt-10 justify-center">
{data.testimonials.map((testimonial, index) => (
<button
onClick={() => setSelectedTestimonialIndex(index)}
className={classNames(
// Common classes
"rounded-full h-3 w-3",
{
"bg-gray-500": index !== selectedTestimonialIndex,
"bg-primary-600": index === selectedTestimonialIndex,
}
)}
key={testimonial.id}
/>
))}
</div>
)}
{/* Logos list */}
<div className="flex flex-row flex-wrap items-center gap-6 sm:gap-20 justify-center mt-10 px-6 sm:px-0 ">
{data.logos.map((logo) => (
<NextImage key={logo.id} width="120" height="33" media={logo.logo} />
))}
</div>
</section>
)
}
export default TestimonialsGroup
================================================
FILE: packages/starters/next-corporate/starter/components/sections.js
================================================
import { useRouter } from "next/router"
import Hero from "@/components/sections/hero"
import LargeVideo from "@/components/sections/large-video"
import FeatureColumnsGroup from "@/components/sections/feature-columns-group"
import FeatureRowsGroup from "@/components/sections/feature-rows-group"
import BottomActions from "@/components/sections/bottom-actions"
import TestimonialsGroup from "@/components/sections/testimonials-group"
import RichText from "./sections/rich-text"
import Pricing from "./sections/pricing"
import LeadForm from "./sections/lead-form"
// Map Strapi sections to section components
const sectionComponents = {
ComponentSectionsHero: Hero,
ComponentSectionsLargeVideo: LargeVideo,
ComponentSectionsFeatureColumnsGroup: FeatureColumnsGroup,
ComponentSectionsFeatureRowsGroup: FeatureRowsGroup,
ComponentSectionsBottomActions: BottomActions,
ComponentSectionsTestimonialsGroup: TestimonialsGroup,
ComponentSectionsRichText: RichText,
ComponentSectionsPricing: Pricing,
ComponentSectionsLeadForm: LeadForm,
}
// Display a section individually
const Section = ({ sectionData }) => {
// Prepare the component
const SectionComponent = sectionComponents[sectionData.__typename]
if (!SectionComponent) {
return null
}
// Display the section
return <SectionComponent data={sectionData} />
}
const PreviewModeBanner = () => {
const router = useRouter()
const exitURL = `/api/exit-preview?redirect=${encodeURIComponent(
router.asPath
)}`
return (
<div className="py-4 bg-red-600 text-red-100 font-semibold uppercase tracking-wide">
<div className="container">
Preview mode is on.{" "}
<a
className="underline"
href={`/api/exit-preview?redirect=${router.asPath}`}
>
Turn off
</a>
</div>
</div>
)
}
// Display the list of sections
const Sections = ({ sections, preview }) => {
return (
<div className="flex flex-col">
{/* Show a banner if preview mode is on */}
{preview && <PreviewModeBanner />}
{/* Show the actual sections */}
{sections.map((section) => (
<Section
sectionData={section}
key={`${section.__typename}${section.id}`}
/>
))}
</div>
)
}
export default Sections
================================================
FILE: packages/starters/next-corporate/starter/jsconfig.json
================================================
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/components/*": [
"components/*"
],
"@/lib/*": [
"lib/*"
],
"@/styles/*": [
"styles/*"
]
}
}
}
================================================
FILE: packages/starters/next-corporate/starter/next.config.js
================================================
module.exports = {
i18n: {
locales: ['en', 'fr'],
defaultLocale: 'en',
},
}
================================================
FILE: packages/starters/next-corporate/starter/package.json
================================================
{
"name": "my-next-corporate",
"version": "1.0.7",
"private": true,
"scripts": {
"dev": "next",
"develop": "next",
"build": "next build",
"start": "next start",
"lint": "next lint",
"lint:fix": "next lint --fix"
},
"dependencies": {
"@tailwindcss/typography": "^0.4.1",
"classnames": "^2.2.6",
"cookie": "^0.4.1",
"date-fns": "2.14.0",
"formik": "^2.2.6",
"js-cookie": "3.0.0-rc.4",
"next": "^11.0.0",
"next-seo": "^4.7.1",
"postcss-import": "^14.0.2",
"prop-types": "^15.7.2",
"qs": "^6.10.1",
"react": "^17.0.0",
"react-dom": "^17.0.0",
"react-icons": "^3.10.0",
"react-markdown": "^6.0.2",
"tailwindcss": "^2.2.6",
"yup": "^0.32.8"
},
"devDependencies": {
"autoprefixer": "^10.3.1",
"eslint": "^7.30.0",
"eslint-config-next": "^11.0.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^3.4.0",
"postcss": "8.3.6",
"postcss-flexbugs-fixes": "^5.0.2",
"postcss-nested": "^5.0.5",
"postcss-preset-env": "^6.7.0",
"prettier": "^2.3.1"
}
}
================================================
FILE: packages/starters/next-corporate/starter/pages/[[...slug]].js
================================================
import ErrorPage from "next/error"
import { getPageData, fetchAPI, getGlobalData } from "utils/api"
import Sections from "@/components/sections"
import Seo from "@/components/elements/seo"
import { useRouter } from "next/router"
import Layout from "@/components/layout"
import { getLocalizedPaths } from "utils/localize"
// The file is called [[...slug]].js because we're using Next's
// optional catch all routes feature. See the related docs:
// https://nextjs.org/docs/routing/dynamic-routes#optional-catch-all-routes
const DynamicPage = ({ sections, metadata, preview, global, pageContext }) => {
const router = useRouter()
// Check if the required data was provided
if (!router.isFallback && !sections?.length) {
return <ErrorPage statusCode={404} />
}
// Loading screen (only possible in preview mode)
if (router.isFallback) {
return <div className="container">Loading...</div>
}
// Merge default site SEO settings with page specific SEO settings
if (metadata.shareImage?.data == null) {
delete metadata.shareImage
}
const metadataWithDefaults = {
...global.attributes.metadata,
...metadata,
}
return (
<Layout global={global} pageContext={pageContext}>
{/* Add meta tags for SEO*/}
<Seo metadata={metadataWithDefaults} />
{/* Display content sections */}
<Sections sections={sections} preview={preview} />
</Layout>
)
}
export async function getStaticPaths(context) {
// Get all pages from Strapi
const pages = await context.locales.reduce(
async (currentPagesPromise, locale) => {
const currentPages = await currentPagesPromise
const localePages = await fetchAPI("/pages", {
locale,
fields: ["slug", "locale"],
})
return [...currentPages, ...localePages.data]
},
Promise.resolve([])
)
const paths = pages.map((page) => {
const { slug, locale } = page.attributes
// Decompose the slug that was saved in Strapi
const slugArray = !slug ? false : slug.split("/")
return {
params: { slug: slugArray },
// Specify the locale to render
locale,
}
})
return { paths, fallback: true }
}
export async function getStaticProps(context) {
const { params, locale, locales, defaultLocale, preview = null } = context
const globalLocale = await getGlobalData(locale)
// Fetch pages. Include drafts if preview mode is on
const pageData = await getPageData({
slug: (!params.slug ? [""] : params.slug).join("/"),
locale,
preview,
})
if (pageData == null) {
// Giving the page no props will trigger a 404 page
return { props: {} }
}
// We have the required page data, pass it to the page component
const { contentSections, metadata, localizations, slug } = pageData.attributes
const pageContext = {
locale,
locales,
defaultLocale,
slug,
localizations,
}
const localizedPaths = getLocalizedPaths(pageContext)
return {
props: {
preview,
sections: contentSections,
metadata,
global: globalLocale.data,
pageContext: {
...pageContext,
localizedPaths,
},
},
}
}
export default DynamicPage
================================================
FILE: packages/starters/next-corporate/starter/pages/_app.js
================================================
import App from "next/app"
import Head from "next/head"
import ErrorPage from "next/error"
import { useRouter } from "next/router"
import { DefaultSeo } from "next-seo"
import { getStrapiMedia } from "utils/media"
import { getGlobalData } from "utils/api"
import "@/styles/index.css"
const MyApp = ({ Component, pageProps }) => {
// Extract the data we need
const { global } = pageProps
if (global == null) {
return <ErrorPage statusCode={404} />
}
const { metadata, favicon, metaTitleSuffix } = global.attributes
return (
<>
{/* Favicon */}
<Head>
<link
rel="shortcut icon"
href={getStrapiMedia(favicon.data.attributes.url)}
/>
</Head>
{/* Global site metadata */}
<DefaultSeo
titleTemplate={`%s | ${metaTitleSuffix}`}
title="Page"
description={metadata.metaDescription}
openGraph={{
images: Object.values(
metadata.shareImage.data.attributes.formats
).map((image) => {
return {
url: getStrapiMedia(image.url),
width: image.width,
height: image.height,
}
}),
}}
twitter={{
cardType: metadata.twitterCardType,
handle: metadata.twitterUsername,
}}
/>
{/* Display the content */}
<Component {...pageProps} />
</>
)
}
// getInitialProps disables automatic static optimization for pages that don't
// have getStaticProps. So [[...slug]] pages still get SSG.
// Hopefully we can replace this with getStaticProps once this issue is fixed:
// https://github.com/vercel/next.js/discussions/10949
MyApp.getInitialProps = async (appContext) => {
// Calls page's `getInitialProps` and fills `appProps.pageProps`
const appProps = await App.getInitialProps(appContext)
const globalLocale = await getGlobalData(appContext.router.locale)
return {
...appProps,
pageProps: {
global: globalLocale,
},
}
}
export default MyApp
================================================
FILE: packages/starters/next-corporate/starter/pages/_document.js
================================================
import Document, { Html, Head, Main, NextScript } from "next/document"
export default class MyDocument extends Document {
render() {
return (
<Html lang="en">
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
================================================
FILE: packages/starters/next-corporate/starter/pages/api/exit-preview.js
================================================
import { redirect } from "next/dist/next-server/server/api-utils"
export default async function exit(req, res) {
// Exit the current user from "Preview Mode". This function accepts no args.
res.clearPreviewData()
// Redirect the user back to a provided redirect path or the index page
res.writeHead(307, { Location: "/" })
res.end()
}
================================================
FILE: packages/starters/next-corporate/starter/pages/api/preview.js
================================================
import { getPageData } from "utils/api"
import { parseCookies } from "utils/parse-cookies"
const preview = async (req, res) => {
// Check the secret and next parameters
// This secret should only be known to this API route and the CMS
if (req.query.secret !== (process.env.PREVIEW_SECRET || "secret-token")) {
return res.status(401).json({ message: "Invalid token" })
}
const cookies = parseCookies(req)
const slugArray = req.query.slug.split("/")
// Fetch the headless CMS to check if the provided `slug` exists
const pageData = await getPageData({
locale,
slug: slugArray.join("/"),
preview: true,
})
// If the slug doesn't exist prevent preview mode from being enabled
if (!pageData) {
return res.status(401).json({ message: "Invalid slug" })
}
// Enable Preview Mode by setting the cookies
res.setPreviewData({})
// Redirect to the path from the fetched post
// We don't redirect to req.query.slug as that might lead to open redirect vulnerabilities
// Prefix with locale so previews are available in all languages
res.writeHead(307, {
Location: `/${pageData.locale}/${pageData.slug}`,
})
res.end()
}
export default preview
// You can view Preview pages with URLs like this:
// http://localhost:3000/api/preview?secret=<preview-secret>&slug=<slug>
// where <preview-secret> is the secret token defined in your .env config
// and where <slug> is the slug you entered in Strapi for your page
// The slug must match the current locale
================================================
FILE: packages/starters/next-corporate/starter/postcss.config.js
================================================
module.exports = {
plugins: [
"postcss-import",
"tailwindcss",
"postcss-flexbugs-fixes",
"postcss-nesting",
"postcss-custom-properties",
"autoprefixer",
],
}
================================================
FILE: packages/starters/next-corporate/starter/public/.gitkeep
================================================
================================================
FILE: packages/starters/next-corporate/starter/styles/index.css
================================================
@tailwind base;
@tailwind components;
@tailwind utilities;
html {
font-size: 13px;
@apply text-gray-900;
}
@media screen and (min-width: 640px) {
html {
font-size: 14px;
}
}
@media screen and (min-width: 768px) {
html {
font-size: 15px;
}
}
.rich-text-hero a {
@apply text-blue-600 underline;
}
.rich-text-banner {
@apply whitespace-pre-line;
a {
@apply underline;
}
}
.title {
@apply text-4xl leading-snug font-semibold;
}
@media (min-width: 768px) {
.title {
@apply text-5xl;
}
}
.with-arrow:after {
background-image: url(data:image/svg+xml;charset=utf-8,%3Csvg%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M1%206a.5.5%200%200%200%200%201V6zM12.854.646a.5.5%200%200%200-.708.708l.708-.708zM18%206.5l.354.354a.5.5%200%200%200%200-.708L18%206.5zm-5.854%205.146a.5.5%200%200%200%20.708.708l-.708-.708zM1%207h16.5V6H1v1zm16.646-.854l-5.5%205.5.708.708%205.5-5.5-.708-.708zm-5.5-4.792l2.75%202.75.708-.708-2.75-2.75-.708.708zm2.75%202.75l2.75%202.75.708-.708-2.75-2.75-.708.708z%22%20fill%3D%22%231264A3%22%2F%3E%3C%2Fsvg%3E);
content: "";
width: 19px;
height: 13px;
display: inline-block;
margin-left: 0.5em;
}
================================================
FILE: packages/starters/next-corporate/starter/tailwind.config.js
================================================
const { colors } = require(`tailwindcss/defaultTheme`)
module.exports = {
mode: "jit", // see https://tailwindcss.com/docs/just-in-time-mode
purge: ["./components/**/*.js", "./pages/**/*.js"],
darkMode: false, // or "media" or "class"
theme: {
extend: {
colors: {
primary: colors.indigo,
},
container: {
center: true,
padding: {
DEFAULT: "1rem",
md: "2rem",
},
},
},
screens: {
sm: "640px",
md: "768px",
lg: "1024px",
xl: "1280px",
},
},
variants: {
extend: {},
},
plugins: [require("@tailwindcss/typography")],
}
================================================
FILE: packages/starters/next-corporate/starter/utils/api.js
================================================
import qs from "qs"
export function getStrapiURL(path) {
return `${
process.env.NEXT_PUBLIC_STRAPI_API_URL || "http://localhost:1337"
}${path}`
}
/**
* Helper to make GET requests to Strapi API endpoints
* @param {string} path Path of the API route
* @param {Object} urlParamsObject URL params object, will be stringified
* @param {RequestInit} options Options passed to fetch
* @returns Parsed API call response
*/
export async function fetchAPI(path, urlParamsObject = {}, options = {}) {
// Merge default and user options
const mergedOptions = {
headers: {
"Content-Type": "application/json",
},
...options,
}
// Build request URL
const queryString = qs.stringify(urlParamsObject)
const requestUrl = `${getStrapiURL(
`/api${path}${queryString ? `?${queryString}` : ""}`
)}`
// Trigger API call
const response = await fetch(requestUrl, mergedOptions)
// Handle response
if (!response.ok) {
console.error(response.statusText)
throw new Error(`An error occurred please try again`)
}
const data = await response.json()
return data
}
/**
*
* @param {Object} options
* @param {string} options.slug The page's slug
* @param {string} options.locale The current locale specified in router.locale
* @param {boolean} options.preview router isPreview value
*/
export async function getPageData({ slug, locale, preview }) {
// Find the pages that match this slug
const gqlEndpoint = getStrapiURL("/graphql")
const pagesRes = await fetch(gqlEndpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
query: `
fragment FileParts on UploadFileEntityResponse {
data {
id
attributes {
alternativeText
width
height
mime
url
formats
}
}
}
query GetPages(
$slug: String!
$publicationState: PublicationState!
$locale: I18NLocaleCode!
) {
pages(
filters: { slug: { eq: $slug } }
publicationState: $publicationState
locale: $locale
) {
data {
id
attributes {
locale
localizations {
data {
id
attributes {
locale
}
}
}
slug
metadata {
metaTitle
metaDescription
shareImage {
...FileParts
}
twitterCardType
twitterUsername
}
contentSections {
__typename
... on ComponentSectionsBottomActions {
id
title
buttons {
id
newTab
text
type
url
}
}
... on ComponentSectionsHero {
id
buttons {
id
newTab
text
type
url
}
title
description
label
picture {
...FileParts
}
}
... on ComponentSectionsFeatureColumnsGroup {
id
features {
id
description
icon {
...FileParts
}
title
}
}
... on ComponentSectionsFeatureRowsGroup {
id
features {
id
description
link {
id
newTab
text
url
}
media {
...FileParts
}
title
}
}
... on ComponentSectionsTestimonialsGroup {
id
description
link {
id
newTab
text
url
}
logos {
id
title
logo {
...FileParts
}
}
testimonials {
id
logo {
...FileParts
}
picture {
...FileParts
}
text
authorName
authorTitle
link
}
title
}
... on ComponentSectionsLargeVideo {
id
description
title
poster {
...FileParts
}
video {
...FileParts
}
}
... on ComponentSectionsRichText {
id
content
}
... on ComponentSectionsPricing {
id
title
plans {
description
features {
id
name
}
id
isRecommended
name
price
pricePeriod
}
}
... on ComponentSectionsLeadForm {
id
emailPlaceholder
location
submitButton {
id
text
type
}
title
}
}
}
}
}
}
`,
variables: {
slug,
publicationState: preview ? "PREVIEW" : "LIVE",
locale,
},
}),
})
const pagesData = await pagesRes.json()
// Make sure we found something, otherwise return null
if (pagesData.data?.pages == null || pagesData.data.pages.length === 0) {
return null
}
// Return the first item since there should only be one result per slug
return pagesData.data.pages.data[0]
}
// Get site data from Strapi (metadata, navbar, footer...)
export async function getGlobalData(locale) {
const gqlEndpoint = getStrapiURL("/graphql")
const globalRes = await fetch(gqlEndpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
query: `
fragment FileParts on UploadFileEntityResponse {
data {
id
attributes {
alternativeText
width
height
mime
url
formats
}
}
}
query GetGlobal($locale: I18NLocaleCode!) {
global(locale: $locale) {
data {
id
attributes {
favicon {
...FileParts
}
metadata {
metaTitle
metaDescription
shareImage {
...FileParts
}
twitterCardType
twitterUsername
}
metaTitleSuffix
notificationBanner {
type
text
}
navbar {
logo {
...FileParts
}
links {
id
url
newTab
text
}
button {
id
url
newTab
text
type
}
}
footer {
logo {
...FileParts
}
smallText
columns {
id
title
links {
id
url
newTab
text
}
}
}
}
}
}
}
`,
variables: {
locale,
},
}),
})
const global = await globalRes.json()
return global.data.global
}
================================================
FILE: packages/starters/next-corporate/starter/utils/button.js
================================================
// Decide what the button will look like based on its type (primary or secondary)
// and on its background (light or dark).
export function getButtonAppearance(type, background) {
if (type === 'primary') {
if (background === 'light') {
// Dark primary button on a light background
return 'dark'
}
// Fully white primary button on a dark background
return 'white'
}
if (type === 'secondary') {
if (background === 'light') {
// Dark outline primary button on a light background
return 'dark-outline'
}
// White outline primary button on a dark background
return 'white-outline'
}
// Shouldn't happen, but default to dark button just in case
return 'dark'
}
================================================
FILE: packages/starters/next-corporate/starter/utils/hooks.js
================================================
import { useEffect } from 'react'
// Got from https://usehooks.com/useLockBodyScroll/
export function useLockBodyScroll() {
useEffect(() => {
// Get original body overflow
const originalStyle = window.getComputedStyle(document.body).overflow
// Prevent scrolling on mount
document.body.style.overflow = 'hidden'
// Re-enable scrolling when component unmounts
return () => (document.body.style.overflow = originalStyle)
}, []) // Empty array ensures effect is only run on mount and unmount
}
export function useOnClickOutside(ref, handler) {
useEffect(() => {
const listener = (event) => {
// Do nothing if clicking ref's element or descendent elements
if (!ref.current || ref.current.contains(event.target)) {
return
}
handler(event)
}
document.addEventListener('mousedown', listener)
document.addEventListener('touchstart', listener)
return () => {
document.removeEventListener('mousedown', listener)
document.removeEventListener('touchstart', listener)
}
}, [ref, handler])
}
================================================
FILE: packages/starters/next-corporate/starter/utils/localize.js
================================================
import { fetchAPI } from "./api"
export async function getLocalizedPage(targetLocale, pageContext) {
const localization = pageContext.localizations.data.find(
(localization) => localization.attributes.locale === targetLocale
)
const localePage = await fetchAPI(`/pages/${localization.id}`)
return localePage
}
export function localizePath(page) {
const { locale, defaultLocale, slug } = page
if (locale === defaultLocale) {
// The default locale is not prefixed
return `/${slug}`
}
// The slug should have a localePrefix
return `/${locale}/${slug}`
}
export function getLocalizedPaths(page) {
const paths = page.locales.map((locale) => {
return {
locale: locale,
href: localizePath({ ...page, locale }),
}
})
return paths
}
================================================
FILE: packages/starters/next-corporate/starter/utils/media.js
================================================
export function getStrapiMedia(url) {
if (url == null) {
return null
}
// Return the full URL if the media is hosted on an external provider
if (url.startsWith("http") || url.startsWith("//")) {
return url
}
// Otherwise prepend the URL path with the Strapi URL
return `${
process.env.NEXT_PUBLIC_STRAPI_API_URL || "http://localhost:1337"
}${url}`
}
================================================
FILE: packages/starters/next-corporate/starter/utils/parse-cookies.js
================================================
import cookie from "cookie"
export function parseCookies(req) {
return cookie.parse(req ? req.headers.cookie || '' : document.cookie)
}
================================================
FILE: packages/starters/next-corporate/starter/utils/types.js
================================================
import PropTypes from "prop-types"
export const linkPropTypes = PropTypes.shape({
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
url: PropTypes.string.isRequired,
text: PropTypes.string.isRequired,
newTab: PropTypes.bool,
})
export const mediaPropTypes = PropTypes.shape({
data: PropTypes.shape({
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
attributes: PropTypes.shape({
alternativeText: PropTypes.string,
mime: PropTypes.string.isRequired,
url: PropTypes.string.isRequired,
}),
}),
})
export const buttonLinkPropTypes = PropTypes.shape({
theme: PropTypes.string,
text: PropTypes.string.isRequired,
newTab: PropTypes.bool,
})
================================================
FILE: packages/starters/next-corporate/starter.json
================================================
{
"template": {
"name": "@strapi/template-corporate",
"version": "^1.0.0"
}
}
================================================
FILE: packages/templates/.gitkeep
================================================
================================================
FILE: packages/templates/blog/README.md
================================================
# strapi-template-blog
A Strapi template to create Strapi projects pre-configured for blog apps.
## Usage
```bash
# Using Yarn
yarn create strapi-app my-app-name --template blog
# Or using NPM
npx create-strapi-app my-app-name --template blog
```
## Starters
This template is used by the following starters:
* [strapi-starter-next-blog](https://github.com/strapi/strapi-starter-next-blog)
* [strapi-starter-nuxt-blog](https://github.com/strapi/strapi-starter-nuxt-blog)
* [strapi-starter-gatsby-blog](https://github.com/strapi/strapi-starter-gatsby-blog)
* [strapi-starter-gridsome-blog](https://github.com/strapi/strapi-starter-gridsome-blog)
================================================
FILE: packages/templates/blog/package.json
================================================
{
"name": "@strapi/template-blog",
"version": "2.0.5",
"description": "Strapi blog template",
"keywords": [
"strapi",
"template",
"blog"
],
"homepage": "https://github.com/strapi/starters-and-templates#readme",
"bugs": {
"url": "https://github.com/strapi/starters-and-templates/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/strapi/starters-and-templates.git"
},
"license": "MIT",
"author": {
"name": "Strapi team",
"email": "hi@strapi.io",
"url": "https://strapi.io"
},
"maintainers": [
{
"name": "Strapi team",
"email": "hi@strapi.io",
"url": "https://strapi.io"
}
],
"gitHead": "d6d5bd264d1df0a0f23b5234d84348f8d78882e3"
}
================================================
FILE: packages/templates/blog/template/data/data.json
================================================
{
"global": {
"siteName": "Strapi Blog",
"defaultSeo": {
"metaTitle": "Page",
"metaDescription": "A blog made with Strapi",
"shareImage": null
},
"siteDescription": "A Blog made with Strapi",
"favicon": null
},
"about": {
"title": "About the strapi blog",
"blocks": [
{
"__component": "shared.quote",
"title": "Thelonius Monk",
"body": "You've got to dig it to dig it, you dig?"
},
{
"__component": "shared.rich-text",
"body": "## Dedit imago conspicuus cum capillis totidem inhibere\n\nLorem markdownum **rerum**, est limine: columbas: ab infelix hostem arbore nudis\ncrudelis. Videtur reliquit ambo ferrum dote sub amne fatis **illuc**, in magis,\nnec."
},
{
"__component": "shared.media",
"file": "coffee-art.jpg"
}
]
},
"categories": [
{
"name": "news",
"slug": "news"
},
{
"name": "tech",
"slug": "tech"
},
{
"name": "food",
"slug": "food"
},
{
"name": "nature",
"slug": "nature"
},
{
"name": "story",
"slug": "story"
}
],
"authors": [
{
"name": "David Doe",
"email": "daviddoe@strapi.io",
"avatar": "daviddoe@strapi.io.jpg"
},
{
"name": "Sarah Baker",
"email": "sarahbaker@strapi.io",
"avatar": "sarahbaker@strapi.io.jpg"
}
],
"articles": [
{
"title": "The internet's Own boy",
"slug": "the-internet-s-own-boy",
"category": {
"id": 5
},
"author": {
"id": 1
},
"description": "Follow the story of Aaron Swartz, the boy who could change the world",
"cover": null,
"blocks": [
{
"__component": "shared.rich-text",
"body": "## Probant \n\nse Lorem markdownum negat. Argo *saxa* videnda cornuaque hunc qui tanta spes teneas! Obliquis est dicenti est salutat ille tamen iuvenum nostrae dolore. - Colores nocituraque comitata eripiunt - Addit quodcunque solum cui et dextram illis - Nulli meus nec extemplo ille ferebat pressit Se blandita fulvae vox gravem Pittheus cesserunt sanguine herbis tu comitum tenuit. Sui in ruunt; Doridaque maculosae fuissem! Et loqui. \n\n## Abit sua\n\nse Lorem markdownum negat. Argo *saxa* videnda cornuaque hunc qui tanta spes teneas! Obliquis est dicenti est salutat ille tamen iuvenum nostrae dolore. - Colores nocituraque comitata eripiunt - Addit quodcunque solum cui et dextram illis - Nulli meus nec extemplo ille ferebat pressit Se blandita fulvae vox gravem Pittheus cesserunt sanguine herbis tu comitum tenuit. Sui in ruunt; Doridaque maculosae fuissem! Et loqui. "
},
{
"__component": "shared.quote",
"title": "Thelonius Monk",
"body": "You've got to dig it to dig it, you dig?"
},
{
"__component": "shared.media",
"file": "coffee-art.jpg"
},
{
"__component": "shared.rich-text",
"body": "## Spatiantia astra \n\nFoeda, medio silva *errandum*: onus formam munere. Mutata bibulis est auxiliare arces etiamnunc verbis virgineo Priamidas illa Thescelus, nam fit locis lucis auras. Exitus hospes gratulor ut pondere [speslimite](http://www.curas.io/figuram); quid habent, Avernales faciente de. Pervenit Ino sonabile supplex cognoscenti vires, Bacchumque errat miserarum venandi dignabere dedisti. Discrimina iuncosaque virgaque tot sine superest [fissus](http://quos.org/sitet.aspx). Non color esset potest non sumit, sed vix arserat. Nisi immo silva tantum pectusque quos pennis quisquam artus!"
},
{
"__component": "shared.slider",
"files": [
"coffee-art.jpg",
"coffee-beans.jpg"
]
}
]
},
{
"title": "This shrimp is awesome",
"slug": "this-shrimp-is-awesome",
"category": {
"id": 4
},
"author": {
"id": 1
},
"description": "Mantis shrimps, or stomatopods, are marine crustaceans of the order Stomatopoda.",
"cover": null,
"blocks": [
{
"__component": "shared.rich-text",
"body": "## Probant \n\nse Lorem markdownum negat. Argo *saxa* videnda cornuaque hunc qui tanta spes teneas! Obliquis est dicenti est salutat ille tamen iuvenum nostrae dolore. - Colores nocituraque comitata eripiunt - Addit quodcunque solum cui et dextram illis - Nulli meus nec extemplo ille ferebat pressit Se blandita fulvae vox gravem Pittheus cesserunt sanguine herbis tu comitum tenuit. Sui in ruunt; Doridaque maculosae fuissem! Et loqui. \n\n## Abit sua\n\nse Lorem markdownum negat. Argo *saxa* videnda cornuaque hunc qui tanta spes teneas! Obliquis est dicenti est salutat ille tamen iuvenum nostrae dolore. - Colores nocituraque comitata eripiunt - Addit quodcunque solum cui et dextram illis - Nulli meus nec extemplo ille ferebat pressit Se blandita fulvae vox gravem Pittheus cesserunt sanguine herbis tu comitum tenuit. Sui in ruunt; Doridaque maculosae fuissem! Et loqui. "
},
{
"__component": "shared.quote",
"title": "Thelonius Monk",
"body": "You've got to dig it to dig it, you dig?"
},
{
"__component": "shared.media",
"file": "coffee-art.jpg"
},
{
"__component": "shared.rich-text",
"body": "## Spatiantia astra \n\nFoeda, medio silva *errandum*: onus formam munere. Mutata bibulis est auxiliare arces etiamnunc verbis virgineo Priamidas illa Thescelus, nam fit locis lucis auras. Exitus hospes gratulor ut pondere [speslimite](http://www.curas.io/figuram); quid habent, Avernales faciente de. Pervenit Ino sonabile supplex cognoscenti vires, Bacchumque errat miserarum venandi dignabere dedisti. Discrimina iuncosaque virgaque tot sine superest [fissus](http://quos.org/sitet.aspx). Non color esset potest non sumit, sed vix arserat. Nisi immo silva tantum pectusque quos pennis quisquam artus!"
},
{
"__component": "shared.slider",
"files": [
"coffee-art.jpg",
"coffee-beans.jpg"
]
}
]
},
{
"title": "A bug is becoming a meme on the internet",
"slug": "a-bug-is-becoming-a-meme-on-the-internet",
"category": {
"id": 2
},
"author": {
"id": 2
},
"description": "How a bug on MySQL is becoming a meme on the internet",
"cover": null,
"blocks": [
{
"__component": "shared.rich-text",
"body": "## Probant \n\nse Lorem markdownum negat. Argo *saxa* videnda cornuaque hunc qui tanta spes teneas! Obliquis est dicenti est salutat ille tamen iuvenum nostrae dolore. - Colores nocituraque comitata eripiunt - Addit quodcunque solum cui et dextram illis - Nulli meus nec extemplo ille ferebat pressit Se blandita fulvae vox gravem Pittheus cesserunt sanguine herbis tu comitum tenuit. Sui in ruunt; Doridaque maculosae fuissem! Et loqui. \n\n## Abit sua\n\nse Lorem markdownum negat. Argo *saxa* videnda cornuaque hunc qui tanta spes teneas! Obliquis est dicenti est salutat ille tamen iuvenum nostrae dolore. - Colores nocituraque comitata eripiunt - Addit quodcunque solum cui et dextram illis - Nulli meus nec extemplo ille ferebat pressit Se blandita fulvae vox gravem Pittheus cesserunt sanguine herbis tu comitum tenuit. Sui in ruunt; Doridaque maculosae fuissem! Et loqui. "
},
{
"__component": "shared.quote",
"title": "Thelonius Monk",
"body": "You've got to dig it to dig it, you dig?"
},
{
"__component": "shared.media",
"file": "coffee-art.jpg"
},
{
"__component": "shared.rich-text",
"body": "## Spatiantia astra \n\nFoeda, medio silva *errandum*: onus formam munere. Mutata bibulis est auxiliare arces etiamnunc verbis virgineo Priamidas illa Thescelus, nam fit locis lucis auras. Exitus hospes gratulor ut pondere [speslimite](http://www.curas.io/figuram); quid habent, Avernales faciente de. Pervenit Ino sonabile supplex cognoscenti vires, Bacchumque errat miserarum venandi dignabere dedisti. Discrimina iuncosaque virgaque tot sine superest [fissus](http://quos.org/sitet.aspx). Non color esset potest non sumit, sed vix arserat. Nisi immo silva tantum pectusque quos pennis quisquam artus!"
},
{
"__component": "shared.slider",
"files": [
"coffee-art.jpg",
"coffee-beans.jpg"
]
}
]
},
{
"title": "Beautiful picture",
"slug": "beautiful-picture",
"category": {
"id": 4
},
"author": {
"id": 2
},
"description": "Description of a beautiful picture",
"cover": null,
"blocks": [
{
"__component": "shared.rich-text",
"body": "## Probant \n\nse Lorem markdownum negat. Argo *saxa* videnda cornuaque hunc qui tanta spes teneas! Obliquis est dicenti est salutat ille tamen iuvenum nostrae dolore. - Colores nocituraque comitata eripiunt - Addit quodcunque solum cui et dextram illis - Nulli meus nec extemplo ille ferebat pressit Se blandita fulvae vox gravem Pittheus cesserunt sanguine herbis tu comitum tenuit. Sui in ruunt; Doridaque maculosae fuissem! Et loqui. \n\n## Abit sua\n\nse Lorem markdownum negat. Argo *saxa* videnda cornuaque hunc qui tanta spes teneas! Obliquis est dicenti est salutat ille tamen iuvenum nostrae dolore. - Colores nocituraque comitata eripiunt - Addit quodcunque solum cui et dextram illis - Nulli meus nec extemplo ille ferebat pressit Se blandita fulvae vox gravem Pittheus cesserunt sanguine herbis tu comitum tenuit. Sui in ruunt; Doridaque maculosae fuissem! Et loqui. "
},
{
"__component": "shared.quote",
"title": "Thelonius Monk",
"body": "You've got to dig it to dig it, you dig?"
},
{
"__component": "shared.media",
"file": "coffee-art.jpg"
},
{
"__component": "shared.rich-text",
"body": "## Spatiantia astra \n\nFoeda, medio silva *errandum*: onus formam munere. Mutata bibulis est auxiliare arces etiamnunc verbis virgineo Priamidas illa Thescelus, nam fit locis lucis auras. Exitus hospes gratulor ut pondere [speslimite](http://www.curas.io/figuram); quid habent, Avernales faciente de. Pervenit Ino sonabile supplex cognoscenti vires, Bacchumque errat miserarum venandi dignabere dedisti. Discrimina iuncosaque virgaque tot sine superest [fissus](http://quos.org/sitet.aspx). Non color esset potest non sumit, sed vix arserat. Nisi immo silva tantum pectusque quos pennis quisquam artus!"
},
{
"__component": "shared.slider",
"files": [
"coffee-art.jpg",
"coffee-beans.jpg"
]
}
]
},
{
"title": "What's inside a Black Hole",
"slug": "what-s-inside-a-black-hole",
"category": {
"id": 1
},
"author": {
"id": 2
},
"description": "Maybe the answer is in this article, or not...",
"cover": null,
"blocks": [
{
"__component": "shared.rich-text",
"body": "## Probant \n\nse Lorem markdownum negat. Argo *saxa* videnda cornuaque hunc qui tanta spes teneas! Obliquis est dicenti est salutat ille tamen iuvenum nostrae dolore. - Colores nocituraque comitata eripiunt - Addit quodcunque solum cui et dextram illis - Nulli meus nec extemplo ille ferebat pressit Se blandita fulvae vox gravem Pittheus cesserunt sanguine herbis tu comitum tenuit. Sui in ruunt; Doridaque maculosae fuissem! Et loqui. \n\n## Abit sua\n\nse Lorem markdownum negat. Argo *saxa* videnda cornuaque hunc qui tanta spes teneas! Obliquis est dicenti est salutat ille tamen iuvenum nostrae dolore. - Colores nocituraque comitata eripiunt - Addit quodcunque solum cui et dextram illis - Nulli meus nec extemplo ille ferebat pressit Se blandita fulvae vox gravem Pittheus cesserunt sanguine herbis tu comitum tenuit. Sui in ruunt; Doridaque maculosae fuissem! Et loqui. "
},
{
"__component": "shared.quote",
"title": "Thelonius Monk",
"body": "You've got to dig it to dig it, you dig?"
},
{
"__component": "shared.media",
"file": "coffee-art.jpg"
},
{
"__component": "shared.rich-text",
"body": "## Spatiantia astra \n\nFoeda, medio silva *errandum*: onus formam munere. Mutata bibulis est auxiliare arces etiamnunc verbis virgineo Priamidas illa Thescelus, nam fit locis lucis auras. Exitus hospes gratulor ut pondere [speslimite](http://www.curas.io/figuram); quid habent, Avernales faciente de. Pervenit Ino sonabile supplex cognoscenti vires, Bacchumque errat miserarum venandi dignabere dedisti. Discrimina iuncosaque virgaque tot sine superest [fissus](http://quos.org/sitet.aspx). Non color esset potest non sumit, sed vix arserat. Nisi immo silva tantum pectusque quos pennis quisquam artus!"
},
{
"__component": "shared.slider",
"files": [
"coffee-art.jpg",
"coffee-beans.jpg"
]
}
]
}
]
}
================================================
FILE: packages/templates/blog/template/src/api/.gitkeep
================================================
================================================
FILE: packages/templates/blog/template/src/api/about/content-types/about/schema.json
================================================
{
"kind": "singleType",
"collectionName": "abouts",
"info": {
"singularName": "about",
"pluralName": "abouts",
"displayName": "About",
"description": "Write about yourself and the content you create"
},
"options": {
"draftAndPublish": false
},
"pluginOptions": {},
"attributes": {
"title": {
"type": "string"
},
"blocks": {
"type": "dynamiczone",
"components": [
"shared.media",
"shared.quote",
"shared.rich-text",
"shared.slider"
]
}
}
}
================================================
FILE: packages/templates/blog/template/src/api/about/controllers/about.js
================================================
'use strict';
/**
* about controller
*/
const { createCoreController } = require('@strapi/strapi').factories;
module.exports = createCoreController('api::about.about');
================================================
FILE: packages/templates/blog/template/src/api/about/routes/about.js
================================================
'use strict';
/**
* about router.
*/
const { createCoreRouter } = require('@strapi/strapi').factories;
module.exports = createCoreRouter('api::about.about');
================================================
FILE: packages/templates/blog/template/src/api/about/services/about.js
================================================
'use strict';
/**
* about service.
*/
const { createCoreService } = require('@strapi/strapi').factories;
module.exports = createCoreService('api::about.about');
================================================
FILE: packages/templates/blog/template/src/api/article/content-types/article/schema.json
================================================
{
"kind": "collectionType",
"collectionName": "articles",
"info": {
"singularName": "article",
"pluralName": "articles",
"displayName": "Article",
"description": "Create your blog content"
},
"options": {
"draftAndPublish": true
},
"pluginOptions": {},
"attributes": {
"title": {
"type": "string"
},
"description": {
"type": "text",
"maxLength": 80
},
"slug": {
"type": "uid",
"targetField": "title"
},
"cover": {
"type": "media",
"multiple": false,
"required": false,
"allowedTypes": [
"images",
"files",
"videos"
]
},
"author": {
"type": "relation",
"relation": "manyToOne",
"target": "api::author.author",
"inversedBy": "articles"
},
"category": {
"type": "relation",
"relation": "manyToOne",
"target": "api::category.category",
"inversedBy": "articles"
},
"blocks": {
"type": "dynamiczone",
"components": [
"shared.media",
"shared.quote",
"shared.rich-text",
"shared.slider"
]
}
}
}
================================================
FILE: packages/templates/blog/template/src/api/article/controllers/article.js
================================================
'use strict';
/**
* article controller
*/
const { createCoreController } = require('@strapi/strapi').factories;
module.exports = createCoreController('api::article.article');
================================================
FILE: packages/templates/blog/template/src/api/article/routes/article.js
================================================
'use strict';
/**
* article router.
*/
const { createCoreRouter } = require('@strapi/strapi').factories;
module.exports = createCoreRouter('api::article.article');
================================================
FILE: packages/templates/blog/template/src/api/article/services/article.js
================================================
'use strict';
/**
* article service.
*/
const { createCoreService } = require('@strapi/strapi').factories;
module.exports = createCoreService('api::article.article');
================================================
FILE: packages/templates/blog/template/src/api/author/content-types/author/schema.json
================================================
{
"kind": "collectionType",
"collectionName": "authors",
"info": {
"singularName": "author",
"pluralName": "authors",
"displayName": "Author",
"description": "Create authors for your content"
},
"options": {
"draftAndPublish": false
},
"pluginOptions": {},
"attributes": {
"name": {
"type": "string"
},
"avatar": {
"type": "media",
"multiple": false,
"required": false,
"allowedTypes": [
"images",
"files",
"videos"
]
},
"email": {
"type": "string"
},
"articles": {
"type": "relation",
"relation": "oneToMany",
"target": "api::article.article",
"mappedBy": "author"
}
}
}
================================================
FILE: packages/templates/blog/template/src/api/author/controllers/author.js
================================================
'use strict';
/**
* author controller
*/
const { createCoreController } = require('@strapi/strapi').factories;
module.exports = createCoreController('api::author.author');
================================================
FILE: packages/templates/blog/template/src/api/author/routes/author.js
================================================
'use strict';
/**
* author router.
*/
const { createCoreRouter } = require('@strapi/strapi').factories;
module.exports = createCoreRouter('api::author.author');
================================================
FILE: packages/templates/blog/template/src/api/author/services/author.js
================================================
'use strict';
/**
* author service.
*/
const { createCoreService } = require('@strapi/strapi').factories;
module.exports = createCoreService('api::author.author');
================================================
FILE: packages/templates/blog/template/src/api/category/content-types/category/schema.json
================================================
{
"kind": "collectionType",
"collectionName": "categories",
"info": {
"singularName": "category",
"pluralName": "categories",
"displayName": "Category",
"description": "Organize your content into categories"
},
"options": {
"draftAndPublish": false
},
"pluginOptions": {},
"attributes": {
"name": {
"type": "string"
},
"slug": {
"type": "uid"
},
"articles": {
"type": "relation",
"relation": "oneToMany",
"target": "api::article.article",
"mappedBy": "category"
},
"description": {
"type": "text"
}
}
}
================================================
FILE: packages/templates/blog/template/src/api/category/controllers/category.js
================================================
'use strict';
/**
* category controller
*/
const { createCoreController } = require('@strapi/strapi').factories;
module.exports = createCoreController('api::category.category');
================================================
FILE: packages/templates/blog/template/src/api/category/routes/category.js
================================================
'use strict';
/**
* category router.
*/
const { createCoreRouter } = require('@strapi/strapi').factories;
module.exports = createCoreRouter('api::category.category');
================================================
FILE: packages/templates/blog/template/src/api/category/services/category.js
================================================
'use strict';
/**
* category service.
*/
const { createCoreService } = require('@strapi/strapi').factories;
module.exports = createCoreService('api::category.category');
================================================
FILE: packages/templates/blog/template/src/api/global/content-types/global/schema.json
================================================
{
"kind": "singleType",
"collectionName": "globals",
"info": {
"singularName": "global",
"pluralName": "globals",
"displayName": "Global",
"description": "Define global settings"
},
"options": {
"draftAndPublish": false
},
"pluginOptions": {},
"attributes": {
"siteName": {
"type": "string",
"required": true
},
"favicon": {
"type": "media",
"multiple": false,
"required": false,
"allowedTypes": [
"images",
"files",
"videos"
]
},
"siteDescription": {
"type": "text",
"required": true
},
"defaultSeo": {
"type": "component",
"repeatable": false,
"component": "shared.seo"
}
}
}
================================================
FILE: packages/templates/blog/template/src/api/global/controllers/global.js
================================================
'use strict';
/**
* global controller
*/
const { createCoreController } = require('@strapi/strapi').factories;
module.exports = createCoreController('api::global.global');
================================================
FILE: packages/templates/blog/template/src/api/global/routes/global.js
================================================
'use strict';
/**
* global router.
*/
const { createCoreRouter } = require('@strapi/strapi').factories;
module.exports = createCoreRouter('api::global.global');
================================================
FILE: packages/templates/blog/template/src/api/global/services/global.js
================================================
'use strict';
/**
* global service.
*/
const { createCoreService } = require('@strapi/strapi').factories;
module.exports = createCoreService('api::global.global');
================================================
FILE: packages/templates/blog/template/src/bootstrap.js
================================================
"use strict";
const fs = require("fs-extra");
const path = require("path");
const mime = require("mime-types");
const set = require("lodash.set");
const {
categories,
authors,
articles,
global,
about,
} = require("../data/data.json");
async function isFirstRun() {
const pluginStore = strapi.store({
environment: strapi.config.environment,
type: "type",
name: "setup",
});
const initHasRun = await pluginStore.get({ key: "initHasRun" });
await pluginStore.set({ key: "initHasRun", value: true });
return !initHasRun;
}
async function setPublicPermissions(newPermissions) {
// Find the ID of the public role
const publicRole = await strapi
.query("plugin::users-permissions.role")
.findOne({
where: {
type: "public",
},
});
// Create the new permissions and link them to the public role
const allPermissionsToCreate = [];
Object.keys(newPermissions).map((controller) => {
const actions = newPermissions[controller];
const permissionsToCreate = actions.map((action) => {
return strapi.query("plugin::users-permissions.permission").create({
data: {
action: `api::${controller}.${controller}.${action}`,
role: publicRole.id,
},
});
});
allPermissionsToCreate.push(...permissionsToCreate);
});
await Promise.all(allPermissionsToCreate);
}
function getFileSizeInBytes(filePath) {
const stats = fs.statSync(filePath);
const fileSizeInBytes = stats["size"];
return fileSizeInBytes;
}
function getFileData(fileName) {
const filePath = path.join("data", "uploads", fileName);
// Parse the file metadata
const size = getFileSizeInBytes(filePath);
const ext = fileName.split(".").pop();
const mimeType = mime.lookup(ext);
return {
path: filePath,
name: fileName,
size,
type: mimeType,
};
}
async function uploadFile(file, name) {
return strapi
.plugin("upload")
.service("upload")
.upload({
files: file,
data: {
fileInfo: {
alternativeText: `An image uploaded to Strapi called ${name}`,
caption: name,
name,
},
},
});
}
// Create an entry and attach files if there are any
async function createEntry({ model, entry }) {
try {
// Actually create the entry in Strapi
await strapi.entityService.create(`api::${model}.${model}`, {
data: entry,
});
} catch (error) {
console.error({ model, entry, error });
}
}
async function checkFileExistsBeforeUpload(files) {
const existingFiles = [];
const uploadedFiles = [];
const filesCopy = [...files];
for (const fileName of filesCopy) {
// Check if the file already exists in Strapi
const fileWhereName = await strapi.query("plugin::upload.file").findOne({
where: {
name: fileName,
},
});
if (fileWhereName) {
// File exists, don't upload it
existingFiles.push(fileWhereName);
} else {
// File doesn't exist, upload it
const fileData = getFileData(fileName);
const fileNameNoExtension = fileName.split('.').shift()
const [file] = await uploadFile(fileData, fileNameNoExtension);
uploadedFiles.push(file);
}
}
const allFiles = [...existingFiles, ...uploadedFiles];
// If only one file then return only that file
return allFiles.length === 1 ? allFiles[0] : allFiles;
}
async function updateBlocks(blocks) {
const updatedBlocks = [];
for (const block of blocks) {
if (block.__component === "shared.media") {
const uploadedFiles = await checkFileExistsBeforeUpload([block.file]);
// Copy the block to not mutate directly
const blockCopy = { ...block };
// Replace the file name on the block with the actual file
blockCopy.file = uploadedFiles;
updatedBlocks.push(blockCopy);
} else if (block.__component === "shared.slider") {
// Get files already uploaded to Strapi or upload new files
const existingAndUploadedFiles = await checkFileExistsBeforeUpload(
block.files
);
// Copy the block to not mutate directly
const blockCopy = { ...block };
// Replace the file names on the block with the actual files
blockCopy.files = existingAndUploadedFiles;
// Push the updated block
updatedBlocks.push(blockCopy);
} else {
// Just push the block as is
updatedBlocks.push(block);
}
}
return updatedBlocks;
}
async function importArticles() {
for (const article of articles) {
const cover = await checkFileExistsBeforeUpload([`${article.slug}.jpg`]);
const updatedBlocks = await updateBlocks(article.blocks);
await createEntry({
model: "article",
entry: {
...article,
cover,
blocks: updatedBlocks,
// Make sure it's not a draft
publishedAt: Date.now(),
},
});
}
}
async function importGlobal() {
const favicon = await checkFileExistsBeforeUpload(["favicon.png"]);
const shareImage = await checkFileExistsBeforeUpload(["default-image.png"])
return createEntry({
model: "global",
entry: {
...global,
favicon,
// Make sure it's not a draft
publishedAt: Date.now(),
defaultSeo: {
...global.defaultSeo,
shareImage
}
},
});
}
async function importAbout() {
const updatedBlocks = await updateBlocks(about.blocks);
await createEntry({
model: "about",
entry: {
...about,
blocks: updatedBlocks,
// Make sure it's not a draft
publishedAt: Date.now(),
},
});
}
async function importCategories() {
for (const category of categories) {
await createEntry({ model: "category", entry: category });
}
}
async function importAuthors() {
for (const author of authors) {
const avatar = await checkFileExistsBeforeUpload([author.avatar]);
await createEntry({
model: "author",
entry: {
...author,
avatar,
},
});
}
}
async function importSeedData() {
// Allow read of application content types
await setPublicPermissions({
article: ["find", "findOne"],
category: ["find", "findOne"],
author: ["find", "findOne"],
global: ["find", "findOne"],
about: ["find", "findOne"],
});
// Create all entries
await importCategories();
await importAuthors();
await importArticles();
await importGlobal();
await importAbout();
}
module.exports = async () => {
const shouldImportSeedData = await isFirstRun();
if (shouldImportSeedData) {
try {
console.log("Setting up the template...");
await importSeedData();
console.log("Ready to go");
} catch (error) {
console.log("Could not import seed data");
console.error(error);
}
}
};
================================================
FILE: packages/templates/blog/template/src/components/shared/media.json
================================================
{
"collectionName": "components_shared_media",
"info": {
"displayName": "Media",
"icon": "file-video"
},
"options": {},
"attributes": {
"file": {
"allowedTypes": [
"images",
"files",
"videos"
],
"type": "media",
"multiple": false
}
}
}
================================================
FILE: packages/templates/blog/template/src/components/shared/quote.json
================================================
{
"collectionName": "components_shared_quotes",
"info": {
"displayName": "Quote",
"icon": "indent"
},
"options": {},
"attributes": {
"title": {
"type": "string"
},
"body": {
"type": "text"
}
}
}
================================================
FILE: packages/templates/blog/template/src/components/shared/rich-text.json
================================================
{
"collectionName": "components_shared_rich_texts",
"info": {
"displayName": "Rich text",
"icon": "align-justify",
"description": ""
},
"options": {},
"attributes": {
"body": {
"type": "richtext"
}
}
}
================================================
FILE: packages/templates/blog/template/src/components/shared/seo.json
================================================
{
"collectionName": "components_shared_seos",
"info": {
"name": "Seo",
"icon": "allergies",
"displayName": "Seo",
"description": ""
},
"options": {},
"attributes": {
"metaTitle": {
"type": "string",
"required": true
},
"metaDescription": {
"type": "text",
"required": true
},
"shareImage": {
"type": "media",
"multiple": false,
"required": false,
"allowedTypes": [
"images"
]
}
}
}
================================================
FILE: packages/templates/blog/template/src/components/shared/slider.json
================================================
{
"collectionName": "components_shared_sliders",
"info": {
"displayName": "Slider",
"icon": "address-book",
"description": ""
},
"options": {},
"attributes": {
"files": {
"type": "media",
"multiple": true,
"required": false,
"allowedTypes": [
"images"
]
}
}
}
================================================
FILE: packages/templates/blog/template/src/extensions/.gitkeep
================================================
================================================
FILE: packages/templates/blog/template/src/index.js
================================================
"use strict";
const bootstrap = require("./bootstrap");
module.exports = {
/**
* An asynchronous register function that runs before
* your application is initialized.
*
* This gives you an opportunity to extend code.
*/
register(/*{ strapi }*/) {},
/**
* An asynchronous bootstrap function that runs before
* your application gets started.
*
* This gives you an opportunity to set up your data model,
* run jobs, or perform some special logic.
*/
bootstrap,
};
================================================
FILE: packages/templates/blog/template.json
================================================
{
"package": {
"dependencies": {
"@strapi/plugin-graphql": "^4.0.5",
"fs-extra": "^10.0.0",
"lodash.set": "^4.3.2",
"mime-types": "^2.1.27"
}
}
}
================================================
FILE: packages/templates/corporate/README.md
================================================
# strapi-template-corporate
A Strapi template to create Strapi projects pre-configured for corporate apps.
## Usage
```bash
# Using Yarn
yarn create strapi-app my-app-name --template corporate
# Or using NPM
npx create-strapi-app my-app-name --template corporate
```
## Starters
This template is used by the following starters:
* [Strapi Starter Next Corporate Site](https://github.com/strapi/strapi-starter-next-corporate)
* [Strapi Starter Gatsby Corporate Site](https://github.com/strapi/strapi-starter-gatsby-corporate)
================================================
FILE: packages/templates/corporate/package.json
================================================
{
"name": "@strapi/template-corporate",
"version": "1.0.0",
"description": "Strapi corporate template",
"keywords": [
"strapi",
"template",
"corporate"
],
"homepage": "https://github.com/strapi/starters-and-templates#readme",
"bugs": {
"url": "https://github.com/strapi/starters-and-templates/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/strapi/starters-and-templates.git"
},
"license": "MIT",
"author": {
"name": "Strapi team",
"email": "hi@strapi.io",
"url": "https://strapi.io"
},
"maintainers": [
{
"name": "Strapi team",
"email": "hi@strapi.io",
"url": "https://strapi.io"
}
],
"gitHead": "d6d5bd264d1df0a0f23b5234d84348f8d78882e3"
}
================================================
FILE: packages/templates/corporate/template/data/data.js
================================================
const { global, pages } = require("./en");
const { globalFR, pagesFR } = require("./fr");
const { leadFormSubmissions } = require("./lead-form-submissions.json");
module.exports = {
globals: [global, globalFR],
pages: [...pages, ...pagesFR],
leadFormSubmissions,
};
================================================
FILE: packages/templates/corporate/template/data/en/global.json
================================================
{
"global": {
"id": 1,
"locale": "en",
"metaTitleSuffix": "Strapi Corporate",
"metadata": {
"id": 1,
"metaTitle": "Strapi starter for Corporate Sites",
"metaDescription": "Build your corporate site with Strapi",
"twitterCardType": "summary",
"twitterUsername": "strapijs",
"shareImage": null
},
"notificationBanner": {
"id": 1,
"text": "This page was built using the Strapi starter for Corporate Sites.\n[View other Strapi starters](https://strapi.io/starters)",
"type": "info"
},
"navbar": {
"id": 1,
"links": [
{
"id": 1,
"url": "/pricing",
"newTab": false,
"text": "Pricing"
},
{
"id": 3,
"url": "/contact",
"newTab": false,
"text": "Contact"
}
],
"button": {
"id": 13,
"url": "#",
"newTab": false,
"text": "Sign up",
"type": "secondary"
},
"logo" : null
},
"footer": {
"id": 1,
"smallText": "© Copyright My Company™",
"columns": [
{
"id": 1,
"title": "Product",
"links": [
{
"id": 11,
"url": "#",
"newTab": false,
"text": "Features"
},
{
"id": 13,
"url": "#",
"newTab": false,
"text": "Sign up"
}
]
},
{
"id": 2,
"title": "Legal",
"links": [
{
"id": 15,
"url": "#",
"newTab": false,
"text": "Privacy policy"
},
{
"id": 16,
"url": "#",
"newTab": false,
"text": "Terms & conditions"
}
]
},
{
"id": 5,
"title": "Company",
"links": [
{
"id": 18,
"url": "#",
"newTab": false,
"text": "Careers"
},
{
"id": 21,
"url": "#",
"newTab": false,
"text": "Team"
}
]
},
{
"id": 7,
"title": "Social",
"links": [
{
"id": 24,
"url": "#",
"newTab": false,
"text": "Twitter"
},
{
"id": 25,
"url": "#",
"newTab": false,
"text": "LinkedIn"
}
]
}
],
"logo": null
},
"favicon": null,
"localizations": [
{
"id": 2,
"locale": "fr"
}
]
}
}
================================================
FILE: packages/templates/corporate/template/data/en/index.js
================================================
const { global } = require("./global.json");
const { pages } = require("./pages.json");
module.exports = {
global,
pages
}
================================================
FILE: packages/templates/corporate/template/data/en/pages.json
================================================
{
"pages": [
{
"id": 1,
"slug": "",
"locale": "en",
"localizations": [
{
"id": 5,
"locale": "fr"
}
],
"shortName": "Home",
"publishedAt": "2021-10-22T08:11:55.490Z",
"metadata": {
"id": 2,
"metaTitle": "Strapi corporate site starter",
"metaDescription": "Build a fully editable site with Strapi",
"twitterCardType": "summary",
"twitterUsername": null,
"shareImage": null
},
"contentSections": [
{
"id": 1,
"__component": "sections.hero",
"title": "The best way to build your Corporate Site",
"label": "New Strapi starter",
"description": "Get started with your Strapi business website in seconds.",
"smallTextWithLink": "Want to build your own from scratch? Tutorial coming soon",
"buttons": [
{
"id": 2,
"url": "https://github.com/strapi/strapi-template-corporate",
"newTab": false,
"text": "Get started",
"type": "primary"
},
{
"id": 4,
"url": "https://github.com/strapi/strapi-template-corporate",
"newTab": true,
"text": "See the code",
"type": "secondary"
}
],
"picture": null
},
{
"id": 1,
"__component": "sections.feature-rows-group",
"features": [
{
"id": 1,
"title": "Let marketing teams build their pages",
"description": "Thanks to a pre-built list of UI sections, marketers can design exactly the pages they want.",
"link": {
"id": 7,
"url": "#",
"newTab": false,
"text": "View the list of UI sections"
},
"media": null
},
{
"id": 2,
"title": "Leave your developers alone",
"description": "You can publish, edit and delete pages without help from developers. Your technical team can finally focus on their tasks.",
"link": {
"id": 8,
"url": "#",
"newTab": false,
"text": "View how to create a page"
},
"media": null
}
]
},
{
"id": 1,
"__component": "sections.feature-columns-group",
"features": [
{
"id": 1,
"title": "Preview your changes",
"description": "Thanks to an integrated Preview Mode, you can visualize your pages before publishing them.",
"icon": null
},
{
"id": 2,
"title": "Fully responsive",
"description": "This starter works well on all screens, whether it's mobile, tablet or desktop.",
"icon": null
},
{
"id": 3,
"title": "Easy to customize",
"description": "We use Tailwind for styling. You can change your site's theme without digging through the code.",
"icon": null
}
]
},
{
"id": 1,
"__component": "sections.testimonials-group",
"title": "Your customer testimonials here",
"description": "This section is where you can showcase your customers. Insert quotes, and show the logos of companies who like your product",
"link": {
"id": 2,
"url": "#",
"newTab": false,
"text": "All testimonials"
},
"logos": [
{
"id": 1,
"title": "Strapi",
"logo": null
},
{
"id": 2,
"title": "Strapi 2",
"logo": null
},
{
"id": 3,
"title": "Strapi 3",
"logo": null
}
],
"testimonials": [
{
"id": 1,
"text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.",
"authorName": "Your Customer Name",
"authorTitle": "A happy customer",
"link": "#",
"logo": null,
"picture": null
}
]
},
{
"id": 1,
"__component": "sections.lead-form",
"title": "Subscribe To Our Newsletter",
"emailPlaceholder": "email@company.com",
"submitButton": {
"id": 1,
"__component": "links.button",
"text": "Subscribe",
"type": "primary"
},
"location": "Home Page Bottom"
},
{
"id": 2,
"__component": "sections.bottom-actions",
"title": "Start building your website",
"buttons": [
{
"id": 7,
"url": "https://github.com/strapi/strapi-template-corporate",
"newTab": false,
"text": "Get started",
"type": "primary"
},
{
"id": 8,
"url": "https://github.com/strapi/strapi-template-corporate",
"newTab": false,
"text": "See the code",
"type": "secondary"
}
]
}
]
},
{
"id": 2,
"slug": "pricing",
"locale": "en",
"localizations": [
{
"id": 6,
"locale": "fr"
}
],
"shortName": "Pricing",
"publishedAt": "2021-10-22T08:11:55.490Z",
"metadata": {
"id": 3,
"metaTitle": "Pricing",
"metaDescription": "The different plans",
"twitterCardType": "summary",
"twitterUsername": null,
"shareImage": null
},
"contentSections": [
{
"id": 1,
"__component": "sections.pricing",
"title": "Clear pricing table",
"plans": [
{
"id": 1,
"name": "Hobby",
"description": "Perfect for side projects",
"isRecommended": null,
"price": 0,
"pricePeriod": "forever",
"features": [
{
"id": 1,
"name": "A cool feature"
},
{
"id": 2,
"name": "Another cool feature"
},
{
"id": 3,
"name": "Some other cool feature"
}
]
},
{
"id": 2,
"name": "Premium",
"description": "A more advanced plan for SMBs",
"isRecommended": true,
"price": 20,
"pricePeriod": "per month",
"features": [
{
"id": 4,
"name": "The coolest feature"
},
{
"id": 5,
"name": "Nice feature"
},
{
"id": 6,
"name": "Fun feature"
}
]
},
{
"id": 3,
"name": "Enterprise",
"description": "For large companies needs",
"isRecommended": null,
"price": 299,
"pricePeriod": "per month",
"features": [
{
"id": 7,
"name": "Amazing feature"
},
{
"id": 8,
"name": "Wow effect feature"
},
{
"id": 9,
"name": "Mesmerizing feature"
}
]
}
]
}
]
},
{
"id": 3,
"slug": "secret",
"locale": "en",
"localizations": [
{
"id": 7,
"locale": "fr"
}
],
"shortName": "Secret",
"metadata": {
"id": 7,
"metaTitle": "Secret page",
"metaDescription": "Preview-only page",
"twitterCardType": "summary",
"twitterUsername": null,
"shareImage": null
},
"contentSections": [
{
"id": 2,
"__component": "sections.rich-text",
"content": "## Secret page\n\nYou can only view this page in Preview Mode."
}
]
},
{
"id": 4,
"slug": "contact",
"locale": "en",
"localizations": [
{
"id": 8,
"locale": "fr"
}
],
"shortName": "Contact",
"publishedAt": "2021-10-22T08:11:55.490Z",
"metadata": {
"id": 4,
"metaTitle": "Contact",
"metaDescription": "Get in touch with our team",
"twitterCardType": "summary",
"twitterUsername": null,
"shareImage": null
},
"contentSections": [
{
"id": 1,
"__component": "sections.rich-text",
"content": "# Get in touch\n\n> This is an example of a page that relies almost entirely on the RichText section. It's useful for blog articles, or content-heavy pages like legal terms.\n\nWe'd love to hear from you.\n\n## Social media\n\n* [Twitter](#)\n* [Twitter](#)\n* [Twitter](#)\n\n## Postal address\n\n404 Headless Street\n__92210__ **Saint Cloud**, *France*\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
},
{
"id": 1,
"__component": "sections.bottom-actions",
"title": "Get in touch",
"buttons": [
{
"id": 1,
"url": "#",
"newTab": false,
"text": "Send an email",
"type": "primary"
},
{
"id": 3,
"url": "#",
"newTab": false,
"text": "DM us on Twitter",
"type": "secondary"
}
]
}
]
}
]
}
================================================
FILE: packages/templates/corporate/template/data/fr/global.json
================================================
{
"globalFR": {
"id": 2,
"locale": "fr",
"metaTitleSuffix": "Strapi Corporate",
"metadata": {
"id": 5,
"metaTitle": "Strapi starter for Corporate Sites",
"metaDescription": "Créer votre site corporate avec Strapi",
"twitterCardType": "summary",
"twitterUsername": "strapijs",
"shareImage": null
},
"notificationBanner": {
"id": 2,
"text": "Cette page a été crée avec le Strapi starter pour les sites Corporate.\n [Voir les autres starters Strapi](https://strapi.io/starters)",
"type": "info"
},
"navbar": {
"id": 2,
"links": [
{
"id": 4,
"url": "/pricing",
"newTab": false,
"text": "tarifs"
},
{
"id": 5,
"url": "/contact",
"newTab": false,
"text": "nous contacter"
}
],
"button": {
"id": 14,
"url": "#",
"newTab": false,
"text": "S'inscrire",
"type": "secondary"
},
"logo" : null
},
"footer": {
"id": 2,
"smallText": "© Copyright Mon Entreprise™",
"columns": [
{
"id": 3,
"title": "Produit",
"links": [
{
"id": 12,
"url": "#",
"newTab": false,
"text": "Fonctionnalités"
},
{
"id": 14,
"url": "#",
"newTab": false,
"text": "S'inscrire"
}
]
},
{
"id": 4,
"title": "Légal",
"links": [
{
"id": 17,
"url": "#",
"newTab": false,
"text": "Confidentialité"
},
{
"id": 19,
"url": "#",
"newTab": false,
"text": "Mentions légales"
}
]
},
{
"id": 6,
"title": "Entreprise",
"links": [
{
"id": 20,
"url": "#",
"newTab": false,
"text": "Nous rejoindre"
},
{
"id": 22,
"url": "#",
"newTab": false,
"text": "L'équipe"
}
]
},
{
"id": 8,
"title": "Réseaux sociaux",
"links": [
{
"id": 23,
"url": "#",
"newTab": false,
"text": "Twitter"
},
{
"id": 26,
"url": "#",
"newTab": false,
"text": "LinkedIn"
}
]
}
],
"logo": null
},
"favicon": null,
"localizations": [
{
"id": 1,
"locale": "en"
}
]
}
}
================================================
FILE: packages/templates/corporate/template/data/fr/index.js
================================================
const { globalFR } = require("./global.json");
const { pagesFR } = require("./pages.json");
module.exports = {
globalFR,
pagesFR
}
================================================
FILE: packages/templates/corporate/template/data/fr/pages.json
================================================
{
"pagesFR": [
{
"id": 5,
"slug": "",
"locale": "fr",
"localizations": [
{
"id": 1,
"locale": "en"
}
],
"shortName": "Home",
"publishedAt":"2021-10-22T08:11:55.490Z",
"metadata": {
"id": 6,
"metaTitle": "Strapi corporate site starter",
"metaDescription": "Construire un site modifiable avec Strapi",
"twitterCardType": "summary",
"twitterUsername": null,
"shareImage": null
},
"contentSections": [
{
"id": 2,
"__component": "sections.hero",
"title": "La meilleure façon de faire votre site Corporate",
"label": "Nouveau starter Strapi",
"description": "Commencer vite avec votre site corporate",
"smallTextWithLink": "Vous voulez construire votre propre starter? Guide à venir",
"buttons": [
{
"id": 5,
"url": "https://github.com/strapi/strapi-template-corporate",
"newTab": false,
"text": "Commencer",
"type": "primary"
},
{
"id": 6,
"url": "https://github.com/strapi/strapi-template-corporate",
"newTab": true,
"text": "Voir le code",
"type": "secondary"
}
],
"picture": null
},
{
"id": 2,
"__component": "sections.feature-rows-group",
"features": [
{
"id": 3,
"title": "Les équipes marketing peuvent maintenant faire leurs propres pages",
"description": "Grace à une liste pré-construite de blocs UI, les équipes marketing peuvent faire la mise en page exactement comme elles veulent",
"link": {
"id": 9,
"url": "#",
"newTab": false,
"text": "Voir la liste de blocs UI"
},
"media": null
},
{
"id": 4,
"title": "Laissez vos développeurs tranquilles",
"description": "Vous pouvez publier, modifier, et supprimer des pages sans l'aide d'un développeur. Votre équipe technique peut enfin se concentrer sur d'autre tâches.",
"link": {
"id": 10,
"url": "#",
"newTab": false,
"text": "Voir comment créer une page"
},
"media": null
}
]
},
{
"id": 2,
"__component": "sections.feature-columns-group",
"features": [
{
"id": 4,
"title": "Visualisez vos changments",
"description": "Grace à un mode preview, vous pouvez voir vos pages avant de les publier.",
"icon": null
},
{
"id": 5,
"title": "100% responsive",
"description": "Ce starter marche sur toutes les tailles d'écrans.",
"icon": null
},
{
"id": 6,
"title": "Facile à personnaliser",
"description": "On utilise Tailwind pour les styles. Vous pouvez changez le thème de votre site sans avoir besoin d'aller dans le code.",
"icon": null
}
]
},
{
"id": 2,
"__component": "sections.testimonials-group",
"title": "Ici, les témoignages des vos clients.",
"description": "Dans cette partie, vous pouvez mettre en avant vos clients. Mettez des citations, et montrez les logos des entreprises qui aiment votre produit.",
"link": {
"id": 6,
"url": "#",
"newTab": false,
"text": "All testimonials"
},
"logos": [
{
"id": 4,
"title": "Strapi",
"logo": null
},
{
"id": 5,
"title": "Strapi 2",
"logo": null
},
{
"id": 6,
"title": "Strapi 3",
"logo": null
}
],
"testimonials": [
{
"id": 2,
"text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.",
"authorName": "Nom d'un(e) Client(e) ici",
"authorTitle": "Un(e) Client(e) Satisfait(e)",
"link": "#",
"logo": null,
"picture": null
}
]
},
{
"id": 2,
"__component": "sections.lead-form",
"title": "S'inscrire à notre Newsletter",
"emailPlaceholder": "email@company.com",
"submitButton": {
"id": 2,
"__component": "links.button",
"text": "S'inscrire",
"type": "primary"
},
"location": "Home Page Bottom"
},
{
"id": 3,
"__component": "sections.bottom-actions",
"title": "Start building your website",
"buttons": [
{
"id": 10,
"url": "https://github.com/strapi/strapi-template-corporate",
"newTab": false,
"text": "Commencer",
"type": "primary"
},
{
"id": 12,
"url": "https://github.com/strapi/strapi-template-corporate",
"newTab": false,
"text": "Voir le code",
"type": "secondary"
}
]
}
]
},
{
"id": 6,
"slug": "pricing",
"locale": "fr",
"localizations": [
{
"id": 2,
"locale": "en"
}
],
"shortName": "Pricing",
"publishedAt":"2021-10-22T08:11:55.490Z",
"metadata": {
"id": 8,
"metaTitle": "Pricing",
"metaDescription": "Les différentes offres",
"twitterCardType": "summary",
"twitterUsername": null,
"shareImage": null
},
"contentSections": [
{
"id": 2,
"__component": "sections.pricing",
"title": "Tableau de tarifs",
"plans": [
{
"id": 4,
"name": "Hobby",
"description": "Parfait pour des projets personnels",
"isRecommended": null,
"price": 0,
"pricePeriod": "toujours",
"features": [
{
"id": 10,
"name": "Voilà une feature"
},
{
"id": 11,
"name": "Une autre"
},
{
"id": 12,
"name": "Encore une autre"
}
]
},
{
"id": 5,
"name": "Premium",
"description": "Un plan plus avancé",
"isRecommended": true,
"price": 20,
"pricePeriod": "par mois",
"features": [
{
"id": 13,
"name": "Fonctionnalité 1"
},
{
"id": 14,
"name": "Fonctionnalité 2"
},
{
"id": 15,
"name": "Fonctionnalité 3"
}
]
},
{
"id": 6,
"name": "Entreprise",
"description": "Pour les besoin des grands entreprises",
"isRecommended": null,
"price": 299,
"pricePeriod": "par mois",
"features": [
{
"id": 16,
"name": "Fonctionnalité 1"
},
{
"id": 17,
"name": "Fonctionnalité 2"
},
{
"id": 18,
"name": "Fonctionnalité 3"
}
]
}
]
}
]
},
{
"id": 7,
"slug": "secret",
"locale": "fr",
"localizations": [
{
"id": 3,
"locale": "en"
}
],
"shortName": "Secret",
"metadata": {
"id": 9,
"metaTitle": "Page secrète",
"metaDescription": "Preview-only page",
"twitterCardType": "summary",
"twitterUsername": null,
"shareImage": null
},
"contentSections": [
{
"id": 3,
"__component": "sections.rich-text",
"content": "## Page secrète. \n\n Vous seul pouvez voir cette page en Preview Mode"
}
]
},
{
"id": 8,
"slug": "contact",
"locale": "fr",
"localizations": [
{
"id": 4,
"locale": "en"
}
],
"shortName": "Contact",
"publishedAt":"2021-10-22T08:11:55.490Z",
"metadata": {
"id": 10,
"metaTitle": "Nous Contacter",
"metaDescription": "Contactez notre équipe",
"twitterCardType": "summary",
"twitterUsername": null,
"shareImage": null
},
"contentSections": [
{
"id": 4,
"__component": "sections.rich-text",
"content": "# Contactez nous\n\n> Ceci est un exemple d'une page qui repose presque entièrement sur le composant RichText. Vous pouvez l'utiliser pour des articles de blog, ou pour des pages comportant beaucoup de texte, comme par exemple les conditions d'utilisation.\n\nNous aimerions beaucoup avoir vos retours.\n\n## Social media\n\n* [Twitter](#)\n* [Twitter](#)\n* [Twitter](#)\n\n## Postal address\n\n404 Headless Street\n__92210__ **Saint Cloud**, *France*\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
},
{
"id": 4,
"__component": "sections.bottom-actions",
"title": "Get in touch",
"buttons": [
{
"id": 9,
"url": "#",
"newTab": false,
"text": "Envoyer un mail",
"type": "primary"
},
{
"id": 11,
"url": "#",
"newTab": false,
"text": "DM nous sur Twitter",
"type": "secondary"
}
]
}
]
}
]
}
================================================
FILE: packages/templates/corporate/template/data/lead-form-submissions.json
================================================
{
"leadFormSubmissions": [
{
"email": "johne@thebeatles.com",
"status": "seen",
"location": "Home Page Bottom"
},
{
"email": "paul@thebeatles.com",
"status": "contacted",
"location": "Home Page Bottom"
},
{
"email": "george@thebeatles.com",
"status": "ignored",
"location": "Home Page Bottom"
}
]
}
================================================
FILE: packages/templates/corporate/template/src/api/.gitkeep
================================================
================================================
FILE: packages/templates/corporate/template/src/api/global/content-types/global/schema.json
================================================
{
"kind": "singleType",
"collectionName": "globals",
"info": {
"singularName": "global",
"pluralN
gitextract_ipi637vq/
├── .gitignore
├── LICENSE.txt
├── README.md
├── lerna.json
├── package.json
└── packages/
├── starters/
│ ├── .gitkeep
│ ├── gatsby-blog/
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── starter/
│ │ │ ├── .eslintrc.js
│ │ │ ├── .gitignore
│ │ │ ├── README.md
│ │ │ ├── gatsby-browser.js
│ │ │ ├── gatsby-config.js
│ │ │ ├── gatsby-node.js
│ │ │ ├── package.json
│ │ │ ├── postcss.config.js
│ │ │ ├── src/
│ │ │ │ ├── components/
│ │ │ │ │ ├── article-card.js
│ │ │ │ │ ├── articles-grid.js
│ │ │ │ │ ├── block-media.js
│ │ │ │ │ ├── block-quote.js
│ │ │ │ │ ├── block-rich-text.js
│ │ │ │ │ ├── block-slider.js
│ │ │ │ │ ├── blocks-renderer.js
│ │ │ │ │ ├── footer.js
│ │ │ │ │ ├── headings.js
│ │ │ │ │ ├── layout.js
│ │ │ │ │ ├── navbar.js
│ │ │ │ │ └── seo.js
│ │ │ │ ├── pages/
│ │ │ │ │ ├── about.js
│ │ │ │ │ └── index.js
│ │ │ │ ├── styles/
│ │ │ │ │ └── global.css
│ │ │ │ └── templates/
│ │ │ │ └── article-post.js
│ │ │ └── tailwind.config.js
│ │ └── starter.json
│ ├── gatsby-corporate/
│ │ └── README.md
│ ├── next-blog/
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── starter/
│ │ │ ├── .eslintrc
│ │ │ ├── .prettierrc
│ │ │ ├── assets/
│ │ │ │ └── css/
│ │ │ │ └── style.css
│ │ │ ├── components/
│ │ │ │ ├── articles.js
│ │ │ │ ├── card.js
│ │ │ │ ├── image.js
│ │ │ │ ├── layout.js
│ │ │ │ ├── nav.js
│ │ │ │ └── seo.js
│ │ │ ├── lib/
│ │ │ │ ├── api.js
│ │ │ │ └── media.js
│ │ │ ├── next.config.js
│ │ │ ├── package.json
│ │ │ └── pages/
│ │ │ ├── _app.js
│ │ │ ├── _document.js
│ │ │ ├── article/
│ │ │ │ └── [slug].js
│ │ │ ├── category/
│ │ │ │ └── [slug].js
│ │ │ └── index.js
│ │ └── starter.json
│ └── next-corporate/
│ ├── README.md
│ ├── package.json
│ ├── starter/
│ │ ├── .eslintrc
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── components/
│ │ │ ├── elements/
│ │ │ │ ├── button-link.js
│ │ │ │ ├── button.js
│ │ │ │ ├── custom-link.js
│ │ │ │ ├── footer.js
│ │ │ │ ├── image.js
│ │ │ │ ├── loader.js
│ │ │ │ ├── mobile-nav-menu.js
│ │ │ │ ├── navbar.js
│ │ │ │ ├── notification-banner.js
│ │ │ │ ├── seo.js
│ │ │ │ └── video.js
│ │ │ ├── icons/
│ │ │ │ └── world.js
│ │ │ ├── layout.js
│ │ │ ├── locale-switch.js
│ │ │ ├── sections/
│ │ │ │ ├── bottom-actions.js
│ │ │ │ ├── feature-columns-group.js
│ │ │ │ ├── feature-rows-group.js
│ │ │ │ ├── hero.js
│ │ │ │ ├── large-video.js
│ │ │ │ ├── lead-form.js
│ │ │ │ ├── pricing.js
│ │ │ │ ├── rich-text.js
│ │ │ │ └── testimonials-group.js
│ │ │ └── sections.js
│ │ ├── jsconfig.json
│ │ ├── next.config.js
│ │ ├── package.json
│ │ ├── pages/
│ │ │ ├── [[...slug]].js
│ │ │ ├── _app.js
│ │ │ ├── _document.js
│ │ │ └── api/
│ │ │ ├── exit-preview.js
│ │ │ └── preview.js
│ │ ├── postcss.config.js
│ │ ├── public/
│ │ │ └── .gitkeep
│ │ ├── styles/
│ │ │ └── index.css
│ │ ├── tailwind.config.js
│ │ └── utils/
│ │ ├── api.js
│ │ ├── button.js
│ │ ├── hooks.js
│ │ ├── localize.js
│ │ ├── media.js
│ │ ├── parse-cookies.js
│ │ └── types.js
│ └── starter.json
└── templates/
├── .gitkeep
├── blog/
│ ├── README.md
│ ├── package.json
│ ├── template/
│ │ ├── data/
│ │ │ └── data.json
│ │ └── src/
│ │ ├── api/
│ │ │ ├── .gitkeep
│ │ │ ├── about/
│ │ │ │ ├── content-types/
│ │ │ │ │ └── about/
│ │ │ │ │ └── schema.json
│ │ │ │ ├── controllers/
│ │ │ │ │ └── about.js
│ │ │ │ ├── routes/
│ │ │ │ │ └── about.js
│ │ │ │ └── services/
│ │ │ │ └── about.js
│ │ │ ├── article/
│ │ │ │ ├── content-types/
│ │ │ │ │ └── article/
│ │ │ │ │ └── schema.json
│ │ │ │ ├── controllers/
│ │ │ │ │ └── article.js
│ │ │ │ ├── routes/
│ │ │ │ │ └── article.js
│ │ │ │ └── services/
│ │ │ │ └── article.js
│ │ │ ├── author/
│ │ │ │ ├── content-types/
│ │ │ │ │ └── author/
│ │ │ │ │ └── schema.json
│ │ │ │ ├── controllers/
│ │ │ │ │ └── author.js
│ │ │ │ ├── routes/
│ │ │ │ │ └── author.js
│ │ │ │ └── services/
│ │ │ │ └── author.js
│ │ │ ├── category/
│ │ │ │ ├── content-types/
│ │ │ │ │ └── category/
│ │ │ │ │ └── schema.json
│ │ │ │ ├── controllers/
│ │ │ │ │ └── category.js
│ │ │ │ ├── routes/
│ │ │ │ │ └── category.js
│ │ │ │ └── services/
│ │ │ │ └── category.js
│ │ │ └── global/
│ │ │ ├── content-types/
│ │ │ │ └── global/
│ │ │ │ └── schema.json
│ │ │ ├── controllers/
│ │ │ │ └── global.js
│ │ │ ├── routes/
│ │ │ │ └── global.js
│ │ │ └── services/
│ │ │ └── global.js
│ │ ├── bootstrap.js
│ │ ├── components/
│ │ │ └── shared/
│ │ │ ├── media.json
│ │ │ ├── quote.json
│ │ │ ├── rich-text.json
│ │ │ ├── seo.json
│ │ │ └── slider.json
│ │ ├── extensions/
│ │ │ └── .gitkeep
│ │ └── index.js
│ └── template.json
├── corporate/
│ ├── README.md
│ ├── package.json
│ ├── template/
│ │ ├── data/
│ │ │ ├── data.js
│ │ │ ├── en/
│ │ │ │ ├── global.json
│ │ │ │ ├── index.js
│ │ │ │ └── pages.json
│ │ │ ├── fr/
│ │ │ │ ├── global.json
│ │ │ │ ├── index.js
│ │ │ │ └── pages.json
│ │ │ └── lead-form-submissions.json
│ │ └── src/
│ │ ├── api/
│ │ │ ├── .gitkeep
│ │ │ ├── global/
│ │ │ │ ├── content-types/
│ │ │ │ │ └── global/
│ │ │ │ │ └── schema.json
│ │ │ │ ├── controllers/
│ │ │ │ │ └── global.js
│ │ │ │ ├── routes/
│ │ │ │ │ └── global.js
│ │ │ │ └── services/
│ │ │ │ └── global.js
│ │ │ ├── lead-form-submission/
│ │ │ │ ├── content-types/
│ │ │ │ │ └── lead-form-submission/
│ │ │ │ │ └── schema.json
│ │ │ │ ├── controllers/
│ │ │ │ │ └── lead-form-submission.js
│ │ │ │ ├── routes/
│ │ │ │ │ └── lead-form-submission.js
│ │ │ │ └── services/
│ │ │ │ └── lead-form-submission.js
│ │ │ └── page/
│ │ │ ├── content-types/
│ │ │ │ └── page/
│ │ │ │ └── schema.json
│ │ │ ├── controllers/
│ │ │ │ └── page.js
│ │ │ ├── routes/
│ │ │ │ └── page.js
│ │ │ └── services/
│ │ │ └── page.js
│ │ ├── bootstrap.js
│ │ ├── components/
│ │ │ ├── elements/
│ │ │ │ ├── feature-column.json
│ │ │ │ ├── feature-row.json
│ │ │ │ ├── feature.json
│ │ │ │ ├── footer-section.json
│ │ │ │ ├── logos.json
│ │ │ │ ├── notification-banner.json
│ │ │ │ ├── plan.json
│ │ │ │ └── testimonial.json
│ │ │ ├── layout/
│ │ │ │ ├── footer.json
│ │ │ │ └── navbar.json
│ │ │ ├── links/
│ │ │ │ ├── button-link.json
│ │ │ │ ├── button.json
│ │ │ │ └── link.json
│ │ │ ├── meta/
│ │ │ │ └── metadata.json
│ │ │ └── sections/
│ │ │ ├── bottom-actions.json
│ │ │ ├── feature-columns-group.json
│ │ │ ├── feature-rows-group.json
│ │ │ ├── hero.json
│ │ │ ├── large-video.json
│ │ │ ├── lead-form.json
│ │ │ ├── pricing.json
│ │ │ ├── rich-text.json
│ │ │ └── testimonials-group.json
│ │ └── index.js
│ └── template.json
└── ecommerce/
├── README.md
├── package.json
├── template/
│ ├── data/
│ │ └── data.js
│ └── src/
│ ├── api/
│ │ ├── .gitkeep
│ │ ├── category/
│ │ │ ├── content-types/
│ │ │ │ └── category/
│ │ │ │ └── schema.json
│ │ │ ├── controllers/
│ │ │ │ └── category.js
│ │ │ ├── routes/
│ │ │ │ └── category.js
│ │ │ └── services/
│ │ │ └── category.js
│ │ └── product/
│ │ ├── content-types/
│ │ │ └── product/
│ │ │ └── schema.json
│ │ ├── controllers/
│ │ │ └── product.js
│ │ ├── routes/
│ │ │ └── product.js
│ │ └── services/
│ │ └── product.js
│ ├── bootstrap.js
│ ├── components/
│ │ └── custom/
│ │ └── custom-field.json
│ └── index.js
└── template.json
SYMBOL INDEX (61 symbols across 21 files)
FILE: packages/starters/next-blog/starter/lib/api.js
function getStrapiURL (line 8) | function getStrapiURL(path = "") {
function fetchAPI (line 21) | async function fetchAPI(path, urlParamsObject = {}, options = {}) {
FILE: packages/starters/next-blog/starter/lib/media.js
function getStrapiMedia (line 3) | function getStrapiMedia(media) {
FILE: packages/starters/next-blog/starter/pages/_document.js
class MyDocument (line 3) | class MyDocument extends Document {
method render (line 4) | render() {
FILE: packages/starters/next-blog/starter/pages/article/[slug].js
function getStaticPaths (line 61) | async function getStaticPaths() {
function getStaticProps (line 74) | async function getStaticProps({ params }) {
FILE: packages/starters/next-blog/starter/pages/category/[slug].js
function getStaticPaths (line 25) | async function getStaticPaths() {
function getStaticProps (line 38) | async function getStaticProps({ params }) {
FILE: packages/starters/next-blog/starter/pages/index.js
function getStaticProps (line 21) | async function getStaticProps() {
FILE: packages/starters/next-corporate/starter/pages/[[...slug]].js
function getStaticPaths (line 45) | async function getStaticPaths(context) {
function getStaticProps (line 74) | async function getStaticProps(context) {
FILE: packages/starters/next-corporate/starter/pages/_document.js
class MyDocument (line 3) | class MyDocument extends Document {
method render (line 4) | render() {
FILE: packages/starters/next-corporate/starter/pages/api/exit-preview.js
function exit (line 3) | async function exit(req, res) {
FILE: packages/starters/next-corporate/starter/utils/api.js
function getStrapiURL (line 3) | function getStrapiURL(path) {
function fetchAPI (line 16) | async function fetchAPI(path, urlParamsObject = {}, options = {}) {
function getPageData (line 50) | async function getPageData({ slug, locale, preview }) {
function getGlobalData (line 260) | async function getGlobalData(locale) {
FILE: packages/starters/next-corporate/starter/utils/button.js
function getButtonAppearance (line 3) | function getButtonAppearance(type, background) {
FILE: packages/starters/next-corporate/starter/utils/hooks.js
function useLockBodyScroll (line 4) | function useLockBodyScroll() {
function useOnClickOutside (line 17) | function useOnClickOutside(ref, handler) {
FILE: packages/starters/next-corporate/starter/utils/localize.js
function getLocalizedPage (line 3) | async function getLocalizedPage(targetLocale, pageContext) {
function localizePath (line 11) | function localizePath(page) {
function getLocalizedPaths (line 23) | function getLocalizedPaths(page) {
FILE: packages/starters/next-corporate/starter/utils/media.js
function getStrapiMedia (line 1) | function getStrapiMedia(url) {
FILE: packages/starters/next-corporate/starter/utils/parse-cookies.js
function parseCookies (line 3) | function parseCookies(req) {
FILE: packages/templates/blog/template/src/bootstrap.js
function isFirstRun (line 15) | async function isFirstRun() {
function setPublicPermissions (line 26) | async function setPublicPermissions(newPermissions) {
function getFileSizeInBytes (line 53) | function getFileSizeInBytes(filePath) {
function getFileData (line 59) | function getFileData(fileName) {
function uploadFile (line 74) | async function uploadFile(file, name) {
function createEntry (line 91) | async function createEntry({ model, entry }) {
function checkFileExistsBeforeUpload (line 102) | async function checkFileExistsBeforeUpload(files) {
function updateBlocks (line 131) | async function updateBlocks(blocks) {
function importArticles (line 161) | async function importArticles() {
function importGlobal (line 179) | async function importGlobal() {
function importAbout (line 197) | async function importAbout() {
function importCategories (line 211) | async function importCategories() {
function importAuthors (line 217) | async function importAuthors() {
function importSeedData (line 231) | async function importSeedData() {
FILE: packages/templates/blog/template/src/index.js
method register (line 11) | register(/*{ strapi }*/) {}
FILE: packages/templates/corporate/template/src/bootstrap.js
function isFirstRun (line 5) | async function isFirstRun() {
function setPublicPermissions (line 16) | async function setPublicPermissions(newPermissions) {
function getFileSizeInBytes (line 43) | function getFileSizeInBytes(filePath) {
function getFileData (line 49) | function getFileData(fileName) {
function createEntry (line 66) | async function createEntry(model, entry, files) {
function importPages (line 104) | async function importPages(pages) {
function importGlobal (line 178) | async function importGlobal() {
function importLeadFormSubmissionData (line 193) | async function importLeadFormSubmissionData() {
function importSeedData (line 199) | async function importSeedData() {
FILE: packages/templates/corporate/template/src/index.js
method bootstrap (line 6) | async bootstrap() {
FILE: packages/templates/ecommerce/template/src/bootstrap.js
function setPublicPermissions (line 10) | async function setPublicPermissions(newPermissions) {
function isFirstRun (line 37) | async function isFirstRun() {
function getFileSizeInBytes (line 48) | function getFileSizeInBytes(filePath) {
function getFileData (line 54) | function getFileData(fileName) {
function createEntry (line 71) | async function createEntry({ model, entry, files }) {
function importCategories (line 109) | async function importCategories() {
function importProducts (line 117) | async function importProducts() {
function importSeedData (line 132) | async function importSeedData() {
FILE: packages/templates/ecommerce/template/src/index.js
method bootstrap (line 6) | async bootstrap() {
Condensed preview — 204 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (255K chars).
[
{
"path": ".gitignore",
"chars": 3180,
"preview": "\n# Created by https://www.toptal.com/developers/gitignore/api/node,macos,windows,visualstudiocode\n# Edit at https://www."
},
{
"path": "LICENSE.txt",
"chars": 1063,
"preview": "MIT License\n\nCopyright (c) 2021 Strapi\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof "
},
{
"path": "README.md",
"chars": 859,
"preview": "> [!WARNING]\n> This repository is only compatible with Strapi v4 version<br/>\n> The most recent information can be found"
},
{
"path": "lerna.json",
"chars": 154,
"preview": "{\n \"packages\": [\n \"packages/templates/*\",\n \"packages/starters/*\"\n ],\n \"npmClient\": \"yarn\",\n \"useWorkspaces\": t"
},
{
"path": "package.json",
"chars": 549,
"preview": "{\n \"name\": \"@strapi/starters-and-templates\",\n \"version\": \"1.0.0\",\n \"private\": true,\n \"description\": \"All Strapi temp"
},
{
"path": "packages/starters/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "packages/starters/gatsby-blog/README.md",
"chars": 1578,
"preview": "# Strapi Starter Gatsby Blog\n\nGatsby starter for creating a blog with Strapi.\n\nThis starter allows you to try Strapi wit"
},
{
"path": "packages/starters/gatsby-blog/package.json",
"chars": 759,
"preview": "{\n \"name\": \"@strapi/starter-gatsby-blog\",\n \"version\": \"1.0.7\",\n \"description\": \"Strapi blog starter with Gatsby\",\n \""
},
{
"path": "packages/starters/gatsby-blog/starter/.eslintrc.js",
"chars": 335,
"preview": "module.exports = {\n globals: {\n __PATH_PREFIX__: true,\n },\n extends: [\"react-app\", \"prettier\"],\n plugins: [\"prett"
},
{
"path": "packages/starters/gatsby-blog/starter/.gitignore",
"chars": 78,
"preview": "node_modules/\n.cache/\npublic\n.env\n.env.production\n.env.local\n.env.development\n"
},
{
"path": "packages/starters/gatsby-blog/starter/README.md",
"chars": 1900,
"preview": "<p align=\"center\">\n <a href=\"https://www.gatsbyjs.com/?utm_source=starter&utm_medium=readme&utm_campaign=minimal-starte"
},
{
"path": "packages/starters/gatsby-blog/starter/gatsby-browser.js",
"chars": 33,
"preview": "import \"./src/styles/global.css\"\n"
},
{
"path": "packages/starters/gatsby-blog/starter/gatsby-config.js",
"chars": 1529,
"preview": "require(\"dotenv\").config({\n path: `.env.${process.env.NODE_ENV}`,\n})\n\nmodule.exports = {\n plugins: [\n \"gatsby-plugi"
},
{
"path": "packages/starters/gatsby-blog/starter/gatsby-node.js",
"chars": 850,
"preview": "const path = require(\"path\")\n\nexports.createPages = async ({ graphql, actions, reporter }) => {\n const { createPage } ="
},
{
"path": "packages/starters/gatsby-blog/starter/package.json",
"chars": 1283,
"preview": "{\n \"name\": \"my-gatsby-blog\",\n \"version\": \"1.0.7\",\n \"private\": true,\n \"description\": \"Strapi Gatsby Blog\",\n \"author\""
},
{
"path": "packages/starters/gatsby-blog/starter/postcss.config.js",
"chars": 82,
"preview": "module.exports = {\n plugins: {\n tailwindcss: {},\n autoprefixer: {},\n },\n}\n"
},
{
"path": "packages/starters/gatsby-blog/starter/src/components/article-card.js",
"chars": 990,
"preview": "import React from \"react\"\nimport { Link, graphql } from \"gatsby\"\nimport { GatsbyImage, getImage } from \"gatsby-plugin-im"
},
{
"path": "packages/starters/gatsby-blog/starter/src/components/articles-grid.js",
"chars": 344,
"preview": "import React from \"react\"\nimport ArticleCard from \"./article-card\"\n\nconst ArticlesGrid = ({ articles }) => {\n return (\n"
},
{
"path": "packages/starters/gatsby-blog/starter/src/components/block-media.js",
"chars": 446,
"preview": "import React from \"react\"\nimport { GatsbyImage, getImage } from \"gatsby-plugin-image\"\n\nconst BlockMedia = ({ data }) => "
},
{
"path": "packages/starters/gatsby-blog/starter/src/components/block-quote.js",
"chars": 451,
"preview": "import React from \"react\"\n\nconst BlockQuote = ({ data }) => {\n return (\n <div className=\"py-6\">\n <blockquote cl"
},
{
"path": "packages/starters/gatsby-blog/starter/src/components/block-rich-text.js",
"chars": 297,
"preview": "import React from \"react\"\n\nconst BlockRichText = ({ data }) => {\n return (\n <div className=\"prose mx-auto py-8\">\n "
},
{
"path": "packages/starters/gatsby-blog/starter/src/components/block-slider.js",
"chars": 736,
"preview": "import React from \"react\"\nimport { GatsbyImage, getImage } from \"gatsby-plugin-image\"\nimport Slider from \"react-slick\"\ni"
},
{
"path": "packages/starters/gatsby-blog/starter/src/components/blocks-renderer.js",
"chars": 1709,
"preview": "import React from \"react\"\nimport { graphql } from \"gatsby\"\nimport BlockRichText from \"./block-rich-text\"\nimport BlockMed"
},
{
"path": "packages/starters/gatsby-blog/starter/src/components/footer.js",
"chars": 306,
"preview": "import React from \"react\"\n\nconst Footer = () => {\n const currentYear = new Date().getFullYear()\n\n return (\n <footer"
},
{
"path": "packages/starters/gatsby-blog/starter/src/components/headings.js",
"chars": 346,
"preview": "import React from \"react\"\n\nconst Headings = ({ title, description }) => {\n return (\n <header className=\"container mt"
},
{
"path": "packages/starters/gatsby-blog/starter/src/components/layout.js",
"chars": 349,
"preview": "import React from \"react\"\nimport Footer from \"./footer\"\nimport Navbar from \"./navbar\"\n\nconst Layout = ({ children }) => "
},
{
"path": "packages/starters/gatsby-blog/starter/src/components/navbar.js",
"chars": 527,
"preview": "import { Link } from \"gatsby\"\nimport React from \"react\"\n\nconst Navbar = () => {\n return (\n <header className=\"bg-pri"
},
{
"path": "packages/starters/gatsby-blog/starter/src/components/seo.js",
"chars": 2230,
"preview": "import React from \"react\"\nimport { Helmet } from \"react-helmet\"\nimport { useStaticQuery, graphql } from \"gatsby\"\n\nconst "
},
{
"path": "packages/starters/gatsby-blog/starter/src/pages/about.js",
"chars": 739,
"preview": "import React from \"react\"\nimport { useStaticQuery, graphql } from \"gatsby\"\nimport Layout from \"../components/layout\"\nimp"
},
{
"path": "packages/starters/gatsby-blog/starter/src/pages/index.js",
"chars": 840,
"preview": "import React from \"react\"\nimport { useStaticQuery, graphql } from \"gatsby\"\nimport Layout from \"../components/layout\"\nimp"
},
{
"path": "packages/starters/gatsby-blog/starter/src/styles/global.css",
"chars": 59,
"preview": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n"
},
{
"path": "packages/starters/gatsby-blog/starter/src/templates/article-post.js",
"chars": 1418,
"preview": "import React from \"react\"\nimport { graphql } from \"gatsby\"\nimport { GatsbyImage, getImage } from \"gatsby-plugin-image\"\ni"
},
{
"path": "packages/starters/gatsby-blog/starter/tailwind.config.js",
"chars": 510,
"preview": "const colors = require(\"tailwindcss/colors\")\n\nmodule.exports = {\n content: [\"./src/**/*.{js,jsx,ts,tsx}\"],\n theme: {\n "
},
{
"path": "packages/starters/gatsby-blog/starter.json",
"chars": 85,
"preview": "{\n \"template\": {\n \"name\": \"@strapi/template-blog\",\n \"version\": \"^2.0.0\"\n }\n}\n"
},
{
"path": "packages/starters/gatsby-corporate/README.md",
"chars": 489,
"preview": "# Placeholder for Strapi Gatsby corporate starter\n\nThe Strapi team will build this starter soon.\n\nIn the meantime, you c"
},
{
"path": "packages/starters/next-blog/README.md",
"chars": 1843,
"preview": "# Strapi Starter Next Blog\n\nNext starter for creating a blog with Strapi.\n\n\n\nThis s"
},
{
"path": "packages/starters/next-blog/package.json",
"chars": 756,
"preview": "{\n \"name\": \"@strapi/starter-next-blog\",\n \"version\": \"1.0.4\",\n \"description\": \"Strapi blog starter with Next.js\",\n \"k"
},
{
"path": "packages/starters/next-blog/starter/.eslintrc",
"chars": 136,
"preview": "{\n \"extends\": [\n \"next\",\n \"prettier\"\n ],\n \"plugins\": [\n \"prettier\"\n ],\n \"rules\": {\n \"prettier/prettier\""
},
{
"path": "packages/starters/next-blog/starter/.prettierrc",
"chars": 106,
"preview": "{\n \"printWidth\": 80,\n \"singleQuote\": false,\n \"trailingComma\": \"es5\",\n \"semi\": false,\n \"tabWidth\": 2\n}"
},
{
"path": "packages/starters/next-blog/starter/assets/css/style.css",
"chars": 550,
"preview": "a {\n text-decoration: none;\n}\n\nh1 {\n font-family: Staatliches;\n font-size: 120px;\n}\n\n#category {\n font-family: Staat"
},
{
"path": "packages/starters/next-blog/starter/components/articles.js",
"chars": 1058,
"preview": "import React from \"react\"\nimport Card from \"./card\"\n\nconst Articles = ({ articles }) => {\n const leftArticlesCount = Ma"
},
{
"path": "packages/starters/next-blog/starter/components/card.js",
"chars": 753,
"preview": "import React from \"react\"\nimport Link from \"next/link\"\nimport NextImage from \"./image\"\n\nconst Card = ({ article }) => {\n"
},
{
"path": "packages/starters/next-blog/starter/components/image.js",
"chars": 533,
"preview": "import { getStrapiMedia } from \"../lib/media\"\nimport NextImage from \"next/image\"\n\nconst Image = ({ image, style }) => {\n"
},
{
"path": "packages/starters/next-blog/starter/components/layout.js",
"chars": 164,
"preview": "import Nav from \"./nav\"\n\nconst Layout = ({ children, categories, seo }) => (\n <>\n <Nav categories={categories} />\n "
},
{
"path": "packages/starters/next-blog/starter/components/nav.js",
"chars": 907,
"preview": "import React from \"react\"\nimport Link from \"next/link\"\n\nconst Nav = ({ categories }) => {\n return (\n <div>\n <na"
},
{
"path": "packages/starters/next-blog/starter/components/seo.js",
"chars": 1528,
"preview": "import Head from \"next/head\"\nimport { useContext } from \"react\"\nimport { GlobalContext } from \"../pages/_app\"\nimport { g"
},
{
"path": "packages/starters/next-blog/starter/lib/api.js",
"chars": 1227,
"preview": "import qs from \"qs\"\n\n/**\n * Get full Strapi URL from path\n * @param {string} path Path of the URL\n * @returns {string} F"
},
{
"path": "packages/starters/next-blog/starter/lib/media.js",
"chars": 203,
"preview": "import { getStrapiURL } from \"./api\"\n\nexport function getStrapiMedia(media) {\n const { url } = media.data.attributes\n "
},
{
"path": "packages/starters/next-blog/starter/next.config.js",
"chars": 165,
"preview": "/**\n * @type {import('next').NextConfig}\n */\nconst nextConfig = {\n images: {\n loader: \"default\",\n domains: [\"loca"
},
{
"path": "packages/starters/next-blog/starter/package.json",
"chars": 716,
"preview": "{\n \"name\": \"my-next-blog\",\n \"version\": \"1.0.3\",\n \"private\": true,\n \"scripts\": {\n \"develop\": \"next dev\",\n \"dev\""
},
{
"path": "packages/starters/next-blog/starter/pages/_app.js",
"chars": 1446,
"preview": "import App from \"next/app\"\nimport Head from \"next/head\"\nimport \"../assets/css/style.css\"\nimport { createContext } from \""
},
{
"path": "packages/starters/next-blog/starter/pages/_document.js",
"chars": 1018,
"preview": "import Document, { Html, Head, Main, NextScript } from \"next/document\"\n\nclass MyDocument extends Document {\n render() {"
},
{
"path": "packages/starters/next-blog/starter/pages/article/[slug].js",
"chars": 2569,
"preview": "import ReactMarkdown from \"react-markdown\"\nimport Moment from \"react-moment\"\nimport { fetchAPI } from \"../../lib/api\"\nim"
},
{
"path": "packages/starters/next-blog/starter/pages/category/[slug].js",
"chars": 1405,
"preview": "import Articles from \"../../components/articles\"\nimport { fetchAPI } from \"../../lib/api\"\nimport Layout from \"../../comp"
},
{
"path": "packages/starters/next-blog/starter/pages/index.js",
"chars": 1114,
"preview": "import React from \"react\"\nimport Articles from \"../components/articles\"\nimport Layout from \"../components/layout\"\nimport"
},
{
"path": "packages/starters/next-blog/starter.json",
"chars": 85,
"preview": "{\n \"template\": {\n \"name\": \"@strapi/template-blog\",\n \"version\": \"^1.0.0\"\n }\n}\n"
},
{
"path": "packages/starters/next-corporate/README.md",
"chars": 4208,
"preview": "# Strapi Starter Next Corporate Site\n\nNext starter for creating a corporate site with Strapi.\n\n[View the live demo](http"
},
{
"path": "packages/starters/next-corporate/package.json",
"chars": 776,
"preview": "{\n \"name\": \"@strapi/starter-next-corporate\",\n \"version\": \"1.0.4\",\n \"description\": \"Strapi corporate starter with Next"
},
{
"path": "packages/starters/next-corporate/starter/.eslintrc",
"chars": 300,
"preview": "{\n \"extends\": [\n \"next\",\n \"prettier\"\n ],\n \"plugins\": [\n \"prettier\"\n ],\n \"rules\": {\n \"prettier/prettier\""
},
{
"path": "packages/starters/next-corporate/starter/.gitignore",
"chars": 371,
"preview": ".vscode\n\n# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_mod"
},
{
"path": "packages/starters/next-corporate/starter/README.md",
"chars": 1529,
"preview": "# Next frontend\n\nThis frontend relies on Next's [Static Generation](https://nextjs.org/docs/basic-features/pages) using "
},
{
"path": "packages/starters/next-corporate/starter/components/elements/button-link.js",
"chars": 1724,
"preview": "import classNames from \"classnames\"\nimport PropTypes from \"prop-types\"\nimport { buttonLinkPropTypes } from \"utils/types\""
},
{
"path": "packages/starters/next-corporate/starter/components/elements/button.js",
"chars": 1725,
"preview": "import classNames from \"classnames\"\nimport PropTypes from \"prop-types\"\nimport { buttonLinkPropTypes } from \"utils/types\""
},
{
"path": "packages/starters/next-corporate/starter/components/elements/custom-link.js",
"chars": 830,
"preview": "import Link from \"next/link\"\nimport PropTypes from \"prop-types\"\nimport { linkPropTypes } from \"utils/types\"\n\nconst Custo"
},
{
"path": "packages/starters/next-corporate/starter/components/elements/footer.js",
"chars": 1880,
"preview": "import PropTypes from \"prop-types\"\nimport { linkPropTypes, mediaPropTypes } from \"utils/types\"\nimport NextImage from \"./"
},
{
"path": "packages/starters/next-corporate/starter/components/elements/image.js",
"chars": 902,
"preview": "import { getStrapiMedia } from \"utils/media\"\nimport Image from \"next/image\"\nimport PropTypes from \"prop-types\"\nimport { "
},
{
"path": "packages/starters/next-corporate/starter/components/elements/loader.js",
"chars": 740,
"preview": "import React from \"react\"\n\nconst Loader = () => {\n return (\n <svg\n viewBox=\"0 0 38 38\"\n className=\"animate"
},
{
"path": "packages/starters/next-corporate/starter/components/elements/mobile-nav-menu.js",
"chars": 2139,
"preview": "import PropTypes from \"prop-types\"\nimport { MdClose, MdChevronRight } from \"react-icons/md\"\nimport { mediaPropTypes, lin"
},
{
"path": "packages/starters/next-corporate/starter/components/elements/navbar.js",
"chars": 3293,
"preview": "import { useState } from \"react\"\nimport PropTypes from \"prop-types\"\nimport Link from \"next/link\"\nimport { useRouter } fr"
},
{
"path": "packages/starters/next-corporate/starter/components/elements/notification-banner.js",
"chars": 906,
"preview": "import Markdown from \"react-markdown\"\nimport classNames from \"classnames\"\nimport { MdClose } from \"react-icons/md\"\n\ncons"
},
{
"path": "packages/starters/next-corporate/starter/components/elements/seo.js",
"chars": 1580,
"preview": "import { NextSeo } from \"next-seo\"\nimport PropTypes from \"prop-types\"\nimport { getStrapiMedia } from \"utils/media\"\nimpor"
},
{
"path": "packages/starters/next-corporate/starter/components/elements/video.js",
"chars": 728,
"preview": "import PropTypes from \"prop-types\"\nimport { getStrapiMedia } from \"utils/media\"\nimport { mediaPropTypes } from \"utils/ty"
},
{
"path": "packages/starters/next-corporate/starter/components/icons/world.js",
"chars": 2326,
"preview": "import React from \"react\"\n\nconst WorldIcon = () => {\n return (\n <div className=\"w-4 h-4 mr-2 \">\n <svg\n c"
},
{
"path": "packages/starters/next-corporate/starter/components/layout.js",
"chars": 908,
"preview": "import { useState } from \"react\"\nimport Navbar from \"./elements/navbar\"\nimport Footer from \"./elements/footer\"\nimport No"
},
{
"path": "packages/starters/next-corporate/starter/components/locale-switch.js",
"chars": 3216,
"preview": "import { useEffect, useState, useRef } from \"react\"\nimport { useRouter } from \"next/router\"\nimport PropTypes from \"prop-"
},
{
"path": "packages/starters/next-corporate/starter/components/sections/bottom-actions.js",
"chars": 660,
"preview": "import ButtonLink from \"@/components/elements/button-link\"\nimport { getButtonAppearance } from \"utils/button\"\n\nconst Bot"
},
{
"path": "packages/starters/next-corporate/starter/components/sections/feature-columns-group.js",
"chars": 576,
"preview": "import NextImage from \"../elements/image\"\n\nconst FeatureColumnsGroup = ({ data }) => {\n return (\n <div className=\"co"
},
{
"path": "packages/starters/next-corporate/starter/components/sections/feature-rows-group.js",
"chars": 1799,
"preview": "import classNames from \"classnames\"\nimport NextImage from \"../elements/image\"\nimport Video from \"../elements/video\"\nimpo"
},
{
"path": "packages/starters/next-corporate/starter/components/sections/hero.js",
"chars": 1420,
"preview": "import Markdown from \"react-markdown\"\nimport { getButtonAppearance } from \"utils/button\"\nimport ButtonLink from \"../elem"
},
{
"path": "packages/starters/next-corporate/starter/components/sections/large-video.js",
"chars": 572,
"preview": "import Video from \"../elements/video\"\n\nconst LargeVideo = ({ data }) => {\n return (\n <section className=\"container f"
},
{
"path": "packages/starters/next-corporate/starter/components/sections/lead-form.js",
"chars": 2229,
"preview": "import { useState } from \"react\"\nimport { fetchAPI } from \"utils/api\"\nimport * as yup from \"yup\"\nimport { Formik, Form, "
},
{
"path": "packages/starters/next-corporate/starter/components/sections/pricing.js",
"chars": 1893,
"preview": "import { MdCheckBox } from \"react-icons/md\"\nimport classNames from \"classnames\"\n\nconst Pricing = ({ data }) => {\n retur"
},
{
"path": "packages/starters/next-corporate/starter/components/sections/rich-text.js",
"chars": 344,
"preview": "import PropTypes from \"prop-types\"\nimport Markdown from \"react-markdown\"\n\nconst RichText = ({ data }) => {\n return (\n "
},
{
"path": "packages/starters/next-corporate/starter/components/sections/testimonials-group.js",
"chars": 3132,
"preview": "import classNames from \"classnames\"\nimport { useState } from \"react\"\nimport NextImage from \"../elements/image\"\nimport Cu"
},
{
"path": "packages/starters/next-corporate/starter/components/sections.js",
"chars": 2302,
"preview": "import { useRouter } from \"next/router\"\nimport Hero from \"@/components/sections/hero\"\nimport LargeVideo from \"@/componen"
},
{
"path": "packages/starters/next-corporate/starter/jsconfig.json",
"chars": 223,
"preview": "{\n \"compilerOptions\": {\n \"baseUrl\": \".\",\n \"paths\": {\n \"@/components/*\": [\n \"components/*\"\n ],\n "
},
{
"path": "packages/starters/next-corporate/starter/next.config.js",
"chars": 88,
"preview": "module.exports = {\n i18n: {\n locales: ['en', 'fr'],\n defaultLocale: 'en',\n },\n}\n"
},
{
"path": "packages/starters/next-corporate/starter/package.json",
"chars": 1103,
"preview": "{\n \"name\": \"my-next-corporate\",\n \"version\": \"1.0.7\",\n \"private\": true,\n \"scripts\": {\n \"dev\": \"next\",\n \"develop"
},
{
"path": "packages/starters/next-corporate/starter/pages/[[...slug]].js",
"chars": 3197,
"preview": "import ErrorPage from \"next/error\"\nimport { getPageData, fetchAPI, getGlobalData } from \"utils/api\"\nimport Sections from"
},
{
"path": "packages/starters/next-corporate/starter/pages/_app.js",
"chars": 2036,
"preview": "import App from \"next/app\"\nimport Head from \"next/head\"\nimport ErrorPage from \"next/error\"\nimport { useRouter } from \"ne"
},
{
"path": "packages/starters/next-corporate/starter/pages/_document.js",
"chars": 290,
"preview": "import Document, { Html, Head, Main, NextScript } from \"next/document\"\n\nexport default class MyDocument extends Document"
},
{
"path": "packages/starters/next-corporate/starter/pages/api/exit-preview.js",
"chars": 347,
"preview": "import { redirect } from \"next/dist/next-server/server/api-utils\"\n\nexport default async function exit(req, res) {\n // E"
},
{
"path": "packages/starters/next-corporate/starter/pages/api/preview.js",
"chars": 1507,
"preview": "import { getPageData } from \"utils/api\"\nimport { parseCookies } from \"utils/parse-cookies\"\n\nconst preview = async (req, "
},
{
"path": "packages/starters/next-corporate/starter/postcss.config.js",
"chars": 186,
"preview": "module.exports = {\n plugins: [\n \"postcss-import\",\n \"tailwindcss\",\n \"postcss-flexbugs-fixes\",\n \"postcss-nest"
},
{
"path": "packages/starters/next-corporate/starter/public/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "packages/starters/next-corporate/starter/styles/index.css",
"chars": 1218,
"preview": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\nhtml {\n font-size: 13px;\n @apply text-gray-900;\n}\n\n@media "
},
{
"path": "packages/starters/next-corporate/starter/tailwind.config.js",
"chars": 651,
"preview": "const { colors } = require(`tailwindcss/defaultTheme`)\n\nmodule.exports = {\n mode: \"jit\", // see https://tailwindcss.com"
},
{
"path": "packages/starters/next-corporate/starter/utils/api.js",
"chars": 9397,
"preview": "import qs from \"qs\"\n\nexport function getStrapiURL(path) {\n return `${\n process.env.NEXT_PUBLIC_STRAPI_API_URL || \"ht"
},
{
"path": "packages/starters/next-corporate/starter/utils/button.js",
"chars": 724,
"preview": "// Decide what the button will look like based on its type (primary or secondary)\n// and on its background (light or dar"
},
{
"path": "packages/starters/next-corporate/starter/utils/hooks.js",
"chars": 1085,
"preview": "import { useEffect } from 'react'\n\n// Got from https://usehooks.com/useLockBodyScroll/\nexport function useLockBodyScroll"
},
{
"path": "packages/starters/next-corporate/starter/utils/localize.js",
"chars": 786,
"preview": "import { fetchAPI } from \"./api\"\n\nexport async function getLocalizedPage(targetLocale, pageContext) {\n const localizati"
},
{
"path": "packages/starters/next-corporate/starter/utils/media.js",
"chars": 380,
"preview": "export function getStrapiMedia(url) {\n if (url == null) {\n return null\n }\n\n // Return the full URL if the media is"
},
{
"path": "packages/starters/next-corporate/starter/utils/parse-cookies.js",
"chars": 139,
"preview": "import cookie from \"cookie\"\n\nexport function parseCookies(req) {\n return cookie.parse(req ? req.headers.cookie || '' : "
},
{
"path": "packages/starters/next-corporate/starter/utils/types.js",
"chars": 734,
"preview": "import PropTypes from \"prop-types\"\n\nexport const linkPropTypes = PropTypes.shape({\n id: PropTypes.oneOfType([PropTypes."
},
{
"path": "packages/starters/next-corporate/starter.json",
"chars": 90,
"preview": "{\n \"template\": {\n \"name\": \"@strapi/template-corporate\",\n \"version\": \"^1.0.0\"\n }\n}\n"
},
{
"path": "packages/templates/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "packages/templates/blog/README.md",
"chars": 651,
"preview": "# strapi-template-blog\n\nA Strapi template to create Strapi projects pre-configured for blog apps.\n\n## Usage\n\n```bash\n# U"
},
{
"path": "packages/templates/blog/package.json",
"chars": 746,
"preview": "{\n \"name\": \"@strapi/template-blog\",\n \"version\": \"2.0.5\",\n \"description\": \"Strapi blog template\",\n \"keywords\": [\n "
},
{
"path": "packages/templates/blog/template/data/data.json",
"chars": 13399,
"preview": "{\n \"global\": {\n \"siteName\": \"Strapi Blog\",\n \"defaultSeo\": {\n \"metaTitle\": \"Page\",\n \"metaDescription\": \""
},
{
"path": "packages/templates/blog/template/src/api/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "packages/templates/blog/template/src/api/about/content-types/about/schema.json",
"chars": 549,
"preview": "{\n \"kind\": \"singleType\",\n \"collectionName\": \"abouts\",\n \"info\": {\n \"singularName\": \"about\",\n \"pluralName\": \"abou"
},
{
"path": "packages/templates/blog/template/src/api/about/controllers/about.js",
"chars": 175,
"preview": "'use strict';\n\n/**\n * about controller\n */\n\nconst { createCoreController } = require('@strapi/strapi').factories;\n\nmodu"
},
{
"path": "packages/templates/blog/template/src/api/about/routes/about.js",
"chars": 163,
"preview": "'use strict';\n\n/**\n * about router.\n */\n\nconst { createCoreRouter } = require('@strapi/strapi').factories;\n\nmodule.expor"
},
{
"path": "packages/templates/blog/template/src/api/about/services/about.js",
"chars": 166,
"preview": "'use strict';\n\n/**\n * about service.\n */\n\nconst { createCoreService } = require('@strapi/strapi').factories;\n\nmodule.exp"
},
{
"path": "packages/templates/blog/template/src/api/article/content-types/article/schema.json",
"chars": 1163,
"preview": "{\n \"kind\": \"collectionType\",\n \"collectionName\": \"articles\",\n \"info\": {\n \"singularName\": \"article\",\n \"pluralName"
},
{
"path": "packages/templates/blog/template/src/api/article/controllers/article.js",
"chars": 181,
"preview": "'use strict';\n\n/**\n * article controller\n */\n\nconst { createCoreController } = require('@strapi/strapi').factories;\n\nmo"
},
{
"path": "packages/templates/blog/template/src/api/article/routes/article.js",
"chars": 169,
"preview": "'use strict';\n\n/**\n * article router.\n */\n\nconst { createCoreRouter } = require('@strapi/strapi').factories;\n\nmodule.exp"
},
{
"path": "packages/templates/blog/template/src/api/article/services/article.js",
"chars": 172,
"preview": "'use strict';\n\n/**\n * article service.\n */\n\nconst { createCoreService } = require('@strapi/strapi').factories;\n\nmodule.e"
},
{
"path": "packages/templates/blog/template/src/api/author/content-types/author/schema.json",
"chars": 732,
"preview": "{\n \"kind\": \"collectionType\",\n \"collectionName\": \"authors\",\n \"info\": {\n \"singularName\": \"author\",\n \"pluralName\":"
},
{
"path": "packages/templates/blog/template/src/api/author/controllers/author.js",
"chars": 178,
"preview": "'use strict';\n\n/**\n * author controller\n */\n\nconst { createCoreController } = require('@strapi/strapi').factories;\n\nmod"
},
{
"path": "packages/templates/blog/template/src/api/author/routes/author.js",
"chars": 166,
"preview": "'use strict';\n\n/**\n * author router.\n */\n\nconst { createCoreRouter } = require('@strapi/strapi').factories;\n\nmodule.expo"
},
{
"path": "packages/templates/blog/template/src/api/author/services/author.js",
"chars": 169,
"preview": "'use strict';\n\n/**\n * author service.\n */\n\nconst { createCoreService } = require('@strapi/strapi').factories;\n\nmodule.ex"
},
{
"path": "packages/templates/blog/template/src/api/category/content-types/category/schema.json",
"chars": 615,
"preview": "{\n \"kind\": \"collectionType\",\n \"collectionName\": \"categories\",\n \"info\": {\n \"singularName\": \"category\",\n \"pluralN"
},
{
"path": "packages/templates/blog/template/src/api/category/controllers/category.js",
"chars": 184,
"preview": "'use strict';\n\n/**\n * category controller\n */\n\nconst { createCoreController } = require('@strapi/strapi').factories;\n\nm"
},
{
"path": "packages/templates/blog/template/src/api/category/routes/category.js",
"chars": 172,
"preview": "'use strict';\n\n/**\n * category router.\n */\n\nconst { createCoreRouter } = require('@strapi/strapi').factories;\n\nmodule.ex"
},
{
"path": "packages/templates/blog/template/src/api/category/services/category.js",
"chars": 175,
"preview": "'use strict';\n\n/**\n * category service.\n */\n\nconst { createCoreService } = require('@strapi/strapi').factories;\n\nmodule."
},
{
"path": "packages/templates/blog/template/src/api/global/content-types/global/schema.json",
"chars": 744,
"preview": "{\n \"kind\": \"singleType\",\n \"collectionName\": \"globals\",\n \"info\": {\n \"singularName\": \"global\",\n \"pluralName\": \"gl"
},
{
"path": "packages/templates/blog/template/src/api/global/controllers/global.js",
"chars": 178,
"preview": "'use strict';\n\n/**\n * global controller\n */\n\nconst { createCoreController } = require('@strapi/strapi').factories;\n\nmod"
},
{
"path": "packages/templates/blog/template/src/api/global/routes/global.js",
"chars": 166,
"preview": "'use strict';\n\n/**\n * global router.\n */\n\nconst { createCoreRouter } = require('@strapi/strapi').factories;\n\nmodule.expo"
},
{
"path": "packages/templates/blog/template/src/api/global/services/global.js",
"chars": 169,
"preview": "'use strict';\n\n/**\n * global service.\n */\n\nconst { createCoreService } = require('@strapi/strapi').factories;\n\nmodule.ex"
},
{
"path": "packages/templates/blog/template/src/bootstrap.js",
"chars": 6788,
"preview": "\"use strict\";\n\nconst fs = require(\"fs-extra\");\nconst path = require(\"path\");\nconst mime = require(\"mime-types\");\nconst s"
},
{
"path": "packages/templates/blog/template/src/components/shared/media.json",
"chars": 312,
"preview": "{\n \"collectionName\": \"components_shared_media\",\n \"info\": {\n \"displayName\": \"Media\",\n \"icon\": \"file-video\"\n },\n "
},
{
"path": "packages/templates/blog/template/src/components/shared/quote.json",
"chars": 243,
"preview": "{\n \"collectionName\": \"components_shared_quotes\",\n \"info\": {\n \"displayName\": \"Quote\",\n \"icon\": \"indent\"\n },\n \"o"
},
{
"path": "packages/templates/blog/template/src/components/shared/rich-text.json",
"chars": 240,
"preview": "{\n \"collectionName\": \"components_shared_rich_texts\",\n \"info\": {\n \"displayName\": \"Rich text\",\n \"icon\": \"align-jus"
},
{
"path": "packages/templates/blog/template/src/components/shared/seo.json",
"chars": 496,
"preview": "{\n \"collectionName\": \"components_shared_seos\",\n \"info\": {\n \"name\": \"Seo\",\n \"icon\": \"allergies\",\n \"displayName"
},
{
"path": "packages/templates/blog/template/src/components/shared/slider.json",
"chars": 330,
"preview": "{\n \"collectionName\": \"components_shared_sliders\",\n \"info\": {\n \"displayName\": \"Slider\",\n \"icon\": \"address-book\",\n"
},
{
"path": "packages/templates/blog/template/src/extensions/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "packages/templates/blog/template/src/index.js",
"chars": 504,
"preview": "\"use strict\";\nconst bootstrap = require(\"./bootstrap\");\n\nmodule.exports = {\n /**\n * An asynchronous register function"
},
{
"path": "packages/templates/blog/template.json",
"chars": 182,
"preview": "{\n \"package\": {\n \"dependencies\": {\n \"@strapi/plugin-graphql\": \"^4.0.5\",\n \"fs-extra\": \"^10.0.0\",\n \"lod"
},
{
"path": "packages/templates/corporate/README.md",
"chars": 531,
"preview": "# strapi-template-corporate\n\nA Strapi template to create Strapi projects pre-configured for corporate apps.\n\n## Usage\n\n`"
},
{
"path": "packages/templates/corporate/package.json",
"chars": 761,
"preview": "{\n \"name\": \"@strapi/template-corporate\",\n \"version\": \"1.0.0\",\n \"description\": \"Strapi corporate template\",\n \"keyword"
},
{
"path": "packages/templates/corporate/template/data/data.js",
"chars": 273,
"preview": "const { global, pages } = require(\"./en\");\nconst { globalFR, pagesFR } = require(\"./fr\");\nconst { leadFormSubmissions } "
},
{
"path": "packages/templates/corporate/template/data/en/global.json",
"chars": 2841,
"preview": "{\n \"global\": {\n \"id\": 1,\n \"locale\": \"en\",\n \"metaTitleSuffix\": \"Strapi Corporate\",\n \"metadata\": {\n \"id\""
},
{
"path": "packages/templates/corporate/template/data/en/index.js",
"chars": 127,
"preview": "const { global } = require(\"./global.json\");\nconst { pages } = require(\"./pages.json\");\n\nmodule.exports = {\n global,\n "
},
{
"path": "packages/templates/corporate/template/data/en/pages.json",
"chars": 10987,
"preview": "{\n \"pages\": [\n {\n \"id\": 1,\n \"slug\": \"\",\n \"locale\": \"en\",\n \"localizations\": [\n {\n "
},
{
"path": "packages/templates/corporate/template/data/fr/global.json",
"chars": 2900,
"preview": "{\n \"globalFR\": {\n \"id\": 2,\n \"locale\": \"fr\",\n \"metaTitleSuffix\": \"Strapi Corporate\",\n \"metadata\": {\n \"i"
},
{
"path": "packages/templates/corporate/template/data/fr/index.js",
"chars": 135,
"preview": "const { globalFR } = require(\"./global.json\");\nconst { pagesFR } = require(\"./pages.json\");\n\nmodule.exports = {\n global"
},
{
"path": "packages/templates/corporate/template/data/fr/pages.json",
"chars": 11248,
"preview": "{\n \"pagesFR\": [\n {\n \"id\": 5,\n \"slug\": \"\",\n \"locale\": \"fr\",\n \"localizations\": [\n {\n "
},
{
"path": "packages/templates/corporate/template/data/lead-form-submissions.json",
"chars": 380,
"preview": "{\n \"leadFormSubmissions\": [\n {\n \"email\": \"johne@thebeatles.com\",\n \"status\": \"seen\",\n \"location\": \"Hom"
},
{
"path": "packages/templates/corporate/template/src/api/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "packages/templates/corporate/template/src/api/global/content-types/global/schema.json",
"chars": 1590,
"preview": "{\n \"kind\": \"singleType\",\n \"collectionName\": \"globals\",\n \"info\": {\n \"singularName\": \"global\",\n \"pluralName\": \"gl"
},
{
"path": "packages/templates/corporate/template/src/api/global/controllers/global.js",
"chars": 178,
"preview": "'use strict';\n\n/**\n * global controller\n */\n\nconst { createCoreController } = require('@strapi/strapi').factories;\n\nmod"
},
{
"path": "packages/templates/corporate/template/src/api/global/routes/global.js",
"chars": 166,
"preview": "'use strict';\n\n/**\n * global router.\n */\n\nconst { createCoreRouter } = require('@strapi/strapi').factories;\n\nmodule.expo"
},
{
"path": "packages/templates/corporate/template/src/api/global/services/global.js",
"chars": 169,
"preview": "'use strict';\n\n/**\n * global service.\n */\n\nconst { createCoreService } = require('@strapi/strapi').factories;\n\nmodule.ex"
},
{
"path": "packages/templates/corporate/template/src/api/lead-form-submission/content-types/lead-form-submission/schema.json",
"chars": 601,
"preview": "{\n \"kind\": \"collectionType\",\n \"collectionName\": \"lead_form_submissions\",\n \"info\": {\n \"singularName\": \"lead-form-su"
},
{
"path": "packages/templates/corporate/template/src/api/lead-form-submission/controllers/lead-form-submission.js",
"chars": 220,
"preview": "'use strict';\n\n/**\n * lead-form-submission controller\n */\n\nconst { createCoreController } = require('@strapi/strapi').f"
},
{
"path": "packages/templates/corporate/template/src/api/lead-form-submission/routes/lead-form-submission.js",
"chars": 208,
"preview": "'use strict';\n\n/**\n * lead-form-submission router.\n */\n\nconst { createCoreRouter } = require('@strapi/strapi').factories"
},
{
"path": "packages/templates/corporate/template/src/api/lead-form-submission/services/lead-form-submission.js",
"chars": 211,
"preview": "'use strict';\n\n/**\n * lead-form-submission service.\n */\n\nconst { createCoreService } = require('@strapi/strapi').factori"
},
{
"path": "packages/templates/corporate/template/src/api/page/content-types/page/schema.json",
"chars": 1383,
"preview": "{\n \"kind\": \"collectionType\",\n \"collectionName\": \"pages\",\n \"info\": {\n \"singularName\": \"page\",\n \"pluralName\": \"pa"
},
{
"path": "packages/templates/corporate/template/src/api/page/controllers/page.js",
"chars": 172,
"preview": "'use strict';\n\n/**\n * page controller\n */\n\nconst { createCoreController } = require('@strapi/strapi').factories;\n\nmodul"
},
{
"path": "packages/templates/corporate/template/src/api/page/routes/page.js",
"chars": 160,
"preview": "'use strict';\n\n/**\n * page router.\n */\n\nconst { createCoreRouter } = require('@strapi/strapi').factories;\n\nmodule.export"
},
{
"path": "packages/templates/corporate/template/src/api/page/services/page.js",
"chars": 163,
"preview": "'use strict';\n\n/**\n * page service.\n */\n\nconst { createCoreService } = require('@strapi/strapi').factories;\n\nmodule.expo"
},
{
"path": "packages/templates/corporate/template/src/bootstrap.js",
"chars": 6575,
"preview": "const fs = require(\"fs\");\nconst { pages, globals, leadFormSubmissions } = require(\"../data/data\");\nconst set = require(\""
},
{
"path": "packages/templates/corporate/template/src/components/elements/feature-column.json",
"chars": 492,
"preview": "{\n \"collectionName\": \"components_slices_feature_columns\",\n \"info\": {\n \"name\": \"FeatureColumn\",\n \"displayName\": \""
},
{
"path": "packages/templates/corporate/template/src/components/elements/feature-row.json",
"chars": 609,
"preview": "{\n \"collectionName\": \"components_slices_feature_rows\",\n \"info\": {\n \"name\": \"FeatureRow\",\n \"displayName\": \"Featur"
},
{
"path": "packages/templates/corporate/template/src/components/elements/feature.json",
"chars": 236,
"preview": "{\n \"collectionName\": \"components_elements_features\",\n \"info\": {\n \"name\": \"feature\",\n \"displayName\": \"Feature\",\n "
},
{
"path": "packages/templates/corporate/template/src/components/elements/footer-section.json",
"chars": 367,
"preview": "{\n \"collectionName\": \"components_links_footer_sections\",\n \"info\": {\n \"name\": \"FooterSection\",\n \"displayName\": \"F"
},
{
"path": "packages/templates/corporate/template/src/components/elements/logos.json",
"chars": 369,
"preview": "{\n \"collectionName\": \"components_elements_logos\",\n \"info\": {\n \"name\": \"logos\",\n \"displayName\": \"Logos\",\n \"ico"
},
{
"path": "packages/templates/corporate/template/src/components/elements/notification-banner.json",
"chars": 420,
"preview": "{\n \"collectionName\": \"components_elements_notification_banners\",\n \"info\": {\n \"name\": \"NotificationBanner\",\n \"dis"
},
{
"path": "packages/templates/corporate/template/src/components/elements/plan.json",
"chars": 551,
"preview": "{\n \"collectionName\": \"components_elements_plans\",\n \"info\": {\n \"name\": \"plan\",\n \"displayName\": \"Pricing plan\",\n "
},
{
"path": "packages/templates/corporate/template/src/components/elements/testimonial.json",
"chars": 675,
"preview": "{\n \"collectionName\": \"components_slices_testimonials\",\n \"info\": {\n \"name\": \"Testimonial\",\n \"displayName\": \"Testi"
},
{
"path": "packages/templates/corporate/template/src/components/layout/footer.json",
"chars": 504,
"preview": "{\n \"collectionName\": \"components_layout_footers\",\n \"info\": {\n \"name\": \"Footer\",\n \"displayName\": \"Footer\",\n \"i"
},
{
"path": "packages/templates/corporate/template/src/components/layout/navbar.json",
"chars": 571,
"preview": "{\n \"collectionName\": \"components_layout_navbars\",\n \"info\": {\n \"name\": \"Navbar\",\n \"displayName\": \"Navbar\",\n \"i"
},
{
"path": "packages/templates/corporate/template/src/components/links/button-link.json",
"chars": 488,
"preview": "{\n \"collectionName\": \"components_links_buttons\",\n \"info\": {\n \"name\": \"Button-link\",\n \"displayName\": \"Button link"
},
{
"path": "packages/templates/corporate/template/src/components/links/button.json",
"chars": 371,
"preview": "{\n \"collectionName\": \"components_links_simple_buttons\",\n \"info\": {\n \"name\": \"Button\",\n \"displayName\": \"Button\",\n"
},
{
"path": "packages/templates/corporate/template/src/components/links/link.json",
"chars": 400,
"preview": "{\n \"collectionName\": \"components_links_links\",\n \"info\": {\n \"name\": \"Link\",\n \"displayName\": \"Link\",\n \"icon\": \""
},
{
"path": "packages/templates/corporate/template/src/components/meta/metadata.json",
"chars": 731,
"preview": "{\n \"collectionName\": \"components_meta_metadata\",\n \"info\": {\n \"name\": \"Metadata\",\n \"displayName\": \"Metadata\",\n "
},
{
"path": "packages/templates/corporate/template/src/components/sections/bottom-actions.json",
"chars": 375,
"preview": "{\n \"collectionName\": \"components_slices_bottom_actions\",\n \"info\": {\n \"name\": \"BottomActions\",\n \"displayName\": \"B"
},
{
"path": "packages/templates/corporate/template/src/components/sections/feature-columns-group.json",
"chars": 352,
"preview": "{\n \"collectionName\": \"components_slices_feature_columns_groups\",\n \"info\": {\n \"name\": \"FeatureColumnsGroup\",\n \"di"
},
{
"path": "packages/templates/corporate/template/src/components/sections/feature-rows-group.json",
"chars": 331,
"preview": "{\n \"collectionName\": \"components_slices_feature_rows_groups\",\n \"info\": {\n \"name\": \"FeatureRowsGroup\",\n \"displayN"
},
{
"path": "packages/templates/corporate/template/src/components/sections/hero.json",
"chars": 638,
"preview": "{\n \"collectionName\": \"components_slices_heroes\",\n \"info\": {\n \"name\": \"Hero\",\n \"displayName\": \"Hero\",\n \"icon\":"
},
{
"path": "packages/templates/corporate/template/src/components/sections/large-video.json",
"chars": 583,
"preview": "{\n \"collectionName\": \"components_slices_large_videos\",\n \"info\": {\n \"name\": \"LargeVideo\",\n \"displayName\": \"Large "
},
{
"path": "packages/templates/corporate/template/src/components/sections/lead-form.json",
"chars": 476,
"preview": "{\n \"collectionName\": \"components_sections_lead_forms\",\n \"info\": {\n \"name\": \"Lead form\",\n \"displayName\": \"Lead fo"
},
{
"path": "packages/templates/corporate/template/src/components/sections/pricing.json",
"chars": 345,
"preview": "{\n \"collectionName\": \"components_sections_pricings\",\n \"info\": {\n \"name\": \"Pricing\",\n \"displayName\": \"Pricing\",\n "
},
{
"path": "packages/templates/corporate/template/src/components/sections/rich-text.json",
"chars": 244,
"preview": "{\n \"collectionName\": \"components_sections_rich_texts\",\n \"info\": {\n \"name\": \"RichText\",\n \"displayName\": \"Rich tex"
},
{
"path": "packages/templates/corporate/template/src/components/sections/testimonials-group.json",
"chars": 657,
"preview": "{\n \"collectionName\": \"components_slices_testimonials_groups\",\n \"info\": {\n \"name\": \"TestimonialsGroup\",\n \"display"
},
{
"path": "packages/templates/corporate/template/src/index.js",
"chars": 127,
"preview": "\"use strict\";\n\nconst boostrap = require('./bootstrap');\n\nmodule.exports = {\n async bootstrap() {\n await boostrap();\n"
},
{
"path": "packages/templates/corporate/template.json",
"chars": 122,
"preview": "{\n \"package\": {\n \"dependencies\": {\n \"@strapi/plugin-graphql\": \"^4.0.0\",\n \"lodash.set\": \"^4.3.2\"\n }\n }\n"
},
{
"path": "packages/templates/ecommerce/README.md",
"chars": 527,
"preview": "# strapi-template-ecommerce\n\nA Strapi template to create Strapi projects pre-configured for e-commerce apps.\n\n## Usage\n\n"
},
{
"path": "packages/templates/ecommerce/package.json",
"chars": 761,
"preview": "{\n \"name\": \"@strapi/template-ecommerce\",\n \"version\": \"1.0.0\",\n \"description\": \"Strapi ecommerce template\",\n \"keyword"
},
{
"path": "packages/templates/ecommerce/template/data/data.js",
"chars": 13494,
"preview": "module.exports = {\n categories: [\n {\n name: \"Back\",\n slug: \"back\",\n },\n {\n name: \"Front\",\n "
},
{
"path": "packages/templates/ecommerce/template/src/api/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "packages/templates/ecommerce/template/src/api/category/content-types/category/schema.json",
"chars": 559,
"preview": "{\n \"kind\": \"collectionType\",\n \"collectionName\": \"categories\",\n \"info\": {\n \"singularName\": \"category\",\n \"pluralN"
},
{
"path": "packages/templates/ecommerce/template/src/api/category/controllers/category.js",
"chars": 184,
"preview": "'use strict';\n\n/**\n * category controller\n */\n\nconst { createCoreController } = require('@strapi/strapi').factories;\n\nm"
},
{
"path": "packages/templates/ecommerce/template/src/api/category/routes/category.js",
"chars": 172,
"preview": "'use strict';\n\n/**\n * category router.\n */\n\nconst { createCoreRouter } = require('@strapi/strapi').factories;\n\nmodule.ex"
},
{
"path": "packages/templates/ecommerce/template/src/api/category/services/category.js",
"chars": 175,
"preview": "'use strict';\n\n/**\n * category service.\n */\n\nconst { createCoreService } = require('@strapi/strapi').factories;\n\nmodule."
},
{
"path": "packages/templates/ecommerce/template/src/api/product/content-types/product/schema.json",
"chars": 1108,
"preview": "{\n \"kind\": \"collectionType\",\n \"collectionName\": \"products\",\n \"info\": {\n \"singularName\": \"product\",\n \"pluralName"
},
{
"path": "packages/templates/ecommerce/template/src/api/product/controllers/product.js",
"chars": 181,
"preview": "'use strict';\n\n/**\n * product controller\n */\n\nconst { createCoreController } = require('@strapi/strapi').factories;\n\nmo"
},
{
"path": "packages/templates/ecommerce/template/src/api/product/routes/product.js",
"chars": 169,
"preview": "'use strict';\n\n/**\n * product router.\n */\n\nconst { createCoreRouter } = require('@strapi/strapi').factories;\n\nmodule.exp"
},
{
"path": "packages/templates/ecommerce/template/src/api/product/services/product.js",
"chars": 172,
"preview": "'use strict';\n\n/**\n * product service.\n */\n\nconst { createCoreService } = require('@strapi/strapi').factories;\n\nmodule.e"
}
]
// ... and 4 more files (download for full content)
About this extraction
This page contains the full source code of the strapi/starters-and-templates GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 204 files (214.7 KB), approximately 63.2k tokens, and a symbol index with 61 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.