mirror of
https://github.com/documenso/documenso.git
synced 2025-11-18 18:51:37 +10:00
wip: refresh design
This commit is contained in:
@ -0,0 +1,151 @@
|
||||
'use client';
|
||||
|
||||
import { Variants, motion } from 'framer-motion';
|
||||
import { Plus } from 'lucide-react';
|
||||
import { useDropzone } from 'react-dropzone';
|
||||
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Card, CardContent } from '@documenso/ui/primitives/card';
|
||||
|
||||
const DocumentDropzoneContainerVariants: Variants = {
|
||||
initial: {
|
||||
scale: 1,
|
||||
},
|
||||
animate: {
|
||||
scale: 1,
|
||||
},
|
||||
hover: {
|
||||
transition: {
|
||||
staggerChildren: 0.05,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const DocumentDropzoneCardLeftVariants: Variants = {
|
||||
initial: {
|
||||
x: 40,
|
||||
y: -10,
|
||||
rotate: -14,
|
||||
},
|
||||
animate: {
|
||||
x: 40,
|
||||
y: -10,
|
||||
rotate: -14,
|
||||
},
|
||||
hover: {
|
||||
x: -25,
|
||||
y: -25,
|
||||
rotate: -22,
|
||||
},
|
||||
};
|
||||
|
||||
const DocumentDropzoneCardRightVariants: Variants = {
|
||||
initial: {
|
||||
x: -40,
|
||||
y: -10,
|
||||
rotate: 14,
|
||||
},
|
||||
animate: {
|
||||
x: -40,
|
||||
y: -10,
|
||||
rotate: 14,
|
||||
},
|
||||
hover: {
|
||||
x: 25,
|
||||
y: -25,
|
||||
rotate: 22,
|
||||
},
|
||||
};
|
||||
|
||||
const DocumentDropzoneCardCenterVariants: Variants = {
|
||||
initial: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
animate: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
hover: {
|
||||
x: 0,
|
||||
y: -25,
|
||||
},
|
||||
};
|
||||
|
||||
export type DocumentDropzoneProps = {
|
||||
className: string;
|
||||
onDrop?: (_file: File) => void | Promise<void>;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
export const DocumentDropzone = ({ className, onDrop, ...props }: DocumentDropzoneProps) => {
|
||||
const { getRootProps, getInputProps } = useDropzone({
|
||||
accept: {
|
||||
'application/pdf': ['.pdf'],
|
||||
},
|
||||
multiple: false,
|
||||
onDrop: ([acceptedFile]) => {
|
||||
if (acceptedFile && onDrop) {
|
||||
onDrop(acceptedFile);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
className={cn('flex', className)}
|
||||
variants={DocumentDropzoneContainerVariants}
|
||||
initial="initial"
|
||||
animate="animate"
|
||||
whileHover="hover"
|
||||
>
|
||||
<Card
|
||||
role="button"
|
||||
className={cn('flex flex-1 cursor-pointer flex-col items-center justify-center', className)}
|
||||
gradient={true}
|
||||
degrees={120}
|
||||
{...getRootProps()}
|
||||
{...props}
|
||||
>
|
||||
<CardContent className="text-muted-foreground/40 flex flex-col items-center justify-center p-6">
|
||||
{/* <FilePlus strokeWidth="1px" className="h-16 w-16"/> */}
|
||||
<div className="flex">
|
||||
<motion.div
|
||||
className="border-muted-foreground/20 group-hover:border-primary/80 z-10 flex aspect-[3/4] w-24 origin-top-right -rotate-[22deg] flex-col gap-y-1 rounded-lg border bg-white/80 px-2 py-4 backdrop-blur-sm"
|
||||
variants={DocumentDropzoneCardLeftVariants}
|
||||
>
|
||||
<div className="bg-muted-foreground/20 group-hover:bg-primary h-2 w-full rounded-[2px]" />
|
||||
<div className="bg-muted-foreground/20 group-hover:bg-primary h-2 w-5/6 rounded-[2px]" />
|
||||
<div className="bg-muted-foreground/20 group-hover:bg-primary h-2 w-full rounded-[2px]" />
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
className="border-muted-foreground/20 group-hover:border-primary/80 z-20 flex aspect-[3/4] w-24 flex-col items-center justify-center gap-y-1 rounded-lg border bg-white/80 px-2 py-4 backdrop-blur-sm"
|
||||
variants={DocumentDropzoneCardCenterVariants}
|
||||
>
|
||||
<Plus
|
||||
strokeWidth="2px"
|
||||
className="text-muted-foreground/20 group-hover:text-primary h-12 w-12"
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
className="border-muted-foreground/20 group-hover:border-primary/80 z-10 flex aspect-[3/4] w-24 origin-top-left rotate-[22deg] flex-col gap-y-1 rounded-lg border bg-white/80 px-2 py-4 backdrop-blur-sm"
|
||||
variants={DocumentDropzoneCardRightVariants}
|
||||
>
|
||||
<div className="bg-muted-foreground/20 group-hover:bg-primary h-2 w-full rounded-[2px]" />
|
||||
<div className="bg-muted-foreground/20 group-hover:bg-primary h-2 w-5/6 rounded-[2px]" />
|
||||
<div className="bg-muted-foreground/20 group-hover:bg-primary h-2 w-full rounded-[2px]" />
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
<input {...getInputProps()} />
|
||||
|
||||
<p className="group-hover:text-primary mt-8 font-medium">Add a document</p>
|
||||
|
||||
<p className="mt-1 text-sm">Drag & drop your document here.</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
43
apps/web/src/components/(dashboard)/layout/desktop-nav.tsx
Normal file
43
apps/web/src/components/(dashboard)/layout/desktop-nav.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
'use client';
|
||||
|
||||
import { HTMLAttributes } from 'react';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
|
||||
export type DesktopNavProps = HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
export const DesktopNav = ({ className, ...props }: DesktopNavProps) => {
|
||||
const pathname = usePathname();
|
||||
|
||||
return (
|
||||
<div className={cn('ml-8 hidden flex-1 gap-x-6 md:flex', className)} {...props}>
|
||||
<Link
|
||||
href="/dashboard"
|
||||
className={cn('font-medium leading-5 text-[#A1A1AA] hover:opacity-80', {
|
||||
'text-primary-foreground': pathname?.startsWith('/dashboard'),
|
||||
})}
|
||||
>
|
||||
Dashboard
|
||||
</Link>
|
||||
<Link
|
||||
href="/documents"
|
||||
className={cn('font-medium leading-5 text-[#A1A1AA] hover:opacity-80', {
|
||||
'text-primary-foreground': pathname?.startsWith('/documents'),
|
||||
})}
|
||||
>
|
||||
Documents
|
||||
</Link>
|
||||
{/* <Link
|
||||
href="/settings/profile"
|
||||
className={cn('font-medium leading-5 text-[#A1A1AA] hover:opacity-80', {
|
||||
'text-primary-foreground': pathname?.startsWith('/settings'),
|
||||
})}
|
||||
>
|
||||
Settings
|
||||
</Link> */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
48
apps/web/src/components/(dashboard)/layout/header.tsx
Normal file
48
apps/web/src/components/(dashboard)/layout/header.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
'use client';
|
||||
|
||||
import { HTMLAttributes } from 'react';
|
||||
|
||||
import Link from 'next/link';
|
||||
|
||||
import { Menu } from 'lucide-react';
|
||||
|
||||
import { User } from '@documenso/prisma/client';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
|
||||
import { Logo } from '~/components/branding/logo';
|
||||
|
||||
import { DesktopNav } from './desktop-nav';
|
||||
import { ProfileDropdown } from './profile-dropdown';
|
||||
|
||||
export type HeaderProps = HTMLAttributes<HTMLDivElement> & {
|
||||
user: User;
|
||||
};
|
||||
|
||||
export const Header = ({ className, user, ...props }: HeaderProps) => {
|
||||
return (
|
||||
<header
|
||||
className={cn(
|
||||
'supports-backdrop-blur:bg-background/60 bg-background/95 sticky top-0 z-40 flex h-16 w-full items-center border-b backdrop-blur',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="mx-auto flex w-full max-w-screen-xl items-center justify-between gap-x-4 px-4 md:justify-normal md:px-8">
|
||||
<Link href="/">
|
||||
<Logo className="h-6 w-auto" />
|
||||
</Link>
|
||||
|
||||
<DesktopNav />
|
||||
|
||||
<div className="flex gap-x-4">
|
||||
<ProfileDropdown user={user} />
|
||||
|
||||
<Button variant="outline" size="sm" className="h-10 w-10 p-0.5 md:hidden">
|
||||
<Menu className="h-6 w-6" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,93 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
|
||||
import { CreditCard, Github, Key, LogOut, User as LucideUser } from 'lucide-react';
|
||||
import { signOut } from 'next-auth/react';
|
||||
|
||||
import { IS_SUBSCRIPTIONS_ENABLED } from '@documenso/lib/constants/features';
|
||||
import { User } from '@documenso/prisma/client';
|
||||
import { Avatar, AvatarFallback } from '@documenso/ui/primitives/avatar';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@documenso/ui/primitives/dropdown-menu';
|
||||
|
||||
export type ProfileDropdownProps = {
|
||||
user: User;
|
||||
};
|
||||
|
||||
export const ProfileDropdown = ({ user }: ProfileDropdownProps) => {
|
||||
const initials =
|
||||
user.name
|
||||
?.split(' ')
|
||||
.map((name) => name.slice(0, 1).toUpperCase())
|
||||
.slice(0, 2)
|
||||
.join('') ?? 'UK';
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" className="relative h-10 w-10 rounded-full">
|
||||
<Avatar className="h-10 w-10">
|
||||
<AvatarFallback>{initials}</AvatarFallback>
|
||||
</Avatar>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent className="w-56" align="end" forceMount>
|
||||
<DropdownMenuLabel>Account</DropdownMenuLabel>
|
||||
|
||||
<DropdownMenuItem asChild>
|
||||
<Link href="/settings/profile" className="cursor-pointer">
|
||||
<LucideUser className="mr-2 h-4 w-4" />
|
||||
Profile
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem asChild>
|
||||
<Link href="/settings/password" className="cursor-pointer">
|
||||
<Key className="mr-2 h-4 w-4" />
|
||||
Password
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
|
||||
{IS_SUBSCRIPTIONS_ENABLED && (
|
||||
<DropdownMenuItem asChild>
|
||||
<Link href="/settings/billing" className="cursor-pointer">
|
||||
<CreditCard className="mr-2 h-4 w-4" />
|
||||
Billing
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<DropdownMenuItem asChild>
|
||||
<Link href="https://github.com/documenso/documenso" className="cursor-pointer">
|
||||
<Github className="mr-2 h-4 w-4" />
|
||||
Star on Github
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<DropdownMenuItem
|
||||
onSelect={() =>
|
||||
signOut({
|
||||
callbackUrl: '/',
|
||||
})
|
||||
}
|
||||
>
|
||||
<LogOut className="mr-2 h-4 w-4" />
|
||||
Sign Out
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
|
||||
import { LucideIcon } from 'lucide-react';
|
||||
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
|
||||
export type CardMetricProps = {
|
||||
icon?: LucideIcon;
|
||||
title: string;
|
||||
value: string | number;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const CardMetric = ({ icon: Icon, title, value, className }: CardMetricProps) => {
|
||||
return (
|
||||
<div className={cn('overflow-hidden rounded-lg border border-slate-200 bg-white')}>
|
||||
<div className="px-4 pb-6 pt-4 sm:px-4 sm:pb-8 sm:pt-4">
|
||||
<div className="flex items-start">
|
||||
{Icon && <Icon className="mr-2 h-4 w-4 text-slate-500" />}
|
||||
|
||||
<h3 className="flex items-end text-sm font-medium text-slate-500">{title}</h3>
|
||||
</div>
|
||||
|
||||
<p className="mt-6 text-4xl font-semibold leading-8 text-gray-900 md:mt-8">
|
||||
{typeof value === 'number' ? value.toLocaleString('en-US') : value}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,87 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { Loader } from 'lucide-react';
|
||||
import { Document as PDFDocument, Page as PDFPage, pdfjs } from 'react-pdf';
|
||||
import 'react-pdf/dist/esm/Page/AnnotationLayer.css';
|
||||
import 'react-pdf/dist/esm/Page/TextLayer.css';
|
||||
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
|
||||
type LoadedPDFDocument = pdfjs.PDFDocumentProxy;
|
||||
|
||||
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
|
||||
'pdfjs-dist/build/pdf.worker.min.js',
|
||||
import.meta.url,
|
||||
).toString();
|
||||
|
||||
export type PDFViewerProps = {
|
||||
className?: string;
|
||||
document: string;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
export const PDFViewer = ({ className, document, ...props }: PDFViewerProps) => {
|
||||
const $el = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [width, setWidth] = useState(0);
|
||||
const [numPages, setNumPages] = useState(0);
|
||||
|
||||
const onDocumentLoaded = (doc: LoadedPDFDocument) => {
|
||||
setNumPages(doc.numPages);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if ($el.current) {
|
||||
const $current = $el.current;
|
||||
|
||||
const { width } = $current.getBoundingClientRect();
|
||||
|
||||
setWidth(width);
|
||||
|
||||
const onResize = () => {
|
||||
const { width } = $current.getBoundingClientRect();
|
||||
|
||||
setWidth(width);
|
||||
};
|
||||
|
||||
window.addEventListener('resize', onResize);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', onResize);
|
||||
};
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div ref={$el} className={cn('overflow-hidden', className)}>
|
||||
<PDFDocument
|
||||
file={`data:application/pdf;base64,${document}`}
|
||||
className="w-full overflow-hidden rounded"
|
||||
onLoadSuccess={(d) => onDocumentLoaded(d)}
|
||||
externalLinkTarget="_blank"
|
||||
loading={
|
||||
<div className="flex min-h-[80vh] flex-col items-center justify-center bg-white/50">
|
||||
<Loader className="h-12 w-12 animate-spin text-slate-500" />
|
||||
|
||||
<p className="mt-4 text-slate-500">Loading document...</p>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{Array(numPages)
|
||||
.fill(null)
|
||||
.map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="border-t-primary mt-8 border-t pt-8 first:mt-0 first:border-t-0 first:pt-0"
|
||||
>
|
||||
<PDFPage pageNumber={i + 1} width={width} />
|
||||
</div>
|
||||
))}
|
||||
</PDFDocument>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PDFViewer;
|
||||
@ -0,0 +1,59 @@
|
||||
'use client';
|
||||
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
||||
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@documenso/ui/primitives/select';
|
||||
|
||||
import { isPeriodSelectorValue } from './types';
|
||||
|
||||
export const PeriodSelector = () => {
|
||||
const pathname = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const period = useMemo(() => {
|
||||
const p = searchParams?.get('period') ?? '';
|
||||
|
||||
return isPeriodSelectorValue(p) ? p : '';
|
||||
}, [searchParams]);
|
||||
|
||||
const onPeriodChange = (newPeriod: string) => {
|
||||
if (!pathname) {
|
||||
return;
|
||||
}
|
||||
|
||||
const params = new URLSearchParams(searchParams?.toString());
|
||||
|
||||
params.set('period', newPeriod);
|
||||
|
||||
if (newPeriod === '') {
|
||||
params.delete('period');
|
||||
}
|
||||
|
||||
router.push(`${pathname}?${params.toString()}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<Select defaultValue={period} onValueChange={onPeriodChange}>
|
||||
<SelectTrigger className="max-w-[200px] text-slate-500">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
|
||||
<SelectContent position="popper">
|
||||
<SelectItem value="">All Time</SelectItem>
|
||||
<SelectItem value="7d">Last 7 days</SelectItem>
|
||||
<SelectItem value="14d">Last 14 days</SelectItem>
|
||||
<SelectItem value="30d">Last 30 days</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,5 @@
|
||||
export type PeriodSelectorValue = '' | '7d' | '14d' | '30d';
|
||||
|
||||
export const isPeriodSelectorValue = (value: unknown): value is PeriodSelectorValue => {
|
||||
return ['', '7d', '14d', '30d'].includes(value as string);
|
||||
};
|
||||
@ -0,0 +1,63 @@
|
||||
'use client';
|
||||
|
||||
import { HTMLAttributes } from 'react';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
|
||||
import { CreditCard, Key, User } from 'lucide-react';
|
||||
|
||||
import { IS_SUBSCRIPTIONS_ENABLED } from '@documenso/lib/constants/features';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
|
||||
export type DesktopNavProps = HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
export const DesktopNav = ({ className, ...props }: DesktopNavProps) => {
|
||||
const pathname = usePathname();
|
||||
|
||||
return (
|
||||
<div className={cn('flex flex-col gap-y-2', className)} {...props}>
|
||||
<Link href="/settings/profile">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className={cn(
|
||||
'w-full justify-start',
|
||||
pathname?.startsWith('/settings/profile') && 'bg-secondary',
|
||||
)}
|
||||
>
|
||||
<User className="mr-2 h-5 w-5" />
|
||||
Profile
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<Link href="/settings/password">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className={cn(
|
||||
'w-full justify-start',
|
||||
pathname?.startsWith('/settings/password') && 'bg-secondary',
|
||||
)}
|
||||
>
|
||||
<Key className="mr-2 h-5 w-5" />
|
||||
Password
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
{IS_SUBSCRIPTIONS_ENABLED && (
|
||||
<Link href="/settings/billing">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className={cn(
|
||||
'w-full justify-start',
|
||||
pathname?.startsWith('/settings/billing') && 'bg-secondary',
|
||||
)}
|
||||
>
|
||||
<CreditCard className="mr-2 h-5 w-5" />
|
||||
Billing
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,66 @@
|
||||
'use client';
|
||||
|
||||
import { HTMLAttributes } from 'react';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
|
||||
import { CreditCard, Key, User } from 'lucide-react';
|
||||
|
||||
import { IS_SUBSCRIPTIONS_ENABLED } from '@documenso/lib/constants/features';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
|
||||
export type MobileNavProps = HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
export const MobileNav = ({ className, ...props }: MobileNavProps) => {
|
||||
const pathname = usePathname();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn('flex flex-wrap items-center justify-start gap-x-2 gap-y-4', className)}
|
||||
{...props}
|
||||
>
|
||||
<Link href="/settings/profile">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className={cn(
|
||||
'w-full justify-start',
|
||||
pathname?.startsWith('/settings/profile') && 'bg-secondary',
|
||||
)}
|
||||
>
|
||||
<User className="mr-2 h-5 w-5" />
|
||||
Profile
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<Link href="/settings/password">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className={cn(
|
||||
'w-full justify-start',
|
||||
pathname?.startsWith('/settings/password') && 'bg-secondary',
|
||||
)}
|
||||
>
|
||||
<Key className="mr-2 h-5 w-5" />
|
||||
Password
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
{IS_SUBSCRIPTIONS_ENABLED && (
|
||||
<Link href="/settings/billing">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className={cn(
|
||||
'w-full justify-start',
|
||||
pathname?.startsWith('/settings/billing') && 'bg-secondary',
|
||||
)}
|
||||
>
|
||||
<CreditCard className="mr-2 h-5 w-5" />
|
||||
Billing
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user