Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
module.exports = {
"env": {
"browser": true,
"es2021": true,
"node": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react/recommended"
],
"overrides": [
{
"env": {
"node": true
},
"files": [
".eslintrc.{js,cjs}"
],
"parserOptions": {
"sourceType": "script"
}
}
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": [
"@typescript-eslint",
"react"
],
"rules": {
"react/react-in-jsx-scope": "off",
}
}
16 changes: 16 additions & 0 deletions app/login/components/Checkbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
interface CheckboxProps {
checked?: boolean;
checkboxStyle?: string;
textStyle?: string;
text?: string;
style?: string;
}

export default function Checkbox(props: CheckboxProps) {
return (
<div className={`${props.style} flex flex-row`}>
<input type="checkbox" defaultChecked={props.checked ?? false} className={`checkbox ${props.checkboxStyle}`} />
<p className={`${props.textStyle} pl-2`}>{props.text}</p>
</div>
);
}
25 changes: 25 additions & 0 deletions app/login/components/PasswordInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use client';
import { useState } from 'react';
import Eye from '../../../public/Eye.svg';
import Image from 'next/image';

export default function PasswordInput() {
const [passwordShown, setPasswordShown] = useState(false);

const togglePasswordVisibility = () => {
setPasswordShown(!passwordShown);
};
return (
<div className='relative flex items-center justify-center border rounded-md'>
<input
className={`'text-xs rounded-md px-4 py-2 bg-inherit flex-1`}
type={passwordShown ? 'text' : 'password'}
name='password'
required
/>
<button onClick={togglePasswordVisibility} className='absolute right-0 mr-3' type='button'>
{passwordShown ? <Image src={Eye} alt={'eye'} /> : <Image src={Eye} alt={'eye'} />}
</button>
</div>
);
}
177 changes: 62 additions & 115 deletions app/login/page.tsx
Original file line number Diff line number Diff line change
@@ -1,118 +1,65 @@
import Link from "next/link";
import { headers, cookies } from "next/headers";
import { createClient } from "@/utils/supabase/server";
import { redirect } from "next/navigation";
import Checkbox from './components/Checkbox';
import { signIn } from '@/utils/supabase/auth';
import Link from 'next/link';
import ETS from '../../public/ETS.svg';
import PasswordInput from './components/PasswordInput';
import Alert from '@/components/Alert';
import { faTriangleExclamation } from '@fortawesome/free-solid-svg-icons';

export default function Login({
searchParams,
}: {
searchParams: { message: string };
}) {
const signIn = async (formData: FormData) => {
"use server";
export default function Login({ searchParams }: { searchParams: { message: string; type: string } }) {
return (
<div className='animate-in relative flex justify-center items-center rounded-2xl w-screen h-screen'>
<div className={`absolute bottom-4 right-4 w-[141px] h-[79px] bg-[url('../public/ETS.svg')] bg-cover`} />
<div className='grid justify-items-center content-center bg-accent rounded-2xl min-h-min min-w-min '>
<h1 className='py-10 text-4xl'>Bienvenue !</h1>
<div className='flex-1 flex flex-col w-full px-8 sm:max-w-md justify-center gap-2'>
<form className='flex-1 flex flex-col w-full justify-center gap-2 text-foreground' action={signIn}>
{searchParams?.message && (
<Alert
customStyle={'flex flex-1 flex-col w-full pb-2 justify-center gap-2'}
text={searchParams.message}
alertType={searchParams.type}
icon={faTriangleExclamation}
/>
)}
<label className='text-md text-primary' htmlFor='email'>
Courriel
</label>
<input className='rounded-md px-4 py-2 bg-inherit border mb-6' name='email' required />
<label className='text-md text-primary' htmlFor='password'>
Mot de passe
</label>
<PasswordInput />
<Checkbox
checked={true}
style='self-end pb-6'
text='Se souvenir de moi'
textStyle='text-primary'
checkboxStyle='checkbox-primary'
/>
<button className='btn btn-ghost bg-secondary rounded-md text-foreground text-base mb-2 hover:bg-secondary/75'>
Se connecter
</button>
</form>

const email = formData.get("email") as string;
const password = formData.get("password") as string;
const cookieStore = cookies();
const supabase = createClient(cookieStore);

const { error } = await supabase.auth.signInWithPassword({
email,
password,
});

if (error) {
return redirect("/login?message=Could not authenticate user");
}

return redirect("/");
};

const signUp = async (formData: FormData) => {
"use server";

const origin = headers().get("origin");
const email = formData.get("email") as string;
const password = formData.get("password") as string;
const cookieStore = cookies();
const supabase = createClient(cookieStore);

const { error } = await supabase.auth.signUp({
email,
password,
options: {
emailRedirectTo: `${origin}/auth/callback`,
},
});

if (error) {
return redirect("/login?message=Could not authenticate user");
}

return redirect("/login?message=Check email to continue sign in process");
};

return (
<div className="flex-1 flex flex-col w-full px-8 sm:max-w-md justify-center gap-2">
<Link
href="/"
className="absolute left-8 top-8 py-2 px-4 rounded-md no-underline text-foreground bg-btn-background hover:bg-btn-background-hover flex items-center group text-sm"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="mr-2 h-4 w-4 transition-transform group-hover:-translate-x-1"
>
<polyline points="15 18 9 12 15 6" />
</svg>{" "}
Back
</Link>

<form
className="animate-in flex-1 flex flex-col w-full justify-center gap-2 text-foreground"
action={signIn}
>
<label className="text-md" htmlFor="email">
Email
</label>
<input
className="rounded-md px-4 py-2 bg-inherit border mb-6"
name="email"
placeholder="[email protected]"
required
/>
<label className="text-md" htmlFor="password">
Password
</label>
<input
className="rounded-md px-4 py-2 bg-inherit border mb-6"
type="password"
name="password"
placeholder="••••••••"
required
/>
<button className="bg-green-700 rounded-md px-4 py-2 text-foreground mb-2">
Sign In
</button>
<button
formAction={signUp}
className="border border-foreground/20 rounded-md px-4 py-2 text-foreground mb-2"
>
Sign Up
</button>
{searchParams?.message && (
<p className="mt-4 p-4 bg-foreground/10 text-foreground text-center">
{searchParams.message}
</p>
)}
</form>
</div>
);
<div className='flex justify-center'>
<p className='text-xs text-primary'>
Vous avez oublié vos informations?
<Link href={'/forgotPassword'} className='text-xs pl-1 underline text-secondary'>
Réinitialisez votre mot de passe
</Link>
</p>
</div>
<div className='flex justify-center'>
<p className='text-xs pb-10 text-primary'>
Vous n&apos;avez pas de compte?
<Link href={'/signUp'} className='text-xs pl-1 underline text-secondary'>
Inscrivez-vous
</Link>
</p>
</div>
</div>
</div>
</div>
);
}
52 changes: 1 addition & 51 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,57 +1,7 @@
import DeployButton from "../components/DeployButton";
import AuthButton from "../components/AuthButton";
import { createClient } from "@/utils/supabase/server";
import ConnectSupabaseSteps from "@/components/ConnectSupabaseSteps";
import SignUpUserSteps from "@/components/SignUpUserSteps";
import Header from "@/components/Header";
import { cookies } from "next/headers";

export default async function Index() {
const cookieStore = cookies();

const canInitSupabaseClient = () => {
// This function is just for the interactive tutorial.
// Feel free to remove it once you have Supabase connected.
try {
createClient(cookieStore);
return true;
} catch (e) {
return false;
}
};

const isSupabaseConnected = canInitSupabaseClient();

return (
<div className="flex-1 w-full flex flex-col gap-20 items-center">
<nav className="w-full flex justify-center border-b border-b-foreground/10 h-16">
<div className="w-full max-w-4xl flex justify-between items-center p-3 text-sm">
<DeployButton />
{isSupabaseConnected && <AuthButton />}
</div>
</nav>

<div className="animate-in flex-1 flex flex-col gap-20 opacity-0 max-w-4xl px-3">
<Header />
<main className="flex-1 flex flex-col gap-6">
<h2 className="font-bold text-4xl mb-4">Next steps</h2>
{isSupabaseConnected ? <SignUpUserSteps /> : <ConnectSupabaseSteps />}
</main>
</div>

<footer className="w-full border-t border-t-foreground/10 p-8 flex justify-center text-center text-xs">
<p>
Powered by{" "}
<a
href="https://supabase.com/?utm_source=create-next-app&utm_medium=template&utm_term=nextjs"
target="_blank"
className="font-bold hover:underline"
rel="noreferrer"
>
Supabase
</a>
</p>
</footer>
</div>
<AuthButton />
);
}
24 changes: 24 additions & 0 deletions components/Alert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";

interface AlertProps {
text: string;
alertType: string;
customStyle?: string;
textStyle?: string;
icon: IconDefinition;
}

export default function Alert(props: AlertProps) {
const alertType = props.alertType ? `alert-${props.alertType}` : 'alert-error';
return (
<div className={props.customStyle}>
<div role='alert' className={`alert ${alertType}`}>
<FontAwesomeIcon icon={props.icon} className='w-5' />
<p className={props.textStyle}>
{props.text}
</p>
</div>
</div>
);
}
Loading