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 = {
|
import AppLink from '@/ui/AppLink';
|
||||||
title: 'Connexion - Peer-at Code'
|
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() {
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-col justify-start space-y-4">
|
<div className="flex flex-col justify-start space-y-4">
|
||||||
<h2 className="mx-auto text-xl font-bold">Connexion</h2>
|
<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>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,15 +1,132 @@
|
||||||
import UserAuthForm from '@/ui/UserAuthForm';
|
'use client';
|
||||||
|
|
||||||
export const metadata = {
|
import AppLink from '@/ui/AppLink';
|
||||||
title: 'Inscription - Peer-at Code'
|
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() {
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-col justify-start space-y-4">
|
<div className="flex flex-col justify-start space-y-4">
|
||||||
<h2 className="mx-auto text-xl font-bold">Créer un compte</h2>
|
<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>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,7 +2,8 @@ import AppLink from '@/ui/AppLink';
|
||||||
import Console from '@/ui/Console';
|
import Console from '@/ui/Console';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
|
|
||||||
export default function Home() {
|
export default function Page() {
|
||||||
|
// TODO: Fix this (image)
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="flex h-screen w-full">
|
<div className="flex h-screen w-full">
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
/**
|
/**
|
||||||
* Un élément de navigation.
|
* A navigation item.
|
||||||
|
*
|
||||||
* @typedef {Object} NavItem
|
* @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 {string} name - The name of the navigation item.
|
||||||
* @property {boolean} [disabled] - Si l'élément de navigation est désactivé.
|
* @property {string} slug - The slug of the navigation item.
|
||||||
|
* @property {boolean} [disabled] - Whether the navigation item is disabled.
|
||||||
*/
|
*/
|
||||||
export type NavItem = {
|
export type NavItem = {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -13,7 +15,8 @@ export type NavItem = {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Les éléments de navigation.
|
* Navigation items.
|
||||||
|
*
|
||||||
* @type {NavItem[]}
|
* @type {NavItem[]}
|
||||||
*/
|
*/
|
||||||
export const navItems: 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';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Permet de créer une classe Tailwind avec clsx et tailwind-merge
|
* Create a Tailwind class with clsx and tailwind-merge to avoid
|
||||||
* pour éviter d'avoir des conflits de classes.
|
* 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[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs));
|
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.
|
* @param string - The string to convert.
|
||||||
* @returns La chaîne de caractères convertie.
|
*
|
||||||
|
* @returns A title case string.
|
||||||
*/
|
*/
|
||||||
export function titleCase(string: string) {
|
export function titleCase(string: string) {
|
||||||
return string
|
return string
|
||||||
|
@ -26,3 +27,27 @@ export function titleCase(string: string) {
|
||||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||||
.join(' ');
|
.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.
|
* 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) {
|
export async function middleware(req: NextRequest) {
|
||||||
const res = NextResponse.next();
|
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;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
matcher: [
|
matcher: [
|
||||||
// On exclut les routes de l'API, les fichiers statiques, les images, les assets, le favicon et le service worker.
|
// 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",
|
"axios": "^1.3.4",
|
||||||
"boring-avatars": "^1.7.0",
|
"boring-avatars": "^1.7.0",
|
||||||
"clsx": "^1.2.1",
|
"clsx": "^1.2.1",
|
||||||
|
"js-cookie": "^3.0.1",
|
||||||
"next": "13.2.1",
|
"next": "13.2.1",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
|
@ -36,6 +37,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/forms": "^0.5.3",
|
"@tailwindcss/forms": "^0.5.3",
|
||||||
|
"@types/js-cookie": "^3.0.3",
|
||||||
"@types/node": "18.11.18",
|
"@types/node": "18.11.18",
|
||||||
"@types/react": "18.0.27",
|
"@types/react": "18.0.27",
|
||||||
"@types/react-dom": "18.0.10",
|
"@types/react-dom": "18.0.10",
|
||||||
|
|
13
pnpm-lock.yaml
generated
13
pnpm-lock.yaml
generated
|
@ -2,6 +2,7 @@ lockfileVersion: 5.4
|
||||||
|
|
||||||
specifiers:
|
specifiers:
|
||||||
'@tailwindcss/forms': ^0.5.3
|
'@tailwindcss/forms': ^0.5.3
|
||||||
|
'@types/js-cookie': ^3.0.3
|
||||||
'@types/node': 18.11.18
|
'@types/node': 18.11.18
|
||||||
'@types/react': 18.0.27
|
'@types/react': 18.0.27
|
||||||
'@types/react-dom': 18.0.10
|
'@types/react-dom': 18.0.10
|
||||||
|
@ -15,6 +16,7 @@ specifiers:
|
||||||
eslint-config-next: 13.2.1
|
eslint-config-next: 13.2.1
|
||||||
eslint-config-prettier: ^8.6.0
|
eslint-config-prettier: ^8.6.0
|
||||||
eslint-plugin-prettier: ^4.2.1
|
eslint-plugin-prettier: ^4.2.1
|
||||||
|
js-cookie: ^3.0.1
|
||||||
next: 13.2.1
|
next: 13.2.1
|
||||||
postcss: ^8.4.21
|
postcss: ^8.4.21
|
||||||
prettier: ^2.8.3
|
prettier: ^2.8.3
|
||||||
|
@ -34,6 +36,7 @@ dependencies:
|
||||||
axios: 1.3.4
|
axios: 1.3.4
|
||||||
boring-avatars: 1.7.0
|
boring-avatars: 1.7.0
|
||||||
clsx: 1.2.1
|
clsx: 1.2.1
|
||||||
|
js-cookie: 3.0.1
|
||||||
next: 13.2.1_biqbaboplfbrettd7655fr4n2y
|
next: 13.2.1_biqbaboplfbrettd7655fr4n2y
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-dom: 18.2.0_react@18.2.0
|
react-dom: 18.2.0_react@18.2.0
|
||||||
|
@ -46,6 +49,7 @@ dependencies:
|
||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@tailwindcss/forms': 0.5.3_tailwindcss@3.2.7
|
'@tailwindcss/forms': 0.5.3_tailwindcss@3.2.7
|
||||||
|
'@types/js-cookie': 3.0.3
|
||||||
'@types/node': 18.11.18
|
'@types/node': 18.11.18
|
||||||
'@types/react': 18.0.27
|
'@types/react': 18.0.27
|
||||||
'@types/react-dom': 18.0.10
|
'@types/react-dom': 18.0.10
|
||||||
|
@ -299,6 +303,10 @@ packages:
|
||||||
'@types/unist': 2.0.6
|
'@types/unist': 2.0.6
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@types/js-cookie/3.0.3:
|
||||||
|
resolution: {integrity: sha512-Xe7IImK09HP1sv2M/aI+48a20VX+TdRJucfq4vfRVy6nWN8PYPOEnlMRSgxJAgYQIXJVL8dZ4/ilAM7dWNaOww==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/json-schema/7.0.11:
|
/@types/json-schema/7.0.11:
|
||||||
resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==}
|
resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -1862,6 +1870,11 @@ packages:
|
||||||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/js-cookie/3.0.1:
|
||||||
|
resolution: {integrity: sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/js-sdsl/4.3.0:
|
/js-sdsl/4.3.0:
|
||||||
resolution: {integrity: sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==}
|
resolution: {integrity: sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
export default function DefaultTags() {
|
export default function DefaultTags() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<link href="/favicon.ico" rel="shortcut icon" />
|
<link href="/favicon.ico" rel="shortcut icon" />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
export default function ErrorMessage({ children }: { children: React.ReactNode }) {
|
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