progress on settings page

This commit is contained in:
Henry Dollman
2024-09-09 20:00:09 -04:00
parent 3b13fadde2
commit 3362a3d1cf
7 changed files with 281 additions and 14 deletions

View File

@@ -4,7 +4,7 @@ export const $router = createRouter(
{
home: '/',
server: '/system/:name',
'forgot-password': '/forgot-password',
settings: '/settings/:name?',
},
{ links: false }
)
@@ -16,7 +16,7 @@ export const navigate = (urlString: string) => {
function onClick(e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) {
e.preventDefault()
$router.open(new URL((e.target as HTMLAnchorElement).href).pathname)
$router.open(new URL((e.currentTarget as HTMLAnchorElement).href).pathname)
}
export const Link = (props: React.AnchorHTMLAttributes<HTMLAnchorElement>) => {

View File

@@ -0,0 +1,46 @@
import { Button } from '@/components/ui/button'
import { Label } from '@/components/ui/label'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { chartTimeData } from '@/lib/utils'
import { Separator } from '@/components/ui/separator'
export default function SettingsProfilePage() {
return (
<div>
{/* <div>
<h3 className="text-lg font-medium mb-1">General</h3>
<p className="text-sm text-muted-foreground">Set your preferred language and timezone.</p>
</div>
<Separator className="mt-6 mb-5" /> */}
<div className="space-y-5">
<div className="space-y-2">
<Label>Default chart period</Label>
{/* <Input placeholder="Username" /> */}
<Select defaultValue="1h">
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
{Object.entries(chartTimeData).map(([value, { label }]) => (
<SelectItem key={label} value={value}>
{label}
</SelectItem>
))}
</SelectContent>
</Select>
<p className="text-[0.8rem] text-muted-foreground">
Sets the default time range for charts.
</p>
</div>
<Separator />
<Button type="submit">Save settings</Button>
</div>
</div>
)
}

View File

@@ -0,0 +1,71 @@
import { Suspense, lazy, useEffect } from 'react'
import { Separator } from '../../ui/separator'
import { SidebarNav } from './sidebar-nav.tsx'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card.tsx'
import { useStore } from '@nanostores/react'
import { $router } from '@/components/router.tsx'
import { redirectPage } from '@nanostores/router'
import { BellIcon, SettingsIcon } from 'lucide-react'
const General = lazy(() => import('./general.tsx'))
const Notifications = lazy(() => import('./notifications.tsx'))
const sidebarNavItems = [
{
title: 'General',
href: '/settings/general',
icon: SettingsIcon,
},
{
title: 'Notifications',
href: '/settings/notifications',
icon: BellIcon,
},
]
export default function SettingsLayout() {
const page = useStore($router)
useEffect(() => {
document.title = 'Settings / Beszel'
// redirect to account page if no page is specified
if (page?.path === '/settings') {
redirectPage($router, 'settings', { name: 'general' })
}
}, [])
return (
<Card className="pt-5 px-4 pb-9 sm:pt-6 sm:px-7">
<CardHeader className="p-0">
<CardTitle className="mb-1">Settings</CardTitle>
<CardDescription>Manage your account settings and set e-mail preferences.</CardDescription>
</CardHeader>
<CardContent className="p-0">
<Separator className="my-5" />
<div className="flex flex-col space-y-8 lg:flex-row lg:space-x-12 lg:space-y-0">
<aside className="lg:w-48 w-full overflow-auto">
<SidebarNav items={sidebarNavItems} />
</aside>
<div className="flex-1">
<Suspense>
{/* @ts-ignore */}
<SettingsContent name={page?.params?.name ?? 'general'} />
</Suspense>
</div>
</div>
</CardContent>
</Card>
)
}
function SettingsContent({ name }: { name: string }) {
switch (name) {
case 'general':
return <General />
// case 'display':
// return <Display />
case 'notifications':
return <Notifications />
}
return ''
}

View File

@@ -0,0 +1,98 @@
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { pb } from '@/lib/stores'
import { Separator } from '@/components/ui/separator'
import { Card } from '@/components/ui/card'
// import { Switch } from '@/components/ui/switch'
import { Trash2Icon } from 'lucide-react'
export default function SettingsNotificationsPage() {
return (
<div>
{/* <div>
<h3 className="text-xl font-medium mb-1">Notifications</h3>
<p className="text-sm text-muted-foreground">Configure how you receive notifications.</p>
</div>
<Separator className="my-6" /> */}
<div className="space-y-5">
<div className="space-y-2">
<div className="mb-4">
<h3 className="mb-1 text-lg font-medium">Email notifications</h3>
<p className="text-sm text-muted-foreground">
Get notified when new alerts are created.
</p>
</div>
<Label className="block">To email(s)</Label>
<Input placeholder="name@example.com" defaultValue={pb.authStore.model?.email} />
<p className="text-[0.8rem] text-muted-foreground">
Separate multiple emails with commas.
</p>
</div>
<Separator />
<div className="space-y-4">
<div>
<h3 className="mb-1 text-lg font-medium">Webhook / Push notifications</h3>
<p className="text-sm text-muted-foreground">
Beszel uses{' '}
<a
href="https://containrrr.dev/shoutrrr/v0.8/services/overview/"
target="_blank"
className="font-medium text-primary opacity-80 hover:opacity-100 duration-100"
>
Shoutrrr
</a>{' '}
to integrate with popular notification services.
</p>
</div>
<Card className="bg-muted/30 p-3.5">
<div className="flex items-center gap-1">
<Label htmlFor="name" className="sr-only">
URL
</Label>
<Input
id="name"
name="name"
className="light:bg-card"
required
placeholder="generic://example.com?@header=value"
/>
<Button
type="button"
variant="outline"
className=""
// onClick={() => append({ value: '' })}
>
Test
</Button>
<Button
type="button"
variant="outline"
size="icon"
className="shrink-0"
// onClick={() => append({ value: '' })}
>
<Trash2Icon className="h-4 w-4" />
</Button>
{/* <Label htmlFor="enabled-01" className="sr-only">
Enabled
</Label>
<Switch defaultChecked id="enabled-01" className="ml-2" /> */}
</div>
</Card>
<Button
type="button"
variant="outline"
size="sm"
className="mt-2"
// onClick={() => append({ value: '' })}
>
Add URL
</Button>
</div>
<Separator />
<Button type="submit">Save settings</Button>
</div>
</div>
)
}

View File

@@ -0,0 +1,40 @@
import { cn } from '@/lib/utils'
import { buttonVariants } from '../../ui/button'
import { $router, Link } from '../../router'
import { useStore } from '@nanostores/react'
import React from 'react'
interface SidebarNavProps extends React.HTMLAttributes<HTMLElement> {
items: {
href: string
title: string
icon?: React.FC<React.SVGProps<SVGSVGElement>>
}[]
}
export function SidebarNav({ className, items, ...props }: SidebarNavProps) {
const page = useStore($router)
return (
<nav
className={cn('flex space-x-2 lg:flex-col lg:space-x-0 lg:space-y-1', className)}
{...props}
>
{items.map((item) => (
<Link
key={item.href}
href={item.href}
className={cn(
buttonVariants({ variant: 'ghost' }),
'flex items-center gap-3',
page?.path === item.href ? 'bg-muted hover:bg-muted' : 'hover:bg-muted/50',
'justify-start'
)}
>
{item.icon && <item.icon className="h-4 w-4" />}
{item.title}
</Link>
))}
</nav>
)
}

View File

@@ -1,5 +1,5 @@
import './index.css'
import React, { Suspense, lazy, useEffect } from 'react'
import { Suspense, lazy, useEffect } from 'react'
import ReactDOM from 'react-dom/client'
import Home from './components/routes/home.tsx'
import { ThemeProvider } from './components/theme-provider.tsx'
@@ -27,6 +27,7 @@ import {
LogOutIcon,
LogsIcon,
ServerIcon,
SettingsIcon,
UserIcon,
UsersIcon,
} from 'lucide-react'
@@ -42,7 +43,7 @@ import {
DropdownMenuTrigger,
DropdownMenuLabel,
} from './components/ui/dropdown-menu.tsx'
import { $router, Link, navigate } from './components/router.tsx'
import { $router, Link } from './components/router.tsx'
import SystemDetail from './components/routes/system.tsx'
import { AddSystemButton } from './components/add-system.tsx'
@@ -50,6 +51,7 @@ import { AddSystemButton } from './components/add-system.tsx'
const CommandPalette = lazy(() => import('./components/command-palette.tsx'))
const LoginPage = lazy(() => import('./components/login/login.tsx'))
const CopyToClipboardDialog = lazy(() => import('./components/copy-to-clipboard.tsx'))
const Settings = lazy(() => import('./components/routes/settings/layout.tsx'))
const App = () => {
const page = useStore($router)
@@ -99,6 +101,12 @@ const App = () => {
return <Home />
} else if (page.route === 'server') {
return <SystemDetail name={page.params.name} />
} else if (page.path.startsWith('/settings')) {
return (
<Suspense>
<Settings />
</Suspense>
)
}
}
@@ -118,20 +126,19 @@ const Layout = () => {
<>
<div className="container">
<div className="flex items-center h-14 md:h-16 bg-card px-4 pr-3 sm:px-6 border bt-0 rounded-md my-4">
<Link
href="/"
aria-label="Home"
className={'p-2 pl-0'}
onClick={(e) => {
e.preventDefault()
navigate('/')
}}
>
<Link href="/" aria-label="Home" className={'p-2 pl-0'}>
<Logo className="h-[1.15em] fill-foreground" />
</Link>
<div className={'flex ml-auto items-center'}>
<ModeToggle />
<Link
href="/settings/general"
aria-label="Settings"
className={cn('', buttonVariants({ variant: 'ghost', size: 'icon' }))}
>
<SettingsIcon className="h-[1.2rem] w-[1.2rem]" />
</Link>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button

View File

@@ -94,5 +94,10 @@ module.exports = {
},
},
},
plugins: [require('tailwindcss-animate')],
plugins: [
require('tailwindcss-animate'),
function ({ addVariant }) {
addVariant('light', '.light &')
},
],
}