├── .env.example ├── .eslintrc.json ├── .gitignore ├── README.md ├── components ├── card │ ├── cardWithImage.tsx │ ├── categoryCard.tsx │ └── horizontalCardWithImage.tsx └── layouts │ ├── categories.tsx │ ├── categoryArticles.tsx │ ├── contentWrapper │ ├── index.tsx │ └── styles.module.scss │ ├── footer.tsx │ ├── header.tsx │ ├── lastArticles.tsx │ └── relatedArticle.tsx ├── next.config.js ├── package-lock.json ├── package.json ├── pages ├── [...slug].tsx ├── _app.tsx ├── admin │ └── [[...pages]].tsx ├── api │ ├── end-preview.ts │ ├── preview.ts │ └── revalidate.ts ├── index.tsx └── sitemap.xml.ts ├── postcss.config.js ├── public ├── favicon.ico └── vercel.svg ├── styles └── globals.css ├── suncel ├── blocks │ ├── hero │ │ ├── blogHero.tsx │ │ ├── categoryHero.tsx │ │ ├── mainHero.tsx │ │ └── titleAndSub │ │ │ ├── index.tsx │ │ │ └── styles.module.scss │ ├── imageBlock.tsx │ └── richTextBlock.tsx ├── globalsSchemas │ ├── footer.ts │ ├── header.ts │ └── index.ts ├── index.ts ├── menuBlocks.tsx ├── pageSchemas │ ├── categoryPage.ts │ ├── index.ts │ └── relatedArticles.ts ├── suncelContextConfig.ts └── wrappers │ ├── richtext │ ├── index.tsx │ └── styles.module.scss │ └── section.tsx ├── tailwind.config.js └── tsconfig.json /.env.example: -------------------------------------------------------------------------------- 1 | # Rename .env.example to .env and update the following 2 | # Get your api keys on app.suncel.io : create a new project or open an existing one, click on the project, go to Api and copy your keys 3 | 4 | #SUNCEL 5 | NEXT_PUBLIC_SUNCEL_KEY= 6 | SUNCEL_REVALIDATE_SECRET= -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.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 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env 30 | .env.local 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Suncel 3 |

4 |

5 | Suncel NextJS Blog Starter 6 |

7 | 8 | - This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
9 | - Access the **[DEMO](https://blog-starter-one-sigma.vercel.app/)**. 10 | - **[Video](https://www.youtube.com/watch?v=d7ELyCNg6y0)** crash course of the Blog 11 | 12 | ## 📖 **Documentation** 13 | 14 | Have a look to our [documentation](https://docs.suncel.io) 15 | 16 | ## 🎉 **Starter features** 17 | 18 | The starter includes all the configuration required by Suncel(admin, preview, page render, etc...). You just need to update your API keys in the .env 19 | 20 | it will includes Tailwind CSS. 21 | 22 | On top of that, you have a Block example and a wrapper of the rich text with some style that you are free to modify. 23 | 24 | ## 🛠️ **Suncel Setup** 25 | 26 | ### Account 27 | 28 | - Create your Suncel account [here](https://app.suncel.io/signup) (you can create a free plan with no time limit) 29 | - Create a project 30 | 31 | ### API Keys 32 | 33 | - After creating an account, head over to the project settings you created earlier. 34 | - Go to the API section, grab your Keys. 35 | - At the root of the project, rename the .env.example file by .env 36 | - Insert your API Keys in the .env of your project (NEXT_PUBLIC_SUNCEL_KEY=... and SUNCEL_REVALIDATE_SECRET=...). You can find these keys on app.suncel.io (Project → ) 37 | 38 | ### Ready to go 39 | 40 | - npm run dev, then access the admin via /admin and login with the same id/pwd you have on app.suncel.io 41 | - In the left menu, go to Globals and create a global of type Header. Add a logo and a link (link+label). 42 | - Copy the id of the Header created in Globals (button "Copy Id") and replace in the code REPLACE_BY_HEADER_GLOBAL_ID by your header ID 43 | - Do the same for the footer with a Global type 'Footer', and in the code, replace REPLACE_BY_FOOTER_GLOBAL_ID by your footer ID 44 | 45 | ### Create your first page and add Blocks 46 | 47 | - create a page and you can now add your blocks, save and publish your page 48 | - [Create your blocks](https://docs.suncel.io/developer/blocks/create-block) in react. 49 | - Check our [Youtube Channel](https://www.youtube.com/@suncel) to see some examples of how to create Blocks 50 | 51 | ### Last Articles 52 | 53 | - In your admin panel, in the pages section, create a **blog** folder that will contain all your articles, click on it, copy the ID of the folder from the url and replace in your code all the occurrences of REPLACE_BY_BLOG_FOLDER_ID by your ID 54 | 55 | ### **📧 Contact** 56 | 57 | Whether you'd like to discuss about this starter template or simply say "hello", I'd love to hear from you. 58 | 59 | Email: **[dev@suncel.io](mailto:dev@suncel.io)** 60 | -------------------------------------------------------------------------------- /components/card/cardWithImage.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from "@suncel/nextjs/components"; 2 | import Image from "next/image"; 3 | import { ArticleCardProps } from "../layouts/lastArticles"; 4 | 5 | export const CardWithImage: React.FC = ({ imageSrc, imageAlt, title, description, link }) => { 6 | return ( 7 | 8 |
9 | {imageAlt} 16 |
17 |
{title}
18 |

{description}

19 |
20 | Read more 21 | 34 |
35 |
36 |
37 | 38 | ); 39 | }; 40 | -------------------------------------------------------------------------------- /components/card/categoryCard.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from "@suncel/nextjs/components"; 2 | import Image from "next/image"; 3 | import { ArticleCardProps } from "../layouts/lastArticles"; 4 | 5 | export const CategoryCard: React.FC = ({ imageSrc, title, link }) => { 6 | return ( 7 | 8 |
9 | Article image 16 | 17 | 20 |
21 | 22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /components/card/horizontalCardWithImage.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from "@suncel/nextjs/components"; 2 | import Image from "next/image"; 3 | import { ArticleCardProps } from "../layouts/lastArticles"; 4 | 5 | export const HorizontalCardWithImage: React.FC = ({ 6 | imageSrc, 7 | imageAlt, 8 | title, 9 | description, 10 | link, 11 | }) => { 12 | return ( 13 | 17 | {imageAlt} 24 |
25 |
{title}
26 |

{description}

27 |
28 | 29 | ); 30 | }; 31 | -------------------------------------------------------------------------------- /components/layouts/categories.tsx: -------------------------------------------------------------------------------- 1 | import { Page } from "@suncel/nextjs/api"; 2 | import React from "react"; 3 | import { CategoryCard } from "../card/categoryCard"; 4 | import { ArticleCardProps } from "./lastArticles"; 5 | 6 | type CategoriesProps = { 7 | articles: ArticleCardProps[]; 8 | }; 9 | 10 | export const Categories: React.FC = ({ articles }) => { 11 | if (!articles || !Array.isArray(articles) || articles?.length == 0) return null; 12 | return ( 13 |
14 |
15 |

16 | Categories 17 |

18 |

19 | We use an agile approach to test assumptions and connect with the needs of your audience early and often. 20 |

21 |
22 |
23 | {articles?.map((article, idx: number) => { 24 | return ; 25 | })} 26 |
27 |
28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /components/layouts/categoryArticles.tsx: -------------------------------------------------------------------------------- 1 | import { useIsAdmin } from "@suncel/nextjs"; 2 | import React from "react"; 3 | import { CardWithImage } from "../card/cardWithImage"; 4 | import { ArticleCardProps } from "./lastArticles"; 5 | 6 | type CategoryArticlesProps = { 7 | articles: ArticleCardProps[]; 8 | isCategoryPage: boolean; 9 | }; 10 | 11 | export const CategoryArticles: React.FC = ({ articles, isCategoryPage }) => { 12 | const isAdmin = useIsAdmin(); 13 | 14 | if (!articles || !Array.isArray(articles) || articles?.length == 0) 15 | return isAdmin && isCategoryPage ? ( 16 |
17 | If you want to add article related to this category : Tag the articles, go in Pages (right side 18 | panel), select a Category name, and Save and Refresh to see updates. 19 |
20 | ) : null; 21 | 22 | return ( 23 | <> 24 | {isAdmin && ( 25 |
26 |
27 | If you want to add article related to this category : Tag the articles, go in Pages (right 28 | side panel), select a Category name, and Save and Refresh to see updates. 29 |
30 |
31 | )} 32 |
33 | {articles?.map((article, idx: number) => { 34 | return ; 35 | })} 36 |
37 | 38 | ); 39 | }; 40 | -------------------------------------------------------------------------------- /components/layouts/contentWrapper/index.tsx: -------------------------------------------------------------------------------- 1 | import classes from "./styles.module.scss"; 2 | 3 | export const ContentWrapper: React.FC<{ children?: React.ReactNode }> = ({ children }) => { 4 | return
{children}
; 5 | }; 6 | -------------------------------------------------------------------------------- /components/layouts/contentWrapper/styles.module.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | } 5 | -------------------------------------------------------------------------------- /components/layouts/footer.tsx: -------------------------------------------------------------------------------- 1 | import { ImageType, LinkType } from "@suncel/nextjs"; 2 | import { Link } from "@suncel/nextjs/components"; 3 | import Image from "next/image"; 4 | 5 | interface FooterLink { 6 | label: string; 7 | link: LinkType; 8 | } 9 | 10 | interface FooterProps { 11 | logo: ImageType; 12 | columns: { 13 | label: string; 14 | links: FooterLink[]; 15 | }[]; 16 | socialMedia: { 17 | facebook: string; 18 | twitter: string; 19 | instagram: string; 20 | github: string; 21 | dribbble: string; 22 | }; 23 | } 24 | 25 | export const Footer: React.FC = ({ logo, columns, socialMedia }) => { 26 | return ( 27 |
28 |
29 |
30 | 31 | {logo.alt!} 32 | 33 |
34 |
35 | {columns?.map(({ label, links }, colIdx) => ( 36 |
37 |

{label}

38 |
    39 | {links?.map((link, linkIdx) => ( 40 |
  • 41 | 42 | {link.label} 43 | 44 |
  • 45 | ))} 46 |
47 |
48 | ))} 49 |
50 |
51 |
52 |
53 | 54 | © 2022{" "} 55 | 56 | Suncel 57 | 58 | . All Rights Reserved. 59 | 60 |
61 | 62 | 69 | Facebook page 70 | 71 | 72 | 79 | Instagram page 80 | 81 | 82 | 85 | Twitter page 86 | 87 | 88 | 95 | GitHub account 96 | 97 | 98 | 105 | Dribbble account 106 | 107 |
108 |
109 |
110 | ); 111 | }; 112 | -------------------------------------------------------------------------------- /components/layouts/header.tsx: -------------------------------------------------------------------------------- 1 | import { ImageType, LinkType } from "@suncel/nextjs"; 2 | import { Link } from "@suncel/nextjs/components"; 3 | import clsx from "clsx"; 4 | import Image from "next/image"; 5 | import { useState } from "react"; 6 | 7 | interface HeaderLink { 8 | label: string; 9 | link: LinkType; 10 | } 11 | interface HeaderProps { 12 | logo: ImageType; 13 | links: HeaderLink[]; 14 | } 15 | 16 | export const Header: React.FC = ({ logo, links }) => { 17 | const [isOpen, setIsOpen] = useState(false); 18 | 19 | return ( 20 | 72 | ); 73 | }; 74 | -------------------------------------------------------------------------------- /components/layouts/lastArticles.tsx: -------------------------------------------------------------------------------- 1 | import { CardWithImage } from "../card/cardWithImage"; 2 | import { HorizontalCardWithImage } from "../card/horizontalCardWithImage"; 3 | 4 | export interface ArticleCardProps { 5 | imageSrc: string; 6 | imageAlt: string; 7 | title: string; 8 | description: string; 9 | link: string; 10 | } 11 | 12 | interface LastArticlesProps { 13 | articles: ArticleCardProps[]; 14 | } 15 | 16 | export const LastArticles: React.FC = ({ articles }) => { 17 | if (!articles || !Array.isArray(articles) || articles?.length == 0) return null; 18 | return ( 19 |
20 |
21 |

Last articles

22 |

23 | Here at Flowbite we focus on markets where technology, innovation, and capital can unlock long-term value and 24 | drive economic growth. 25 |

26 |
27 |
28 | {articles.map((article, idx) => { 29 | if (idx == 0) 30 | return ( 31 |
32 | 33 |
34 | ); 35 | return ; 36 | })} 37 |
38 |
39 | ); 40 | }; 41 | -------------------------------------------------------------------------------- /components/layouts/relatedArticle.tsx: -------------------------------------------------------------------------------- 1 | import { useIsAdmin } from "@suncel/nextjs"; 2 | import { CardWithImage } from "../card/cardWithImage"; 3 | import { HorizontalCardWithImage } from "../card/horizontalCardWithImage"; 4 | import { ArticleCardProps } from "./lastArticles"; 5 | 6 | interface RelatedArticlesProps { 7 | articles: ArticleCardProps[]; 8 | isRelatedArticlePage: boolean; 9 | } 10 | 11 | export const RelatedArticles: React.FC = ({ articles, isRelatedArticlePage }) => { 12 | const isAdmin = useIsAdmin(); 13 | 14 | if (!articles || !Array.isArray(articles) || articles?.length == 0) 15 | return isAdmin && isRelatedArticlePage ? ( 16 |
17 | If you want to add related articles : go in Pages (right side panel), add{" "} 18 | Related Articles, and Save and Refresh to see updates on related articles. 19 |
20 | ) : null; 21 | return ( 22 |
23 | {isAdmin && ( 24 |
25 | If you want to add related articles : go in Pages (right side panel), add{" "} 26 | Related Articles, and Save and Refresh to see updates on related articles. 27 |
28 | )} 29 |
30 |

Related articles

31 |

32 | Here at Flowbite we focus on markets where technology, innovation, and capital can unlock long-term value and 33 | drive economic growth. 34 |

35 |
36 |
37 | {articles.map((article, idx) => { 38 | return ; 39 | })} 40 |
41 |
42 | ); 43 | }; 44 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | swcMinify: true, 5 | 6 | images: { 7 | domains: ["assets.suncel.io"], 8 | }, 9 | }; 10 | 11 | module.exports = nextConfig; 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-suncel-project", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@react-icons/all-files": "^4.1.0", 13 | "@suncel/nextjs": "~1.1.2", 14 | "@tailwindcss/line-clamp": "^0.4.2", 15 | "@tiptap/extension-blockquote": "^2.0.0-beta.202", 16 | "clsx": "^1.2.1", 17 | "next": "13.0.4", 18 | "react": "18.2.0", 19 | "react-dom": "18.2.0", 20 | "sass": "^1.56.1" 21 | }, 22 | "devDependencies": { 23 | "@types/node": "18.11.9", 24 | "@types/react": "18.0.25", 25 | "@types/react-dom": "18.0.9", 26 | "autoprefixer": "^10.4.13", 27 | "eslint": "8.28.0", 28 | "eslint-config-next": "13.0.4", 29 | "flowbite-typography": "^1.0.3", 30 | "postcss": "^8.4.19", 31 | "tailwindcss": "^3.2.4", 32 | "typescript": "4.9.3" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /pages/[...slug].tsx: -------------------------------------------------------------------------------- 1 | // 2 | import { GetStaticPaths, GetStaticProps } from "next"; 3 | import React, { ReactElement } from "react"; 4 | import { PageRender, getSuncelStaticProps, getSuncelStaticPaths } from "@suncel/nextjs"; 5 | import { ContentWrapper } from "@/components/layouts/contentWrapper"; 6 | import { Header } from "@/components/layouts/header"; 7 | import { getGlobal, getPage, getPages, Page } from "@suncel/nextjs/api"; 8 | import { Footer } from "@/components/layouts/footer"; 9 | import { RelatedArticles } from "@/components/layouts/relatedArticle"; 10 | import { CategoryArticles } from "@/components/layouts/categoryArticles"; 11 | import { suncelContextConfig } from "@/suncel/suncelContextConfig"; 12 | 13 | export default function Slug(props: any) { 14 | if (!props?.suncel) return
Cannot load the page
; 15 | 16 | return ; 17 | } 18 | 19 | Slug.getLayout = function getLayout(page: ReactElement) { 20 | const { header, footer, relatedArticles, categoryArticles, isCategoryPage, isRelatedArticlePage } = page?.props; 21 | 22 | return ( 23 | 24 |
25 | {page} 26 | 27 | 28 |