diff --git a/app/dashboard/layout.tsx b/app/dashboard/layout.tsx
index cc82104..d132f03 100644
--- a/app/dashboard/layout.tsx
+++ b/app/dashboard/layout.tsx
@@ -1,16 +1,11 @@
import { type ReactNode } from 'react';
-import Sidenav from '@/ui/dashboard/Sidenav';
+import Wrapper from '@/ui/dashboard/Wrapper';
export default function Layout({ children }: { children: ReactNode }) {
return (
);
}
diff --git a/app/dashboard/puzzles/[id]/page.tsx b/app/dashboard/puzzles/[id]/page.tsx
new file mode 100644
index 0000000..659f9ca
--- /dev/null
+++ b/app/dashboard/puzzles/[id]/page.tsx
@@ -0,0 +1,16 @@
+import Puzzle from '@/ui/Puzzle';
+
+export default async function Page({ params }: { params: { id: string } }) {
+ const { id } = params;
+
+ return (
+ //
+
+ //
+ );
+}
+
+// export async function generateStaticParams() {
+// const { puzzles } = await getPuzzles();
+// return puzzles.map(({ id }) => ({ params: { id } }));
+// }
diff --git a/app/dashboard/puzzles/page.tsx b/app/dashboard/puzzles/page.tsx
index e5e4acf..ac633e1 100644
--- a/app/dashboard/puzzles/page.tsx
+++ b/app/dashboard/puzzles/page.tsx
@@ -1,39 +1,18 @@
-import Button from '@/ui/Button';
-import Input from '@/ui/Input';
-import ToHTML from '@/ui/ToHTML';
+import Puzzles from '@/ui/Puzzles';
+import SWRFallback from '@/ui/SWRFallback';
+
+export default async function Page() {
+ // const puzzles = await getPuzzles();
-export default function Page() {
- // on va utiliser react-hook-form https://react-hook-form.com/
return (
-
-
Titre du puzzle
-
Chapitre 2 - Les boucles à boucler
+
-
-
-
-
);
}
diff --git a/app/fonts/Karrik-Regular.woff2 b/app/fonts/Karrik-Regular.woff2
new file mode 100644
index 0000000..8754815
Binary files /dev/null and b/app/fonts/Karrik-Regular.woff2 differ
diff --git a/app/fonts/Typefesse_Claire-Obscure.woff2 b/app/fonts/Typefesse_Claire-Obscure.woff2
new file mode 100644
index 0000000..42a7dce
Binary files /dev/null and b/app/fonts/Typefesse_Claire-Obscure.woff2 differ
diff --git a/app/fonts/Typefesse_Pleine.woff2 b/app/fonts/Typefesse_Pleine.woff2
new file mode 100644
index 0000000..4b47d54
Binary files /dev/null and b/app/fonts/Typefesse_Pleine.woff2 differ
diff --git a/app/layout.tsx b/app/layout.tsx
index 379035b..6e68694 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,12 +1,34 @@
import '@/styles/globals.css';
import 'remixicon/fonts/remixicon.css';
+import { Fira_Code } from '@next/font/google';
+import localFont from '@next/font/local';
import { cn } from '@/lib/utils';
import { type ReactNode } from 'react';
+const sans = localFont({
+ variable: '--font-sans',
+ src: './fonts/Karrik-Regular.woff2',
+ weight: 'variable'
+});
+
+const code = Fira_Code({
+ variable: '--font-code',
+ subsets: ['latin'],
+ weight: 'variable'
+});
+
export default function RootLayout({ children }: { children: ReactNode }) {
return (
-
+
{children}
diff --git a/lib/hooks/use-puzzles.ts b/lib/hooks/use-puzzles.ts
new file mode 100644
index 0000000..5179084
--- /dev/null
+++ b/lib/hooks/use-puzzles.ts
@@ -0,0 +1,14 @@
+import useSWR from 'swr';
+import { getChapters, getPuzzle, getPuzzles } from '../puzzles';
+
+export function useChapters() {
+ return useSWR('chapters', () => getChapters());
+}
+
+export function usePuzzles() {
+ return useSWR('puzzles', () => getPuzzles());
+}
+
+export function usePuzzle(id: string) {
+ return useSWR(`puzzles/${id}`, () => getPuzzle(id));
+}
diff --git a/lib/puzzles.ts b/lib/puzzles.ts
new file mode 100644
index 0000000..b0cd355
--- /dev/null
+++ b/lib/puzzles.ts
@@ -0,0 +1,74 @@
+export const getChapters = async (): Promise => {
+ const req = await fetch(`http://170.75.166.204/chapters`);
+
+ const chapters = await req.json();
+
+ if (!req.ok) {
+ throw new Error('Failed to fetch puzzles');
+ }
+
+ if (!chapters) {
+ return [];
+ }
+
+ return chapters as Chapter[];
+};
+
+export const getPuzzlesByChapter = async (chapitre: string): Promise => {
+ const req = await fetch(`http://170.75.166.204/chapter/${chapitre}`);
+
+ const { puzzles } = await req.json();
+
+ if (!req.ok) {
+ throw new Error('Failed to fetch puzzles');
+ }
+
+ if (!puzzles) {
+ return [];
+ }
+
+ return puzzles as Puzzle[];
+};
+
+export const getPuzzles = async (): Promise<{ chapters: Chapter[]; puzzles: Puzzle[] }> => {
+ const chapters = await getChapters();
+ let puzzles: Puzzle[] = [];
+
+ for (const chapter of chapters) {
+ const puzzlesByChapter = await getPuzzlesByChapter(chapter.id);
+ puzzles = [...puzzles, ...puzzlesByChapter];
+ }
+
+ return {
+ chapters: chapters as Chapter[],
+ puzzles: puzzles as Puzzle[]
+ };
+};
+
+export const getPuzzle = async (id: string): Promise => {
+ const req = await fetch(`http://170.75.166.204/puzzle/${id}`);
+
+ const puzzle = await req.json();
+
+ if (!req.ok) {
+ throw new Error('Failed to fetch puzzle');
+ }
+
+ if (!puzzle) {
+ return {} as Puzzle;
+ }
+
+ return puzzle as Puzzle;
+};
+
+export type Puzzle = {
+ chapter: string;
+ name: string;
+ id: string;
+ content: string;
+};
+
+export type Chapter = {
+ name: string;
+ id: string;
+};
diff --git a/next.config.js b/next.config.js
index b859826..d83edcb 100644
--- a/next.config.js
+++ b/next.config.js
@@ -1,6 +1,6 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
- reactStrictMode: true, // Recommended for the `pages` directory, default in `app`.
+ reactStrictMode: true,
swcMinify: true,
experimental: {
appDir: true
diff --git a/package.json b/package.json
index 1fb1da7..9858462 100644
--- a/package.json
+++ b/package.json
@@ -26,6 +26,7 @@
"next": "13.1.6",
"react": "18.2.0",
"react-dom": "18.2.0",
+ "react-hook-form": "^7.43.1",
"remixicon": "^2.5.0",
"swr": "^2.0.3",
"tailwind-merge": "^1.9.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 317a6a7..11eab58 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -20,6 +20,7 @@ specifiers:
prettier-plugin-tailwindcss: ^0.2.2
react: 18.2.0
react-dom: 18.2.0
+ react-hook-form: ^7.43.1
remixicon: ^2.5.0
swr: ^2.0.3
tailwind-merge: ^1.9.0
@@ -33,6 +34,7 @@ dependencies:
next: 13.1.6_biqbaboplfbrettd7655fr4n2y
react: 18.2.0
react-dom: 18.2.0_react@18.2.0
+ react-hook-form: 7.43.1_react@18.2.0
remixicon: 2.5.0
swr: 2.0.3_react@18.2.0
tailwind-merge: 1.9.0
@@ -2268,6 +2270,15 @@ packages:
scheduler: 0.23.0
dev: false
+ /react-hook-form/7.43.1_react@18.2.0:
+ resolution: {integrity: sha512-+s3+s8LLytRMriwwuSqeLStVjRXFGxgjjx2jED7Z+wz1J/88vpxieRQGvJVvzrzVxshZ0BRuocFERb779m2kNg==}
+ engines: {node: '>=12.22.0'}
+ peerDependencies:
+ react: ^16.8.0 || ^17 || ^18
+ dependencies:
+ react: 18.2.0
+ dev: false
+
/react-is/16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
dev: true
diff --git a/tailwind.config.js b/tailwind.config.js
index 6713391..a342765 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -1,3 +1,5 @@
+const defaultTheme = require('tailwindcss/defaultTheme');
+
/** @type {import('tailwindcss').Config} */
module.exports = {
mode: 'jit',
@@ -7,9 +9,84 @@ module.exports = {
},
theme: {
extend: {
+ fontFamily: {
+ sans: ['var(--font-sans)', ...defaultTheme.fontFamily.sans],
+ code: ['var(--font-code)', ...defaultTheme.fontFamily.serif]
+ },
colors: {
- 'light-dark': '#282828',
- dark: '#202020'
+ ...require('tailwindcss/colors'),
+ primary: {
+ 900: 'hsl(258deg 15% 7%)',
+ 800: 'hsl(258deg 15% 11%)',
+ 700: 'hsl(258deg 15% 15%)',
+ 600: 'hsl(258deg 15% 20%)',
+ 500: 'hsl(258deg 15% 25%)',
+ 400: 'hsl(258deg 14% 35%)',
+ 300: 'hsl(258deg 13% 45%)',
+ 200: 'hsl(258deg 13% 55%)',
+ 100: 'hsl(258deg 10% 65%)',
+ 50: 'hsl(258deg 8% 85%)',
+ 0: 'hsl(258deg 8% 100%)'
+ },
+ brand: {
+ DEFAULT: '#1c56cb',
+ accent: '#236bfe'
+ },
+ success: {
+ DEFAULT: 'hsl(104deg 39% 59%)',
+ secondary: '#b5e4ca',
+ background: '#60a747'
+ },
+ info: {
+ DEFAULT: 'hsl(258deg 78% 77%)',
+ secondary: '#ccb8f9',
+ background: '#9878de'
+ },
+ warning: {
+ DEFAULT: 'hsl(39deg 100% 67%)',
+ secondary: '#ffd88d',
+ background: '#da9b34'
+ },
+ error: {
+ DEFAULT: 'hsl(7deg 100% 67%)',
+ secondary: '#ffbc99',
+ background: '#cd4634'
+ },
+ highlight: {
+ primary: 'hsl(258deg 15% 17%)',
+ secondary: 'hsl(258deg 10% 46%)'
+ },
+ product: {
+ ignite: 'hsl(8deg 89% 57%)',
+ pipe: 'hsl(214deg 100% 58%)',
+ channels: 'hsl(46deg 74% 51%)'
+ }
+ },
+ backgroundColor: {
+ primary: {
+ DEFAULT: 'hsl(258deg 15% 7%)',
+ 900: 'hsl(258deg 15% 7%)',
+ 800: 'hsl(258deg 15% 11%)',
+ 700: 'hsl(258deg 15% 15%)',
+ 600: 'hsl(258deg 15% 20%)',
+ 500: 'hsl(258deg 15% 25%)',
+ 400: 'hsl(258deg 14% 35%)',
+ 300: 'hsl(258deg 13% 45%)',
+ 200: 'hsl(258deg 13% 55%)',
+ 100: 'hsl(258deg 10% 65%)',
+ 50: 'hsl(258deg 8% 85%)',
+ 0: 'hsl(258deg 8% 100%)'
+ },
+ secondary: 'hsl(258deg 15% 11%)',
+ tertiary: 'hsl(258deg 15% 17%)',
+ contrast: '#4f5450'
+ },
+ textColor: {
+ primary: 'hsl(258deg 8% 100%)',
+ secondary: 'hsl(258deg 8% 84%)',
+ tertiary: 'hsl(258deg 8% 65%)',
+ secondaryAccent: '#e2e8f0',
+ muted: 'hsl(258deg 7% 46%)'
}
}
},
diff --git a/ui/Button.tsx b/ui/Button.tsx
index 86f62e3..2ec101f 100644
--- a/ui/Button.tsx
+++ b/ui/Button.tsx
@@ -4,16 +4,17 @@ import { forwardRef } from 'react';
const Button = forwardRef<
HTMLButtonElement,
React.ButtonHTMLAttributes & {
- kind?: 'default' | 'outline' | 'danger';
+ kind?: 'default' | 'danger' | 'brand';
}
>(({ kind = 'default', className, ...props }, ref) => (