feat: tempcommit (broken input)
This commit is contained in:
parent
7d2d5c55e8
commit
6435bd4a8e
6 changed files with 349 additions and 215 deletions
|
@ -1,19 +1,20 @@
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { LucideProps } from 'lucide-react';
|
||||||
import { IconProp } from '@fortawesome/fontawesome-svg-core';
|
import React, { ForwardRefExoticComponent, RefAttributes } from 'react';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
||||||
|
|
||||||
export function IconButton({
|
export function IconButton({
|
||||||
icon,
|
icon,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: {
|
}: {
|
||||||
icon: IconProp;
|
icon?: ForwardRefExoticComponent<
|
||||||
|
Omit<LucideProps, 'ref'> & RefAttributes<SVGSVGElement>
|
||||||
|
>;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
} & React.ComponentProps<typeof Button>) {
|
} & React.ComponentProps<typeof Button>) {
|
||||||
return (
|
return (
|
||||||
<Button type='button' variant='secondary' {...props}>
|
<Button type='button' variant='secondary' {...props}>
|
||||||
<FontAwesomeIcon icon={icon} className='mr-2' />
|
{icon && React.createElement(icon, { className: 'mr-2' })}
|
||||||
{children}
|
{children}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { signIn } from '@/auth';
|
import { signIn } from '@/auth';
|
||||||
import { IconButton } from '@/components/buttons/icon-button';
|
import { IconButton } from '@/components/buttons/icon-button';
|
||||||
import { faOpenid } from '@fortawesome/free-brands-svg-icons';
|
import { Fingerprint, ScanEye } from 'lucide-react';
|
||||||
|
|
||||||
export default function SSOLogin({
|
export default function SSOLogin({
|
||||||
provider,
|
provider,
|
||||||
|
@ -22,7 +22,7 @@ export default function SSOLogin({
|
||||||
className='w-full'
|
className='w-full'
|
||||||
type='submit'
|
type='submit'
|
||||||
variant='secondary'
|
variant='secondary'
|
||||||
icon={faOpenid}
|
icon={Fingerprint}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
Login with {providerDisplayName}
|
Login with {providerDisplayName}
|
||||||
|
|
|
@ -62,18 +62,20 @@ export function AppSidebar() {
|
||||||
<>
|
<>
|
||||||
<Sidebar collapsible='icon' variant='sidebar'>
|
<Sidebar collapsible='icon' variant='sidebar'>
|
||||||
<SidebarHeader className='overflow-hidden'>
|
<SidebarHeader className='overflow-hidden'>
|
||||||
<Logo
|
<Link href='/home'>
|
||||||
colorType='colored'
|
<Logo
|
||||||
logoType='combo'
|
colorType='colored'
|
||||||
height={50}
|
logoType='combo'
|
||||||
className='group-data-[collapsible=icon]:hidden min-w-[203px]'
|
height={50}
|
||||||
></Logo>
|
className='group-data-[collapsible=icon]:hidden min-w-[203px]'
|
||||||
<Logo
|
></Logo>
|
||||||
colorType='colored'
|
<Logo
|
||||||
logoType='submark'
|
colorType='colored'
|
||||||
height={50}
|
logoType='submark'
|
||||||
className='group-data-[collapsible=]:hidden group-data-[mobile=true]/mobile:hidden'
|
height={50}
|
||||||
></Logo>
|
className='group-data-[collapsible=]:hidden group-data-[mobile=true]/mobile:hidden'
|
||||||
|
></Logo>
|
||||||
|
</Link>
|
||||||
</SidebarHeader>
|
</SidebarHeader>
|
||||||
<SidebarContent className='grid grid-rows-[auto_1fr_auto]'>
|
<SidebarContent className='grid grid-rows-[auto_1fr_auto]'>
|
||||||
<Collapsible defaultOpen className='group/collapsible'>
|
<Collapsible defaultOpen className='group/collapsible'>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { Input, Textarea } from '@/components/ui/input';
|
import { Input, Textarea } from '@/components/ui/input';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import React from 'react';
|
import React, { ForwardRefExoticComponent, RefAttributes } from 'react';
|
||||||
import { Button } from '../ui/button';
|
import { Button } from '../ui/button';
|
||||||
import { Eye, EyeOff } from 'lucide-react';
|
import { Eye, EyeOff, LucideProps } from 'lucide-react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
export default function LabeledInput({
|
export default function LabeledInput({
|
||||||
|
@ -12,6 +12,7 @@ export default function LabeledInput({
|
||||||
placeholder,
|
placeholder,
|
||||||
value,
|
value,
|
||||||
name,
|
name,
|
||||||
|
icon,
|
||||||
variantSize = 'default',
|
variantSize = 'default',
|
||||||
autocomplete,
|
autocomplete,
|
||||||
error,
|
error,
|
||||||
|
@ -22,11 +23,22 @@ export default function LabeledInput({
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
value?: string;
|
value?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
|
icon?: ForwardRefExoticComponent<
|
||||||
|
Omit<LucideProps, 'ref'> & RefAttributes<SVGSVGElement>
|
||||||
|
>;
|
||||||
variantSize?: 'default' | 'big' | 'textarea';
|
variantSize?: 'default' | 'big' | 'textarea';
|
||||||
autocomplete?: string;
|
autocomplete?: string;
|
||||||
error?: string;
|
error?: string;
|
||||||
} & React.InputHTMLAttributes<HTMLInputElement>) {
|
} & React.InputHTMLAttributes<HTMLInputElement>) {
|
||||||
const [passwordVisible, setPasswordVisible] = React.useState(false);
|
const [passwordVisible, setPasswordVisible] = React.useState(false);
|
||||||
|
const [inputValue, setInputValue] = React.useState(value || '');
|
||||||
|
|
||||||
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setInputValue(e.target.value);
|
||||||
|
if (rest.onChange) {
|
||||||
|
rest.onChange(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='grid grid-cols-1 gap-1'>
|
<div className='grid grid-cols-1 gap-1'>
|
||||||
|
@ -50,18 +62,32 @@ export default function LabeledInput({
|
||||||
className={cn(
|
className={cn(
|
||||||
type === 'password' ? 'pr-[50px]' : '',
|
type === 'password' ? 'pr-[50px]' : '',
|
||||||
variantSize === 'big'
|
variantSize === 'big'
|
||||||
? 'h-12 file:h-10 text-lg gplaceholder:text-lg sm:text-2xl sm:placeholder:text-2xl'
|
? 'h-12 file:h-10 text-lg placeholder:text-lg sm:text-2xl sm:placeholder:text-2xl'
|
||||||
: '',
|
: '',
|
||||||
|
icon && inputValue === '' ? 'pl-10' : '',
|
||||||
|
'transition-all duration-300 ease-in-out',
|
||||||
)}
|
)}
|
||||||
type={passwordVisible ? 'text' : type}
|
type={passwordVisible ? 'text' : type}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
defaultValue={value}
|
value={inputValue}
|
||||||
id={name}
|
id={name}
|
||||||
name={name}
|
name={name}
|
||||||
autoComplete={autocomplete}
|
autoComplete={autocomplete}
|
||||||
|
onChange={handleInputChange}
|
||||||
{...rest}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
|
{icon && (
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
'absolute left-3 top-1/2 -translate-y-1/2 text-muted-input transition-all duration-300 ease-in-out',
|
||||||
|
inputValue === ''
|
||||||
|
? 'opacity-100 scale-100'
|
||||||
|
: 'opacity-0 scale-75 pointer-events-none',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{React.createElement(icon)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
{type === 'password' && (
|
{type === 'password' && (
|
||||||
<Button
|
<Button
|
||||||
className='absolute right-0 top-0 w-[36px] h-[36px]'
|
className='absolute right-0 top-0 w-[36px] h-[36px]'
|
||||||
|
@ -74,7 +100,6 @@ export default function LabeledInput({
|
||||||
)}
|
)}
|
||||||
</span>
|
</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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,11 +4,21 @@ import React, { useState, useRef } from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
import LabeledInput from '@/components/custom-ui/labeled-input';
|
import LabeledInput from '@/components/custom-ui/labeled-input';
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import useZodForm from '@/lib/hooks/useZodForm';
|
import useZodForm from '@/lib/hooks/useZodForm';
|
||||||
import { loginSchema, registerSchema } from '@/lib/auth/validation';
|
import { loginSchema, registerSchema } from '@/lib/auth/validation';
|
||||||
import { loginAction } from '@/lib/auth/login';
|
import { loginAction } from '@/lib/auth/login';
|
||||||
import { registerAction } from '@/lib/auth/register';
|
import { registerAction } from '@/lib/auth/register';
|
||||||
|
import { IconButton } from '../buttons/icon-button';
|
||||||
|
import {
|
||||||
|
FileKey,
|
||||||
|
FileKey2,
|
||||||
|
LogIn,
|
||||||
|
MailOpen,
|
||||||
|
RotateCcwKey,
|
||||||
|
UserCheck,
|
||||||
|
UserPen,
|
||||||
|
UserPlus,
|
||||||
|
} from 'lucide-react';
|
||||||
|
|
||||||
function LoginFormElement({
|
function LoginFormElement({
|
||||||
setIsSignUp,
|
setIsSignUp,
|
||||||
|
@ -56,6 +66,7 @@ function LoginFormElement({
|
||||||
<LabeledInput
|
<LabeledInput
|
||||||
type='text'
|
type='text'
|
||||||
label='E-Mail or Username'
|
label='E-Mail or Username'
|
||||||
|
icon={UserCheck}
|
||||||
placeholder='What you are known as'
|
placeholder='What you are known as'
|
||||||
error={formState.errors.email?.message}
|
error={formState.errors.email?.message}
|
||||||
{...register('email')}
|
{...register('email')}
|
||||||
|
@ -64,16 +75,22 @@ function LoginFormElement({
|
||||||
<LabeledInput
|
<LabeledInput
|
||||||
type='password'
|
type='password'
|
||||||
label='Password'
|
label='Password'
|
||||||
|
icon={FileKey}
|
||||||
placeholder="Let's hope you remember it"
|
placeholder="Let's hope you remember it"
|
||||||
error={formState.errors.password?.message}
|
error={formState.errors.password?.message}
|
||||||
{...register('password')}
|
{...register('password')}
|
||||||
data-cy='password-input'
|
data-cy='password-input'
|
||||||
/>
|
/>
|
||||||
<div className='grid grid-rows-2 gap-2'>
|
<div className='grid grid-rows-2 gap-2'>
|
||||||
<Button type='submit' variant='primary' data-cy='login-button'>
|
<IconButton
|
||||||
|
type='submit'
|
||||||
|
variant='primary'
|
||||||
|
data-cy='login-button'
|
||||||
|
icon={LogIn}
|
||||||
|
>
|
||||||
Login
|
Login
|
||||||
</Button>
|
</IconButton>
|
||||||
<Button
|
<IconButton
|
||||||
type='button'
|
type='button'
|
||||||
variant='outline_primary'
|
variant='outline_primary'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -81,9 +98,10 @@ function LoginFormElement({
|
||||||
setIsSignUp((v) => !v);
|
setIsSignUp((v) => !v);
|
||||||
}}
|
}}
|
||||||
data-cy='register-switch'
|
data-cy='register-switch'
|
||||||
|
icon={UserPlus}
|
||||||
>
|
>
|
||||||
Sign Up
|
Sign Up
|
||||||
</Button>
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{formState.errors.root?.message && (
|
{formState.errors.root?.message && (
|
||||||
|
@ -156,27 +174,30 @@ function RegisterFormElement({
|
||||||
{...register('lastName')}
|
{...register('lastName')}
|
||||||
data-cy='last-name-input'
|
data-cy='last-name-input'
|
||||||
/>
|
/>
|
||||||
<LabeledInput
|
|
||||||
type='email'
|
|
||||||
label='E-Mail'
|
|
||||||
placeholder='Your email address'
|
|
||||||
autocomplete='email'
|
|
||||||
error={formState.errors.email?.message}
|
|
||||||
{...register('email')}
|
|
||||||
data-cy='email-input'
|
|
||||||
/>
|
|
||||||
<LabeledInput
|
<LabeledInput
|
||||||
type='text'
|
type='text'
|
||||||
label='Username'
|
label='Username'
|
||||||
|
icon={UserPen}
|
||||||
placeholder='Your username'
|
placeholder='Your username'
|
||||||
autocomplete='username'
|
autocomplete='username'
|
||||||
error={formState.errors.username?.message}
|
error={formState.errors.username?.message}
|
||||||
{...register('username')}
|
{...register('username')}
|
||||||
data-cy='username-input'
|
data-cy='username-input'
|
||||||
/>
|
/>
|
||||||
|
<LabeledInput
|
||||||
|
type='email'
|
||||||
|
label='E-Mail'
|
||||||
|
icon={MailOpen}
|
||||||
|
placeholder='Your email address'
|
||||||
|
autocomplete='email'
|
||||||
|
error={formState.errors.email?.message}
|
||||||
|
{...register('email')}
|
||||||
|
data-cy='email-input'
|
||||||
|
/>
|
||||||
<LabeledInput
|
<LabeledInput
|
||||||
type='password'
|
type='password'
|
||||||
label='Password'
|
label='Password'
|
||||||
|
icon={FileKey2}
|
||||||
placeholder='Create a password'
|
placeholder='Create a password'
|
||||||
autocomplete='new-password'
|
autocomplete='new-password'
|
||||||
error={formState.errors.password?.message}
|
error={formState.errors.password?.message}
|
||||||
|
@ -186,6 +207,7 @@ function RegisterFormElement({
|
||||||
<LabeledInput
|
<LabeledInput
|
||||||
type='password'
|
type='password'
|
||||||
label='Confirm Password'
|
label='Confirm Password'
|
||||||
|
icon={RotateCcwKey}
|
||||||
placeholder='Repeat your password'
|
placeholder='Repeat your password'
|
||||||
autocomplete='new-password'
|
autocomplete='new-password'
|
||||||
error={formState.errors.confirmPassword?.message}
|
error={formState.errors.confirmPassword?.message}
|
||||||
|
@ -193,19 +215,25 @@ function RegisterFormElement({
|
||||||
data-cy='confirm-password-input'
|
data-cy='confirm-password-input'
|
||||||
/>
|
/>
|
||||||
<div className='grid grid-rows-2 gap-2'>
|
<div className='grid grid-rows-2 gap-2'>
|
||||||
<Button type='submit' variant='primary' data-cy='register-button'>
|
<IconButton
|
||||||
|
type='submit'
|
||||||
|
variant='primary'
|
||||||
|
data-cy='register-button'
|
||||||
|
icon={UserPlus}
|
||||||
|
>
|
||||||
Sign Up
|
Sign Up
|
||||||
</Button>
|
</IconButton>
|
||||||
<Button
|
<IconButton
|
||||||
type='button'
|
type='button'
|
||||||
variant='outline_primary'
|
variant='outline_primary'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
formRef?.current?.reset();
|
formRef?.current?.reset();
|
||||||
setIsSignUp((v) => !v);
|
setIsSignUp((v) => !v);
|
||||||
}}
|
}}
|
||||||
|
icon={LogIn}
|
||||||
>
|
>
|
||||||
Back to Login
|
Back to Login
|
||||||
</Button>
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{formState.errors.root?.message && (
|
{formState.errors.root?.message && (
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from '@/components/ui/card';
|
} from '@/components/ui/card';
|
||||||
import { Input } from '@/components/ui/input';
|
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { ScrollableSettingsWrapper } from '@/components/wrappers/settings-scroll';
|
import { ScrollableSettingsWrapper } from '@/components/wrappers/settings-scroll';
|
||||||
import { Switch } from '@/components/ui/switch';
|
import { Switch } from '@/components/ui/switch';
|
||||||
|
@ -28,8 +28,24 @@ import LabeledInput from '../custom-ui/labeled-input';
|
||||||
import { GroupWrapper } from '../wrappers/group-wrapper';
|
import { GroupWrapper } from '../wrappers/group-wrapper';
|
||||||
|
|
||||||
import ProfilePictureUpload from './profile-picture-upload';
|
import ProfilePictureUpload from './profile-picture-upload';
|
||||||
import { LogOut } from 'lucide-react';
|
import {
|
||||||
import { signOut } from '@/auth';
|
CalendarArrowDown,
|
||||||
|
CalendarArrowUp,
|
||||||
|
CalendarCheck,
|
||||||
|
CalendarClock,
|
||||||
|
CalendarCog,
|
||||||
|
CalendarPlus,
|
||||||
|
CalendarPlus2,
|
||||||
|
ClockAlert,
|
||||||
|
ClockFading,
|
||||||
|
FileKey,
|
||||||
|
FileKey2,
|
||||||
|
MailOpen,
|
||||||
|
RotateCcwKey,
|
||||||
|
UserLock,
|
||||||
|
UserPen,
|
||||||
|
} from 'lucide-react';
|
||||||
|
import { IconButton } from '../buttons/icon-button';
|
||||||
|
|
||||||
export default function SettingsPage() {
|
export default function SettingsPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
@ -46,29 +62,31 @@ export default function SettingsPage() {
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Account Settings</CardTitle>
|
<CardTitle>Account Settings</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className='space-y-6 mt-2'>
|
<CardContent className='space-y-6 my-2'>
|
||||||
|
{/*-------------------- General Settings --------------------*/}
|
||||||
<GroupWrapper title='General Settings'>
|
<GroupWrapper title='General Settings'>
|
||||||
<div className='space-y-4'>
|
<div className='space-y-4'>
|
||||||
<div>
|
<div>
|
||||||
<LabeledInput
|
<LabeledInput
|
||||||
label='First Name'
|
|
||||||
type='text'
|
type='text'
|
||||||
|
label='First Name'
|
||||||
placeholder='First Name'
|
placeholder='First Name'
|
||||||
defaultValue={data?.data.user.first_name ?? ''}
|
defaultValue={data?.data.user.first_name ?? ''}
|
||||||
></LabeledInput>
|
></LabeledInput>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<LabeledInput
|
<LabeledInput
|
||||||
label='Last Name'
|
|
||||||
type='text'
|
type='text'
|
||||||
|
label='Last Name'
|
||||||
placeholder='Last Name'
|
placeholder='Last Name'
|
||||||
defaultValue={data?.data.user.last_name ?? ''}
|
defaultValue={data?.data.user.last_name ?? ''}
|
||||||
></LabeledInput>
|
></LabeledInput>
|
||||||
</div>
|
</div>
|
||||||
<div className='space-y-2'>
|
<div className='space-y-2'>
|
||||||
<LabeledInput
|
<LabeledInput
|
||||||
label='Display Name'
|
|
||||||
type='text'
|
type='text'
|
||||||
|
label='Display Name'
|
||||||
|
icon={UserPen}
|
||||||
placeholder='Display Name'
|
placeholder='Display Name'
|
||||||
defaultValue={data?.data.user.name}
|
defaultValue={data?.data.user.name}
|
||||||
></LabeledInput>
|
></LabeledInput>
|
||||||
|
@ -77,6 +95,7 @@ export default function SettingsPage() {
|
||||||
<LabeledInput
|
<LabeledInput
|
||||||
type='email'
|
type='email'
|
||||||
label='Email Address'
|
label='Email Address'
|
||||||
|
icon={MailOpen}
|
||||||
placeholder='Your E-Mail'
|
placeholder='Your E-Mail'
|
||||||
defaultValue={data?.data.user.email ?? ''}
|
defaultValue={data?.data.user.email ?? ''}
|
||||||
></LabeledInput>
|
></LabeledInput>
|
||||||
|
@ -87,39 +106,42 @@ export default function SettingsPage() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</GroupWrapper>
|
</GroupWrapper>
|
||||||
|
{/*-------------------- General Settings --------------------*/}
|
||||||
|
{/*-------------------- Reset Password --------------------*/}
|
||||||
<GroupWrapper title='Reset Password'>
|
<GroupWrapper title='Reset Password'>
|
||||||
<div className='flex items-center justify-evenly sm:flex-row flex-col gap-6'>
|
<div className='flex items-center justify-evenly sm:flex-row flex-col gap-6'>
|
||||||
<div>
|
<div>
|
||||||
<LabeledInput
|
<LabeledInput
|
||||||
type='password'
|
type='password'
|
||||||
label='Current Password'
|
label='Current Password'
|
||||||
placeholder='Current Password'
|
icon={FileKey}
|
||||||
defaultValue={data?.data.user.first_name ?? ''}
|
|
||||||
></LabeledInput>
|
></LabeledInput>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<LabeledInput
|
<LabeledInput
|
||||||
type='password'
|
type='password'
|
||||||
label='New Password'
|
label='New Password'
|
||||||
placeholder='New Password'
|
icon={FileKey2}
|
||||||
defaultValue={data?.data.user.first_name ?? ''}
|
|
||||||
></LabeledInput>
|
></LabeledInput>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<LabeledInput
|
<LabeledInput
|
||||||
type='password'
|
type='password'
|
||||||
label='Repeat Password'
|
label='Repeat Password'
|
||||||
placeholder='Repeat Password'
|
icon={RotateCcwKey}
|
||||||
defaultValue={data?.data.user.first_name ?? ''}
|
|
||||||
></LabeledInput>
|
></LabeledInput>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</GroupWrapper>
|
</GroupWrapper>
|
||||||
|
{/*-------------------- Reset Password --------------------*/}
|
||||||
|
{/*-------------------- Profile Picture --------------------*/}
|
||||||
<GroupWrapper title='Profile Picture'>
|
<GroupWrapper title='Profile Picture'>
|
||||||
<div className='space-y-2 grid grid-cols-[1fr_auto]'>
|
<div className='space-y-2 grid grid-cols-[1fr_auto]'>
|
||||||
<ProfilePictureUpload className='file:border file:rounded-md file:hover:bg-disabled-destructive' />
|
<ProfilePictureUpload className='file:border file:rounded-md file:hover:bg-disabled-destructive' />
|
||||||
</div>
|
</div>
|
||||||
</GroupWrapper>
|
</GroupWrapper>
|
||||||
|
{/*-------------------- Profile Picture --------------------*/}
|
||||||
|
{/*-------------------- Regional Settings --------------------*/}
|
||||||
<GroupWrapper title='Regional Settings'>
|
<GroupWrapper title='Regional Settings'>
|
||||||
<div className='space-y-2 grid sm:grid-cols-[1fr_auto] sm:flex-row gap-4'>
|
<div className='space-y-2 grid sm:grid-cols-[1fr_auto] sm:flex-row gap-4'>
|
||||||
<div className='grid gap-1'>
|
<div className='grid gap-1'>
|
||||||
|
@ -127,6 +149,7 @@ export default function SettingsPage() {
|
||||||
type='text'
|
type='text'
|
||||||
label='Timezone'
|
label='Timezone'
|
||||||
placeholder='Europe/Berlin'
|
placeholder='Europe/Berlin'
|
||||||
|
icon={CalendarClock}
|
||||||
defaultValue={data?.data.user.timezone ?? ''}
|
defaultValue={data?.data.user.timezone ?? ''}
|
||||||
></LabeledInput>
|
></LabeledInput>
|
||||||
</div>
|
</div>
|
||||||
|
@ -146,23 +169,26 @@ export default function SettingsPage() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</GroupWrapper>
|
</GroupWrapper>
|
||||||
<div className='flex items-center justify-evenly sm:flex-row flex-col gap-6'>
|
{/*-------------------- Regional Settings --------------------*/}
|
||||||
<Button
|
<GroupWrapper title='DANGER ZONE - INSTANT DELETE - NO CONFIRMATION'>
|
||||||
onClick={() => {
|
<div className='flex items-center justify-evenly sm:flex-row flex-col gap-6'>
|
||||||
deleteUser.mutate(undefined, {
|
<Button
|
||||||
onSuccess: () => {
|
onClick={() => {
|
||||||
router.push('/api/logout');
|
deleteUser.mutate(undefined, {
|
||||||
},
|
onSuccess: () => {
|
||||||
});
|
router.push('/api/logout');
|
||||||
}}
|
},
|
||||||
variant='destructive'
|
});
|
||||||
>
|
}}
|
||||||
Delete Account
|
variant='destructive'
|
||||||
</Button>
|
>
|
||||||
<span className='text-sm text-muted-foreground pt-1'>
|
Delete Account
|
||||||
Permanently delete your account and all associated data.
|
</Button>
|
||||||
</span>
|
<span className='text-sm text-muted-foreground pt-1'>
|
||||||
</div>
|
Permanently delete your account and all associated data.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</GroupWrapper>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</ScrollableSettingsWrapper>
|
</ScrollableSettingsWrapper>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -175,8 +201,9 @@ export default function SettingsPage() {
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Notification Preferences</CardTitle>
|
<CardTitle>Notification Preferences</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className='space-y-6'>
|
<CardContent className='space-y-6 my-2'>
|
||||||
<GroupWrapper>
|
{/*-------------------- All --------------------*/}
|
||||||
|
<GroupWrapper title='All'>
|
||||||
<div className='flex items-center justify-between'>
|
<div className='flex items-center justify-between'>
|
||||||
<Label
|
<Label
|
||||||
htmlFor='masterEmailNotifications'
|
htmlFor='masterEmailNotifications'
|
||||||
|
@ -187,7 +214,8 @@ export default function SettingsPage() {
|
||||||
<Switch id='masterEmailNotifications' />
|
<Switch id='masterEmailNotifications' />
|
||||||
</div>
|
</div>
|
||||||
</GroupWrapper>
|
</GroupWrapper>
|
||||||
|
{/*-------------------- All --------------------*/}
|
||||||
|
{/*-------------------- Meetings --------------------*/}
|
||||||
<GroupWrapper title='Meetings'>
|
<GroupWrapper title='Meetings'>
|
||||||
<div className='space-y-4'>
|
<div className='space-y-4'>
|
||||||
<div className='flex items-center justify-between space-x-2'>
|
<div className='flex items-center justify-between space-x-2'>
|
||||||
|
@ -239,6 +267,8 @@ export default function SettingsPage() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</GroupWrapper>
|
</GroupWrapper>
|
||||||
|
{/*-------------------- Meetings --------------------*/}
|
||||||
|
{/*-------------------- Social --------------------*/}
|
||||||
<GroupWrapper title='Social'>
|
<GroupWrapper title='Social'>
|
||||||
<div className='space-y-4'>
|
<div className='space-y-4'>
|
||||||
<div className='flex items-center justify-between space-x-2'>
|
<div className='flex items-center justify-between space-x-2'>
|
||||||
|
@ -255,6 +285,7 @@ export default function SettingsPage() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</GroupWrapper>
|
</GroupWrapper>
|
||||||
|
{/*-------------------- Social --------------------*/}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</ScrollableSettingsWrapper>
|
</ScrollableSettingsWrapper>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -267,8 +298,39 @@ export default function SettingsPage() {
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Calendar & Availability</CardTitle>
|
<CardTitle>Calendar & Availability</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className='space-y-6'>
|
<CardContent className='space-y-6 my-2'>
|
||||||
{/*--------------------* Calendar --------------------*/}
|
{/*-------------------- Date & Time Format --------------------*/}
|
||||||
|
<GroupWrapper title='Date & Time Format'>
|
||||||
|
<div className='flex flex-col space-y-4'>
|
||||||
|
<div className='grid grid-cols-1 gap-1'>
|
||||||
|
<Label htmlFor='dateFormat'>Date Format</Label>
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger id='dateFormat'>
|
||||||
|
<SelectValue placeholder='Select date format' />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value='ddmmyyyy'>DD/MM/YYYY</SelectItem>
|
||||||
|
<SelectItem value='mmddyyyy'>MM/DD/YYYY</SelectItem>
|
||||||
|
<SelectItem value='yyyymmdd'>YYYY-MM-DD</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className='grid grid-cols-1 gap-1'>
|
||||||
|
<Label htmlFor='timeFormat'>Time Format</Label>
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger id='timeFormat'>
|
||||||
|
<SelectValue placeholder='Select time format' />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value='24h'>24-hour</SelectItem>
|
||||||
|
<SelectItem value='12h'>12-hour</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</GroupWrapper>
|
||||||
|
{/*-------------------- Date & Time Format --------------------*/}
|
||||||
|
{/*-------------------- Calendar --------------------*/}
|
||||||
<GroupWrapper title='Calendar'>
|
<GroupWrapper title='Calendar'>
|
||||||
<div className='space-y-4'>
|
<div className='space-y-4'>
|
||||||
<div className='grid grid-cols-1 gap-1'>
|
<div className='grid grid-cols-1 gap-1'>
|
||||||
|
@ -306,9 +368,8 @@ export default function SettingsPage() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</GroupWrapper>
|
</GroupWrapper>
|
||||||
{/*--------------------* Calendar --------------------*/}
|
{/*-------------------- Calendar --------------------*/}
|
||||||
|
{/*-------------------- Availability --------------------*/}
|
||||||
{/*--------------------* Availability --------------------*/}
|
|
||||||
<GroupWrapper title='Availability'>
|
<GroupWrapper title='Availability'>
|
||||||
<div className='space-y-4'>
|
<div className='space-y-4'>
|
||||||
<div className='space-y-2'>
|
<div className='space-y-2'>
|
||||||
|
@ -325,8 +386,9 @@ export default function SettingsPage() {
|
||||||
<LabeledInput
|
<LabeledInput
|
||||||
type='text'
|
type='text'
|
||||||
label='Minimum Notice for Bookings'
|
label='Minimum Notice for Bookings'
|
||||||
|
icon={ClockAlert}
|
||||||
subtext='Min time before a booking can be made.'
|
subtext='Min time before a booking can be made.'
|
||||||
placeholder='e.g., 1h'
|
placeholder='e.g. 1h'
|
||||||
defaultValue={''}
|
defaultValue={''}
|
||||||
></LabeledInput>
|
></LabeledInput>
|
||||||
</div>
|
</div>
|
||||||
|
@ -334,43 +396,65 @@ export default function SettingsPage() {
|
||||||
<LabeledInput
|
<LabeledInput
|
||||||
type='text'
|
type='text'
|
||||||
label='Booking Window (days in advance)'
|
label='Booking Window (days in advance)'
|
||||||
|
icon={ClockFading}
|
||||||
subtext='Max time in advance a booking can be made.'
|
subtext='Max time in advance a booking can be made.'
|
||||||
placeholder='e.g., 30d'
|
placeholder='e.g. 30d'
|
||||||
defaultValue={''}
|
defaultValue={''}
|
||||||
></LabeledInput>
|
></LabeledInput>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</GroupWrapper>
|
</GroupWrapper>
|
||||||
{/*--------------------* Availability --------------------*/}
|
{/*-------------------- Availability --------------------*/}
|
||||||
|
{/*-------------------- iCalendar Integration --------------------*/}
|
||||||
{/*--------------------* iCalendar Integration --------------------*/}
|
<GroupWrapper title='iCalendar Integration'>
|
||||||
<fieldset className='space-y-4 p-4 border rounded-md'>
|
<div className='space-y-4'>
|
||||||
<legend className='text-sm font-medium px-1'>
|
<form
|
||||||
iCalendar Integration
|
onSubmit={(e) => {
|
||||||
</legend>
|
e.preventDefault();
|
||||||
<div className='space-y-2'>
|
}}
|
||||||
<LabeledInput
|
className='space-y-2'
|
||||||
type='url'
|
>
|
||||||
label='Import iCal Feed URL'
|
<LabeledInput
|
||||||
subtext='Max time in advance a booking can be made.'
|
type='url'
|
||||||
placeholder='https://calendar.example.com/feed.ics'
|
label='Import iCal Feed URL'
|
||||||
defaultValue={''}
|
icon={CalendarCheck}
|
||||||
></LabeledInput>
|
placeholder='https://calendar.example.com/feed.ics'
|
||||||
<Button size='sm' className='mt-1'>
|
defaultValue={''}
|
||||||
Add Feed
|
name='icalUrl'
|
||||||
</Button>
|
required
|
||||||
|
></LabeledInput>
|
||||||
|
<IconButton
|
||||||
|
type='submit'
|
||||||
|
size='sm'
|
||||||
|
className='mt-1'
|
||||||
|
variant={'secondary'}
|
||||||
|
icon={CalendarPlus}
|
||||||
|
title='Submit iCal URL'
|
||||||
|
>
|
||||||
|
Add Feed
|
||||||
|
</IconButton>
|
||||||
|
</form>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label>Export Your Calendar</Label>
|
||||||
|
<IconButton
|
||||||
|
variant='outline_muted'
|
||||||
|
size='sm'
|
||||||
|
icon={CalendarArrowUp}
|
||||||
|
>
|
||||||
|
Get iCal Export URL
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
variant='outline_muted'
|
||||||
|
size='sm'
|
||||||
|
className='ml-2'
|
||||||
|
icon={CalendarArrowDown}
|
||||||
|
>
|
||||||
|
Download .ics File
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='space-y-2'>
|
</GroupWrapper>
|
||||||
<Label>Export Your Calendar</Label>
|
{/*-------------------- iCalendar Integration --------------------*/}
|
||||||
<Button variant='outline_muted' size='sm'>
|
|
||||||
Get iCal Export URL
|
|
||||||
</Button>
|
|
||||||
<Button variant='outline_muted' size='sm' className='ml-2'>
|
|
||||||
Download .ics File
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
{/*--------------------* iCalendar Integration --------------------*/}
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</ScrollableSettingsWrapper>
|
</ScrollableSettingsWrapper>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -383,79 +467,94 @@ export default function SettingsPage() {
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Sharing & Privacy</CardTitle>
|
<CardTitle>Sharing & Privacy</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className='space-y-6'>
|
<CardContent className='space-y-6 my-2'>
|
||||||
<div className='space-y-2'>
|
{/*-------------------- Privacy Settigs --------------------*/}
|
||||||
<Label htmlFor='defaultVisibility'>
|
<GroupWrapper title='Privacy Settings'>
|
||||||
Default Calendar Visibility
|
<div className='flex flex-col space-y-4'>
|
||||||
</Label>
|
<div className='grid grid-cols-1 gap-1'>
|
||||||
<Select>
|
<Label htmlFor='defaultVisibility'>
|
||||||
<SelectTrigger id='defaultVisibility'>
|
Default Calendar Visibility
|
||||||
<SelectValue placeholder='Select visibility' />
|
</Label>
|
||||||
</SelectTrigger>
|
<span className='text-sm text-muted-foreground'>
|
||||||
<SelectContent>
|
Default setting for new friends.
|
||||||
<SelectItem value='private'>
|
</span>
|
||||||
Private (Only You)
|
<Select>
|
||||||
</SelectItem>
|
<SelectTrigger id='defaultVisibility'>
|
||||||
<SelectItem value='freebusy'>
|
<SelectValue placeholder='Select visibility' />
|
||||||
Free/Busy for Friends
|
</SelectTrigger>
|
||||||
</SelectItem>
|
<SelectContent>
|
||||||
<SelectItem value='fulldetails'>
|
<SelectItem value='private'>
|
||||||
Full Details for Friends
|
Private (Only You)
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
</SelectContent>
|
<SelectItem value='freebusy'>Free/Busy</SelectItem>
|
||||||
</Select>
|
<SelectItem value='fulldetails'>
|
||||||
</div>
|
Full Details
|
||||||
<div className='space-y-2'>
|
</SelectItem>
|
||||||
<Label htmlFor='whoCanSeeFull'>
|
</SelectContent>
|
||||||
Who Can See Your Full Calendar Details?
|
</Select>
|
||||||
</Label>
|
</div>
|
||||||
<span className='text-sm text-muted-foreground'>
|
<div className='grid grid-cols-1 gap-1'>
|
||||||
(Override for Default Visibility)
|
<Label htmlFor='whoCanSeeFull'>
|
||||||
<br />
|
Who Can See Your Full Calendar Details?
|
||||||
<span className='text-sm text-muted-foreground'>
|
</Label>
|
||||||
This setting will override the default visibility for your
|
<span className='text-sm text-muted-foreground'>
|
||||||
calendar. You can set specific friends or groups to see
|
(Override for Default Visibility)
|
||||||
your full calendar details.
|
<br />
|
||||||
</span>
|
<span className='text-sm text-muted-foreground'>
|
||||||
</span>
|
This setting will override the default visibility for
|
||||||
<Select>
|
your calendar. You can set specific friends or groups
|
||||||
<SelectTrigger id='whoCanSeeFull'>
|
to see your full calendar details.
|
||||||
<SelectValue placeholder='Select audience' />
|
</span>
|
||||||
</SelectTrigger>
|
</span>
|
||||||
<SelectContent>
|
<Select>
|
||||||
<SelectItem value='me'>Only Me</SelectItem>
|
<SelectTrigger id='whoCanSeeFull'>
|
||||||
<SelectItem value='friends'>My Friends</SelectItem>
|
<SelectValue placeholder='Select audience' />
|
||||||
<SelectItem value='specific'>
|
</SelectTrigger>
|
||||||
Specific Friends/Groups (manage separately)
|
<SelectContent>
|
||||||
</SelectItem>
|
<SelectItem value='me'>Only Me</SelectItem>
|
||||||
</SelectContent>
|
<SelectItem value='friends'>My Friends</SelectItem>
|
||||||
</Select>
|
<SelectItem value='specific'>
|
||||||
</div>
|
Specific Friends/Groups (manage separately)
|
||||||
<div className='space-y-2'>
|
</SelectItem>
|
||||||
<Label htmlFor='whoCanBook'>
|
</SelectContent>
|
||||||
Who Can Book Time With You?
|
</Select>
|
||||||
</Label>
|
</div>
|
||||||
<Select>
|
<div className='grid grid-cols-1 gap-1'>
|
||||||
<SelectTrigger id='whoCanBook'>
|
<Label htmlFor='whoCanBook'>
|
||||||
<SelectValue placeholder='Select audience' />
|
Who Can Book Time With You?
|
||||||
</SelectTrigger>
|
</Label>
|
||||||
<SelectContent>
|
<Select>
|
||||||
<SelectItem value='none'>No One</SelectItem>
|
<SelectTrigger id='whoCanBook'>
|
||||||
<SelectItem value='friends'>My Friends</SelectItem>
|
<SelectValue placeholder='Select audience' />
|
||||||
<SelectItem value='specific'>
|
</SelectTrigger>
|
||||||
Specific Friends/Groups (manage separately)
|
<SelectContent>
|
||||||
</SelectItem>
|
<SelectItem value='none'>No One</SelectItem>
|
||||||
</SelectContent>
|
<SelectItem value='friends'>My Friends</SelectItem>
|
||||||
</Select>
|
<SelectItem value='specific'>
|
||||||
</div>
|
Specific Friends/Groups (manage separately)
|
||||||
<div className='space-y-2'>
|
</SelectItem>
|
||||||
<Label>Blocked Users</Label>
|
</SelectContent>
|
||||||
<Button variant='outline_muted'>Manage Blocked Users</Button>
|
</Select>
|
||||||
<span className='text-sm text-muted-foreground'>
|
</div>
|
||||||
Prevent specific users from seeing your calendar or booking
|
<div className='space-y-4'>
|
||||||
time.
|
<div className='grid grid-cols-1 gap-1'>
|
||||||
</span>
|
<Label>Blocked Users</Label>
|
||||||
</div>
|
<span className='text-sm text-muted-foreground'>
|
||||||
|
Prevent specific users from seeing your calendar or
|
||||||
|
booking time.
|
||||||
|
</span>
|
||||||
|
<IconButton
|
||||||
|
variant='outline_muted'
|
||||||
|
size='sm'
|
||||||
|
icon={UserLock}
|
||||||
|
>
|
||||||
|
Manage Blocked Users
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</GroupWrapper>
|
||||||
|
{/*-------------------- Privacy Settigs --------------------*/}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</ScrollableSettingsWrapper>
|
</ScrollableSettingsWrapper>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -468,36 +567,15 @@ export default function SettingsPage() {
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Appearance</CardTitle>
|
<CardTitle>Appearance</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className='space-y-6'>
|
<CardContent className='space-y-6 my-2'>
|
||||||
<div className='space-y-2'>
|
{/*-------------------- Change Theme --------------------*/}
|
||||||
<Label htmlFor='theme'>Theme</Label>
|
<GroupWrapper title='Change Theme'>
|
||||||
<ThemePicker />
|
<div className='space-y-2'>
|
||||||
</div>
|
<Label htmlFor='theme'>Theme</Label>
|
||||||
<div className='space-y-2'>
|
<ThemePicker />
|
||||||
<Label htmlFor='dateFormat'>Date Format</Label>
|
</div>
|
||||||
<Select>
|
</GroupWrapper>
|
||||||
<SelectTrigger id='dateFormat'>
|
{/*-------------------- Change Theme --------------------*/}
|
||||||
<SelectValue placeholder='Select date format' />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value='ddmmyyyy'>DD/MM/YYYY</SelectItem>
|
|
||||||
<SelectItem value='mmddyyyy'>MM/DD/YYYY</SelectItem>
|
|
||||||
<SelectItem value='yyyymmdd'>YYYY-MM-DD</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
<div className='space-y-2'>
|
|
||||||
<Label htmlFor='timeFormat'>Time Format</Label>
|
|
||||||
<Select>
|
|
||||||
<SelectTrigger id='timeFormat'>
|
|
||||||
<SelectValue placeholder='Select time format' />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value='24h'>24-hour</SelectItem>
|
|
||||||
<SelectItem value='12h'>12-hour</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</ScrollableSettingsWrapper>
|
</ScrollableSettingsWrapper>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue