├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── README.md
├── next-env.d.ts
├── next.config.js
├── package.json
├── postcss.config.js
├── public
├── favicon.ico
├── favicon
│ ├── android-chrome-192x192.png
│ ├── android-chrome-256x256.png
│ ├── apple-touch-icon.png
│ ├── browserconfig.xml
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── favicon.ico
│ ├── mstile-150x150.png
│ ├── safari-pinned-tab.svg
│ └── site.webmanifest
├── fonts
│ ├── Inter-italic.var-latin.woff2
│ ├── Inter-italic.var.woff2
│ ├── Inter-roman.var-latin.woff2
│ └── Inter-roman.var.woff2
├── img
│ ├── annie-spratt-QckxruozjRg-unsplash.jpg
│ ├── ayo-ogunseinde-sibvworyqs0-unsplash.jpg
│ ├── foto-sushi-6anudmpilw4-unsplash.jpg
│ ├── harps-joseph-tavpde7fxgy-unsplash.jpg
│ ├── headway-jfR5wu2hMI0-unsplash.jpg
│ ├── kal-visuals-b1hg7qi-zcc-unsplash.jpg
│ ├── logo-cms.jpg
│ ├── logo.png
│ ├── nesa-by-makers-IgUR1iX0mqM-unsplash.jpg
│ ├── scott-graham-5fNmWej4tAA-unsplash.jpg
│ ├── screenshot.png
│ ├── serverless-saas.jpg
│ └── xps-ezyq1hol5_8-unsplash.jpg
└── vercel.svg
├── src
├── cms
│ ├── config.ts
│ └── previews
│ │ ├── HomePreview.tsx
│ │ └── PostPreview.tsx
├── components
│ ├── elements
│ │ └── Button.tsx
│ ├── forms
│ │ ├── LoginForm.tsx
│ │ ├── ResetPasswordForm.tsx
│ │ └── SignUpForm.tsx
│ ├── home
│ │ ├── BlogCard.tsx
│ │ ├── BlogSection
│ │ │ ├── Posts1.tsx
│ │ │ ├── Posts2.tsx
│ │ │ ├── Posts3.tsx
│ │ │ └── index.tsx
│ │ ├── FeatureSection
│ │ │ ├── Features1.tsx
│ │ │ ├── Features2.tsx
│ │ │ ├── Features3.tsx
│ │ │ └── index.tsx
│ │ ├── Footer.tsx
│ │ ├── Header.tsx
│ │ ├── HeroSection
│ │ │ ├── Hero1.tsx
│ │ │ ├── Hero2.tsx
│ │ │ ├── Hero3.tsx
│ │ │ └── index.tsx
│ │ ├── Layout.tsx
│ │ ├── PricingSection
│ │ │ └── index.tsx
│ │ ├── SEO.js
│ │ ├── StepsSection
│ │ │ ├── Steps1.tsx
│ │ │ ├── Steps2.tsx
│ │ │ ├── Steps3.tsx
│ │ │ └── index.tsx
│ │ └── TeamSection
│ │ │ ├── Team1.tsx
│ │ │ ├── Team2.tsx
│ │ │ ├── Team3.tsx
│ │ │ └── index.tsx
│ └── icons
│ │ └── Spinner.tsx
├── content
│ ├── pages
│ │ └── home.md
│ └── posts
│ │ ├── code-splitting-by-routes-and-components.md
│ │ ├── here-is-what-i-learned-at-the-worlds-biggest-react-conference.md
│ │ ├── interesting-web-technologies-to-follow-in-2019.md
│ │ └── why-side-projects-are-so-damn-important.md
├── css
│ └── tailwind.css
├── interfaces
│ └── home.ts
├── pages
│ ├── _app.tsx
│ ├── admin.tsx
│ ├── blog
│ │ ├── [slug].tsx
│ │ └── index.tsx
│ ├── index.tsx
│ ├── login.tsx
│ ├── reset-password.tsx
│ └── signup.tsx
└── utils
│ └── getIcon.tsx
├── tailwind.config.js
├── tsconfig.json
└── yarn.lock
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "@typescript-eslint/parser",
3 | "extends": [
4 | "plugin:@typescript-eslint/recommended",
5 | "plugin:react/recommended",
6 | "plugin:prettier/recommended"
7 | ],
8 | "plugins": ["@typescript-eslint", "react", "react-hooks", "prettier"],
9 | "rules": {
10 | "react/react-in-jsx-scope": "off",
11 | "@typescript-eslint/no-explicit-any": "off",
12 | "react/prop-types": "off",
13 | "react-hooks/rules-of-hooks": "warn",
14 | "react-hooks/exhaustive-deps": "warn"
15 | },
16 | "globals": {
17 | "React": "writable"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 |
21 | # debug
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 | firestore-debug.log
26 | ui-debug.log
27 |
28 | # local env files
29 | .env.local
30 | .env.development.local
31 | .env.test.local
32 | .env.production.local
33 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": true,
3 | "singleQuote": true
4 | }
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NextWind - A flexible landing page & blog template
2 |
3 | Made with [Next.js](https://nextjs.org/docs), [TypeScript](https://www.typescriptlang.org/), [Netlify CMS](https://www.netlifycms.org/), [Tailwind CSS](https://tailwindcss.com/) and [Tailblocks](https://mertjf.github.io/tailblocks/).
4 |
5 |
6 |

7 |
8 |
9 | ## Getting Started
10 |
11 | #### Netlify CMS
12 |
13 | Netlify CMS is an open-source git-based content management library. Content is stored in your Git repository alongside your code for easier versioning, multi-channel publishing, and the option to handle content updates directly in Git. It's like a UI for editing your markdown files that we use to show the landing page and the blog posts.
14 |
15 | - Create a new repository on [Github](https://github.com/)
16 | - Open `cms/config.js` in this project and update `backend.repo` with your new repository name.
17 | - Push the code of this project to this new repo.
18 |
19 | You can now start the project with `yarn dev` or `npm run dev` and navigate to `/admin`. You can now login with Github and manage the content of the landing page of blog posts with a nice UI. When you make a change you can hit the "Publish" button, this will result in making a commit to your repository with the changes made to the corresponding markdown file.
20 |
21 | Before deploying your application you need to enable basic GitHub authentication. Follow the authentication provider setup steps in the [Netlify docs](https://docs.netlify.com/visitor-access/oauth-provider-tokens/#setup-and-settings).
22 |
23 | #### Test
24 |
25 | You can use the `test-repo` backend to try out Netlify CMS without connecting to a Git repo. With this backend, you can write and publish content normally, but any changes will disappear when you reload the page.
26 |
27 | Note: The test-repo backend can't access your local file system, nor does it connect to a Git repo, thus you won't see any existing files while using it.
28 |
29 | To enable this backend, add the `test-repo` string to your `cms/config.js` file:
30 |
31 | ```
32 | backend: {
33 | name: 'test-repo',
34 | ...
35 | },
36 | ```
37 |
38 | Note: It's recommended to connect your git repo with Vercel or Netlify for automatic deployments. When you hit the "publish" button inside the CMS, a commit will be made to your repo that includes the changes you made to the page. With automatic deployments activated this means a deployment will be triggered after you publish any changes. After this deployment, your changes will be live.
39 |
40 | #### Deploy
41 |
42 | Because Github requires a server for authentication, Netlify facilitates basic GitHub authentication.
43 |
44 | To enable basic GitHub authentication follow the authentication provider setup steps in the [Netlify docs](https://docs.netlify.com/visitor-access/oauth-provider-tokens/#setup-and-settings).
45 |
46 | ---
47 |
48 | ## Need more features?
49 |
50 | [Serverless SaaS](https://serverless.page) is a modern starter-kit that aims to be the perfect starting point for your next React app to build full-stack SaaS applications. Save time and skip implementing authentication, payments, teams, and more.
51 |
52 | ---
53 |
54 | ## Learn More
55 |
56 | To learn more about Next.js, take a look at the following resources:
57 |
58 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API
59 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial
60 | - [Netlify CMS](https://www.netlifycms.org/) - learn about Netlify CMS
61 | - [Tailwind CSS](https://tailwindcss.com/) - learn about Tailwind CSS
62 | - [Tailblocks](https://mertjf.github.io/tailblocks/) - ready-to-use Tailwind CSS blocks
63 | - [Serverless SaaS](https://serverless.page) - premium SaaS boilerplate
64 |
--------------------------------------------------------------------------------
/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | webpack: (cfg) => {
3 | cfg.module.rules.push({
4 | test: /\.md$/,
5 | loader: 'frontmatter-markdown-loader',
6 | });
7 | return cfg;
8 | },
9 | };
10 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nextwind",
3 | "version": "1.0.0",
4 | "scripts": {
5 | "build": "next build",
6 | "dev": "next dev",
7 | "export": "next export",
8 | "lint": "eslint --ext .ts,.tsx",
9 | "lint:fix": "eslint --ext .ts,.tsx --fix",
10 | "start": "next start"
11 | },
12 | "husky": {
13 | "hooks": {
14 | "pre-commit": "pretty-quick --staged && npm run lint"
15 | }
16 | },
17 | "dependencies": {
18 | "@stripe/stripe-js": "1.9.0",
19 | "@tailwindcss/typography": "0.2.0",
20 | "autoprefixer": "10.0.2",
21 | "netlify-cms-app": "2.12.25",
22 | "next": "10.0.0",
23 | "postcss": "8.1.8",
24 | "react": "16.13.1",
25 | "react-dom": "16.13.1",
26 | "react-hook-form": "6.9.2",
27 | "tailwindcss": "2.0.1"
28 | },
29 | "devDependencies": {
30 | "@types/node": "14.11.5",
31 | "@types/react": "16.9.51",
32 | "@types/webpack-env": "1.15.3",
33 | "@typescript-eslint/eslint-plugin": "4.4.0",
34 | "@typescript-eslint/parser": "4.4.0",
35 | "eslint": "7.10.0",
36 | "eslint-config-prettier": "6.12.0",
37 | "eslint-plugin-prettier": "3.1.4",
38 | "eslint-plugin-react": "7.21.3",
39 | "eslint-plugin-react-hooks": "4.1.2",
40 | "frontmatter-markdown-loader": "3.6.1",
41 | "husky": "4.3.0",
42 | "lint-staged": "10.4.0",
43 | "prettier": "^2.2.1",
44 | "pretty-quick": "3.0.2",
45 | "typescript": "4.0.3"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakeprins/nextwind/ca7843c4405a27dbc9f904af209831e5f35fded4/public/favicon.ico
--------------------------------------------------------------------------------
/public/favicon/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakeprins/nextwind/ca7843c4405a27dbc9f904af209831e5f35fded4/public/favicon/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/favicon/android-chrome-256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakeprins/nextwind/ca7843c4405a27dbc9f904af209831e5f35fded4/public/favicon/android-chrome-256x256.png
--------------------------------------------------------------------------------
/public/favicon/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakeprins/nextwind/ca7843c4405a27dbc9f904af209831e5f35fded4/public/favicon/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/favicon/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #2b5797
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/public/favicon/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakeprins/nextwind/ca7843c4405a27dbc9f904af209831e5f35fded4/public/favicon/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicon/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakeprins/nextwind/ca7843c4405a27dbc9f904af209831e5f35fded4/public/favicon/favicon-32x32.png
--------------------------------------------------------------------------------
/public/favicon/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakeprins/nextwind/ca7843c4405a27dbc9f904af209831e5f35fded4/public/favicon/favicon.ico
--------------------------------------------------------------------------------
/public/favicon/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakeprins/nextwind/ca7843c4405a27dbc9f904af209831e5f35fded4/public/favicon/mstile-150x150.png
--------------------------------------------------------------------------------
/public/favicon/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
78 |
--------------------------------------------------------------------------------
/public/favicon/site.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "NextWind",
3 | "short_name": "NextWind",
4 | "icons": [
5 | {
6 | "src": "/android-chrome-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "/android-chrome-256x256.png",
12 | "sizes": "256x256",
13 | "type": "image/png"
14 | }
15 | ],
16 | "theme_color": "#ffffff",
17 | "background_color": "#ffffff",
18 | "display": "standalone"
19 | }
20 |
--------------------------------------------------------------------------------
/public/fonts/Inter-italic.var-latin.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakeprins/nextwind/ca7843c4405a27dbc9f904af209831e5f35fded4/public/fonts/Inter-italic.var-latin.woff2
--------------------------------------------------------------------------------
/public/fonts/Inter-italic.var.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakeprins/nextwind/ca7843c4405a27dbc9f904af209831e5f35fded4/public/fonts/Inter-italic.var.woff2
--------------------------------------------------------------------------------
/public/fonts/Inter-roman.var-latin.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakeprins/nextwind/ca7843c4405a27dbc9f904af209831e5f35fded4/public/fonts/Inter-roman.var-latin.woff2
--------------------------------------------------------------------------------
/public/fonts/Inter-roman.var.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakeprins/nextwind/ca7843c4405a27dbc9f904af209831e5f35fded4/public/fonts/Inter-roman.var.woff2
--------------------------------------------------------------------------------
/public/img/annie-spratt-QckxruozjRg-unsplash.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakeprins/nextwind/ca7843c4405a27dbc9f904af209831e5f35fded4/public/img/annie-spratt-QckxruozjRg-unsplash.jpg
--------------------------------------------------------------------------------
/public/img/ayo-ogunseinde-sibvworyqs0-unsplash.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakeprins/nextwind/ca7843c4405a27dbc9f904af209831e5f35fded4/public/img/ayo-ogunseinde-sibvworyqs0-unsplash.jpg
--------------------------------------------------------------------------------
/public/img/foto-sushi-6anudmpilw4-unsplash.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakeprins/nextwind/ca7843c4405a27dbc9f904af209831e5f35fded4/public/img/foto-sushi-6anudmpilw4-unsplash.jpg
--------------------------------------------------------------------------------
/public/img/harps-joseph-tavpde7fxgy-unsplash.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakeprins/nextwind/ca7843c4405a27dbc9f904af209831e5f35fded4/public/img/harps-joseph-tavpde7fxgy-unsplash.jpg
--------------------------------------------------------------------------------
/public/img/headway-jfR5wu2hMI0-unsplash.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakeprins/nextwind/ca7843c4405a27dbc9f904af209831e5f35fded4/public/img/headway-jfR5wu2hMI0-unsplash.jpg
--------------------------------------------------------------------------------
/public/img/kal-visuals-b1hg7qi-zcc-unsplash.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakeprins/nextwind/ca7843c4405a27dbc9f904af209831e5f35fded4/public/img/kal-visuals-b1hg7qi-zcc-unsplash.jpg
--------------------------------------------------------------------------------
/public/img/logo-cms.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakeprins/nextwind/ca7843c4405a27dbc9f904af209831e5f35fded4/public/img/logo-cms.jpg
--------------------------------------------------------------------------------
/public/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakeprins/nextwind/ca7843c4405a27dbc9f904af209831e5f35fded4/public/img/logo.png
--------------------------------------------------------------------------------
/public/img/nesa-by-makers-IgUR1iX0mqM-unsplash.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakeprins/nextwind/ca7843c4405a27dbc9f904af209831e5f35fded4/public/img/nesa-by-makers-IgUR1iX0mqM-unsplash.jpg
--------------------------------------------------------------------------------
/public/img/scott-graham-5fNmWej4tAA-unsplash.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakeprins/nextwind/ca7843c4405a27dbc9f904af209831e5f35fded4/public/img/scott-graham-5fNmWej4tAA-unsplash.jpg
--------------------------------------------------------------------------------
/public/img/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakeprins/nextwind/ca7843c4405a27dbc9f904af209831e5f35fded4/public/img/screenshot.png
--------------------------------------------------------------------------------
/public/img/serverless-saas.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakeprins/nextwind/ca7843c4405a27dbc9f904af209831e5f35fded4/public/img/serverless-saas.jpg
--------------------------------------------------------------------------------
/public/img/xps-ezyq1hol5_8-unsplash.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakeprins/nextwind/ca7843c4405a27dbc9f904af209831e5f35fded4/public/img/xps-ezyq1hol5_8-unsplash.jpg
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/cms/config.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | cms_manual_init: true,
3 | backend: {
4 | name: 'github',
5 | repo: 'jakeprins/nextwind',
6 | branch: 'master',
7 | },
8 | media_folder: 'public/img',
9 | public_folder: 'img',
10 | logo_url: 'https://demo.serverless.page/img/logo-cms.jpg',
11 | site_url: 'https://demo.serverless.page',
12 | collections: [
13 | {
14 | name: 'pages',
15 | label: 'Pages',
16 | files: [
17 | {
18 | label: 'Home',
19 | name: 'home',
20 | file: 'src/content/pages/home.md',
21 | fields: [
22 | {
23 | label: 'Hero Section Version',
24 | name: 'hero_version',
25 | widget: 'number',
26 | value_type: 'int',
27 | min: 1,
28 | max: 3,
29 | },
30 | {
31 | label: 'Hero Title',
32 | name: 'hero_title',
33 | widget: 'string',
34 | },
35 | {
36 | label: 'Hero Description',
37 | name: 'hero_description',
38 | widget: 'markdown',
39 | },
40 | {
41 | label: 'Hero Image',
42 | name: 'hero_image',
43 | widget: 'image',
44 | },
45 | {
46 | label: 'Feature Section Version',
47 | name: 'feature_version',
48 | widget: 'number',
49 | value_type: 'int',
50 | min: 1,
51 | max: 3,
52 | },
53 | {
54 | label: 'Feature Title',
55 | name: 'feature_title',
56 | widget: 'string',
57 | },
58 | {
59 | label: 'Feature Description',
60 | name: 'feature_description',
61 | widget: 'string',
62 | },
63 | {
64 | label: 'Features',
65 | name: 'features',
66 | widget: 'list',
67 | fields: [
68 | {
69 | label: 'Name',
70 | name: 'name',
71 | widget: 'string',
72 | },
73 | {
74 | label: 'Description',
75 | name: 'description',
76 | widget: 'text',
77 | },
78 | ],
79 | },
80 | {
81 | label: 'Steps Section Version',
82 | name: 'steps_version',
83 | widget: 'number',
84 | value_type: 'int',
85 | min: 1,
86 | max: 3,
87 | },
88 | {
89 | label: 'Steps',
90 | name: 'steps',
91 | widget: 'list',
92 | fields: [
93 | {
94 | label: 'Name',
95 | name: 'name',
96 | widget: 'string',
97 | },
98 | {
99 | label: 'Description',
100 | name: 'description',
101 | widget: 'text',
102 | },
103 | ],
104 | },
105 | {
106 | label: 'Steps Image',
107 | name: 'steps_image',
108 | widget: 'image',
109 | },
110 | {
111 | label: 'Pricing Title',
112 | name: 'pricing_title',
113 | widget: 'string',
114 | },
115 | {
116 | label: 'Pricing Description',
117 | name: 'pricing_description',
118 | widget: 'string',
119 | },
120 | {
121 | label: 'Plans',
122 | name: 'plans',
123 | widget: 'list',
124 | fields: [
125 | {
126 | label: 'Name',
127 | name: 'name',
128 | widget: 'string',
129 | },
130 | {
131 | label: 'Description',
132 | name: 'description',
133 | widget: 'text',
134 | },
135 | {
136 | label: 'Price',
137 | name: 'price',
138 | widget: 'string',
139 | },
140 | {
141 | label: 'USPs',
142 | name: 'usps',
143 | widget: 'list',
144 | },
145 | ],
146 | },
147 | {
148 | label: 'Team Section Version',
149 | name: 'team_version',
150 | widget: 'number',
151 | value_type: 'int',
152 | min: 1,
153 | max: 3,
154 | },
155 | {
156 | label: 'Team Title',
157 | name: 'team_title',
158 | widget: 'string',
159 | },
160 | {
161 | label: 'Team Description',
162 | name: 'team_description',
163 | widget: 'markdown',
164 | },
165 | {
166 | label: 'Team',
167 | name: 'team',
168 | widget: 'list',
169 | fields: [
170 | {
171 | label: 'Name',
172 | name: 'name',
173 | widget: 'string',
174 | },
175 | {
176 | label: 'Description',
177 | name: 'description',
178 | widget: 'text',
179 | },
180 | {
181 | label: 'Position',
182 | name: 'position',
183 | widget: 'string',
184 | },
185 | {
186 | label: 'Image',
187 | name: 'image',
188 | widget: 'image',
189 | },
190 | ],
191 | },
192 | {
193 | label: 'Blog Section Version',
194 | name: 'blog_version',
195 | widget: 'number',
196 | value_type: 'int',
197 | min: 1,
198 | max: 3,
199 | },
200 | {
201 | label: 'Blog Title',
202 | name: 'blog_title',
203 | widget: 'string',
204 | },
205 | {
206 | label: 'Blog Description',
207 | name: 'blog_description',
208 | widget: 'string',
209 | },
210 | {
211 | label: 'Featured Posts',
212 | name: 'posts',
213 | widget: 'relation',
214 | collection: 'posts',
215 | searchFields: ['title'],
216 | valueField: '{{slug}}',
217 | displayFields: ['title'],
218 | multiple: true,
219 | },
220 | ],
221 | },
222 | ],
223 | },
224 | {
225 | name: 'posts',
226 | label: 'Posts',
227 | folder: 'src/content/posts',
228 | create: true,
229 | slug: '{{slug}}',
230 | preview_path: 'posts/{{fields.slug}}',
231 | fields: [
232 | {
233 | label: 'Title',
234 | name: 'title',
235 | widget: 'string',
236 | },
237 | {
238 | label: 'Slug',
239 | name: 'slug',
240 | widget: 'string',
241 | },
242 | {
243 | label: 'Draft',
244 | name: 'draft',
245 | widget: 'boolean',
246 | default: true,
247 | },
248 | {
249 | label: 'Publish Date',
250 | name: 'date',
251 | widget: 'datetime',
252 | },
253 | {
254 | label: 'Description',
255 | name: 'description',
256 | widget: 'text',
257 | },
258 | {
259 | label: 'Category',
260 | name: 'category',
261 | widget: 'string',
262 | },
263 | {
264 | label: 'Image',
265 | name: 'image',
266 | widget: 'image',
267 | },
268 | {
269 | label: 'Body',
270 | name: 'body',
271 | widget: 'markdown',
272 | },
273 | {
274 | label: 'Tags',
275 | name: 'tags',
276 | widget: 'list',
277 | },
278 | ],
279 | },
280 | ],
281 | };
282 |
--------------------------------------------------------------------------------
/src/cms/previews/HomePreview.tsx:
--------------------------------------------------------------------------------
1 | import { ComponentType } from 'react';
2 | import HeroSection from 'components/home/HeroSection';
3 | import FeatureSection from 'components/home/FeatureSection';
4 | import StepsSection from 'components/home/StepsSection';
5 | import TeamSection from 'components/home/TeamSection';
6 | import PricingSection from 'components/home/PricingSection';
7 | import BlogSection from 'components/home/BlogSection';
8 |
9 | const HomePreview: ComponentType = ({ entry, widgetsFor }) => {
10 | const features = [];
11 | widgetsFor('features').map((feature) => {
12 | features.push({
13 | name: feature?.getIn(['data', 'name']),
14 | description: feature?.getIn(['data', 'description']),
15 | });
16 | });
17 |
18 | const steps = [];
19 | widgetsFor('steps').map((step) => {
20 | steps.push({
21 | name: step?.getIn(['data', 'name']),
22 | description: step?.getIn(['data', 'description']),
23 | });
24 | });
25 |
26 | const plans = [];
27 | widgetsFor('plans').map((plan) => {
28 | plans.push({
29 | name: plan?.getIn(['data', 'name']),
30 | description: plan?.getIn(['data', 'description']),
31 | price: plan?.getIn(['data', 'price']),
32 | usps: plan?.getIn(['data', 'usps']),
33 | });
34 | });
35 |
36 | const slugs = [];
37 | widgetsFor('posts').map((post) => {
38 | slugs.push(post?.getIn(['data']));
39 | });
40 |
41 | const team = [];
42 | widgetsFor('team').map((member) => {
43 | team.push({
44 | name: member?.getIn(['data', 'name']),
45 | description: member?.getIn(['data', 'description']),
46 | position: member?.getIn(['data', 'position']),
47 | image: member?.getIn(['data', 'image']),
48 | });
49 | });
50 |
51 | return (
52 | <>
53 |
59 |
65 |
70 |
75 |
81 |
87 | >
88 | );
89 | };
90 |
91 | export default HomePreview;
92 |
--------------------------------------------------------------------------------
/src/cms/previews/PostPreview.tsx:
--------------------------------------------------------------------------------
1 | import { ComponentType } from 'react';
2 | import BlogCard from 'components/home/BlogCard';
3 |
4 | const PostPreview: ComponentType = ({ entry, widgetFor }) => {
5 | const post = {
6 | title: entry.getIn(['data', 'title']),
7 | description: entry.getIn(['data', 'description']),
8 | image: entry.getIn(['data', 'image']),
9 | category: entry.getIn(['data', 'category']),
10 | body: widgetFor('body'),
11 | };
12 |
13 | return (
14 | <>
15 |
16 |
25 |
26 |
27 |
28 |
29 | {post.body}
30 |
31 |
32 |
33 |
34 |
42 | >
43 | );
44 | };
45 |
46 | export default PostPreview;
47 |
--------------------------------------------------------------------------------
/src/components/elements/Button.tsx:
--------------------------------------------------------------------------------
1 | import Spinner from 'components/icons/Spinner';
2 |
3 | interface ButtonProps {
4 | title?: string;
5 | isLoading?: boolean;
6 | }
7 |
8 | const Button = ({
9 | isLoading = false,
10 | title,
11 | children,
12 | ...buttonProps
13 | }: ButtonProps &
14 | React.ButtonHTMLAttributes): JSX.Element => {
15 | return (
16 |
27 | );
28 | };
29 |
30 | export default Button;
31 |
--------------------------------------------------------------------------------
/src/components/forms/LoginForm.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { useForm } from 'react-hook-form';
3 | import Link from 'next/link';
4 |
5 | import Button from 'components/elements/Button';
6 |
7 | interface LoginData {
8 | email: string;
9 | password: string;
10 | }
11 |
12 | const LoginForm: React.FC = () => {
13 | const { register, errors, handleSubmit } = useForm();
14 | const [isLoading, setIsLoading] = useState(false);
15 |
16 | const onSubmit = (data: LoginData) => {
17 | console.log(data);
18 | setIsLoading(true);
19 | setTimeout(() => (window.location.href = 'https://serverless.page'), 3000);
20 | };
21 |
22 | return (
23 |
100 | );
101 | };
102 |
103 | export default LoginForm;
104 |
--------------------------------------------------------------------------------
/src/components/forms/ResetPasswordForm.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { useForm } from 'react-hook-form';
3 |
4 | import Button from 'components/elements/Button';
5 |
6 | const ResetPasswordForm: React.FC = () => {
7 | const { register, errors, handleSubmit } = useForm();
8 | const [isLoading, setIsLoading] = useState(false);
9 |
10 | const onSubmit = (data: { email: string }) => {
11 | console.log(data);
12 | setIsLoading(true);
13 | setTimeout((window.location.href = 'https://serverless.page'), 3000);
14 | };
15 |
16 | return (
17 |
53 | );
54 | };
55 |
56 | export default ResetPasswordForm;
57 |
--------------------------------------------------------------------------------
/src/components/forms/SignUpForm.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { useForm } from 'react-hook-form';
3 |
4 | import Button from 'components/elements/Button';
5 |
6 | export interface SignUpData {
7 | name: string;
8 | email: string;
9 | password: string;
10 | }
11 |
12 | const SignUpForm: React.FC = () => {
13 | const { register, errors, handleSubmit } = useForm();
14 | const [isLoading, setIsLoading] = useState(false);
15 |
16 | const onSubmit = (data: SignUpData): void => {
17 | console.log(data);
18 | setIsLoading(true);
19 | setTimeout(() => (window.location.href = 'https://serverless.page'), 3000);
20 | };
21 |
22 | return (
23 |
111 | );
112 | };
113 |
114 | export default SignUpForm;
115 |
--------------------------------------------------------------------------------
/src/components/home/BlogCard.tsx:
--------------------------------------------------------------------------------
1 | interface Post {
2 | attributes: {
3 | title: string;
4 | image: string;
5 | category: string;
6 | description: string;
7 | date?: Date;
8 | slug?: string;
9 | };
10 | }
11 |
12 | const BlogCard: React.FC<{ post: Post; version: number }> = ({
13 | post,
14 | version,
15 | }) => {
16 | const { attributes } = post;
17 |
18 | if (version === 2) {
19 | return (
20 |
21 |
22 |
23 | {attributes.category}
24 |
25 |
26 | {attributes.date &&
27 | new Date(attributes.date).toLocaleDateString().split('T')[0]}
28 |
29 |
30 |
55 |
56 | );
57 | }
58 |
59 | if (version === 3) {
60 | return (
61 |
90 | );
91 | }
92 |
93 | return (
94 |
95 |

101 |
102 |
103 | {attributes.category}
104 |
105 |
106 | {attributes.title}
107 |
108 |
{attributes.description}
109 |
129 |
130 |
131 | );
132 | };
133 |
134 | export default BlogCard;
135 |
--------------------------------------------------------------------------------
/src/components/home/BlogSection/Posts1.tsx:
--------------------------------------------------------------------------------
1 | const Posts1: React.FC<{
2 | posts: { attributes: any }[];
3 | }> = ({ posts }) => {
4 | return (
5 |
6 | {posts?.map((post, i) => {
7 | return (
8 |
9 |
10 |

16 |
17 |
18 | {post.attributes.category}
19 |
20 |
21 | {post.attributes.title}
22 |
23 |
24 | {post.attributes.description}
25 |
26 |
46 |
47 |
48 |
49 | );
50 | })}
51 |
52 | );
53 | };
54 |
55 | export default Posts1;
56 |
--------------------------------------------------------------------------------
/src/components/home/BlogSection/Posts2.tsx:
--------------------------------------------------------------------------------
1 | const Posts2: React.FC<{
2 | posts: { attributes: any }[];
3 | }> = ({ posts }) => {
4 | return (
5 |
6 | {posts?.map((post, i) => {
7 | return (
8 |
9 |
10 |
11 | {post.attributes.category}
12 |
13 |
14 | {post.attributes.date &&
15 | new Date(post.attributes.date)
16 | .toLocaleDateString()
17 | .split('T')[0]}
18 |
19 |
20 |
44 |
45 | );
46 | })}
47 |
48 | );
49 | };
50 |
51 | export default Posts2;
52 |
--------------------------------------------------------------------------------
/src/components/home/BlogSection/Posts3.tsx:
--------------------------------------------------------------------------------
1 | const Posts3: React.FC<{
2 | posts: { attributes: any }[];
3 | }> = ({ posts }) => {
4 | return (
5 |
6 | {posts?.map((post, i) => {
7 | return (
8 |
41 | );
42 | })}
43 |
44 | );
45 | };
46 |
47 | export default Posts3;
48 |
--------------------------------------------------------------------------------
/src/components/home/BlogSection/index.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import Posts1 from './Posts1';
3 | import Posts2 from './Posts2';
4 | import Posts3 from './Posts3';
5 |
6 | const BlogSection: React.FC<{
7 | version: number;
8 | title: string;
9 | description: string;
10 | slugs: string[];
11 | }> = ({ title, description, slugs, version }) => {
12 | const [posts, setPosts] = useState([]);
13 |
14 | useEffect(() => {
15 | const getPosts = async () => {
16 | const postsPromises = await slugs.map(async (slug) => {
17 | return import(`../../../content/posts/${slug}.md`);
18 | });
19 |
20 | Promise.all(postsPromises).then(setPosts);
21 | };
22 |
23 | getPosts();
24 | }, [slugs]);
25 |
26 | // List of different component versions. You can easily switch between versions from the CMS.
27 | const components = {
28 | 1: Posts1,
29 | 2: Posts2,
30 | 3: Posts3,
31 | };
32 |
33 | // Use the version prop to determine which component to render. Fallback to 1.
34 | const BlogPostList = components[version] || components[1];
35 |
36 | return (
37 |
38 |
39 |
40 |
41 | {title}
42 |
43 |
44 | {description}
45 |
46 |
47 |
48 |
49 |
50 | );
51 | };
52 |
53 | export default BlogSection;
54 |
--------------------------------------------------------------------------------
/src/components/home/FeatureSection/Features1.tsx:
--------------------------------------------------------------------------------
1 | import { getIcon } from 'utils/getIcon';
2 | import { Feature } from 'interfaces/home';
3 |
4 | interface Props {
5 | features: Feature[];
6 | }
7 |
8 | const Features1: React.FC = ({ features }) => {
9 | return (
10 |
11 | {features?.map((feature: Feature, i: number) => {
12 | return (
13 |
17 |
18 | {getIcon(i, 8)}
19 |
20 |
21 |
22 | {feature.name}
23 |
24 |
{feature.description}
25 |
26 |
27 | );
28 | })}
29 |
30 | );
31 | };
32 |
33 | export default Features1;
34 |
--------------------------------------------------------------------------------
/src/components/home/FeatureSection/Features2.tsx:
--------------------------------------------------------------------------------
1 | import { getIcon } from 'utils/getIcon';
2 | import { Feature } from 'interfaces/home';
3 |
4 | interface Props {
5 | features: Feature[];
6 | }
7 |
8 | const Features2: React.FC = ({ features }) => {
9 | return (
10 |
11 | {features?.map((feature: Feature, i: number) => {
12 | return (
13 |
14 |
15 |
16 |
17 | {getIcon(i, 5)}
18 |
19 |
20 | {feature.name}
21 |
22 |
23 |
42 |
43 |
44 | );
45 | })}
46 |
47 | );
48 | };
49 |
50 | export default Features2;
51 |
--------------------------------------------------------------------------------
/src/components/home/FeatureSection/Features3.tsx:
--------------------------------------------------------------------------------
1 | import { getIcon } from 'utils/getIcon';
2 | import { Feature } from 'interfaces/home';
3 |
4 | interface Props {
5 | features: Feature[];
6 | }
7 |
8 | const Features3: React.FC = ({ features }) => {
9 | return (
10 |
11 | {features?.map((feature: Feature, i: number) => {
12 | return (
13 |
14 |
15 | {getIcon(i, 10)}
16 |
17 |
37 |
38 | );
39 | })}
40 |
41 | );
42 | };
43 |
44 | export default Features3;
45 |
--------------------------------------------------------------------------------
/src/components/home/FeatureSection/index.tsx:
--------------------------------------------------------------------------------
1 | import { Feature } from 'interfaces/home';
2 | import Features1 from './Features1';
3 | import Features2 from './Features2';
4 | import Features3 from './Features3';
5 |
6 | interface Props {
7 | title: string;
8 | description: string;
9 | features: Feature[];
10 | version: number;
11 | }
12 |
13 | const FeatureSection: React.FC = ({
14 | title,
15 | description,
16 | features,
17 | version,
18 | }) => {
19 | // List of different component versions. You can easily switch between versions from the CMS.
20 | const components = {
21 | 1: Features1,
22 | 2: Features2,
23 | 3: Features3,
24 | };
25 |
26 | // Use the version prop to determine which component to render. Fallback to 1.
27 | const FeatureList = components[version] || components[1];
28 |
29 | return (
30 |
31 |
32 |
33 |
34 | {title}
35 |
36 |
37 | {description}
38 |
39 |
42 |
43 |
44 |
47 |
48 |
49 | );
50 | };
51 |
52 | export default FeatureSection;
53 |
--------------------------------------------------------------------------------
/src/components/home/Footer.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 |
3 | const Footer = (): JSX.Element => {
4 | return (
5 |
83 | );
84 | };
85 |
86 | export default Footer;
87 |
--------------------------------------------------------------------------------
/src/components/home/Header.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 |
3 | const Header = (): JSX.Element => {
4 | return (
5 |
55 | );
56 | };
57 |
58 | export default Header;
59 |
--------------------------------------------------------------------------------
/src/components/home/HeroSection/Hero1.tsx:
--------------------------------------------------------------------------------
1 | interface Props {
2 | title: string;
3 | description: string;
4 | image: string;
5 | }
6 |
7 | const Hero1: React.FC = ({ title, description, image }): JSX.Element => {
8 | return (
9 |
10 |
11 |
12 |
13 | {title}
14 |
15 |
{description}
16 |
17 |
20 |
23 |
24 |
25 |
26 |

32 |
33 |
34 |
35 | );
36 | };
37 |
38 | export default Hero1;
39 |
--------------------------------------------------------------------------------
/src/components/home/HeroSection/Hero2.tsx:
--------------------------------------------------------------------------------
1 | interface Props {
2 | title: string;
3 | description: string;
4 | image: string;
5 | }
6 |
7 | const Hero2: React.FC = ({ title, description, image }): JSX.Element => {
8 | return (
9 |
10 |
11 |
12 |

18 |
19 |
20 |
21 | {title}
22 |
23 |
{description}
24 |
25 |
28 |
31 |
32 |
33 |
34 |
35 | );
36 | };
37 |
38 | export default Hero2;
39 |
--------------------------------------------------------------------------------
/src/components/home/HeroSection/Hero3.tsx:
--------------------------------------------------------------------------------
1 | interface Props {
2 | title: string;
3 | description: string;
4 | image: string;
5 | }
6 |
7 | const Hero3: React.FC = ({ title, description, image }): JSX.Element => {
8 | return (
9 |
10 |
11 |

17 |
18 |
19 | {title}
20 |
21 |
{description}
22 |
23 |
26 |
29 |
30 |
31 |
32 |
33 | );
34 | };
35 |
36 | export default Hero3;
37 |
--------------------------------------------------------------------------------
/src/components/home/HeroSection/index.tsx:
--------------------------------------------------------------------------------
1 | import Hero1 from './Hero1';
2 | import Hero2 from './Hero2';
3 | import Hero3 from './Hero3';
4 |
5 | interface Props {
6 | version: number;
7 | title: string;
8 | description: string;
9 | image: string;
10 | }
11 |
12 | const HeroSection: React.FC = ({
13 | version,
14 | title,
15 | description,
16 | image,
17 | }): JSX.Element => {
18 | // List of different component versions. You can easily switch between versions from the CMS.
19 | const components = {
20 | 1: Hero1,
21 | 2: Hero2,
22 | 3: Hero3,
23 | };
24 |
25 | // Use the version prop to determine which component to render. Fallback to 1.
26 | const HeroComponent = components[version] || components[1];
27 |
28 | // Default return version 1 (with hero image on the right)
29 | return (
30 |
31 | );
32 | };
33 |
34 | export default HeroSection;
35 |
--------------------------------------------------------------------------------
/src/components/home/Layout.tsx:
--------------------------------------------------------------------------------
1 | import { ReactNode } from 'react';
2 | import { NextPage } from 'next';
3 |
4 | import Header from 'components/home/Header';
5 | import Footer from 'components/home/Footer';
6 |
7 | interface Props {
8 | children: ReactNode;
9 | }
10 |
11 | const Layout: NextPage = ({ children }) => {
12 | return (
13 |
14 |
15 | {children}
16 |
17 |
18 | );
19 | };
20 |
21 | export default Layout;
22 |
--------------------------------------------------------------------------------
/src/components/home/PricingSection/index.tsx:
--------------------------------------------------------------------------------
1 | interface Props {
2 | title: string;
3 | description: string;
4 | plans: Array<{
5 | name: string;
6 | description: string;
7 | price: string;
8 | usps: { name: string }[];
9 | }>;
10 | }
11 |
12 | const PricingSection: React.FC = ({ title, description, plans }) => {
13 | return (
14 |
15 |
16 |
17 |
18 | {title}
19 |
20 |
21 | {description}
22 |
23 |
26 |
27 |
28 | {plans?.map((plan, i) => {
29 | return (
30 |
31 |
32 |
33 | {plan.name}
34 |
35 |
36 | {plan.price}
37 |
38 | /mo
39 |
40 |
41 |
42 | {plan.usps?.map((usp, i) => {
43 | return (
44 |
48 |
49 |
60 |
61 | {usp}
62 |
63 | );
64 | })}
65 |
66 |
80 |
81 | {plan.description}
82 |
83 |
84 |
85 | );
86 | })}
87 |
88 |
89 |
90 | );
91 | };
92 |
93 | export default PricingSection;
94 |
--------------------------------------------------------------------------------
/src/components/home/SEO.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 |
3 | const SEO = () => (
4 |
5 | NextWind
6 |
10 |
14 |
15 |
16 |
17 |
21 |
22 | {/* Twitter */}
23 |
24 |
28 |
32 |
33 |
34 |
35 | {/* Favicon */}
36 |
41 |
47 |
53 |
54 |
59 |
60 |
61 |
62 | {/* Google Analytics */}
63 | {/*
67 |