Adding auth v1
This commit is contained in:
parent
126fb658fb
commit
f4dad5a7bc
11 changed files with 272 additions and 28 deletions
|
@ -1 +1,2 @@
|
|||
NEXT_PUBLIC_API_URL=API
|
||||
NEXT_PUBLIC_API_URL=API
|
||||
NEXT_PUBLIC_SITE_URL=SITE
|
|
@ -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<LoginData>({
|
||||
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 (
|
||||
<>
|
||||
<div className="flex flex-col justify-start space-y-4">
|
||||
<h2 className="mx-auto text-xl font-bold">Connexion</h2>
|
||||
<UserAuthForm />
|
||||
<form
|
||||
className="flex w-52 flex-col justify-center space-y-4 sm:w-72"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
>
|
||||
<Input
|
||||
label="Nom d'utilisateur"
|
||||
type="text"
|
||||
placeholder="PeerAt"
|
||||
required
|
||||
error={errors.pseudo?.message}
|
||||
{...register('pseudo')}
|
||||
/>
|
||||
<Input
|
||||
label="Mot de passe"
|
||||
type="password"
|
||||
placeholder="MotDePasse123"
|
||||
required
|
||||
error={errors.passwd?.message}
|
||||
{...register('passwd')}
|
||||
/>
|
||||
<Button type="submit" kind="brand">
|
||||
Se connecter
|
||||
</Button>
|
||||
<p className="flex flex-col items-center text-sm text-muted">
|
||||
Vous n'avez pas de compte?
|
||||
<AppLink className="text-brand underline" href={'/sign-up'}>
|
||||
S'inscrire maintenant
|
||||
</AppLink>
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -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<RegisterData>({
|
||||
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 (
|
||||
<>
|
||||
<div className="flex flex-col justify-start space-y-4">
|
||||
<h2 className="mx-auto text-xl font-bold">Créer un compte</h2>
|
||||
<UserAuthForm />
|
||||
<form
|
||||
className="flex w-52 flex-col justify-center space-y-4 sm:w-72"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
>
|
||||
<Input
|
||||
label="Adresse e-mail"
|
||||
type="email"
|
||||
placeholder="peer-at@exemple.be"
|
||||
required
|
||||
error={errors.email?.message}
|
||||
{...register('email')}
|
||||
/>
|
||||
<Input label="Nom" type="lastname" placeholder="Doe" {...register('lastname')} />
|
||||
<Input label="Prénom" type="firstname" placeholder="John" {...register('firstname')} />
|
||||
<Input
|
||||
label="Nom d'utilisateur"
|
||||
type="text"
|
||||
placeholder="PeerAt"
|
||||
required
|
||||
error={errors.pseudo?.message}
|
||||
{...register('pseudo')}
|
||||
/>
|
||||
<Input
|
||||
label="Mot de passe"
|
||||
type="password"
|
||||
placeholder="MotDePasse123"
|
||||
required
|
||||
{...register('passwd', {
|
||||
minLength: {
|
||||
value: 4,
|
||||
message: 'Le mot de passe doit contenir au moins 8 caractères'
|
||||
}
|
||||
})}
|
||||
/>
|
||||
<Button type="submit" kind="brand">
|
||||
S'inscrire
|
||||
</Button>
|
||||
{/* <p className="items-center text-sm text-gray-400">
|
||||
En cliquant sur continuer, vous acceptez les{' '}
|
||||
<AppLink className="text-white underline" href="/privacy-policy" target="_blank">
|
||||
Politique de confidentialité
|
||||
</AppLink>
|
||||
.
|
||||
</p> */}
|
||||
<p className="flex flex-col items-center text-sm text-muted">
|
||||
Vous possédez un compte?
|
||||
<AppLink className="text-brand underline" href={'/sign-in'}>
|
||||
Se connecter
|
||||
</AppLink>
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -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 (
|
||||
<div>
|
||||
<div className="flex h-screen w-full">
|
||||
|
|
|
@ -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[] = [
|
||||
|
|
39
lib/utils.ts
39
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;
|
||||
};
|
||||
|
|
|
@ -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).*)'
|
||||
]
|
||||
};
|
||||
|
|
|
@ -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",
|
||||
|
|
13
pnpm-lock.yaml
generated
13
pnpm-lock.yaml
generated
|
@ -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
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
export default function DefaultTags() {
|
||||
return (
|
||||
<>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link href="/favicon.ico" rel="shortcut icon" />
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
export default function ErrorMessage({ children }: { children: React.ReactNode }) {
|
||||
return <p className="text-xs text-orange-500">{children}</p>;
|
||||
return <p className="text-xs text-warning">{children}</p>;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue