mirror of
https://codeberg.org/MarkusThielker/finances.git
synced 2025-07-01 19:19:18 +00:00
N-FIN-5: add navigation to layout
This commit is contained in:
parent
799655b061
commit
15f806980b
12 changed files with 1056 additions and 21 deletions
|
@ -3,13 +3,10 @@ import React from 'react';
|
|||
import { getUser } from '@/auth';
|
||||
import { redirect } from 'next/navigation';
|
||||
import signOut from '@/lib/actions/signOut';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import Link from 'next/link';
|
||||
import { ChevronLeft } from 'lucide-react';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import SignOutForm from '@/components/form/signOutForm';
|
||||
import { URL_HOME, URL_SIGN_IN } from '@/lib/constants';
|
||||
import { URL_SIGN_IN } from '@/lib/constants';
|
||||
|
||||
export default async function AccountPage() {
|
||||
|
||||
|
@ -20,13 +17,8 @@ export default async function AccountPage() {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col items-center justify-center relative">
|
||||
<Button variant="ghost" size="icon" className="absolute top-4 left-4" asChild>
|
||||
<Link href={URL_HOME}>
|
||||
<ChevronLeft/>
|
||||
</Link>
|
||||
</Button>
|
||||
<Card className="w-full max-w-md">
|
||||
<div className="flex flex-col items-center">
|
||||
<Card className="w-full max-w-md mt-12">
|
||||
<CardHeader>
|
||||
<CardTitle>Hey, {user?.username}!</CardTitle>
|
||||
<CardDescription>This is your account overview.</CardDescription>
|
||||
|
|
7
src/app/categories/page.tsx
Normal file
7
src/app/categories/page.tsx
Normal file
|
@ -0,0 +1,7 @@
|
|||
export default async function CategoriesPage() {
|
||||
return (
|
||||
<main className="flex items-center justify-center min-h-screen text-3xl">
|
||||
Categories
|
||||
</main>
|
||||
);
|
||||
}
|
7
src/app/entities/page.tsx
Normal file
7
src/app/entities/page.tsx
Normal file
|
@ -0,0 +1,7 @@
|
|||
export default async function EntitiesPage() {
|
||||
return (
|
||||
<main className="flex items-center justify-center min-h-screen text-3xl">
|
||||
Entities
|
||||
</main>
|
||||
);
|
||||
}
|
|
@ -4,6 +4,7 @@ import './globals.css';
|
|||
import { cn } from '@/lib/utils';
|
||||
import { Toaster } from '@/components/ui/sonner';
|
||||
import React from 'react';
|
||||
import Navigation from '@/components/navigation';
|
||||
|
||||
const inter = Inter({subsets: ['latin']});
|
||||
|
||||
|
@ -20,6 +21,7 @@ export default function RootLayout({
|
|||
return (
|
||||
<html lang="en">
|
||||
<body className={cn('dark', inter.className)}>
|
||||
<Navigation/>
|
||||
<main>
|
||||
{children}
|
||||
<Toaster/>
|
||||
|
|
|
@ -1,17 +1,8 @@
|
|||
import { Button } from '@/components/ui/button';
|
||||
import { User } from 'lucide-react';
|
||||
import React from 'react';
|
||||
import Link from 'next/link';
|
||||
import { URL_ACCOUNT } from '@/lib/constants';
|
||||
|
||||
export default async function Home() {
|
||||
return (
|
||||
<main className="flex min-h-screen flex-col items-center justify-center p-24 text-3xl space-y-4 relative">
|
||||
<Button variant="ghost" size="icon" className="absolute top-4 right-4" asChild>
|
||||
<Link href={URL_ACCOUNT}>
|
||||
<User/>
|
||||
</Link>
|
||||
</Button>
|
||||
<main className="flex items-center justify-center min-h-screen text-3xl">
|
||||
Next Finances
|
||||
</main>
|
||||
);
|
||||
|
|
7
src/app/payments/page.tsx
Normal file
7
src/app/payments/page.tsx
Normal file
|
@ -0,0 +1,7 @@
|
|||
export default async function PaymentsPage() {
|
||||
return (
|
||||
<main className="flex items-center justify-center min-h-screen text-3xl">
|
||||
Payments
|
||||
</main>
|
||||
);
|
||||
}
|
65
src/components/navigation.tsx
Normal file
65
src/components/navigation.tsx
Normal file
|
@ -0,0 +1,65 @@
|
|||
'use client';
|
||||
|
||||
import {
|
||||
NavigationMenu,
|
||||
NavigationMenuItem,
|
||||
NavigationMenuLink,
|
||||
NavigationMenuList,
|
||||
navigationMenuTriggerStyle,
|
||||
} from '@/components/ui/navigation-menu';
|
||||
import React from 'react';
|
||||
import Link from 'next/link';
|
||||
import { User } from 'lucide-react';
|
||||
|
||||
export default function Navigation() {
|
||||
|
||||
return (
|
||||
<div className="flex sticky items-center border-b border-border bg-background">
|
||||
<NavigationMenu>
|
||||
<NavigationMenuList className="flex w-screen items-center justify-between px-4 py-2">
|
||||
<div className="inline-flex space-x-2">
|
||||
|
||||
<img src={'/logo_white.png'} alt="Finances" className="h-10 w-10 mx-3"/>
|
||||
|
||||
<NavigationMenuItem>
|
||||
<Link href="/" legacyBehavior passHref>
|
||||
<NavigationMenuLink className={navigationMenuTriggerStyle()}>
|
||||
Dashboard
|
||||
</NavigationMenuLink>
|
||||
</Link>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<Link href="/payments" legacyBehavior passHref>
|
||||
<NavigationMenuLink className={navigationMenuTriggerStyle()}>
|
||||
Payments
|
||||
</NavigationMenuLink>
|
||||
</Link>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<Link href="/entities" legacyBehavior passHref>
|
||||
<NavigationMenuLink className={navigationMenuTriggerStyle()}>
|
||||
Entities
|
||||
</NavigationMenuLink>
|
||||
</Link>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<Link href="/categories" legacyBehavior passHref>
|
||||
<NavigationMenuLink className={navigationMenuTriggerStyle()}>
|
||||
Categories
|
||||
</NavigationMenuLink>
|
||||
</Link>
|
||||
</NavigationMenuItem>
|
||||
</div>
|
||||
|
||||
<NavigationMenuItem>
|
||||
<Link href="/account" legacyBehavior passHref>
|
||||
<NavigationMenuLink className={navigationMenuTriggerStyle()}>
|
||||
<User/>
|
||||
</NavigationMenuLink>
|
||||
</Link>
|
||||
</NavigationMenuItem>
|
||||
</NavigationMenuList>
|
||||
</NavigationMenu>
|
||||
</div>
|
||||
);
|
||||
}
|
128
src/components/ui/navigation-menu.tsx
Normal file
128
src/components/ui/navigation-menu.tsx
Normal file
|
@ -0,0 +1,128 @@
|
|||
import * as React from 'react';
|
||||
import * as NavigationMenuPrimitive from '@radix-ui/react-navigation-menu';
|
||||
import { cva } from 'class-variance-authority';
|
||||
import { ChevronDown } from 'lucide-react';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const NavigationMenu = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
|
||||
>(({className, children, ...props}, ref) => (
|
||||
<NavigationMenuPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'relative z-10 flex max-w-max flex-1 items-center justify-center',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<NavigationMenuViewport/>
|
||||
</NavigationMenuPrimitive.Root>
|
||||
));
|
||||
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName;
|
||||
|
||||
const NavigationMenuList = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.List>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
|
||||
>(({className, ...props}, ref) => (
|
||||
<NavigationMenuPrimitive.List
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'group flex flex-1 list-none items-center justify-center space-x-1',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName;
|
||||
|
||||
const NavigationMenuItem = NavigationMenuPrimitive.Item;
|
||||
|
||||
const navigationMenuTriggerStyle = cva(
|
||||
'group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50',
|
||||
);
|
||||
|
||||
const NavigationMenuTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
|
||||
>(({className, children, ...props}, ref) => (
|
||||
<NavigationMenuPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(navigationMenuTriggerStyle(), 'group', className)}
|
||||
{...props}
|
||||
>
|
||||
{children}{' '}
|
||||
<ChevronDown
|
||||
className="relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</NavigationMenuPrimitive.Trigger>
|
||||
));
|
||||
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName;
|
||||
|
||||
const NavigationMenuContent = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
|
||||
>(({className, ...props}, ref) => (
|
||||
<NavigationMenuPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName;
|
||||
|
||||
const NavigationMenuLink = NavigationMenuPrimitive.Link;
|
||||
|
||||
const NavigationMenuViewport = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
|
||||
>(({className, ...props}, ref) => (
|
||||
<div className={cn('absolute left-0 top-full flex justify-center')}>
|
||||
<NavigationMenuPrimitive.Viewport
|
||||
className={cn(
|
||||
'origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]',
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
NavigationMenuViewport.displayName =
|
||||
NavigationMenuPrimitive.Viewport.displayName;
|
||||
|
||||
const NavigationMenuIndicator = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
|
||||
>(({className, ...props}, ref) => (
|
||||
<NavigationMenuPrimitive.Indicator
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md"/>
|
||||
</NavigationMenuPrimitive.Indicator>
|
||||
));
|
||||
NavigationMenuIndicator.displayName =
|
||||
NavigationMenuPrimitive.Indicator.displayName;
|
||||
|
||||
export {
|
||||
navigationMenuTriggerStyle,
|
||||
NavigationMenu,
|
||||
NavigationMenuList,
|
||||
NavigationMenuItem,
|
||||
NavigationMenuContent,
|
||||
NavigationMenuTrigger,
|
||||
NavigationMenuLink,
|
||||
NavigationMenuIndicator,
|
||||
NavigationMenuViewport,
|
||||
};
|
160
src/components/ui/select.tsx
Normal file
160
src/components/ui/select.tsx
Normal file
|
@ -0,0 +1,160 @@
|
|||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import * as SelectPrimitive from '@radix-ui/react-select';
|
||||
import { Check, ChevronDown, ChevronUp } from 'lucide-react';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const Select = SelectPrimitive.Root;
|
||||
|
||||
const SelectGroup = SelectPrimitive.Group;
|
||||
|
||||
const SelectValue = SelectPrimitive.Value;
|
||||
|
||||
const SelectTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
||||
>(({className, children, ...props}, ref) => (
|
||||
<SelectPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SelectPrimitive.Icon asChild>
|
||||
<ChevronDown className="h-4 w-4 opacity-50"/>
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
));
|
||||
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
|
||||
|
||||
const SelectScrollUpButton = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
||||
>(({className, ...props}, ref) => (
|
||||
<SelectPrimitive.ScrollUpButton
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'flex cursor-default items-center justify-center py-1',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronUp className="h-4 w-4"/>
|
||||
</SelectPrimitive.ScrollUpButton>
|
||||
));
|
||||
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
|
||||
|
||||
const SelectScrollDownButton = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
|
||||
>(({className, ...props}, ref) => (
|
||||
<SelectPrimitive.ScrollDownButton
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'flex cursor-default items-center justify-center py-1',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronDown className="h-4 w-4"/>
|
||||
</SelectPrimitive.ScrollDownButton>
|
||||
));
|
||||
SelectScrollDownButton.displayName =
|
||||
SelectPrimitive.ScrollDownButton.displayName;
|
||||
|
||||
const SelectContent = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
||||
>(({className, children, position = 'popper', ...props}, ref) => (
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||
position === 'popper' &&
|
||||
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
|
||||
className,
|
||||
)}
|
||||
position={position}
|
||||
{...props}
|
||||
>
|
||||
<SelectScrollUpButton/>
|
||||
<SelectPrimitive.Viewport
|
||||
className={cn(
|
||||
'p-1',
|
||||
position === 'popper' &&
|
||||
'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]',
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</SelectPrimitive.Viewport>
|
||||
<SelectScrollDownButton/>
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
));
|
||||
SelectContent.displayName = SelectPrimitive.Content.displayName;
|
||||
|
||||
const SelectLabel = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
||||
>(({className, ...props}, ref) => (
|
||||
<SelectPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn('py-1.5 pl-8 pr-2 text-sm font-semibold', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
SelectLabel.displayName = SelectPrimitive.Label.displayName;
|
||||
|
||||
const SelectItem = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
||||
>(({className, children, ...props}, ref) => (
|
||||
<SelectPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<SelectPrimitive.ItemIndicator>
|
||||
<Check className="h-4 w-4"/>
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</span>
|
||||
|
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
));
|
||||
SelectItem.displayName = SelectPrimitive.Item.displayName;
|
||||
|
||||
const SelectSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
||||
>(({className, ...props}, ref) => (
|
||||
<SelectPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn('-mx-1 my-1 h-px bg-muted', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
|
||||
|
||||
export {
|
||||
Select,
|
||||
SelectGroup,
|
||||
SelectValue,
|
||||
SelectTrigger,
|
||||
SelectContent,
|
||||
SelectLabel,
|
||||
SelectItem,
|
||||
SelectSeparator,
|
||||
SelectScrollUpButton,
|
||||
SelectScrollDownButton,
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue