feat(LRL App): init V0

This commit is contained in:
2025-10-22 17:09:48 +02:00
parent d3303fee95
commit 0924da3cda
475 changed files with 44862 additions and 7 deletions

View File

@@ -0,0 +1,14 @@
import AppLayoutTemplate from '@/layouts/app/app-header-layout';
import { type BreadcrumbItem } from '@/types';
import { type ReactNode } from 'react';
interface AppLayoutProps {
children: ReactNode;
breadcrumbs?: BreadcrumbItem[];
}
export default ({ children, breadcrumbs, ...props }: AppLayoutProps) => (
<AppLayoutTemplate breadcrumbs={breadcrumbs} {...props}>
{children}
</AppLayoutTemplate>
);

View File

@@ -0,0 +1,17 @@
import { AppContent } from '@/components/app-content';
import { AppHeader } from '@/components/app-header';
import { AppShell } from '@/components/app-shell';
import { type BreadcrumbItem } from '@/types';
import type { PropsWithChildren } from 'react';
export default function AppHeaderLayout({
children,
breadcrumbs,
}: PropsWithChildren<{ breadcrumbs?: BreadcrumbItem[] }>) {
return (
<AppShell>
<AppHeader breadcrumbs={breadcrumbs} />
<AppContent>{children}</AppContent>
</AppShell>
);
}

View File

@@ -0,0 +1,21 @@
import { AppContent } from '@/components/app-content';
import { AppShell } from '@/components/app-shell';
import { AppSidebar } from '@/components/app-sidebar';
import { AppSidebarHeader } from '@/components/app-sidebar-header';
import { type BreadcrumbItem } from '@/types';
import { type PropsWithChildren } from 'react';
export default function AppSidebarLayout({
children,
breadcrumbs = [],
}: PropsWithChildren<{ breadcrumbs?: BreadcrumbItem[] }>) {
return (
<AppShell variant="sidebar">
<AppSidebar />
<AppContent variant="sidebar" className="overflow-x-hidden">
<AppSidebarHeader breadcrumbs={breadcrumbs} />
{children}
</AppContent>
</AppShell>
);
}

View File

@@ -0,0 +1,18 @@
import AuthLayoutTemplate from '@/layouts/auth/auth-simple-layout';
export default function AuthLayout({
children,
title,
description,
...props
}: {
children: React.ReactNode;
title: string;
description: string;
}) {
return (
<AuthLayoutTemplate title={title} description={description} {...props}>
{children}
</AuthLayoutTemplate>
);
}

View File

@@ -0,0 +1,48 @@
import AppLogoIcon from '@/components/app-logo-icon';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { home } from '@/routes';
import { Link } from '@inertiajs/react';
import { type PropsWithChildren } from 'react';
export default function AuthCardLayout({
children,
title,
description,
}: PropsWithChildren<{
name?: string;
title?: string;
description?: string;
}>) {
return (
<div className="flex min-h-svh flex-col items-center justify-center gap-6 bg-muted p-6 md:p-10">
<div className="flex w-full max-w-md flex-col gap-6">
<Link
href={home()}
className="flex items-center gap-2 self-center font-medium"
>
<div className="flex h-9 w-9 items-center justify-center">
<AppLogoIcon className="size-9 fill-current text-black dark:text-white" />
</div>
</Link>
<div className="flex flex-col gap-6">
<Card className="rounded-xl">
<CardHeader className="px-10 pt-8 pb-0 text-center">
<CardTitle className="text-xl">{title}</CardTitle>
<CardDescription>{description}</CardDescription>
</CardHeader>
<CardContent className="px-10 py-8">
{children}
</CardContent>
</Card>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,44 @@
import AppLogoIcon from '@/components/app-logo-icon';
import { home } from '@/routes';
import { Link } from '@inertiajs/react';
import { type PropsWithChildren } from 'react';
interface AuthLayoutProps {
name?: string;
title?: string;
description?: string;
}
export default function AuthSimpleLayout({
children,
title,
description,
}: PropsWithChildren<AuthLayoutProps>) {
return (
<div className="flex min-h-svh flex-col items-center justify-center gap-6 bg-background p-6 md:p-10">
<div className="w-full max-w-sm">
<div className="flex flex-col gap-8">
<div className="flex flex-col items-center gap-4">
<Link
href={home()}
className="flex flex-col items-center font-medium no-underline"
>
<div className="mb-1 flex h-9 w-9 items-center justify-center rounded-md">
<AppLogoIcon className="size-9 text-[var(--foreground)] dark:text-white" />
</div>
<h1 className="text-black">Le Retzien Libre</h1>
</Link>
{/*<div className="space-y-2 text-center">
<h1 className="text-xl font-medium">{title}</h1>
<p className="text-center text-sm text-muted-foreground">
{description}
</p>
</div>*/}
</div>
{children}
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,62 @@
import AppLogoIcon from '@/components/app-logo-icon';
import { home } from '@/routes';
import { type SharedData } from '@/types';
import { Link, usePage } from '@inertiajs/react';
import { type PropsWithChildren } from 'react';
interface AuthLayoutProps {
title?: string;
description?: string;
}
export default function AuthSplitLayout({
children,
title,
description,
}: PropsWithChildren<AuthLayoutProps>) {
const { name, quote } = usePage<SharedData>().props;
return (
<div className="relative grid h-dvh flex-col items-center justify-center px-8 sm:px-0 lg:max-w-none lg:grid-cols-2 lg:px-0">
<div className="relative hidden h-full flex-col bg-muted p-10 text-white lg:flex dark:border-r">
<div className="absolute inset-0 bg-zinc-900" />
<Link
href={home()}
className="relative z-20 flex items-center text-lg font-medium"
>
<AppLogoIcon className="mr-2 size-8 fill-current text-white" />
{name}
</Link>
{quote && (
<div className="relative z-20 mt-auto">
<blockquote className="space-y-2">
<p className="text-lg">
&ldquo;{quote.message}&rdquo;
</p>
<footer className="text-sm text-neutral-300">
{quote.author}
</footer>
</blockquote>
</div>
)}
</div>
<div className="w-full lg:p-8">
<div className="mx-auto flex w-full flex-col justify-center space-y-6 sm:w-[350px]">
<Link
href={home()}
className="relative z-20 flex items-center justify-center lg:hidden"
>
<AppLogoIcon className="h-10 fill-current text-black sm:h-12" />
</Link>
<div className="flex flex-col items-start gap-2 text-left sm:items-center sm:text-center">
<h1 className="text-xl font-medium">{title}</h1>
<p className="text-sm text-balance text-muted-foreground">
{description}
</p>
</div>
{children}
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,96 @@
import {Link, router, usePage} from "@inertiajs/react";
import {dashboard, home, login, logout, register, contact, membership} from "@/routes";
import AppLogoIcon from "@/components/app-logo-icon";
import {Button} from "@/components/ui/button";
import type {SharedData} from "@/types";
import {useMobileNavigation} from "@/hooks/use-mobile-navigation";
import {LogOut} from "lucide-react";
export default function NavGuestLayout() {
const {auth} = usePage<SharedData>().props;
const cleanup = useMobileNavigation();
const handleLogout = () => {
cleanup();
router.flushAll();
};
return (
<>
<header
className="flex justify-between mb-6 w-full max-w-[335px] text-sm not-has-[nav]:hidden lg:max-w-7xl">
<div className="flex items-center justify-start">
<Link
href={home()}
className="flex items-center justify-start gap-2 font-medium no-underline"
>
<div className="mb-1 flex h-9 w-9 items-center justify-center rounded-md">
<AppLogoIcon className="size-9 text-[var(--foreground)] dark:text-white"/>
</div>
<h1 className="text-black">Le Retzien Libre</h1>
</Link>
</div>
<nav className="flex items-center justify-end gap-4">
<Link
href={home()}
className="inline-block px-5 py-1.5 text-lg leading-normal text-[#1b1b18] hover:border-[#1915014a] dark:border-[#3E3E3A] dark:text-[#EDEDEC] dark:hover:border-[#62605b] no-underline"
>
Accueil
</Link>
<Link
href="#"
className="inline-block px-5 py-1.5 text-lg leading-normal text-[#1b1b18] hover:border-[#1915014a] dark:border-[#3E3E3A] dark:text-[#EDEDEC] dark:hover:border-[#62605b] no-underline"
>
Nos Services
</Link>
<Link
href="#"
className="inline-block px-5 py-1.5 text-lg leading-normal text-[#1b1b18] hover:border-[#1915014a] dark:border-[#3E3E3A] dark:text-[#EDEDEC] dark:hover:border-[#62605b] no-underline"
>
Le Blog
</Link>
<Link
href={contact()}
className="inline-block px-5 py-1.5 text-lg leading-normal text-[#1b1b18] hover:border-[#1915014a] dark:border-[#3E3E3A] dark:text-[#EDEDEC] dark:hover:border-[#62605b] no-underline"
>
Contact
</Link>
</nav>
<nav className="flex items-center justify-end gap-4">
{auth.user ? (
<>
<Link
href={dashboard()}
className=" no-underline"
>
<Button variant="outline">Tableau de bord</Button>
</Link>
<Link
className="bg-transparent border-0 shadow-none"
href={logout()}
onClick={handleLogout}
data-test="logout-button"
>
<Button variant="secondary">Se déconnecter</Button>
</Link>
</>
) : (
<>
<Link
href={login()}
>
<Button variant="outline">Se connecter</Button>
</Link>
<Link
href={membership()}
>
<Button>Adhérer</Button>
</Link>
</>
)}
</nav>
</header>
</>
)
}

View File

@@ -0,0 +1,89 @@
import Heading from '@/components/heading';
import { Button } from '@/components/ui/button';
import { Separator } from '@/components/ui/separator';
import { cn } from '@/lib/utils';
import { edit as editAppearance } from '@/routes/appearance';
import { edit as editPassword } from '@/routes/password';
import { edit } from '@/routes/profile';
import { show } from '@/routes/two-factor';
import { type NavItem } from '@/types';
import { Link } from '@inertiajs/react';
import { type PropsWithChildren } from 'react';
const sidebarNavItems: NavItem[] = [
{
title: 'Profile',
href: edit(),
icon: null,
},
{
title: 'Password',
href: editPassword(),
icon: null,
},
{
title: 'Two-Factor Auth',
href: show(),
icon: null,
},
{
title: 'Appearance',
href: editAppearance(),
icon: null,
},
];
export default function SettingsLayout({ children }: PropsWithChildren) {
// When server-side rendering, we only render the layout on the client...
if (typeof window === 'undefined') {
return null;
}
const currentPath = window.location.pathname;
return (
<div className="px-4 py-6">
<Heading
title="Settings"
description="Manage your profile and account settings"
/>
<div className="flex flex-col lg:flex-row lg:space-x-12">
<aside className="w-full max-w-xl lg:w-48">
<nav className="flex flex-col space-y-1 space-x-0">
{sidebarNavItems.map((item, index) => (
<Button
key={`${typeof item.href === 'string' ? item.href : item.href.url}-${index}`}
size="sm"
variant="ghost"
asChild
className={cn('w-full justify-start', {
'bg-muted':
currentPath ===
(typeof item.href === 'string'
? item.href
: item.href.url),
})}
>
<Link href={item.href}>
{item.icon && (
<item.icon className="h-4 w-4" />
)}
{item.title}
</Link>
</Button>
))}
</nav>
</aside>
<Separator className="my-6 lg:hidden" />
<div className="flex-1 md:max-w-2xl">
<section className="max-w-xl space-y-12">
{children}
</section>
</div>
</div>
</div>
);
}