Compare commits
13 Commits
9da068ae95
...
98d68a535b
| Author | SHA1 | Date | |
|---|---|---|---|
| 98d68a535b | |||
| 130473d3e4 | |||
| 42b0e8b3a0 | |||
| 99f3be65bc | |||
| cc6341adb2 | |||
| e967d9d8ac | |||
| 44c19d2813 | |||
| 3d944825ee | |||
| be8e360eb2 | |||
| ae7f66aa78 | |||
| b7e3b56739 | |||
| 09680eac63 | |||
| 130ce79f17 |
12
auth.d.ts
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
import { DefaultSession } from "next-auth";
|
||||
|
||||
declare module "next-auth" {
|
||||
export interface User extends Partial<DefaultSession<User>> {
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
}
|
||||
|
||||
export interface Session {
|
||||
user: User;
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,13 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
webpack(config) {
|
||||
config.module.rules.push({
|
||||
test: /\.svg$/,
|
||||
use: ['@svgr/webpack'],
|
||||
});
|
||||
return config;
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
@ -10,8 +10,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@tanstack/react-query": "^5.69.0",
|
||||
"@tanstack/react-table": "^8.21.2",
|
||||
"axios": "^1.8.4",
|
||||
"clsx": "^2.1.1",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"next": "15.2.3",
|
||||
"next-auth": "^4.24.11",
|
||||
"nextjs-toploader": "^3.8.15",
|
||||
"nuqs": "^2.4.1",
|
||||
"react": "^19.0.0",
|
||||
@ -22,6 +26,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
|
||||
2109
pnpm-lock.yaml
generated
9
public/favicon.svg
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
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 bg-blue-100">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
33
src/app/(auth)/login/page.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
"use client";
|
||||
|
||||
import Form from "#/components/form/form"
|
||||
import { loginSchema } from "#/schema/loginSchema"
|
||||
|
||||
export default function LoginPage() {
|
||||
return(
|
||||
<div>
|
||||
<Form
|
||||
title="Connexion"
|
||||
className="bg-white p-10 shadow-2xl w-3/4 lg:w-lg"
|
||||
fields={[
|
||||
{
|
||||
label: "Email",
|
||||
name: "email",
|
||||
type: "email",
|
||||
placeholder: "Entrer votre email"
|
||||
},
|
||||
{
|
||||
label: "Password",
|
||||
name: "password",
|
||||
type: "password",
|
||||
placeholder: "Enter votre mot de passe",
|
||||
showPasswordToggle: true
|
||||
}
|
||||
]}
|
||||
submit={undefined}
|
||||
schema={loginSchema}
|
||||
child={<button type="submit" className="btn-auth">Connexion</button>}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
129
src/app/admin/home/page.tsx
Normal file
@ -0,0 +1,129 @@
|
||||
"use client"
|
||||
|
||||
import Table from "#/components/table/table"
|
||||
import { ColumnDef } from "@tanstack/react-table"
|
||||
|
||||
|
||||
export default function HomePage () {
|
||||
type Payment = {
|
||||
id: string
|
||||
amount: number
|
||||
status: "pending" | "processing" | "success" | "failed"
|
||||
email: string
|
||||
}
|
||||
|
||||
|
||||
const data: Payment[] = [
|
||||
{
|
||||
id: "728ed52f",
|
||||
amount: 100,
|
||||
status: "pending",
|
||||
email: "m@example.com",
|
||||
},
|
||||
{
|
||||
id: "728ed521",
|
||||
amount: 200,
|
||||
status: "pending",
|
||||
email: "j@example.com",
|
||||
},
|
||||
{
|
||||
id: "728ed528",
|
||||
amount: 300,
|
||||
status: "processing",
|
||||
email: "f@example.com",
|
||||
},
|
||||
{
|
||||
id: "728ed52g",
|
||||
amount: 600,
|
||||
status: "success",
|
||||
email: "h@example.com",
|
||||
},
|
||||
{
|
||||
id: "728ed520",
|
||||
amount: 50,
|
||||
status: "failed",
|
||||
email: "k@example.com",
|
||||
},
|
||||
{
|
||||
id: "728ed529",
|
||||
amount: 200,
|
||||
status: "pending",
|
||||
email: "l@example.com",
|
||||
},
|
||||
{
|
||||
id: "728ed526",
|
||||
amount: 150,
|
||||
status: "processing",
|
||||
email: "d@example.com",
|
||||
},
|
||||
{
|
||||
id: "728ed523",
|
||||
amount: 100,
|
||||
status: "success",
|
||||
email: "o@example.com",
|
||||
},
|
||||
{
|
||||
id: "728ed52y",
|
||||
amount: 100,
|
||||
status: "failed",
|
||||
email: "v@example.com",
|
||||
},
|
||||
// ...
|
||||
]
|
||||
|
||||
|
||||
|
||||
const columns: ColumnDef<Payment>[] = [
|
||||
{
|
||||
id: "select",
|
||||
header: ({ table }) => (
|
||||
<input checked={
|
||||
table.getIsAllPageRowsSelected() ||
|
||||
(table.getIsSomePageRowsSelected() && undefined)
|
||||
} onChange={(value) => table.toggleAllPageRowsSelected(!!value)} type="checkbox" name="" id="" />
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<input checked={row.getIsSelected()} onChange={(value) => row.toggleSelected(!!value)} type="checkbox" name="" id="" />
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "status",
|
||||
header: "Status",
|
||||
},
|
||||
{
|
||||
accessorKey: "email",
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<p>Email</p>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "amount",
|
||||
header: () => <div className="">Amount</div>,
|
||||
cell: ({ row }) => {
|
||||
const amount = parseFloat(row.getValue("amount"))
|
||||
const formatted = new Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
}).format(amount)
|
||||
|
||||
return <div className="font-medium">{formatted}</div>
|
||||
},
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
]
|
||||
|
||||
return(
|
||||
<>
|
||||
<Table
|
||||
columns={columns}
|
||||
data={data}
|
||||
pageSize={5}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
43
src/app/admin/layout.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
import { ReactNode } from "react";
|
||||
import "../../assets/css/admin.css"
|
||||
import Sidebar from "../components/sidebar";
|
||||
import Header from "../components/adminHeader";
|
||||
|
||||
export default function Dashboard({ children }: { children: ReactNode }) {
|
||||
|
||||
return (
|
||||
|
||||
<div className="r-flex-between">
|
||||
<Sidebar />
|
||||
<main className="flex-grow-1 min-w-0 pt-md-1 pt-sm-5">
|
||||
<Header/>
|
||||
<div className="p-4">
|
||||
{children}
|
||||
</div>
|
||||
</main>
|
||||
</div >
|
||||
)
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
<div className="sidebar r-m-0 d-flex flex-column pt-[16px] max-w-[90px] h-[100vh] relative ">
|
||||
<div className="logo r-flex-center pt-[13px] px-[20px] ">
|
||||
<icons.Logo aria-label="Logo" className="scale-95" />
|
||||
</div>
|
||||
<div className="nav-menu r-column-center h-max pt-[160px] r-gap-40 ">
|
||||
<Link href="#" className="nav-item r-flex-center ">
|
||||
<icons.HomeIcon aria-label="Home" className="nav-home scale-100" />
|
||||
</Link>
|
||||
<Link href="#" className="nav-item border-none r-flex-center ">
|
||||
<icons.CompaniesIcon aria-label="Companies" className="scale-100 " width={24} height={24} />
|
||||
</Link>
|
||||
<Link href="#" className="nav-item r-flex-center ">
|
||||
<icons.UserGroup aria-label="Admins" className="scale-100" width={24} height={24} />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="logout absolute bottom-[60px] left-[22px]">
|
||||
<icons.Logout aria-label="Logout" />
|
||||
</div>
|
||||
*/
|
||||
6
src/app/admin/page.tsx
Normal file
@ -0,0 +1,6 @@
|
||||
export default function Admin () {
|
||||
return(
|
||||
<>
|
||||
</>
|
||||
)
|
||||
}
|
||||
68
src/app/api/auth/[...nextauth]/route.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import NextAuth, { User } from "next-auth";
|
||||
import Credentials from "next-auth/providers/credentials";
|
||||
import axios from "axios";
|
||||
import { jwtDecode } from "jwt-decode";
|
||||
|
||||
const handler = NextAuth({
|
||||
providers: [
|
||||
Credentials({
|
||||
credentials: {
|
||||
email: {},
|
||||
password: {},
|
||||
},
|
||||
async authorize(credentials) {
|
||||
try {
|
||||
const response = await axios.post(
|
||||
'private-docs-api.intside.co/users/login/',
|
||||
{
|
||||
email: credentials?.email,
|
||||
password: credentials?.password,
|
||||
}
|
||||
)
|
||||
|
||||
const { access_token, refresh_token } = response.data;
|
||||
const { id } = jwtDecode(access_token) as { id: string };
|
||||
|
||||
return {
|
||||
id: id,
|
||||
email: credentials?.email,
|
||||
access_token: access_token,
|
||||
refresh_token: refresh_token
|
||||
} as User;
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error)) {
|
||||
if (error.response?.status === 401) {
|
||||
throw new Error("Email ou mot de passe incorrect");
|
||||
}
|
||||
throw new Error(error.response?.data?.message || error.message);
|
||||
}
|
||||
throw new Error("Une erreur est survenue");
|
||||
}
|
||||
},
|
||||
})
|
||||
],
|
||||
session: {
|
||||
strategy: "jwt",
|
||||
},
|
||||
callbacks: {
|
||||
async jwt({ token, user }) {
|
||||
if (user) {
|
||||
token.access_token = user.access_token;
|
||||
token.refresh_token = user.refresh_token;
|
||||
}
|
||||
return token;
|
||||
},
|
||||
async session({ session, token }) {
|
||||
return {
|
||||
...session,
|
||||
user: {
|
||||
...session.user,
|
||||
...token,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
secret: process.env.AUTH_SECRET ?? "",
|
||||
});
|
||||
|
||||
export { handler as GET, handler as POST };
|
||||
18
src/app/components/adminHeader.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import Image from "next/image";
|
||||
import { icons } from "#/assets/icons"
|
||||
|
||||
export default function AdminHeader() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<nav className="header r-flex-between px-[44px] py-[20px] ">
|
||||
<p className="name text-[26px]">Bienvenue, <span>Ken B.</span> </p>
|
||||
<div className="r-flex-between justify-center items-center r-gap-12">
|
||||
<Image src={icons.notificationsIcon} alt="Notifications" />
|
||||
<Image src={icons.profilePicture} alt="ProfilePicture" />
|
||||
<Image src={icons.arrowUp} alt="arrowUp" />
|
||||
</div>
|
||||
</nav>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
Before Width: | Height: | Size: 582 KiB |
21
src/app/components/navItem.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
|
||||
interface ItemProps {
|
||||
link: string;
|
||||
iconSrc: string;
|
||||
label: string;
|
||||
isActive: boolean;
|
||||
isNavHome?: boolean;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export default function NavItem({ link, iconSrc, label, isActive, isNavHome, onClick }: ItemProps) {
|
||||
return (
|
||||
<>
|
||||
<Link href={link} onClick={onClick} className={`nav-item r-flex-center ${isActive ? "active" : ""}`} >
|
||||
<Image src={iconSrc} alt={label} className={`scale-100 ${isNavHome ? "nav-home" : ""}`} />
|
||||
</Link>
|
||||
</>
|
||||
);
|
||||
}
|
||||
38
src/app/components/sidebar.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
"use client"
|
||||
import Image from "next/image";
|
||||
import { icons } from "#/assets/icons"
|
||||
import NavItem from "./navItem";
|
||||
import { useState } from "react";
|
||||
|
||||
|
||||
export default function Sidebar() {
|
||||
|
||||
const [activeItem, setActiveItem] = useState("home")
|
||||
|
||||
const handleNavMenu = (item:string) => {
|
||||
setActiveItem(item)
|
||||
console.log("active: ", item);
|
||||
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="sidebar r-m-0 d-flex flex-column pt-[25px] max-w-[90px] h-[100vh] relative ">
|
||||
<div className="logo r-flex-center px-[20px] ">
|
||||
<Image src={icons.logo} alt="Logo" className="scale-95" />
|
||||
</div>
|
||||
<div className="nav-menu r-column-center h-max pt-[160px] r-gap-40 ">
|
||||
<NavItem link="#" iconSrc={icons.homeIcon} label="Home" isNavHome={true} isActive={activeItem === "home"} onClick={() => handleNavMenu("home") } />
|
||||
<NavItem link="#" iconSrc={icons.companiesIcon} label="Organizations" isActive={activeItem === "organizations"} onClick={() => handleNavMenu("organizations") } />
|
||||
<NavItem link="#" iconSrc={icons.userGroup} label="Admins" isActive={activeItem === "admins"} onClick={() => handleNavMenu("admins") } />
|
||||
</div>
|
||||
<div className="logout absolute bottom-[40px] left-[28px]">
|
||||
<button type="button" className="cursor-pointer">
|
||||
<Image src={icons.logout} alt="Logout" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@ -1,26 +1,67 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
:root {
|
||||
--foreground: #04060F;
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
--primary: #246BFD;
|
||||
--cinder: #E7E5E4;
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-geist-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
[ data-theme="dark"] {
|
||||
--foreground: #04060F;
|
||||
--background: #ffffff;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
--foreground: #04060F;
|
||||
--background: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-family: Inter, sans-serif;
|
||||
}
|
||||
|
||||
.input-form {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: 1px solid #d1d5dc;
|
||||
border-radius: 9999px;
|
||||
color: black;
|
||||
|
||||
&: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%);
|
||||
}
|
||||
|
||||
.btn-auth {
|
||||
border-radius: 9999px;
|
||||
background-color: #246BFD;
|
||||
color: white;
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: rgb(22, 77, 185);
|
||||
}
|
||||
}
|
||||
@ -1,21 +1,14 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import { Inter } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import NextTopLoader from "nextjs-toploader";
|
||||
import "../assets/css/ruben-ui.css"
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Private Docs",
|
||||
description: "Un application de gestion de documents",
|
||||
description: "L'appli de gestion de documents par excellence !",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
@ -25,10 +18,12 @@ export default function RootLayout({
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
<NextTopLoader />
|
||||
<head>
|
||||
<link rel="icon" href="/favicon.svg" />
|
||||
<link rel="favicon.svg" href="/favicon.svg" />
|
||||
</head>
|
||||
<body className={inter.className}>
|
||||
<NextTopLoader color="#246BFD" shadow="0" />
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -1,103 +1,10 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
|
||||
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={180}
|
||||
height={38}
|
||||
priority
|
||||
/>
|
||||
<ol className="list-inside list-decimal text-sm/6 text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
|
||||
<li className="mb-2 tracking-[-.01em]">
|
||||
Get started by editing{" "}
|
||||
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-[family-name:var(--font-geist-mono)] font-semibold">
|
||||
src/app/page.tsx
|
||||
</code>
|
||||
.
|
||||
</li>
|
||||
<li className="tracking-[-.01em]">
|
||||
Save and see your changes instantly.
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<div className="flex gap-4 items-center flex-col sm:flex-row">
|
||||
<a
|
||||
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:w-auto"
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
Deploy now
|
||||
</a>
|
||||
<a
|
||||
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-full sm:w-auto md:w-[158px]"
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Read our docs
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
<footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center">
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/file.svg"
|
||||
alt="File icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Learn
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/window.svg"
|
||||
alt="Window icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Examples
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/globe.svg"
|
||||
alt="Globe icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Go to nextjs.org →
|
||||
</a>
|
||||
</footer>
|
||||
<Link href="/admin">See the admin page</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
28
src/assets/css/admin.css
Normal file
@ -0,0 +1,28 @@
|
||||
.sidebar{
|
||||
border-right: 1px solid var(--cinder);
|
||||
}
|
||||
|
||||
.nav-item .nav-home{
|
||||
margin-bottom: -10px;
|
||||
}
|
||||
|
||||
.nav-item{
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
}
|
||||
|
||||
.nav-item.active{
|
||||
border-right: 2px solid var(--primary);
|
||||
}
|
||||
|
||||
.nav-item svg{
|
||||
color: var(--primary)!important;
|
||||
background-color: var(--primary)!important;
|
||||
fill: var(--primary)!important;
|
||||
stroke: var(--primary)!important
|
||||
}
|
||||
|
||||
.nav-home{
|
||||
margin-top: -11px;
|
||||
margin-bottom: -11px;
|
||||
}
|
||||
160
src/assets/css/ruben-ui.css
Normal file
@ -0,0 +1,160 @@
|
||||
/* Extra Small (XS) */
|
||||
|
||||
.r-p-0 {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.r-m-0 {
|
||||
margin: 0;
|
||||
}
|
||||
.r-p-m-0{
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.r-p-0auto {
|
||||
padding: 0 auto;
|
||||
}
|
||||
|
||||
.r-m-0auto {
|
||||
margin: 0 auto;
|
||||
}
|
||||
.r-flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.r-flex-center {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.r-flex-between {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.r-flex-around {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.r-flex-evenly {
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
.r-flex-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.r-column-center,.r-column-md-row, .r-column-lg-row, .r-column-xl-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.r-row-center, .r-row-md-column, .r-row-lg-column, .r-row-xl-column {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.r-gap-2{
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.r-gap-4{
|
||||
gap: 4px;
|
||||
}
|
||||
.r-gap-6{
|
||||
gap: 6px;
|
||||
}
|
||||
.r-gap-8{
|
||||
gap: 8px;
|
||||
}
|
||||
.r-gap-10{
|
||||
gap: 10px;
|
||||
}
|
||||
.r-gap-12{
|
||||
gap: 12px;
|
||||
}
|
||||
.r-gap-14{
|
||||
gap: 14px;
|
||||
}
|
||||
.r-gap-16{
|
||||
gap: 16px;
|
||||
}
|
||||
.r-gap-18{
|
||||
gap: 18px;
|
||||
}
|
||||
.r-gap-20{
|
||||
gap: 20px;
|
||||
}
|
||||
.r-gap-22{
|
||||
gap: 22px;
|
||||
}
|
||||
.r-gap-24{
|
||||
gap: 24px;
|
||||
}
|
||||
.r-gap-32{
|
||||
gap: 32px;
|
||||
}
|
||||
.r-gap-36{
|
||||
gap: 36px;
|
||||
}
|
||||
.r-gap-40{
|
||||
gap: 40px;
|
||||
}
|
||||
.r-gap-42{
|
||||
gap: 42px;
|
||||
}
|
||||
.r-gap-50{
|
||||
gap: 50px;
|
||||
}
|
||||
.r-gap-60{
|
||||
gap: 60px;
|
||||
}
|
||||
.r-gap-70{
|
||||
gap: 70px;
|
||||
}
|
||||
/* Small (SM) */
|
||||
@media (min-width: 640px) {
|
||||
/* Styles for small devices and up */
|
||||
}
|
||||
|
||||
/* Medium (MD) */
|
||||
@media (min-width: 768px) {
|
||||
.r-column-md-row {
|
||||
flex-direction: row;
|
||||
}
|
||||
.r-row-md-column{
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
/* Large (LG) */
|
||||
@media (min-width: 1024px) {
|
||||
.r-column-lg-row {
|
||||
flex-direction: row;
|
||||
}
|
||||
.r-row-lg-column{
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
/* Extra Large (XL) */
|
||||
@media (min-width: 1280px) {
|
||||
.r-column-xl-row {
|
||||
flex-direction: row;
|
||||
}
|
||||
.r-row-xl-column{
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
/* 2XL (XXL) */
|
||||
@media (min-width: 1536px) {
|
||||
/* Styles for very large screens */
|
||||
}
|
||||
|
Before Width: | Height: | Size: 927 B After Width: | Height: | Size: 927 B |
|
Before Width: | Height: | Size: 600 B After Width: | Height: | Size: 600 B |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 592 B After Width: | Height: | Size: 592 B |
|
Before Width: | Height: | Size: 286 B After Width: | Height: | Size: 286 B |
|
Before Width: | Height: | Size: 535 B After Width: | Height: | Size: 535 B |
@ -1,4 +1,4 @@
|
||||
<svg width="48" height="49" viewBox="0 0 48 49" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg viewBox="0 0 48 49" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M21.02 15.34L15.63 19.54C14.73 20.24 14 21.73 14 22.86V30.27C14 32.59 15.89 34.49 18.21 34.49H29.79C32.11 34.49 34 32.59 34 30.28V23C34 21.79 33.19 20.24 32.2 19.55L26.02 15.22C24.62 14.24 22.37 14.29 21.02 15.34Z" stroke="#246BFD" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M24 30.49V27.49" stroke="#246BFD" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 523 B After Width: | Height: | Size: 500 B |
|
Before Width: | Height: | Size: 191 B After Width: | Height: | Size: 191 B |
|
Before Width: | Height: | Size: 191 B After Width: | Height: | Size: 191 B |
|
Before Width: | Height: | Size: 207 B After Width: | Height: | Size: 207 B |
|
Before Width: | Height: | Size: 331 B After Width: | Height: | Size: 331 B |
|
Before Width: | Height: | Size: 244 B After Width: | Height: | Size: 244 B |
|
Before Width: | Height: | Size: 307 B After Width: | Height: | Size: 307 B |
5
src/assets/icons/archives.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19.5 10.72V19.5C19.5 21.5 19 22.5 16.5 22.5H7.5C5 22.5 4.5 21.5 4.5 19.5V10.72" stroke="#9FA8BC" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5 2.5H19C21 2.5 22 3.5 22 5.5V7.5C22 9.5 21 10.5 19 10.5H5C3 10.5 2 9.5 2 7.5V5.5C2 3.5 3 2.5 5 2.5Z" stroke="#9FA8BC" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10.1799 14.5H13.8199" stroke="#9FA8BC" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 592 B |
3
src/assets/icons/arrowLeft.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="7" height="12" viewBox="0 0 7 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 11L1 6L6 1" stroke="#9FA8BC" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 191 B |
3
src/assets/icons/arrowRight.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="7" height="12" viewBox="0 0 7 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1 11L6 6L1 1" stroke="#9FA8BC" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 191 B |
9
src/assets/icons/buildings.svg
Normal file
@ -0,0 +1,9 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13 22H5C3 22 2 21 2 19V11C2 9 3 8 5 8H10V19C10 21 11 22 13 22Z" stroke="#9FA8BC" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10.11 4C10.03 4.3 10 4.63 10 5V8H5V6C5 4.9 5.9 4 7 4H10.11Z" stroke="#9FA8BC" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M14 8V13" stroke="#9FA8BC" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M18 8V13" stroke="#9FA8BC" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M17 17H15C14.45 17 14 17.45 14 18V22H18V18C18 17.45 17.55 17 17 17Z" stroke="#9FA8BC" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6 13V17" stroke="#9FA8BC" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10 19V5C10 3 11 2 13 2H19C21 2 22 3 22 5V19C22 21 21 22 19 22H13C11 22 10 21 10 19Z" stroke="#9FA8BC" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
3
src/assets/icons/checked.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5 3C4.46957 3 3.96086 3.21071 3.58579 3.58579C3.21071 3.96086 3 4.46957 3 5V19C3 19.5304 3.21071 20.0391 3.58579 20.4142C3.96086 20.7893 4.46957 21 5 21H19C19.5304 21 20.0391 20.7893 20.4142 20.4142C20.7893 20.0391 21 19.5304 21 19V5C21 4.46957 20.7893 3.96086 20.4142 3.58579C20.0391 3.21071 19.5304 3 19 3H5ZM16.95 9.796C17.1376 9.60849 17.2431 9.35412 17.2432 9.08885C17.2433 8.82358 17.138 8.56914 16.9505 8.3815C16.763 8.19386 16.5086 8.08839 16.2434 8.0883C15.9781 8.0882 15.7236 8.19349 15.536 8.381L10.586 13.331L8.465 11.21C8.37216 11.1171 8.26192 11.0434 8.14059 10.9931C8.01926 10.9428 7.8892 10.9168 7.75785 10.9168C7.49258 10.9167 7.23814 11.022 7.0505 11.2095C6.86286 11.397 6.75739 11.6514 6.7573 11.9166C6.7572 12.1819 6.86249 12.4364 7.05 12.624L9.808 15.382C9.91015 15.4842 10.0314 15.5653 10.1649 15.6206C10.2984 15.6759 10.4415 15.7044 10.586 15.7044C10.7305 15.7044 10.8736 15.6759 11.0071 15.6206C11.1406 15.5653 11.2618 15.4842 11.364 15.382L16.95 9.796Z" fill="#246BFD"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
3
src/assets/icons/cross.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.0091 12.1536L15.3973 17.5357C15.6833 17.8213 16.0711 17.9818 16.4754 17.9818C16.8797 17.9818 17.2675 17.8213 17.5535 17.5357C17.8394 17.2501 18 16.8628 18 16.4589C18 16.055 17.8394 15.6677 17.5535 15.3821L12.1632 10L17.5524 4.61791C17.6939 4.4765 17.8062 4.30863 17.8827 4.12389C17.9593 3.93916 17.9987 3.74117 17.9986 3.54123C17.9986 3.34129 17.9591 3.14332 17.8825 2.95862C17.8058 2.77392 17.6935 2.60611 17.5519 2.46476C17.4104 2.32342 17.2423 2.21131 17.0574 2.13484C16.8724 2.05837 16.6742 2.01904 16.474 2.01909C16.2739 2.01914 16.0757 2.05856 15.8908 2.13512C15.7058 2.21167 15.5378 2.32386 15.3963 2.46527L10.0091 7.84736L4.62088 2.46527C4.48035 2.31981 4.31223 2.20375 4.12632 2.12388C3.94041 2.04401 3.74044 2.00192 3.53807 2.00006C3.3357 1.99821 3.13499 2.03664 2.94764 2.1131C2.7603 2.18955 2.59008 2.30251 2.44691 2.44539C2.30374 2.58826 2.19049 2.75818 2.11377 2.94524C2.03705 3.13229 1.99839 3.33274 2.00005 3.53488C2.00171 3.73702 2.04366 3.9368 2.12345 4.12258C2.20324 4.30835 2.31927 4.47639 2.46477 4.61689L7.85504 10L2.46579 15.3831C2.32028 15.5236 2.20426 15.6917 2.12447 15.8774C2.04468 16.0632 2.00273 16.263 2.00107 16.4651C1.9994 16.6673 2.03806 16.8677 2.11478 17.0548C2.19151 17.2418 2.30476 17.4117 2.44793 17.5546C2.5911 17.6975 2.76132 17.8104 2.94866 17.8869C3.136 17.9634 3.33671 18.0018 3.53908 17.9999C3.74145 17.9981 3.94142 17.956 4.12733 17.8761C4.31324 17.7963 4.48137 17.6802 4.62189 17.5347L10.0091 12.1536Z" fill="#9FA8BC"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 750 B After Width: | Height: | Size: 750 B |
|
Before Width: | Height: | Size: 855 B After Width: | Height: | Size: 855 B |
|
Before Width: | Height: | Size: 549 B After Width: | Height: | Size: 549 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 699 B After Width: | Height: | Size: 699 B |
|
Before Width: | Height: | Size: 419 B After Width: | Height: | Size: 419 B |
170
src/assets/icons/index.ts
Normal file
@ -0,0 +1,170 @@
|
||||
import addIcon from "./add.svg"
|
||||
import calendarIcon from "./Calendar Mark.svg"
|
||||
import docummentTextIcon from "./document-text.svg"
|
||||
import folderIcon from "./Document.svg"
|
||||
import editIcon from "./edit-2.svg"
|
||||
import gridIcon from "./element-3-white.svg"
|
||||
import gridBlueIcon from "./element-3.svg"
|
||||
import profilePicture from "./Ellipse 2.svg"
|
||||
import eyeIcon from "./eye.svg"
|
||||
import eyeSlashIcon from "./eye-slash.svg"
|
||||
import checkboxchedIcon from "./checked.svg"
|
||||
import crossIcon from "./cross.svg"
|
||||
import addBlueIcon from "./icon-add.svg"
|
||||
import archivesIcon from "./archives.svg"
|
||||
import notificationsIcon from "./notifications.svg"
|
||||
import timerIcon from "./Line Duotone.svg"
|
||||
import logo from "./logo.svg"
|
||||
import logoutRed from "./logout-red.svg"
|
||||
import logout from "./logout.svg"
|
||||
import maximizeIcon from "./maximize-3.svg"
|
||||
import menuIcon from "./Menu Dots.svg"
|
||||
import messagesIcon from "./message.svg"
|
||||
import homeIcon from "./NavItem.svg"
|
||||
import companiesIcon from "./buildings.svg"
|
||||
import arrowLeft from "./arrowLeft.svg"
|
||||
import arrowRight from "./arrowLeft.svg"
|
||||
import filesIcon from "./ph_files.svg"
|
||||
import pdfIcon from "./prime_file-pdf.svg"
|
||||
import wordIcon from "./prime_file-word.svg"
|
||||
import userIcon from "./profile.svg"
|
||||
import userGroup from "./profile-2user.svg"
|
||||
import userGroupBlue from "./profile-2user-blue.svg"
|
||||
import rectanagle from "./Rectangle.svg"
|
||||
import searchIcon from "./Search.svg"
|
||||
import settingsIcon from "./setting-2.svg"
|
||||
import filterIcon from "./setting-3.svg"
|
||||
import shareIcon from "./share.svg"
|
||||
import starIcon from "./star.svg"
|
||||
import arrowUp from "./Vector.svg"
|
||||
|
||||
|
||||
export const icons = {
|
||||
addIcon,
|
||||
calendarIcon,
|
||||
docummentTextIcon,
|
||||
folderIcon,
|
||||
editIcon,
|
||||
gridIcon,
|
||||
gridBlueIcon,
|
||||
profilePicture,
|
||||
eyeIcon,
|
||||
eyeSlashIcon,
|
||||
checkboxchedIcon,
|
||||
crossIcon,
|
||||
addBlueIcon,
|
||||
archivesIcon,
|
||||
notificationsIcon,
|
||||
timerIcon,
|
||||
logo,
|
||||
companiesIcon,
|
||||
logout,
|
||||
logoutRed,
|
||||
maximizeIcon,
|
||||
menuIcon,
|
||||
messagesIcon,
|
||||
homeIcon,
|
||||
arrowLeft,
|
||||
arrowRight,
|
||||
filesIcon,
|
||||
pdfIcon,
|
||||
wordIcon,
|
||||
userIcon,
|
||||
userGroup,
|
||||
userGroupBlue,
|
||||
rectanagle,
|
||||
searchIcon,
|
||||
settingsIcon,
|
||||
filterIcon,
|
||||
shareIcon,
|
||||
starIcon,
|
||||
arrowUp
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
|
||||
import AddIcon from "./add.svg";
|
||||
import CalendarIcon from "./Calendar Mark.svg";
|
||||
import DocummentTextIcon from "./document-text.svg";
|
||||
import FolderIcon from "./Document.svg";
|
||||
import EditIcon from "./edit-2.svg";
|
||||
import GridIcon from "./element-3-white.svg";
|
||||
import GridBlueIcon from "./element-3.svg";
|
||||
import ProfilePicture from "./Ellipse 2.svg";
|
||||
import EyeIcon from "./eye.svg";
|
||||
import EyeSlashIcon from "./eye-slash.svg";
|
||||
import CheckboxchedIcon from "./checked.svg";
|
||||
import CrossIcon from "./cross.svg";
|
||||
import AddBlueIcon from "./icon-add.svg";
|
||||
import ArchivesIcon from "./archives.svg";
|
||||
import NotificationsIcon from "./notifications.svg";
|
||||
import TimerIcon from "./Line Duotone.svg";
|
||||
import Logo from "./logo.svg";
|
||||
import LogoutRed from "./logout-red.svg";
|
||||
import Logout from "./logout.svg";
|
||||
import MaximizeIcon from "./maximize-3.svg";
|
||||
import MenuIcon from "./Menu Dots.svg";
|
||||
import MessagesIcon from "./message.svg";
|
||||
import HomeIcon from "./NavItem.svg";
|
||||
import CompaniesIcon from "./buildings.svg";
|
||||
import ArrowLeft from "./arrowLeft.svg";
|
||||
import ArrowRight from "./arrowLeft.svg";
|
||||
import FilesIcon from "./ph_files.svg";
|
||||
import PdfIcon from "./prime_file-pdf.svg";
|
||||
import WordIcon from "./prime_file-word.svg";
|
||||
import UserIcon from "./profile.svg";
|
||||
import UserGroup from "./profile-2user.svg";
|
||||
import UserGroupBlue from "./profile-2user-blue.svg";
|
||||
import Rectanagle from "./Rectangle.svg";
|
||||
import SearchIcon from "./Search.svg";
|
||||
import SettingsIcon from "./setting-2.svg";
|
||||
import FilterIcon from "./setting-3.svg";
|
||||
import ShareIcon from "./share.svg";
|
||||
import StarIcon from "./star.svg";
|
||||
import ArrowUp from "./Vector.svg";
|
||||
|
||||
export const icons = {
|
||||
AddIcon,
|
||||
CalendarIcon,
|
||||
DocummentTextIcon,
|
||||
FolderIcon,
|
||||
EditIcon,
|
||||
GridIcon,
|
||||
GridBlueIcon,
|
||||
ProfilePicture,
|
||||
EyeIcon,
|
||||
EyeSlashIcon,
|
||||
CheckboxchedIcon,
|
||||
CrossIcon,
|
||||
AddBlueIcon,
|
||||
ArchivesIcon,
|
||||
NotificationsIcon,
|
||||
TimerIcon,
|
||||
Logo,
|
||||
CompaniesIcon,
|
||||
Logout,
|
||||
LogoutRed,
|
||||
MaximizeIcon,
|
||||
MenuIcon,
|
||||
MessagesIcon,
|
||||
HomeIcon,
|
||||
ArrowLeft,
|
||||
ArrowRight,
|
||||
FilesIcon,
|
||||
PdfIcon,
|
||||
WordIcon,
|
||||
UserIcon,
|
||||
UserGroup,
|
||||
UserGroupBlue,
|
||||
Rectanagle,
|
||||
SearchIcon,
|
||||
SettingsIcon,
|
||||
FilterIcon,
|
||||
ShareIcon,
|
||||
StarIcon,
|
||||
ArrowUp
|
||||
};
|
||||
|
||||
*/
|
||||
9
src/assets/icons/logo.svg
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 667 B After Width: | Height: | Size: 667 B |
5
src/assets/icons/logout.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.8999 7.55999C9.2099 3.95999 11.0599 2.48999 15.1099 2.48999H15.2399C19.7099 2.48999 21.4999 4.27999 21.4999 8.74999V15.27C21.4999 19.74 19.7099 21.53 15.2399 21.53H15.1099C11.0899 21.53 9.2399 20.08 8.9099 16.54" stroke="#9FA8BC" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M15.0001 12H3.62012" stroke="#9FA8BC" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5.85 8.65002L2.5 12L5.85 15.35" stroke="#9FA8BC" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 655 B |
|
Before Width: | Height: | Size: 595 B After Width: | Height: | Size: 595 B |
|
Before Width: | Height: | Size: 989 B After Width: | Height: | Size: 989 B |
5
src/assets/icons/notifications.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 9C11.59 9 11.25 8.71843 11.25 8.37888V5.62112C11.25 5.28157 11.59 5 12 5C12.41 5 12.75 5.28157 12.75 5.62112V8.37888C12.75 8.72671 12.41 9 12 9Z" fill="#9FA8BC"/>
|
||||
<path d="M12.0161 19.6454C9.61036 19.6454 7.21398 19.2668 4.92949 18.5096C4.08097 18.2326 3.43758 17.6324 3.15785 16.8753C2.87811 16.1181 2.97136 15.2502 3.42825 14.493L4.61246 12.5355C4.87354 12.1015 5.10666 11.289 5.10666 10.7811V8.84208C5.10666 5.06555 8.20237 2 12.0161 2C15.8298 2 18.9255 5.06555 18.9255 8.84208V10.7811C18.9255 11.2797 19.1586 12.1015 19.4197 12.5355L20.6039 14.493C21.0421 15.2132 21.1167 16.072 20.8277 16.8568C20.5386 17.6417 19.9046 18.2419 19.1027 18.5096C16.8182 19.276 14.4218 19.6454 12.0161 19.6454ZM12.0161 3.39427C8.9763 3.39427 6.50532 5.84117 6.50532 8.85131V10.7904C6.50532 11.5383 6.20694 12.6186 5.81531 13.2557L4.63111 15.2225C4.38867 15.6195 4.33273 16.0443 4.47259 16.4136C4.61246 16.783 4.92949 17.06 5.37706 17.2077C9.66631 18.6204 14.3845 18.6204 18.6737 17.2077C19.0747 17.0784 19.3824 16.783 19.5223 16.3951C19.6714 16.0073 19.6248 15.5826 19.4104 15.2225L18.2262 13.265C17.8345 12.6278 17.5361 11.5475 17.5361 10.7996V8.86055C17.5268 5.84117 15.0558 3.39427 12.0161 3.39427Z" fill="#9FA8BC"/>
|
||||
<path d="M12 22C10.951 22 9.92157 21.5937 9.17647 20.8919C8.43137 20.1902 8 19.2207 8 18.2327H9.47059C9.47059 18.8605 9.7451 19.47 10.2157 19.9132C10.6863 20.3564 11.3333 20.6149 12 20.6149C13.3922 20.6149 14.5294 19.5438 14.5294 18.2327H16C16 20.3102 14.2059 22 12 22Z" fill="#9FA8BC"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 737 B After Width: | Height: | Size: 737 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 806 B After Width: | Height: | Size: 806 B |
104
src/components/floatingLabelInput.tsx
Normal file
@ -0,0 +1,104 @@
|
||||
"use client";
|
||||
|
||||
import { FloatingLabelInputProps } from '#/types';
|
||||
import React, { useState } from 'react';
|
||||
import Image from 'next/image';
|
||||
import { icons } from '#/assets/icons';
|
||||
|
||||
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 ? (
|
||||
<Image
|
||||
src={icons.eyeSlashIcon}
|
||||
width={20}
|
||||
height={20}
|
||||
alt=''
|
||||
/>
|
||||
) : (
|
||||
<Image
|
||||
src={icons.eyeIcon}
|
||||
width={20}
|
||||
height={20}
|
||||
alt=''
|
||||
/>
|
||||
)}
|
||||
</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">
|
||||
<label
|
||||
htmlFor={name}
|
||||
className="input-label text-gray-400 text-sm"
|
||||
>
|
||||
{label}
|
||||
</label>
|
||||
{renderInput()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
74
src/components/form/form.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
"use client";
|
||||
|
||||
import FloatingLabelInput from "../floatingLabelInput"
|
||||
import { FormProps } from "#/types"
|
||||
import { FormEvent, useState } from "react"
|
||||
|
||||
export default function Form({
|
||||
fields,
|
||||
submit,
|
||||
className,
|
||||
child,
|
||||
title,
|
||||
schema
|
||||
} : FormProps) {
|
||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||
|
||||
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault()
|
||||
|
||||
const formData = new FormData(e.currentTarget)
|
||||
const data = Object.fromEntries(formData)
|
||||
|
||||
console.log("FORM DATA = ", data)
|
||||
const result = schema.safeParse(data);
|
||||
console.log("ZOD = ", result.error?.format())
|
||||
|
||||
if(!result.success) {
|
||||
const formatedErrors = result.error.format() as Record<string, { _errors?: string[] }>;
|
||||
|
||||
const newErrors: Record<string, string> = {};
|
||||
Object.keys(formatedErrors).forEach((field) => {
|
||||
if (field !== "_errors" && formatedErrors[field]._errors?.length) {
|
||||
newErrors[field] = formatedErrors[field]._errors[0];
|
||||
}
|
||||
});
|
||||
|
||||
setErrors(newErrors)
|
||||
} else {
|
||||
setErrors({})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<form className={className} onSubmit={handleSubmit}>
|
||||
<div className="flex justify-center text-black">
|
||||
<p className="text-3xl font-bold">{title}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-8 mt-2">
|
||||
|
||||
{
|
||||
fields.map((item, index) => (
|
||||
<div key={index}>
|
||||
<FloatingLabelInput
|
||||
label={item.label}
|
||||
name={item.name}
|
||||
type={item.type}
|
||||
button={item.button}
|
||||
defaultValue={item.defaultValue}
|
||||
options={item.options}
|
||||
placeholder={item.placeholder}
|
||||
showPasswordToggle={item.showPasswordToggle}
|
||||
/>
|
||||
|
||||
<span className="text-red-500 text-xs mt-1">{errors[item.name]}</span>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
{child}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
)
|
||||
}
|
||||
17
src/components/header.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import { icons } from "#/assets/icons";
|
||||
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 gap-2">
|
||||
<Image
|
||||
src={icons.logo}
|
||||
alt="Private Docs"
|
||||
className="text-red-500 h-auto"
|
||||
/>
|
||||
<p className="text-2xl font-bold text-black">Private Docs</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
141
src/components/table/table.tsx
Normal file
@ -0,0 +1,141 @@
|
||||
"use client";
|
||||
import {
|
||||
ColumnDef,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
useReactTable,
|
||||
getPaginationRowModel,
|
||||
SortingState,
|
||||
ColumnFiltersState,
|
||||
getFilteredRowModel,
|
||||
} from "@tanstack/react-table"
|
||||
import { useState } from "react";
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
|
||||
interface DataTableProps<TData, TValue> {
|
||||
columns: ColumnDef<TData, TValue>[]
|
||||
data: TData[],
|
||||
pageSize?: number
|
||||
}
|
||||
|
||||
export default function Table<TData, TValue>({
|
||||
columns,
|
||||
data,
|
||||
pageSize = 10
|
||||
}: DataTableProps<TData, TValue>) {
|
||||
const [rowSelection, setRowSelection] = useState({})
|
||||
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>(
|
||||
[]
|
||||
)
|
||||
|
||||
const table = useReactTable({
|
||||
data,
|
||||
columns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
|
||||
state: {
|
||||
rowSelection,
|
||||
columnFilters,
|
||||
},
|
||||
initialState: {
|
||||
pagination: {
|
||||
pageSize: pageSize,
|
||||
}
|
||||
},
|
||||
getFilteredRowModel: getFilteredRowModel(),
|
||||
onRowSelectionChange: setRowSelection,
|
||||
onColumnFiltersChange: setColumnFilters,
|
||||
})
|
||||
|
||||
const totalPages = table.getPageCount()
|
||||
const currentPage = table.getState().pagination.pageIndex + 1
|
||||
|
||||
const getPageNumbers = () => {
|
||||
const pages = []
|
||||
for (let i = 1; i <= totalPages; i++) {
|
||||
pages.push(i)
|
||||
}
|
||||
return pages
|
||||
}
|
||||
|
||||
return(
|
||||
<div>
|
||||
<div className="rounded-lg border border-gray-200">
|
||||
<table className="w-full overflow-x-auto rounded-lg " style={{ borderTopLeftRadius: '10px', borderTopRightRadius: '10px', }}>
|
||||
<thead className="h-10">
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<tr key={headerGroup.id} className="rounded-lg">
|
||||
{headerGroup.headers.map((header) => {
|
||||
return(
|
||||
<th key={header.id} className="bg-blue-300 p-3 text-start">
|
||||
{flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
</th>
|
||||
)
|
||||
})}
|
||||
</tr>
|
||||
))}
|
||||
</thead>
|
||||
<tbody>
|
||||
{table.getRowModel().rows.length ? (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<tr key={row.id} className={clsx('hover:bg-gray-300 border-t border-gray-200', { 'bg-gray-300': row.getIsSelected()})}>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<td key={cell.id} className="p-3 text-start">
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))
|
||||
)
|
||||
: (
|
||||
<tr>
|
||||
<td colSpan={columns.length}>
|
||||
Aucun résultats
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-end space-x-2 py-4">
|
||||
<button
|
||||
className="border bg-gray-200 shadow-xs hover:bg-gray-300 hover:text-black px-3 py-1 rounded"
|
||||
onClick={() => table.previousPage()}
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
>
|
||||
Précédent
|
||||
</button>
|
||||
|
||||
<div className="flex space-x-1">
|
||||
{getPageNumbers().map((pageNumber) => (
|
||||
<button
|
||||
key={pageNumber}
|
||||
className={clsx(
|
||||
"px-3 py-1 rounded",
|
||||
pageNumber === currentPage
|
||||
? "bg-blue-500 text-white"
|
||||
: "bg-gray-200 hover:bg-gray-300"
|
||||
)}
|
||||
onClick={() => table.setPageIndex(pageNumber - 1)}
|
||||
>
|
||||
{pageNumber}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<button
|
||||
className="border bg-gray-200 shadow-xs hover:bg-gray-300 hover:text-black px-3 py-1 rounded"
|
||||
onClick={() => table.nextPage()}
|
||||
disabled={!table.getCanNextPage()}
|
||||
>
|
||||
Suivant
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
6
src/schema/loginSchema.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const loginSchema = z.object({
|
||||
email: z.string().email("Email invalide"),
|
||||
password: z.string().min(8, "Le mot de passe doit contenir au moins 8 caractères")
|
||||
});
|
||||
22
src/types/index.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { FormEventHandler, ReactNode } from "react";
|
||||
import { ZodSchema } from "zod";
|
||||
|
||||
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 {
|
||||
title?: string,
|
||||
fields: FloatingLabelInputProps[],
|
||||
submit: FormEventHandler<HTMLFormElement> | undefined,
|
||||
className: string,
|
||||
child: ReactNode,
|
||||
schema: ZodSchema
|
||||
}
|
||||