feat: tempcommit

This commit is contained in:
Maximilian Liebmann 2025-06-26 20:25:13 +02:00
parent 7691bd2fac
commit 6619f0a9eb
5 changed files with 190 additions and 71 deletions

View file

@ -9,6 +9,7 @@
--font-heading: 'Comfortaa', sans-serif; --font-heading: 'Comfortaa', sans-serif;
--font-label: 'Varela Round', sans-serif; --font-label: 'Varela Round', sans-serif;
--font-button: 'Varela Round', sans-serif; --font-button: 'Varela Round', sans-serif;
--font-sans: var(--font-label);
--transparent: transparent; --transparent: transparent;
@ -28,7 +29,7 @@
--background: var(--neutral-800); --background: var(--neutral-800);
--background-reversed: var(--neutral-000); --background-reversed: var(--neutral-000);
--base: var(--neutral-800); --basecl: var(--neutral-800);
--text: var(--neutral-000); --text: var(--neutral-000);
--text-alt: var(--neutral-900); --text-alt: var(--neutral-900);
--text-input: var(--text); --text-input: var(--text);
@ -49,11 +50,23 @@
--active-secondary: oklch(0.4254 0.133 272.15); --active-secondary: oklch(0.4254 0.133 272.15);
--disabled-secondary: oklch(0.4937 0.1697 271.26 / 0.5); --disabled-secondary: oklch(0.4937 0.1697 271.26 / 0.5);
--destructive: oklch(60.699% 0.20755 25.945);
--hover-destructive: oklch(60.699% 0.20755 25.945 / 0.8);
--active-destructive: oklch(50.329% 0.17084 25.842);
--disabled-destructive: oklch(60.699% 0.20755 25.945 / 0.4);
--muted: var(--color-neutral-700); --muted: var(--color-neutral-700);
--hover-muted: var(--color-neutral-600); --hover-muted: var(--color-neutral-600);
--active-muted: var(--color-neutral-400); --active-muted: var(--color-neutral-400);
--disabled-muted: var(--color-neutral-400); --disabled-muted: var(--color-neutral-400);
--toaster-default-bg: var(--color-neutral-150);
--toaster-success-bg: oklch(54.147% 0.09184 144.208);
--toaster-error-bg: oklch(52.841% 0.10236 27.274);
--toaster-info-bg: oklch(44.298% 0.05515 259.369);
--toaster-warning-bg: oklch(61.891% 0.07539 102.943);
--toaster-notification-bg: var(--color-neutral-150);
--card: var(--neutral-800); --card: var(--neutral-800);
--sidebar-width-icon: 32px; --sidebar-width-icon: 32px;
@ -80,8 +93,6 @@
--accent-foreground: oklch(0.21 0.034 264.665); --accent-foreground: oklch(0.21 0.034 264.665);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.928 0.006 264.531); --border: oklch(0.928 0.006 264.531);
--input: oklch(0.928 0.006 264.531); --input: oklch(0.928 0.006 264.531);
@ -115,6 +126,62 @@
--sidebar-ring: oklch(0.707 0.022 261.325); --sidebar-ring: oklch(0.707 0.022 261.325);
} }
h1 {
font-family: var(--font-heading);
font-size: 40px;
font-style: normal;
font-weight: 700;
line-height: normal;
}
h2 {
font-family: var(--font-heading);
font-size: 36px;
font-style: normal;
font-weight: 600;
line-height: normal;
}
h3 {
font-family: var(--font-heading);
font-size: 32px;
font-style: normal;
font-weight: 600;
line-height: normal;
}
h4 {
font-family: var(--font-heading);
font-size: 28px;
font-style: normal;
font-weight: 600;
line-height: normal;
}
h5 {
font-family: var(--font-heading);
font-size: 26px;
font-style: normal;
font-weight: 600;
line-height: normal;
}
h6 {
font-family: var(--font-heading);
font-size: 20px;
font-style: normal;
font-weight: 600;
line-height: normal;
}
p {
font-family: var(--font-label);
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: normal;
}
@font-face { @font-face {
font-family: 'Comfortaa'; font-family: 'Comfortaa';
font-style: normal; font-style: normal;
@ -153,7 +220,7 @@
--color-background: var(--neutral-750); --color-background: var(--neutral-750);
--color-background-reversed: var(--background-reversed); --color-background-reversed: var(--background-reversed);
--color-base: var(--neutral-800); --color-basecl: var(--neutral-800);
--color-text: var(--text); --color-text: var(--text);
--color-text-alt: var(--text-alt); --color-text-alt: var(--text-alt);
--color-text-input: var(--text-input); --color-text-input: var(--text-input);
@ -175,11 +242,23 @@
--color-active-secondary: var(--active-secondary); --color-active-secondary: var(--active-secondary);
--color-disabled-secondary: var(--disabled-secondary); --color-disabled-secondary: var(--disabled-secondary);
--color-destructive: var(--destructive);
--color-hover-destructive: var(--hover-destructive);
--color-active-destructive: var(--active-destructive);
--color-disabled-destructive: var(--disabled-destructive);
--color-muted: var(--muted); --color-muted: var(--muted);
--color-hover-muted: var(--hover-muted); --color-hover-muted: var(--hover-muted);
--color-active-muted: var(--active-muted); --color-active-muted: var(--active-muted);
--color-disabled-muted: var(--disabled-muted); --color-disabled-muted: var(--disabled-muted);
--color-toaster-default-bg: var(--toaster-default-bg);
--color-toaster-success-bg: var(--toaster-success-bg);
--color-toaster-error-bg: var(--toaster-error-bg);
--color-toaster-info-bg: var(--toaster-info-bg);
--color-toaster-warning-bg: var(--toaster-warning-bg);
--color-toaster-notification-bg: var(--toaster-notification-bg);
/* Custom values */ /* Custom values */
--radius-sm: calc(var(--radius) - 4px); --radius-sm: calc(var(--radius) - 4px);
@ -220,8 +299,6 @@
--color-accent-foreground: var(--accent-foreground); --color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border); --color-border: var(--border);
--color-input: var(--input); --color-input: var(--input);
@ -277,7 +354,7 @@
--background: var(--neutral-750); --background: var(--neutral-750);
--background-reversed: var(--neutral-000); --background-reversed: var(--neutral-000);
--base: var(--neutral-750); --basecl: var(--neutral-750);
--text: var(--neutral-000); --text: var(--neutral-000);
--text-alt: var(--neutral-900); --text-alt: var(--neutral-900);
--text-input: var(--text); --text-input: var(--text);
@ -297,11 +374,23 @@
--active-secondary: oklch(0.4471 0.15 271.61); --active-secondary: oklch(0.4471 0.15 271.61);
--disabled-secondary: oklch(0.6065 0.213 271.11 / 0.4); --disabled-secondary: oklch(0.6065 0.213 271.11 / 0.4);
--destructive: oklch(0.58 0.2149 27.13);
--hover-destructive: oklch(0.58 0.2149 27.13 / 0.8);
--active-destructive: oklch(45.872% 0.16648 26.855);
--disabled-destructive: oklch(0.58 0.2149 27.13 / 0.4);
--muted: var(--color-neutral-650); --muted: var(--color-neutral-650);
--hover-muted: var(--color-neutral-500); --hover-muted: var(--color-neutral-500);
--active-muted: var(--color-neutral-400); --active-muted: var(--color-neutral-400);
--disabled-muted: var(--color-neutral-400); --disabled-muted: var(--color-neutral-400);
--toaster-default-bg: var(--color-neutral-150);
--toaster-success-bg: var(--color-green-200);
--toaster-error-bg: var(--color-red-200);
--toaster-info-bg: var(--color-blue-200);
--toaster-warning-bg: var(--color-yellow-200);
--toaster-notification-bg: var(--color-neutral-150);
--card: var(--neutral-750); --card: var(--neutral-750);
/* ------------------- */ /* ------------------- */
@ -326,8 +415,6 @@
--accent-foreground: oklch(0.985 0.002 247.839); --accent-foreground: oklch(0.985 0.002 247.839);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%); --border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%); --input: oklch(1 0 0 / 15%);

View file

@ -1,5 +1,8 @@
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import React from 'react';
import { Button } from '../ui/button';
import { Eye, EyeOff } from 'lucide-react';
export default function LabeledInput({ export default function LabeledInput({
type, type,
@ -11,7 +14,7 @@ export default function LabeledInput({
error, error,
...rest ...rest
}: { }: {
type: 'text' | 'email' | 'password'; type: 'text' | 'email' | 'password' | 'file';
label: string; label: string;
placeholder?: string; placeholder?: string;
value?: string; value?: string;
@ -19,19 +22,33 @@ export default function LabeledInput({
autocomplete?: string; autocomplete?: string;
error?: string; error?: string;
} & React.InputHTMLAttributes<HTMLInputElement>) { } & React.InputHTMLAttributes<HTMLInputElement>) {
const [passwordVisible, setPasswordVisible] = React.useState(false);
return ( return (
<div className='grid grid-cols-1 gap-1'> <div className='grid grid-cols-1 gap-1'>
<Label htmlFor={name}>{label}</Label> <Label htmlFor={name}>{label}</Label>
<span className='relative'>
<Input <Input
type={type} className={type === 'password' ? 'pr-[50px]' : ''}
placeholder={placeholder} type={passwordVisible ? 'text' : type}
defaultValue={value} placeholder={placeholder}
id={name} defaultValue={value}
name={name} id={name}
autoComplete={autocomplete} name={name}
{...rest} autoComplete={autocomplete}
/> {...rest}
/>
{type === 'password' && (
<Button
className='absolute right-0 top-0 w-[36px] h-[36px]'
type='button'
variant={'outline_muted'}
onClick={() => setPasswordVisible((visible) => !visible)}
>
{passwordVisible ? <Eye /> : <EyeOff />}
</Button>
)}
</span>
{error && <p className='text-red-500 text-sm mt-1'>{error}</p>} {error && <p className='text-red-500 text-sm mt-1'>{error}</p>}
</div> </div>
); );

View file

@ -45,13 +45,13 @@ const settingsSections: SettingsSection[] = [
{ {
label: 'Account', label: 'Account',
value: 'general', value: 'general',
description: 'Manage your account details and preferences', description: 'Manage account details',
icon: User, icon: User,
}, },
{ {
label: 'Notifications', label: 'Notifications',
value: 'notifications', value: 'notifications',
description: 'Choose how you want to be notified', description: 'Choose notification Preferences',
icon: Bell, icon: Bell,
}, },
{ {

View file

@ -27,6 +27,9 @@ import { useGetApiUserMe } from '@/generated/api/user/user';
import { ThemePicker } from './theme-picker'; import { ThemePicker } from './theme-picker';
import LabeledInput from '../custom-ui/labeled-input'; import LabeledInput from '../custom-ui/labeled-input';
import { GroupWrapper } from '../wrappers/group-wrapper'; import { GroupWrapper } from '../wrappers/group-wrapper';
import { Avatar } from '../ui/avatar';
import Image from 'next/image';
import { User } from 'lucide-react';
export default function SettingsPage() { export default function SettingsPage() {
const router = useRouter(); const router = useRouter();
@ -43,51 +46,50 @@ export default function SettingsPage() {
<CardTitle>Account Settings</CardTitle> <CardTitle>Account Settings</CardTitle>
</CardHeader> </CardHeader>
<CardContent className='space-y-6 mt-2'> <CardContent className='space-y-6 mt-2'>
<GroupWrapper legend='General Settings'> <GroupWrapper title='General Settings'>
<div className='space-y-4'> <div className='space-y-4'>
<div className='flex items-center justify-evenly'> <div className='flex items-center justify-evenly'>
<div> <div>
<Label htmlFor='displayName'>First Name</Label> <LabeledInput
<Input label='First Name'
id='displayName' type='text'
placeholder='Your Name' placeholder='First Name'
defaultValue={data?.data.user.first_name ?? ''} defaultValue={data?.data.user.first_name ?? ''}
/> ></LabeledInput>
</div> </div>
<div> <div>
<Label htmlFor='displayName'>Last Name</Label> <LabeledInput
<Input label='Last Name'
id='displayName' type='text'
placeholder='Your Name' placeholder='Last Name'
defaultValue={data?.data.user.last_name ?? ''} defaultValue={data?.data.user.last_name ?? ''}
/> ></LabeledInput>
</div> </div>
</div> </div>
<div className='space-y-2'> <div className='space-y-2'>
<Label htmlFor='displayName'>Display Name</Label> <LabeledInput
<Input label='Display Name'
id='displayName' type='text'
placeholder='Your Name' placeholder='Display Name'
defaultValue={data?.data.user.name} defaultValue={data?.data.user.name}
/> ></LabeledInput>
</div> </div>
<div className='space-y-2'> <div className='space-y-2 space-b-2'>
<Label htmlFor='email'>Email Address</Label> <LabeledInput
<Input
id='email'
type='email' type='email'
placeholder='your.email@example.com' label='Email Address'
readOnly placeholder='Your E-Mail'
defaultValue={data?.data.user.email} defaultValue={data?.data.user.email ?? ''}
/> ></LabeledInput>
<p className='text-sm text-muted-foreground'>
<span className='text-sm text-muted-foreground'>
Email might be managed by your SSO provider. Email might be managed by your SSO provider.
</p> </span>
</div> </div>
</div> </div>
</GroupWrapper> </GroupWrapper>
<GroupWrapper legend='Reset Password'> <GroupWrapper title='Reset Password'>
<div className='flex items-center justify-evenly'> <div className='flex items-center justify-evenly sm:flex-row flex-col gap-6'>
<div> <div>
<LabeledInput <LabeledInput
type='password' type='password'
@ -114,12 +116,25 @@ export default function SettingsPage() {
</div> </div>
</div> </div>
</GroupWrapper> </GroupWrapper>
<div className='space-y-2'> <div className='space-y-2 grid grid-cols-[1fr_auto] gap-4'>
<Label htmlFor='profilePicture'>Profile Picture</Label> <LabeledInput
<Input id='profilePicture' type='file' /> label='Profile Picture'
<p className='text-sm text-muted-foreground'> type='file'
Upload a new profile picture. placeholder='Upload Profile Picture'
</p> defaultValue={data?.data.user.image ?? ''}
></LabeledInput>
<Avatar className='flex justify-center items-center'>
{data?.data.user.image ? (
<Image
src={data?.data.user.image}
alt='Avatar'
width='20'
height='20'
/>
) : (
<User />
)}
</Avatar>
</div> </div>
<div className='space-y-2'> <div className='space-y-2'>
<Label htmlFor='timezone'>Timezone</Label> <Label htmlFor='timezone'>Timezone</Label>
@ -143,9 +158,9 @@ export default function SettingsPage() {
</div> </div>
<div className='pt-4'> <div className='pt-4'>
<Button variant='secondary'>Delete Account</Button> <Button variant='secondary'>Delete Account</Button>
<p className='text-sm text-muted-foreground pt-1'> <span className='text-sm text-muted-foreground pt-1'>
Permanently delete your account and all associated data. Permanently delete your account and all associated data.
</p> </span>
</div> </div>
</CardContent> </CardContent>
</ScrollableSettingsWrapper> </ScrollableSettingsWrapper>
@ -277,10 +292,10 @@ export default function SettingsPage() {
</legend> </legend>
<div className='space-y-2'> <div className='space-y-2'>
<Label>Working Hours</Label> <Label>Working Hours</Label>
<p className='text-sm text-muted-foreground'> <span className='text-sm text-muted-foreground'>
Define your typical available hours (e.g., Monday-Friday, Define your typical available hours (e.g., Monday-Friday,
9 AM - 5 PM). 9 AM - 5 PM).
</p> </span>
<Button variant='outline_muted' size='sm'> <Button variant='outline_muted' size='sm'>
Set Working Hours Set Working Hours
</Button> </Button>
@ -289,9 +304,9 @@ export default function SettingsPage() {
<Label htmlFor='minNoticeBooking'> <Label htmlFor='minNoticeBooking'>
Minimum Notice for Bookings Minimum Notice for Bookings
</Label> </Label>
<p className='text-sm text-muted-foreground'> <span className='text-sm text-muted-foreground'>
Min time before a booking can be made. Min time before a booking can be made.
</p> </span>
<div className='space-y-2'> <div className='space-y-2'>
<Input <Input
id='bookingWindow' id='bookingWindow'
@ -304,9 +319,9 @@ export default function SettingsPage() {
<Label htmlFor='bookingWindow'> <Label htmlFor='bookingWindow'>
Booking Window (days in advance) Booking Window (days in advance)
</Label> </Label>
<p className='text-sm text-muted-foreground'> <span className='text-sm text-muted-foreground'>
Max time in advance a booking can be made. Max time in advance a booking can be made.
</p> </span>
<Input <Input
id='bookingWindow' id='bookingWindow'
type='number' type='number'
@ -378,7 +393,7 @@ export default function SettingsPage() {
<Label htmlFor='whoCanSeeFull'> <Label htmlFor='whoCanSeeFull'>
Who Can See Your Full Calendar Details? Who Can See Your Full Calendar Details?
</Label> </Label>
<p className='text-sm text-muted-foreground'> <span className='text-sm text-muted-foreground'>
(Override for Default Visibility) (Override for Default Visibility)
<br /> <br />
<span className='text-sm text-muted-foreground'> <span className='text-sm text-muted-foreground'>
@ -386,7 +401,7 @@ export default function SettingsPage() {
calendar. You can set specific friends or groups to see calendar. You can set specific friends or groups to see
your full calendar details. your full calendar details.
</span> </span>
</p> </span>
<Select> <Select>
<SelectTrigger id='whoCanSeeFull'> <SelectTrigger id='whoCanSeeFull'>
<SelectValue placeholder='Select audience' /> <SelectValue placeholder='Select audience' />
@ -420,10 +435,10 @@ export default function SettingsPage() {
<div className='space-y-2'> <div className='space-y-2'>
<Label>Blocked Users</Label> <Label>Blocked Users</Label>
<Button variant='outline_muted'>Manage Blocked Users</Button> <Button variant='outline_muted'>Manage Blocked Users</Button>
<p className='text-sm text-muted-foreground'> <span className='text-sm text-muted-foreground'>
Prevent specific users from seeing your calendar or booking Prevent specific users from seeing your calendar or booking
time. time.
</p> </span>
</div> </div>
</CardContent> </CardContent>
</ScrollableSettingsWrapper> </ScrollableSettingsWrapper>

View file

@ -3,20 +3,20 @@ import type * as React from 'react';
interface ScrollableSettingsWrapperProps { interface ScrollableSettingsWrapperProps {
className?: string; className?: string;
legend?: string; title?: string;
children: React.ReactNode; children: React.ReactNode;
} }
export function GroupWrapper({ export function GroupWrapper({
className, className,
legend, title,
children, children,
}: ScrollableSettingsWrapperProps) { }: ScrollableSettingsWrapperProps) {
return ( return (
<fieldset <fieldset
className={cn('space-t-4 p-4 border rounded-md shadow-md', className)} className={cn('space-t-4 p-4 border rounded-md shadow-md', className)}
> >
<legend className='text-sm font-medium px-1'>{legend}</legend> <legend className='text-sm font-medium px-1'>{title}</legend>
{children} {children}
</fieldset> </fieldset>
); );