feat: init default components and feat: implementating login page
This commit is contained in:
parent
130ce79f17
commit
be8e360eb2
12
src/app/(auth)/layout.tsx
Normal file
12
src/app/(auth)/layout.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import Header from "#/components/header";
|
||||||
|
|
||||||
|
export default function AuthLayout({ children }: { children: React.ReactNode }) {
|
||||||
|
return(
|
||||||
|
<div className="flex flex-col min-h-screen">
|
||||||
|
<Header />
|
||||||
|
<div className="flex flex-1 justify-center items-center">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
27
src/app/(auth)/login/page.tsx
Normal file
27
src/app/(auth)/login/page.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import Form from "#/components/form/form"
|
||||||
|
|
||||||
|
export default function LoginPage() {
|
||||||
|
return(
|
||||||
|
<div>
|
||||||
|
<Form
|
||||||
|
className="bg-white p-2 w-[28rem]"
|
||||||
|
fields={[
|
||||||
|
{
|
||||||
|
label: "Email",
|
||||||
|
name: "email",
|
||||||
|
type: "email",
|
||||||
|
placeholder: "Entrer votre email"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Password",
|
||||||
|
name: "password",
|
||||||
|
type: "password",
|
||||||
|
placeholder: "Enter votre mot de passe"
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
submit={undefined}
|
||||||
|
child={<button type="submit">Login</button>}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--background: #ffffff;
|
--background: #ffffff;
|
||||||
--foreground: #171717;
|
--foreground: #171717;
|
||||||
@ -24,3 +25,30 @@ body {
|
|||||||
color: var(--foreground);
|
color: var(--foreground);
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.input-form {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid #d1d5dc;
|
||||||
|
border-radius: 9999px;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline-color: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-label {
|
||||||
|
position: absolute;
|
||||||
|
left: 12px;
|
||||||
|
top: -0.45rem;
|
||||||
|
background-color: white;
|
||||||
|
padding-inline: 4px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-floating {
|
||||||
|
position: absolute;
|
||||||
|
right: 12px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
94
src/components/floatingLabelInput.tsx
Normal file
94
src/components/floatingLabelInput.tsx
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { FloatingLabelInputProps } from '#/types';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export default function FloatingLabelInput({
|
||||||
|
label,
|
||||||
|
placeholder,
|
||||||
|
type,
|
||||||
|
options,
|
||||||
|
button,
|
||||||
|
showPasswordToggle = false,
|
||||||
|
name,
|
||||||
|
defaultValue
|
||||||
|
}: FloatingLabelInputProps) {
|
||||||
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
|
||||||
|
const renderInput = () => {
|
||||||
|
switch(type) {
|
||||||
|
case 'select':
|
||||||
|
return (
|
||||||
|
<div className="relative w-full">
|
||||||
|
<select
|
||||||
|
className="input-form focus:ring-2 focus:ring-blue-500"
|
||||||
|
required
|
||||||
|
name={name}
|
||||||
|
defaultValue={defaultValue}
|
||||||
|
>
|
||||||
|
{options?.map((option, index) => (
|
||||||
|
<option key={index} value={option}>{option}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
{button && <div className="btn-floating">{button}</div>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'password':
|
||||||
|
return (
|
||||||
|
<div className="relative w-full">
|
||||||
|
<input
|
||||||
|
name={name}
|
||||||
|
type={showPassword ? "text" : "password"}
|
||||||
|
placeholder={placeholder}
|
||||||
|
className="input-form focus:ring-2 focus:ring-blue-500 pr-10"
|
||||||
|
defaultValue={defaultValue}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
{/* {showPasswordToggle && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowPassword(!showPassword)}
|
||||||
|
className="btn-floating text-gray-500 hover:text-gray-700 focus:outline-none"
|
||||||
|
>
|
||||||
|
{showPassword ? (
|
||||||
|
<EyeClosedIcon size={20} />
|
||||||
|
) : (
|
||||||
|
<EyeOpenIcon size={20} />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
)} */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<div className="relative w-full">
|
||||||
|
<input
|
||||||
|
type={type}
|
||||||
|
placeholder={placeholder}
|
||||||
|
className="input-form focus:ring-2 focus:ring-blue-500"
|
||||||
|
required
|
||||||
|
name={name}
|
||||||
|
defaultValue={defaultValue}
|
||||||
|
/>
|
||||||
|
{button && <div className="absolute right-0 top-1/2 transform -translate-y-1/2">{button}</div>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative mb-9">
|
||||||
|
<label
|
||||||
|
htmlFor={name}
|
||||||
|
className="input-label text-gray-400 text-sm"
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
|
{renderInput()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
31
src/components/form/form.tsx
Normal file
31
src/components/form/form.tsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import FloatingLabelInput from "../floatingLabelInput"
|
||||||
|
import { FormProps } from "#/types"
|
||||||
|
|
||||||
|
export default function Form({
|
||||||
|
fields,
|
||||||
|
submit,
|
||||||
|
className,
|
||||||
|
child
|
||||||
|
} : FormProps) {
|
||||||
|
return (
|
||||||
|
<form className={className} onSubmit={submit}>
|
||||||
|
{
|
||||||
|
fields.map((item, index) => (
|
||||||
|
<FloatingLabelInput
|
||||||
|
key={index}
|
||||||
|
label={item.label}
|
||||||
|
name={item.name}
|
||||||
|
type={item.type}
|
||||||
|
button={item.button}
|
||||||
|
defaultValue={item.defaultValue}
|
||||||
|
options={item.options}
|
||||||
|
placeholder={item.placeholder}
|
||||||
|
showPasswordToggle={item.showPasswordToggle}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
{child}
|
||||||
|
</form>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
17
src/components/header.tsx
Normal file
17
src/components/header.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
|
export default function Header() {
|
||||||
|
return(
|
||||||
|
<div className="w-full bg-white shadow-md py-4">
|
||||||
|
<div className="container mx-auto text-center flex items-center justify-center">
|
||||||
|
<Image
|
||||||
|
src="/file.svg"
|
||||||
|
alt="Private Docs"
|
||||||
|
width={100}
|
||||||
|
height={100}
|
||||||
|
className="text-red-500 h-auto"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
5
src/components/table/table.tsx
Normal file
5
src/components/table/table.tsx
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export default function Table() {
|
||||||
|
return(
|
||||||
|
<></>
|
||||||
|
)
|
||||||
|
}
|
||||||
19
src/types/index.ts
Normal file
19
src/types/index.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { FormEventHandler, ReactNode } from "react";
|
||||||
|
|
||||||
|
export interface FloatingLabelInputProps {
|
||||||
|
label: string;
|
||||||
|
placeholder?: string;
|
||||||
|
type: 'text' | 'password' | 'select' | 'email' | 'number';
|
||||||
|
options?: string[];
|
||||||
|
button?: React.ReactNode;
|
||||||
|
showPasswordToggle?: boolean;
|
||||||
|
name: string;
|
||||||
|
defaultValue?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FormProps {
|
||||||
|
fields: FloatingLabelInputProps[],
|
||||||
|
submit: FormEventHandler<HTMLFormElement> | undefined,
|
||||||
|
className: string,
|
||||||
|
child: ReactNode
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user