completed the frontend dashboard component and bound it to the tenant admin pwa for the optimal onboarding experience.. Added a profile page.
This commit is contained in:
@@ -1,14 +1,19 @@
|
||||
import AppLogoIcon from './app-logo-icon';
|
||||
import { usePage } from '@inertiajs/react';
|
||||
|
||||
import { type SharedData } from '@/types';
|
||||
|
||||
export default function AppLogo() {
|
||||
const { translations } = usePage<SharedData>().props;
|
||||
const areaLabel =
|
||||
(translations?.dashboard?.navigation?.group_label as string | undefined) ?? 'Kundenbereich';
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex aspect-square size-8 items-center justify-center rounded-md bg-sidebar-primary text-sidebar-primary-foreground">
|
||||
<AppLogoIcon className="size-5 fill-current text-white dark:text-black" />
|
||||
<div className="flex items-center gap-3">
|
||||
<img src="/logo-transparent-md.png" alt="Fotospiel" className="h-10 w-auto" />
|
||||
<div className="grid text-left leading-tight">
|
||||
<span className="text-sm font-semibold text-sidebar-foreground">Fotospiel</span>
|
||||
<span className="text-xs text-sidebar-foreground/70">{areaLabel}</span>
|
||||
</div>
|
||||
<div className="ml-1 grid flex-1 text-left text-sm">
|
||||
<span className="mb-0.5 truncate leading-tight font-semibold">Laravel Starter Kit</span>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,11 +8,11 @@ interface AppShellProps {
|
||||
}
|
||||
|
||||
export function AppShell({ children, variant = 'header' }: AppShellProps) {
|
||||
const isOpen = usePage<SharedData>().props.sidebarOpen;
|
||||
const { sidebarOpen = false } = usePage<SharedData>().props;
|
||||
|
||||
if (variant === 'header') {
|
||||
return <div className="flex min-h-screen w-full flex-col">{children}</div>;
|
||||
}
|
||||
|
||||
return <SidebarProvider defaultOpen={isOpen}>{children}</SidebarProvider>;
|
||||
return <SidebarProvider defaultOpen={sidebarOpen}>{children}</SidebarProvider>;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { NavFooter } from '@/components/nav-footer';
|
||||
import { NavMain } from '@/components/nav-main';
|
||||
import { NavUser } from '@/components/nav-user';
|
||||
import { Sidebar, SidebarContent, SidebarFooter, SidebarHeader, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from '@/components/ui/sidebar';
|
||||
import { dashboard } from '@/routes';
|
||||
import { type NavItem } from '@/types';
|
||||
import { Link } from '@inertiajs/react';
|
||||
import { BookOpen, Folder, LayoutGrid, UserRound } from 'lucide-react';
|
||||
import { LayoutGrid, UserRound } from 'lucide-react';
|
||||
import AppLogo from './app-logo';
|
||||
|
||||
const mainNavItems: NavItem[] = [
|
||||
@@ -21,19 +20,6 @@ const mainNavItems: NavItem[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const footerNavItems: NavItem[] = [
|
||||
{
|
||||
title: 'Repository',
|
||||
href: 'https://github.com/laravel/react-starter-kit',
|
||||
icon: Folder,
|
||||
},
|
||||
{
|
||||
title: 'Documentation',
|
||||
href: 'https://laravel.com/docs/starter-kits#react',
|
||||
icon: BookOpen,
|
||||
},
|
||||
];
|
||||
|
||||
export function AppSidebar() {
|
||||
return (
|
||||
<Sidebar collapsible="icon" variant="inset">
|
||||
@@ -54,7 +40,6 @@ export function AppSidebar() {
|
||||
</SidebarContent>
|
||||
|
||||
<SidebarFooter>
|
||||
<NavFooter items={footerNavItems} className="mt-auto" />
|
||||
<NavUser />
|
||||
</SidebarFooter>
|
||||
</Sidebar>
|
||||
|
||||
86
resources/js/components/dashboard-language-switcher.tsx
Normal file
86
resources/js/components/dashboard-language-switcher.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
import { useState } from 'react';
|
||||
import { router, usePage } from '@inertiajs/react';
|
||||
import { Check, Languages } from 'lucide-react';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { type SharedData } from '@/types';
|
||||
import { setLocale } from '@/routes';
|
||||
|
||||
export function DashboardLanguageSwitcher() {
|
||||
const page = usePage<SharedData>();
|
||||
const { locale, supportedLocales, translations } = page.props;
|
||||
const locales = supportedLocales && supportedLocales.length > 0 ? supportedLocales : ['de', 'en'];
|
||||
const activeLocale = locales.includes(locale as string) && typeof locale === 'string' ? locale : locales[0];
|
||||
const languageCopy = (translations?.dashboard?.language_switcher as Record<string, string | undefined>) ?? {};
|
||||
const label = languageCopy.label ?? 'Sprache';
|
||||
const changeLabel = languageCopy.change ?? 'Sprache wechseln';
|
||||
|
||||
const [pendingLocale, setPendingLocale] = useState<string | null>(null);
|
||||
|
||||
const handleChange = (nextLocale: string) => {
|
||||
if (nextLocale === activeLocale || pendingLocale === nextLocale || !locales.includes(nextLocale)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setPendingLocale(nextLocale);
|
||||
|
||||
router.post(
|
||||
setLocale().url,
|
||||
{ locale: nextLocale },
|
||||
{
|
||||
preserveScroll: true,
|
||||
preserveState: true,
|
||||
onFinish: () => setPendingLocale(null),
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="flex items-center gap-2 rounded-full border-white/30 bg-white/80 px-3 text-xs font-semibold uppercase tracking-wide text-slate-900 backdrop-blur transition hover:bg-white dark:border-white/20 dark:bg-white/10 dark:text-white"
|
||||
aria-label={label}
|
||||
>
|
||||
<Languages className="size-4" />
|
||||
<span>{label}</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="min-w-[9rem]">
|
||||
<DropdownMenuLabel className="text-xs font-semibold text-muted-foreground uppercase tracking-wide">
|
||||
{changeLabel}
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
{locales.map((code) => {
|
||||
const isActive = code === activeLocale;
|
||||
const isPending = code === pendingLocale;
|
||||
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
key={code}
|
||||
onSelect={(event) => {
|
||||
event.preventDefault();
|
||||
handleChange(code);
|
||||
}}
|
||||
disabled={isPending}
|
||||
className="flex items-center justify-between gap-3"
|
||||
>
|
||||
<span className="text-sm font-medium uppercase tracking-wide">{code}</span>
|
||||
{(isActive || isPending) && <Check className="size-4 text-pink-500" />}
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
})}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +1,16 @@
|
||||
import { SidebarGroup, SidebarGroupLabel, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from '@/components/ui/sidebar';
|
||||
import { type NavItem } from '@/types';
|
||||
import { type NavItem, type SharedData } from '@/types';
|
||||
import { Link, usePage } from '@inertiajs/react';
|
||||
|
||||
export function NavMain({ items = [] }: { items: NavItem[] }) {
|
||||
const page = usePage();
|
||||
const page = usePage<SharedData>();
|
||||
const { translations } = page.props;
|
||||
const groupLabel =
|
||||
(translations?.dashboard?.navigation?.group_label as string | undefined) ?? 'Kundenbereich';
|
||||
|
||||
return (
|
||||
<SidebarGroup className="px-2 py-0">
|
||||
<SidebarGroupLabel>Platform</SidebarGroupLabel>
|
||||
<SidebarGroupLabel>{groupLabel}</SidebarGroupLabel>
|
||||
<SidebarMenu>
|
||||
{items.map((item) => (
|
||||
<SidebarMenuItem key={item.title}>
|
||||
|
||||
@@ -2,10 +2,10 @@ import { DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSep
|
||||
import { UserInfo } from '@/components/user-info';
|
||||
import { useMobileNavigation } from '@/hooks/use-mobile-navigation';
|
||||
import { logout } from '@/routes';
|
||||
import { edit } from '@/routes/settings/profile';
|
||||
import profileRoutes from '@/routes/profile';
|
||||
import { type User } from '@/types';
|
||||
import { Link, router } from '@inertiajs/react';
|
||||
import { LogOut, Settings } from 'lucide-react';
|
||||
import { LogOut, UserRound } from 'lucide-react';
|
||||
|
||||
interface UserMenuContentProps {
|
||||
user: User;
|
||||
@@ -29,9 +29,9 @@ export function UserMenuContent({ user }: UserMenuContentProps) {
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem asChild>
|
||||
<Link className="block w-full" href={edit()} as="button" prefetch onClick={cleanup}>
|
||||
<Settings className="mr-2" />
|
||||
Settings
|
||||
<Link className="block w-full" href={profileRoutes.index().url} as="button" prefetch onClick={cleanup}>
|
||||
<UserRound className="mr-2" />
|
||||
Profil
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
@@ -39,7 +39,7 @@ export function UserMenuContent({ user }: UserMenuContentProps) {
|
||||
<DropdownMenuItem asChild>
|
||||
<Link className="block w-full" href={logout()} as="button" onClick={handleLogout}>
|
||||
<LogOut className="mr-2" />
|
||||
Log out
|
||||
Abmelden
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user