diff --git a/.eslintrc.json b/.eslintrc.json
new file mode 100644
index 0000000..bffb357
--- /dev/null
+++ b/.eslintrc.json
@@ -0,0 +1,3 @@
+{
+ "extends": "next/core-web-vitals"
+}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c87c9b3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,36 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+.pnpm-debug.log*
+
+# local env files
+.env*.local
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
diff --git a/README.md b/README.md
index 8f12c11..c5ec5c5 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,26 @@
# hlilo-website
+## Par où commencer
+
+```bash
+yarn dev
+```
+
+Ouvrir [http://localhost:3000](http://localhost:3000).
+
+## Voir aussi
+
+[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.
+
+The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
+
+This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
+
+## En savoir plus
+
+To learn more about Next.js, take a look at the following resources:
+
+- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
+- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
+
+You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
diff --git a/config.json b/config.json
new file mode 100644
index 0000000..708c99e
--- /dev/null
+++ b/config.json
@@ -0,0 +1,19 @@
+{
+ "base_url": "https://example.com",
+ "site_title": "Next.js Static CMS Blog",
+ "site_description": "example.com",
+ "site_keywords": [
+ {
+ "keyword": "Next.js"
+ },
+ {
+ "keyword": "Static CMS"
+ },
+ {
+ "keyword": "React"
+ }
+ ],
+ "posts_per_page": 5,
+ "twitter_account": "@my-account",
+ "github_account": "myaccount"
+}
\ No newline at end of file
diff --git a/next.config.js b/next.config.js
new file mode 100644
index 0000000..a843cbe
--- /dev/null
+++ b/next.config.js
@@ -0,0 +1,6 @@
+/** @type {import('next').NextConfig} */
+const nextConfig = {
+ reactStrictMode: true,
+}
+
+module.exports = nextConfig
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..9a47f76
--- /dev/null
+++ b/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "hlilo-website",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start",
+ "lint": "next lint"
+ },
+ "dependencies": {
+ "@staticcms/core": "^1.2.13",
+ "@types/node": "18.15.11",
+ "@types/react": "18.0.31",
+ "@types/react-dom": "18.0.11",
+ "eslint": "8.37.0",
+ "eslint-config-next": "13.2.4",
+ "next": "13.2.4",
+ "react": "18.2.0",
+ "react-dom": "18.2.0",
+ "typescript": "5.0.3"
+ }
+}
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000..718d6fe
Binary files /dev/null and b/public/favicon.ico differ
diff --git a/public/next.svg b/public/next.svg
new file mode 100644
index 0000000..5174b28
--- /dev/null
+++ b/public/next.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/site.webmanifest b/public/site.webmanifest
new file mode 100644
index 0000000..0d6e338
--- /dev/null
+++ b/public/site.webmanifest
@@ -0,0 +1,14 @@
+{
+ "short_name": "",
+ "name": "",
+ "icons": [
+ {
+ "src": "icon.png",
+ "type": "image/png",
+ "sizes": "192x192"
+ }
+ ],
+ "start_url": "/",
+ "background_color": "#000000",
+ "theme_color": "#000000"
+}
diff --git a/public/thirteen.svg b/public/thirteen.svg
new file mode 100644
index 0000000..8977c1b
--- /dev/null
+++ b/public/thirteen.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/vercel.svg b/public/vercel.svg
new file mode 100644
index 0000000..d2f8422
--- /dev/null
+++ b/public/vercel.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/components/Burger.tsx b/src/components/Burger.tsx
new file mode 100644
index 0000000..735caf8
--- /dev/null
+++ b/src/components/Burger.tsx
@@ -0,0 +1,60 @@
+type Props = {
+ active: boolean;
+ onClick: () => void;
+};
+export default function Burger({ active, onClick }: Props) {
+ return (
+
+ );
+}
diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx
new file mode 100644
index 0000000..9a579f3
--- /dev/null
+++ b/src/components/Layout.tsx
@@ -0,0 +1,23 @@
+import Head from "next/head";
+import Navigation from "./Navigation";
+
+type Props = {
+ children: React.ReactNode;
+};
+export default function Layout({ children }: Props) {
+ return (
+
+
+
+
+
+
+
+
+
+ {children}
+
+ );
+}
diff --git a/src/components/Navigation.tsx b/src/components/Navigation.tsx
new file mode 100644
index 0000000..ec40c0a
--- /dev/null
+++ b/src/components/Navigation.tsx
@@ -0,0 +1,86 @@
+import Link from "next/link";
+import { useRouter } from "next/router";
+import Burger from "./Burger";
+import { useState } from "react";
+
+export default function Navigation() {
+ const router = useRouter();
+ const [active, setActive] = useState(false);
+ return (
+ <>
+ setActive(!active)} />
+
+
+ -
+
+ about
+
+
+ -
+
+ blog
+
+
+
+
+
+ >
+ );
+}
diff --git a/src/components/Pagination.tsx b/src/components/Pagination.tsx
new file mode 100644
index 0000000..1b34188
--- /dev/null
+++ b/src/components/Pagination.tsx
@@ -0,0 +1,46 @@
+import { generatePagination } from "../lib/pagination";
+import Link from "next/link";
+
+type Props = {
+ current: number;
+ pages: number;
+ link: {
+ href: (page: number) => string;
+ as: (page: number) => string;
+ };
+};
+export default function Pagination({ current, pages, link }: Props) {
+ const pagination = generatePagination(current, pages);
+ return (
+
+ {pagination.map((it, i) => (
+ -
+ {it.excerpt ? (
+ "..."
+ ) : (
+
+ {it.page}
+
+ )}
+
+ ))}
+
+
+ );
+}
diff --git a/src/components/cms/CMSPage.tsx b/src/components/cms/CMSPage.tsx
new file mode 100644
index 0000000..4e8da9e
--- /dev/null
+++ b/src/components/cms/CMSPage.tsx
@@ -0,0 +1,39 @@
+import CMS from "@staticcms/core";
+import { useEffect } from "react";
+
+import config from "./config";
+
+interface PostData {
+ title: string;
+ date: string;
+ body: string;
+}
+
+const CMSPage = () => {
+ useEffect(() => {
+ if (process.env.NODE_ENV === "development") {
+ config.local_backend = true;
+ }
+
+ CMS.init({ config });
+ }, []);
+
+ return (
+
+
+
+ );
+};
+
+CMSPage.displayName = "CMSPage";
+
+export default CMSPage;
diff --git a/src/components/cms/config.ts b/src/components/cms/config.ts
new file mode 100644
index 0000000..b3a0992
--- /dev/null
+++ b/src/components/cms/config.ts
@@ -0,0 +1,137 @@
+import type { Config } from "@staticcms/core";
+
+const config: Config = {
+ backend: { name: "git-gateway", branch: "static" },
+ media_folder: "public/images",
+ public_folder: "/images",
+ collections: [
+ {
+ name: "config",
+ label: "Config",
+ delete: false,
+ editor: { preview: false },
+ files: [
+ {
+ name: "general",
+ label: "Site Config",
+ file: "config.json",
+ description: "General site settings",
+ fields: [
+ { label: "URL", name: "base_url", widget: "string", hint: "Do not enter the trailing slash of the URL" },
+ { label: "Site title", name: "site_title", widget: "string" },
+ { label: "Site description", name: "site_description", widget: "string" },
+ {
+ label: "Site keywords",
+ name: "site_keywords",
+ widget: "list",
+ summary: "{{fields.keyword.keyword}}",
+ fields: [{ label: "Keyword", name: "keyword", widget: "string" }],
+ },
+ { label: "Twitter account", name: "twitter_account", widget: "string" },
+ { label: "GitHub account", name: "github_account", widget: "string" },
+ ],
+ },
+ ],
+ },
+ {
+ name: "meta",
+ label: "Meta",
+ delete: false,
+ editor: { preview: false },
+ files: [
+ {
+ name: "authors",
+ label: "Authors",
+ file: "meta/authors.yml",
+ description: "Author descriptions",
+ fields: [
+ {
+ name: "authors",
+ label: "Authors",
+ label_singular: "Author",
+ widget: "list",
+ fields: [
+ { label: "Slug", name: "slug", widget: "string", hint: "The part of a URL identifies the author" },
+ { label: "Name", name: "name", widget: "string", hint: "First and Last" },
+ { label: "Introduction", name: "introduction", widget: "text" },
+ ],
+ },
+ ],
+ },
+ {
+ name: "tags",
+ label: "Tags",
+ file: "meta/tags.yml",
+ description: "List of tags",
+ fields: [
+ {
+ name: "tags",
+ label: "Tags",
+ label_singular: "Tag",
+ widget: "list",
+ fields: [
+ { label: "Slug", name: "slug", widget: "string", hint: "The part of a URL identifies the tag" },
+ { label: "Display Name", name: "name", widget: "string", hint: "Tag name for displaying on the site" },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ name: "posts",
+ label: "Posts",
+ folder: "content/posts/",
+ extension: "mdx",
+ format: "frontmatter",
+ create: true,
+ slug: "{{slug}}",
+ identifier_field: "slug",
+ summary: "{{title}}",
+ fields: [
+ { label: "Slug", name: "slug", widget: "string" },
+ { label: "Title", name: "title", widget: "string" },
+ {
+ label: "Publish Date",
+ name: "date",
+ widget: "datetime",
+ format: "yyyy-MM-dd",
+ date_format: "yyyy-MM-dd",
+ time_format: false,
+ },
+ {
+ label: "Author",
+ name: "author",
+ widget: "relation",
+ collection: "meta",
+ file: "authors",
+ search_fields: ["authors.*.name"],
+ display_fields: ["authors.*.name"],
+ value_field: "authors.*.slug",
+ },
+ {
+ label: "Tags",
+ label_singular: "Tag",
+ name: "tags",
+ widget: "list",
+ summary: "{{fields.tag}}",
+ fields: [
+ {
+ label: "Tag",
+ name: "tag",
+ widget: "relation",
+ collection: "meta",
+ file: "tags",
+ search_fields: ["tags.*.name"],
+ display_fields: ["tags.*.name"],
+ value_field: "tags.*.slug",
+ },
+ ],
+ },
+ { label: "Body", name: "body", widget: "markdown" },
+ ],
+ },
+ ],
+};
+
+export default config;
diff --git a/src/components/meta/BasicMeta.tsx b/src/components/meta/BasicMeta.tsx
new file mode 100644
index 0000000..ad0a3cb
--- /dev/null
+++ b/src/components/meta/BasicMeta.tsx
@@ -0,0 +1,39 @@
+import Head from "next/head";
+import config from "../../lib/config";
+
+type Props = {
+ title?: string;
+ description?: string;
+ keywords?: string[];
+ author?: string;
+ url: string;
+};
+export default function BasicMeta({
+ title,
+ description,
+ keywords,
+ author,
+ url,
+}: Props) {
+ return (
+
+
+ {title ? [title, config.site_title].join(" | ") : config.site_title}
+
+
+ it.keyword).join(",")
+ }
+ />
+ {author ? : null}
+
+
+ );
+}
diff --git a/src/components/meta/JsonLdMeta.tsx b/src/components/meta/JsonLdMeta.tsx
new file mode 100644
index 0000000..14b4780
--- /dev/null
+++ b/src/components/meta/JsonLdMeta.tsx
@@ -0,0 +1,42 @@
+import { BlogPosting } from "schema-dts";
+import { jsonLdScriptProps } from "react-schemaorg";
+import config from "../../lib/config";
+import { formatISO } from "date-fns";
+import Head from "next/head";
+
+type Props = {
+ url: string;
+ title: string;
+ keywords?: string[];
+ date: Date;
+ author?: string;
+ image?: string;
+ description?: string;
+};
+export default function JsonLdMeta({
+ url,
+ title,
+ keywords,
+ date,
+ author,
+ image,
+ description,
+}: Props) {
+ return (
+
+