diff --git a/.env.template b/.env.template index 7abc754..ec60957 100644 --- a/.env.template +++ b/.env.template @@ -1 +1,2 @@ -NEXT_PUBLIC_API_URL=API \ No newline at end of file +NEXT_PUBLIC_API_URL=API +NEXT_PUBLIC_SITE_URL=SITE \ No newline at end of file diff --git a/app/(auth)/sign-in/page.tsx b/app/(auth)/sign-in/page.tsx index 9a0377e..aa16143 100644 --- a/app/(auth)/sign-in/page.tsx +++ b/app/(auth)/sign-in/page.tsx @@ -1,15 +1,84 @@ -import UserAuthForm from '@/ui/UserAuthForm'; +'use client'; -export const metadata = { - title: 'Connexion - Peer-at Code' +import AppLink from '@/ui/AppLink'; +import Button from '@/ui/Button'; +import Input from '@/ui/Input'; +import cookies from 'js-cookie'; +import { useRouter } from 'next/navigation'; +import { useForm } from 'react-hook-form'; + +type LoginData = { + pseudo: string; + passwd: string; }; export default function Page() { + const { + register, + handleSubmit, + formState: { errors }, + setError + } = useForm({ + defaultValues: { + pseudo: '', + passwd: '' + } + }); + + const router = useRouter(); + + async function onSubmit(data: LoginData) { + const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/login`, { + method: 'POST', + body: JSON.stringify(data) + }); + + if (res.ok) { + const token = res.headers.get('Authorization')?.split(' ')[1]; + if (token) cookies.set('token', token); + if (cookies.get('token')) router.push('/dashboard'); + } else { + setError('passwd', { + type: 'manual', + message: "Nom d'utilisateur ou mot de passe incorrect" + }); + } + } + return ( <>

Connexion

- +
+ + + +

+ Vous n'avez pas de compte? + + S'inscrire maintenant + +

+
); diff --git a/app/(auth)/sign-up/page.tsx b/app/(auth)/sign-up/page.tsx index d385d60..4474424 100644 --- a/app/(auth)/sign-up/page.tsx +++ b/app/(auth)/sign-up/page.tsx @@ -1,15 +1,132 @@ -import UserAuthForm from '@/ui/UserAuthForm'; +'use client'; -export const metadata = { - title: 'Inscription - Peer-at Code' +import AppLink from '@/ui/AppLink'; +import Button from '@/ui/Button'; +import Input from '@/ui/Input'; +import cookies from 'js-cookie'; +import { useRouter } from 'next/navigation'; +import { useForm } from 'react-hook-form'; + +type RegisterData = { + pseudo: string; + email: string; + passwd: string; + firstname: string; + lastname: string; + description: string; + sgroup: string; + avatar: string; }; export default function Page() { + const { + register, + handleSubmit, + formState: { errors }, + setError + } = useForm({ + defaultValues: { + pseudo: '', + email: '', + passwd: '', + firstname: '', + lastname: '', + description: '', + sgroup: '', + avatar: '' + } + }); + + const router = useRouter(); + + async function onSubmit(data: RegisterData) { + const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/register`, { + method: 'POST', + body: JSON.stringify(data) + }); + + const { username_valid, email_valid } = await res.json(); + + if (!username_valid || !email_valid) { + if (!username_valid) { + setError('pseudo', { + type: 'manual', + message: "Nom d'utilisateur indisponible" + }); + } + if (!email_valid) { + setError('email', { + type: 'manual', + message: 'Email déjà utilisé' + }); + } + } + + console.log(errors); + console.log(username_valid, email_valid); + + if (res.ok) { + const token = res.headers.get('Authorization')?.split(' ')[1]; + if (token) cookies.set('token', token); + if (cookies.get('token')) router.push('/dashboard'); + } + } + return ( <>

Créer un compte

- +
+ + + + + + + {/*

+ En cliquant sur continuer, vous acceptez les{' '} + + Politique de confidentialité + + . +

*/} +

+ Vous possédez un compte? + + Se connecter + +

+
); diff --git a/app/page.tsx b/app/page.tsx index e8a673e..342a155 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -2,7 +2,8 @@ import AppLink from '@/ui/AppLink'; import Console from '@/ui/Console'; import Image from 'next/image'; -export default function Home() { +export default function Page() { + // TODO: Fix this (image) return (
diff --git a/lib/nav-items.ts b/lib/nav-items.ts index 9754c02..c2d7558 100644 --- a/lib/nav-items.ts +++ b/lib/nav-items.ts @@ -1,9 +1,11 @@ /** - * Un élément de navigation. + * A navigation item. + * * @typedef {Object} NavItem - * @property {string} name - Le nom de l'élément de navigation. - * @property {string} slug - Le slug de l'élément de navigation. - * @property {boolean} [disabled] - Si l'élément de navigation est désactivé. + * + * @property {string} name - The name of the navigation item. + * @property {string} slug - The slug of the navigation item. + * @property {boolean} [disabled] - Whether the navigation item is disabled. */ export type NavItem = { name: string; @@ -13,7 +15,8 @@ export type NavItem = { }; /** - * Les éléments de navigation. + * Navigation items. + * * @type {NavItem[]} */ export const navItems: NavItem[] = [ diff --git a/lib/utils.ts b/lib/utils.ts index 51e4966..35d8238 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -2,22 +2,23 @@ import { clsx, type ClassValue } from 'clsx'; import { twMerge } from 'tailwind-merge'; /** - * Permet de créer une classe Tailwind avec clsx et tailwind-merge - * pour éviter d'avoir des conflits de classes. + * Create a Tailwind class with clsx and tailwind-merge to avoid + * class conflicts. * - * @param inputs - Les classes à ajouter à la classe Tailwind. + * @param inputs - Tailwind classes to merge. * - * @returns La classe Tailwind. + * @returns A Tailwind class. */ export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } /** - * Permet de convertir une chaîne de caractères en majuscules. + * Convert a string to title case. * - * @param string - La chaîne de caractères à convertir. - * @returns La chaîne de caractères convertie. + * @param string - The string to convert. + * + * @returns A title case string. */ export function titleCase(string: string) { return string @@ -26,3 +27,27 @@ export function titleCase(string: string) { .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) .join(' '); } + +/** + * Convert a string to a slug. + * + * @param pathname - The pathname to append to the URL. + * + * @returns The full URL. + */ +export const getURL = (pathname?: string) => { + let url = + process.env.NEXT_PUBLIC_SITE_URL ?? // Set this to your site URL in production env. + process.env.NEXT_PUBLIC_VERCEL_URL ?? // Automatically set by Vercel. + 'http://localhost:3000/'; + // Make sure to include `https://` when not localhost. + url = url.includes('http') ? url : `https://${url}`; + // Make sure to including trailing `/`. + url = url.charAt(url.length - 1) === '/' ? url : `${url}/`; + + if (pathname) { + // Add pathname without starting `/` + url += pathname.slice(1); + } + return url; +}; diff --git a/middleware.ts b/middleware.ts index 97b60c6..b7cf541 100644 --- a/middleware.ts +++ b/middleware.ts @@ -1,19 +1,33 @@ -import { type NextRequest, NextResponse } from 'next/server'; +import { NextResponse, type NextRequest } from 'next/server'; +import { getURL } from './lib/utils'; /** * Permet de créer un middleware Next.js qui sera exécuté avant chaque requête. * - * @param req - La requête. + * @param req - La requête Next.js */ export async function middleware(req: NextRequest) { const res = NextResponse.next(); - console.log('Res', res); + + // on donne accès à l'API depuis n'importe quelle origine + res.headers.set('Access-Control-Allow-Origin', '*'); + res.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); + + const token = req.cookies.get('token')?.value; + + if (req.nextUrl.pathname.includes('dashboard') && !token) + return NextResponse.redirect(getURL('/sign-in')); + else if (req.nextUrl.pathname.includes('sign') && token) + return NextResponse.redirect(getURL('/dashboard')); + + res.headers.set('Authorization', `Bearer ${token}`); + return res; } export const config = { matcher: [ // On exclut les routes de l'API, les fichiers statiques, les images, les assets, le favicon et le service worker. - // '/((?!api|_next/static|_next/image|assets|favicon|sw.js).*)' + '/((?!api|_next/static|_next/image|assets|favicon|sw.js).*)' ] }; diff --git a/package.json b/package.json index ff2a682..b39a88e 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "axios": "^1.3.4", "boring-avatars": "^1.7.0", "clsx": "^1.2.1", + "js-cookie": "^3.0.1", "next": "13.2.1", "react": "18.2.0", "react-dom": "18.2.0", @@ -36,6 +37,7 @@ }, "devDependencies": { "@tailwindcss/forms": "^0.5.3", + "@types/js-cookie": "^3.0.3", "@types/node": "18.11.18", "@types/react": "18.0.27", "@types/react-dom": "18.0.10", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index da1c096..8ae78e8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2,6 +2,7 @@ lockfileVersion: 5.4 specifiers: '@tailwindcss/forms': ^0.5.3 + '@types/js-cookie': ^3.0.3 '@types/node': 18.11.18 '@types/react': 18.0.27 '@types/react-dom': 18.0.10 @@ -15,6 +16,7 @@ specifiers: eslint-config-next: 13.2.1 eslint-config-prettier: ^8.6.0 eslint-plugin-prettier: ^4.2.1 + js-cookie: ^3.0.1 next: 13.2.1 postcss: ^8.4.21 prettier: ^2.8.3 @@ -34,6 +36,7 @@ dependencies: axios: 1.3.4 boring-avatars: 1.7.0 clsx: 1.2.1 + js-cookie: 3.0.1 next: 13.2.1_biqbaboplfbrettd7655fr4n2y react: 18.2.0 react-dom: 18.2.0_react@18.2.0 @@ -46,6 +49,7 @@ dependencies: devDependencies: '@tailwindcss/forms': 0.5.3_tailwindcss@3.2.7 + '@types/js-cookie': 3.0.3 '@types/node': 18.11.18 '@types/react': 18.0.27 '@types/react-dom': 18.0.10 @@ -299,6 +303,10 @@ packages: '@types/unist': 2.0.6 dev: false + /@types/js-cookie/3.0.3: + resolution: {integrity: sha512-Xe7IImK09HP1sv2M/aI+48a20VX+TdRJucfq4vfRVy6nWN8PYPOEnlMRSgxJAgYQIXJVL8dZ4/ilAM7dWNaOww==} + dev: true + /@types/json-schema/7.0.11: resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} dev: true @@ -1862,6 +1870,11 @@ packages: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} dev: true + /js-cookie/3.0.1: + resolution: {integrity: sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==} + engines: {node: '>=12'} + dev: false + /js-sdsl/4.3.0: resolution: {integrity: sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==} dev: true diff --git a/ui/DefaultTags.tsx b/ui/DefaultTags.tsx index 2fd53ae..e5cee56 100644 --- a/ui/DefaultTags.tsx +++ b/ui/DefaultTags.tsx @@ -1,7 +1,6 @@ export default function DefaultTags() { return ( <> - ); diff --git a/ui/ErrorMessage.tsx b/ui/ErrorMessage.tsx index ad1747a..f3d3cf9 100644 --- a/ui/ErrorMessage.tsx +++ b/ui/ErrorMessage.tsx @@ -1,3 +1,3 @@ export default function ErrorMessage({ children }: { children: React.ReactNode }) { - return

{children}

; + return

{children}

; }