B2B-36: add personal dashboard (#20)
* B2B-36: add dashboard cards * B2B-36: add dashboard cards * card variants, some improvements, gen db types * add menus to home page * update db types * remove unnecessary card variant --------- Co-authored-by: Helena <helena@Helenas-MacBook-Pro.local>
This commit is contained in:
237
app/home/(user)/_components/dashboard.tsx
Normal file
237
app/home/(user)/_components/dashboard.tsx
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { InfoTooltip } from '@/components/ui/info-tooltip';
|
||||||
|
import { toTitleCase } from '@/lib/utils';
|
||||||
|
import { BlendingModeIcon, RulerHorizontalIcon } from '@radix-ui/react-icons';
|
||||||
|
import {
|
||||||
|
Activity,
|
||||||
|
ChevronRight,
|
||||||
|
Clock9,
|
||||||
|
Droplets,
|
||||||
|
LineChart,
|
||||||
|
Pill,
|
||||||
|
Scale,
|
||||||
|
TrendingUp,
|
||||||
|
User,
|
||||||
|
} from 'lucide-react';
|
||||||
|
|
||||||
|
import { usePersonalAccountData } from '@kit/accounts/hooks/use-personal-account-data';
|
||||||
|
import { useUserWorkspace } from '@kit/accounts/hooks/use-user-workspace';
|
||||||
|
import { Button } from '@kit/ui/button';
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardFooter,
|
||||||
|
CardHeader,
|
||||||
|
CardProps,
|
||||||
|
} from '@kit/ui/card';
|
||||||
|
import { PageDescription } from '@kit/ui/page';
|
||||||
|
import { Trans } from '@kit/ui/trans';
|
||||||
|
import { cn } from '@kit/ui/utils';
|
||||||
|
|
||||||
|
const dummyCards = [
|
||||||
|
{
|
||||||
|
title: 'dashboard:gender',
|
||||||
|
description: 'dashboard:male',
|
||||||
|
icon: <User />,
|
||||||
|
iconBg: 'bg-success',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'dashboard:age',
|
||||||
|
description: '43',
|
||||||
|
icon: <Clock9 />,
|
||||||
|
iconBg: 'bg-success',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'dashboard:height',
|
||||||
|
description: '183',
|
||||||
|
icon: <RulerHorizontalIcon className="size-4" />,
|
||||||
|
iconBg: 'bg-success',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'dashboard:weight',
|
||||||
|
description: '92kg',
|
||||||
|
icon: <Scale />,
|
||||||
|
iconBg: 'bg-warning',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'dashboard:bmi',
|
||||||
|
description: '27.5',
|
||||||
|
icon: <TrendingUp />,
|
||||||
|
iconBg: 'bg-warning',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'dashboard:bloodPressure',
|
||||||
|
description: '160/98',
|
||||||
|
icon: <Activity />,
|
||||||
|
iconBg: 'bg-warning',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'dashboard:cholesterol',
|
||||||
|
description: '5',
|
||||||
|
icon: <BlendingModeIcon className="size-4" />,
|
||||||
|
iconBg: 'bg-destructive',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'dashboard:ldlCholesterol',
|
||||||
|
description: '3,6',
|
||||||
|
icon: <Pill />,
|
||||||
|
iconBg: 'bg-warning',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Score 2',
|
||||||
|
description: 'Normis',
|
||||||
|
icon: <LineChart />,
|
||||||
|
iconBg: 'bg-success',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'dashboard:smoking',
|
||||||
|
description: 'dashboard:respondToQuestion',
|
||||||
|
descriptionColor: 'text-primary',
|
||||||
|
icon: (
|
||||||
|
<Button size="icon" variant="outline" className="px-2 text-black">
|
||||||
|
<ChevronRight className="size-4 stroke-2" />
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
cardVariant: 'gradient-success' as CardProps['variant'],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const dummyRecommendations = [
|
||||||
|
{
|
||||||
|
icon: <BlendingModeIcon className="size-4" />,
|
||||||
|
color: 'bg-cyan/10 text-cyan',
|
||||||
|
title: 'Kolesterooli kontroll',
|
||||||
|
description: 'HDL-kolestrool',
|
||||||
|
tooltipContent: 'Selgitus',
|
||||||
|
price: '20,00 €',
|
||||||
|
buttonText: 'Telli',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <BlendingModeIcon className="size-4" />,
|
||||||
|
color: 'bg-primary/10 text-primary',
|
||||||
|
title: 'Kolesterooli kontroll',
|
||||||
|
tooltipContent: 'Selgitus',
|
||||||
|
description: 'LDL-Kolesterool',
|
||||||
|
buttonText: 'Broneeri',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <Droplets />,
|
||||||
|
color: 'bg-destructive/10 text-destructive',
|
||||||
|
title: 'Vererõhu kontroll',
|
||||||
|
tooltipContent: 'Selgitus',
|
||||||
|
description: 'Score-Risk 2',
|
||||||
|
price: '20,00 €',
|
||||||
|
buttonText: 'Telli',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function Dashboard() {
|
||||||
|
const userWorkspace = useUserWorkspace();
|
||||||
|
const account = usePersonalAccountData(userWorkspace.user.id);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<h4>
|
||||||
|
<Trans i18nKey={'common:welcome'} />
|
||||||
|
{account?.data?.name ? `, ${toTitleCase(account.data.name)}` : ''}
|
||||||
|
</h4>
|
||||||
|
<PageDescription>
|
||||||
|
<Trans i18nKey={'dashboard:recentlyCheckedDescription'} />:
|
||||||
|
</PageDescription>
|
||||||
|
</div>
|
||||||
|
<div className="grid auto-rows-fr grid-cols-5 gap-3">
|
||||||
|
{dummyCards.map(
|
||||||
|
({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
icon,
|
||||||
|
iconBg,
|
||||||
|
cardVariant,
|
||||||
|
descriptionColor,
|
||||||
|
}) => (
|
||||||
|
<Card
|
||||||
|
key={title}
|
||||||
|
variant={cardVariant}
|
||||||
|
className="flex flex-col justify-between"
|
||||||
|
>
|
||||||
|
<CardHeader className="items-end-safe">
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'flex size-8 items-center-safe justify-center-safe rounded-full text-white',
|
||||||
|
iconBg,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{icon}
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardFooter className="flex flex-col items-start">
|
||||||
|
<h5>
|
||||||
|
<Trans i18nKey={title} />
|
||||||
|
</h5>
|
||||||
|
<CardDescription className={descriptionColor}>
|
||||||
|
<Trans i18nKey={description} />
|
||||||
|
</CardDescription>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="items-start">
|
||||||
|
<h4>
|
||||||
|
<Trans i18nKey="dashboard:recommendedForYou" />
|
||||||
|
</h4>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-6">
|
||||||
|
{dummyRecommendations.map(
|
||||||
|
(
|
||||||
|
{
|
||||||
|
icon,
|
||||||
|
color,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
tooltipContent,
|
||||||
|
price,
|
||||||
|
buttonText,
|
||||||
|
},
|
||||||
|
index,
|
||||||
|
) => {
|
||||||
|
return (
|
||||||
|
<div className="flex justify-between" key={index}>
|
||||||
|
<div className="flex flex-row items-center gap-4">
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'flex size-8 items-center-safe justify-center-safe rounded-full text-white',
|
||||||
|
color,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{icon}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="inline-flex items-center gap-1 align-baseline text-sm font-medium">
|
||||||
|
{title}
|
||||||
|
<InfoTooltip content={tooltipContent} />
|
||||||
|
</div>
|
||||||
|
<p className="text-muted-foreground text-sm">
|
||||||
|
{description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="grid w-36 auto-rows-fr grid-cols-2 items-center gap-4">
|
||||||
|
<p className="text-sm font-medium"> {price}</p>
|
||||||
|
<Button size="sm" variant="secondary">
|
||||||
|
{buttonText}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,65 +1,43 @@
|
|||||||
import {
|
import { ShoppingCart } from 'lucide-react';
|
||||||
BorderedNavigationMenu,
|
|
||||||
BorderedNavigationMenuItem,
|
import { Button } from '@kit/ui/button';
|
||||||
} from '@kit/ui/bordered-navigation-menu';
|
import { Trans } from '@kit/ui/trans';
|
||||||
import { If } from '@kit/ui/if';
|
import { cn } from '@kit/ui/utils';
|
||||||
|
|
||||||
import { AppLogo } from '~/components/app-logo';
|
import { AppLogo } from '~/components/app-logo';
|
||||||
import { ProfileAccountDropdownContainer } from '~/components/personal-account-dropdown-container';
|
import { ProfileAccountDropdownContainer } from '~/components/personal-account-dropdown-container';
|
||||||
import featuresFlagConfig from '~/config/feature-flags.config';
|
import { Search } from '~/components/ui/search';
|
||||||
import { personalAccountNavigationConfig } from '~/config/personal-account-navigation.config';
|
|
||||||
|
|
||||||
|
import { SIDEBAR_WIDTH } from '../../../../packages/ui/src/shadcn/constants';
|
||||||
// home imports
|
// home imports
|
||||||
import { HomeAccountSelector } from '../_components/home-account-selector';
|
|
||||||
import { UserNotifications } from '../_components/user-notifications';
|
import { UserNotifications } from '../_components/user-notifications';
|
||||||
import { type UserWorkspace } from '../_lib/server/load-user-workspace';
|
import { type UserWorkspace } from '../_lib/server/load-user-workspace';
|
||||||
|
|
||||||
export function HomeMenuNavigation(props: { workspace: UserWorkspace }) {
|
export function HomeMenuNavigation(props: { workspace: UserWorkspace }) {
|
||||||
const { workspace, user, accounts } = props.workspace;
|
const { workspace, user } = props.workspace;
|
||||||
|
|
||||||
const routes = personalAccountNavigationConfig.routes.reduce<
|
|
||||||
Array<{
|
|
||||||
path: string;
|
|
||||||
label: string;
|
|
||||||
Icon?: React.ReactNode;
|
|
||||||
end?: boolean | ((path: string) => boolean);
|
|
||||||
}>
|
|
||||||
>((acc, item) => {
|
|
||||||
if ('children' in item) {
|
|
||||||
return [...acc, ...item.children];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('divider' in item) {
|
|
||||||
return acc;
|
|
||||||
}
|
|
||||||
|
|
||||||
return [...acc, item];
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'flex w-full flex-1 justify-between'}>
|
<div className={'flex w-full flex-1 items-center justify-between gap-3'}>
|
||||||
<div className={'flex items-center space-x-8'}>
|
<div className={cn('flex items-center', `w-[${SIDEBAR_WIDTH}]`)}>
|
||||||
<AppLogo />
|
<AppLogo />
|
||||||
|
|
||||||
<BorderedNavigationMenu>
|
|
||||||
{routes.map((route) => (
|
|
||||||
<BorderedNavigationMenuItem {...route} key={route.path} />
|
|
||||||
))}
|
|
||||||
</BorderedNavigationMenu>
|
|
||||||
</div>
|
</div>
|
||||||
|
<Search
|
||||||
|
className="flex grow"
|
||||||
|
startElement={<Trans i18nKey="common:search" values={{ end: '...' }} />}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className={'flex justify-end space-x-2.5'}>
|
<div className="flex items-center justify-end gap-3">
|
||||||
|
<Button variant="outline">
|
||||||
|
<ShoppingCart className="stroke-[1.5px]" />
|
||||||
|
<Trans i18nKey="common:shoppingCart" /> (0)
|
||||||
|
</Button>
|
||||||
<UserNotifications userId={user.id} />
|
<UserNotifications userId={user.id} />
|
||||||
|
|
||||||
<If condition={featuresFlagConfig.enableTeamAccounts}>
|
|
||||||
<HomeAccountSelector userId={user.id} accounts={accounts} />
|
|
||||||
</If>
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<ProfileAccountDropdownContainer
|
<ProfileAccountDropdownContainer
|
||||||
user={user}
|
user={user}
|
||||||
account={workspace}
|
account={workspace}
|
||||||
showProfileName={false}
|
showProfileName
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,61 +1,29 @@
|
|||||||
import { If } from '@kit/ui/if';
|
|
||||||
import {
|
import {
|
||||||
Sidebar,
|
Sidebar,
|
||||||
SidebarContent,
|
SidebarContent,
|
||||||
SidebarFooter,
|
|
||||||
SidebarHeader,
|
SidebarHeader,
|
||||||
SidebarNavigation,
|
SidebarNavigation,
|
||||||
} from '@kit/ui/shadcn-sidebar';
|
} from '@kit/ui/shadcn-sidebar';
|
||||||
import { cn } from '@kit/ui/utils';
|
import { Trans } from '@kit/ui/trans';
|
||||||
|
|
||||||
import { AppLogo } from '~/components/app-logo';
|
|
||||||
import { ProfileAccountDropdownContainer } from '~/components/personal-account-dropdown-container';
|
|
||||||
import featuresFlagConfig from '~/config/feature-flags.config';
|
|
||||||
import { personalAccountNavigationConfig } from '~/config/personal-account-navigation.config';
|
import { personalAccountNavigationConfig } from '~/config/personal-account-navigation.config';
|
||||||
import { UserNotifications } from '~/home/(user)/_components/user-notifications';
|
|
||||||
|
|
||||||
// home imports
|
export function HomeSidebar() {
|
||||||
import type { UserWorkspace } from '../_lib/server/load-user-workspace';
|
|
||||||
import { HomeAccountSelector } from './home-account-selector';
|
|
||||||
|
|
||||||
interface HomeSidebarProps {
|
|
||||||
workspace: UserWorkspace;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function HomeSidebar(props: HomeSidebarProps) {
|
|
||||||
const { workspace, user, accounts } = props.workspace;
|
|
||||||
const collapsible = personalAccountNavigationConfig.sidebarCollapsedStyle;
|
const collapsible = personalAccountNavigationConfig.sidebarCollapsedStyle;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sidebar collapsible={collapsible}>
|
<Sidebar collapsible={collapsible}>
|
||||||
<SidebarHeader className={'h-16 justify-center'}>
|
<SidebarHeader className="h-24 justify-center">
|
||||||
<div className={'flex items-center justify-between gap-x-3'}>
|
<div className="mt-24 flex items-center">
|
||||||
<If
|
<h5>
|
||||||
condition={featuresFlagConfig.enableTeamAccounts}
|
<Trans i18nKey="common:myActions" />
|
||||||
fallback={
|
</h5>
|
||||||
<AppLogo
|
|
||||||
className={cn(
|
|
||||||
'p-2 group-data-[minimized=true]:max-w-full group-data-[minimized=true]:py-0',
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<HomeAccountSelector userId={user.id} accounts={accounts} />
|
|
||||||
</If>
|
|
||||||
|
|
||||||
<div className={'group-data-[minimized=true]:hidden'}>
|
|
||||||
<UserNotifications userId={user.id} />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</SidebarHeader>
|
</SidebarHeader>
|
||||||
|
|
||||||
<SidebarContent>
|
<SidebarContent>
|
||||||
<SidebarNavigation config={personalAccountNavigationConfig} />
|
<SidebarNavigation config={personalAccountNavigationConfig} />
|
||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
|
|
||||||
<SidebarFooter>
|
|
||||||
<ProfileAccountDropdownContainer user={user} account={workspace} />
|
|
||||||
</SidebarFooter>
|
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ function SidebarLayout({ children }: React.PropsWithChildren) {
|
|||||||
<SidebarProvider defaultOpen={state.open}>
|
<SidebarProvider defaultOpen={state.open}>
|
||||||
<Page style={'sidebar'}>
|
<Page style={'sidebar'}>
|
||||||
<PageNavigation>
|
<PageNavigation>
|
||||||
<HomeSidebar workspace={workspace} />
|
<HomeSidebar />
|
||||||
</PageNavigation>
|
</PageNavigation>
|
||||||
|
|
||||||
<PageMobileNavigation className={'flex items-center justify-between'}>
|
<PageMobileNavigation className={'flex items-center justify-between'}>
|
||||||
@@ -58,8 +58,8 @@ function HeaderLayout({ children }: React.PropsWithChildren) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<UserWorkspaceContextProvider value={workspace}>
|
<UserWorkspaceContextProvider value={workspace}>
|
||||||
<Page style={'header'}>
|
<Page style={'header'} >
|
||||||
<PageNavigation>
|
<PageNavigation >
|
||||||
<HomeMenuNavigation workspace={workspace} />
|
<HomeMenuNavigation workspace={workspace} />
|
||||||
</PageNavigation>
|
</PageNavigation>
|
||||||
|
|
||||||
@@ -67,7 +67,14 @@ function HeaderLayout({ children }: React.PropsWithChildren) {
|
|||||||
<MobileNavigation workspace={workspace} />
|
<MobileNavigation workspace={workspace} />
|
||||||
</PageMobileNavigation>
|
</PageMobileNavigation>
|
||||||
|
|
||||||
{children}
|
<SidebarProvider defaultOpen>
|
||||||
|
<Page style={'sidebar'}>
|
||||||
|
<PageNavigation>
|
||||||
|
<HomeSidebar />
|
||||||
|
</PageNavigation>
|
||||||
|
{children}
|
||||||
|
</Page>
|
||||||
|
</SidebarProvider>
|
||||||
</Page>
|
</Page>
|
||||||
</UserWorkspaceContextProvider>
|
</UserWorkspaceContextProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Trans } from '@kit/ui/trans';
|
|||||||
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
|
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
|
||||||
import { withI18n } from '~/lib/i18n/with-i18n';
|
import { withI18n } from '~/lib/i18n/with-i18n';
|
||||||
|
|
||||||
|
import Dashboard from './_components/dashboard';
|
||||||
// local imports
|
// local imports
|
||||||
import { HomeLayoutPageHeader } from './_components/home-page-header';
|
import { HomeLayoutPageHeader } from './_components/home-page-header';
|
||||||
|
|
||||||
@@ -21,10 +22,12 @@ function UserHomePage() {
|
|||||||
<>
|
<>
|
||||||
<HomeLayoutPageHeader
|
<HomeLayoutPageHeader
|
||||||
title={<Trans i18nKey={'common:routes.home'} />}
|
title={<Trans i18nKey={'common:routes.home'} />}
|
||||||
description={<Trans i18nKey={'common:homeTabDescription'} />}
|
description={<></>}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PageBody></PageBody>
|
<PageBody>
|
||||||
|
<Dashboard />
|
||||||
|
</PageBody>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
16
components/ui/info-tooltip.tsx
Normal file
16
components/ui/info-tooltip.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@kit/ui/tooltip";
|
||||||
|
import { Info } from "lucide-react";
|
||||||
|
|
||||||
|
export function InfoTooltip({ content }: { content?: string }) {
|
||||||
|
if (!content) return null;
|
||||||
|
return (
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger>
|
||||||
|
<Info className="size-4 cursor-pointer" />
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>{content}</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
33
components/ui/search.tsx
Normal file
33
components/ui/search.tsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import React, { JSX, ReactNode } from 'react';
|
||||||
|
|
||||||
|
import { cn } from '@kit/ui/utils';
|
||||||
|
|
||||||
|
export type SearchProps = React.InputHTMLAttributes<HTMLInputElement> & {
|
||||||
|
startElement?: string | JSX.Element;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Search = React.forwardRef<HTMLInputElement, SearchProps>(
|
||||||
|
({ className, startElement, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'border-input ring-offset-background focus-within:ring-ring flex h-10 items-center rounded-md border bg-white pl-3 text-sm focus-within:ring-1 focus-within:ring-offset-2',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{!!startElement && startElement}
|
||||||
|
<input
|
||||||
|
{...props}
|
||||||
|
type="search"
|
||||||
|
ref={ref}
|
||||||
|
className="placeholder:text-muted-foreground w-full p-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Search.displayName = 'Search';
|
||||||
|
|
||||||
|
export { Search };
|
||||||
@@ -13,6 +13,12 @@ const PathsSchema = z.object({
|
|||||||
}),
|
}),
|
||||||
app: z.object({
|
app: z.object({
|
||||||
home: z.string().min(1),
|
home: z.string().min(1),
|
||||||
|
booking: z.string().min(1),
|
||||||
|
myOrders: z.string().min(1),
|
||||||
|
analysisResults: z.string().min(1),
|
||||||
|
orderAnalysisPackage: z.string().min(1),
|
||||||
|
orderAnalysis: z.string().min(1),
|
||||||
|
orderHealthAnalysis: z.string().min(1),
|
||||||
personalAccountSettings: z.string().min(1),
|
personalAccountSettings: z.string().min(1),
|
||||||
personalAccountBilling: z.string().min(1),
|
personalAccountBilling: z.string().min(1),
|
||||||
personalAccountBillingReturn: z.string().min(1),
|
personalAccountBillingReturn: z.string().min(1),
|
||||||
@@ -47,6 +53,13 @@ const pathsConfig = PathsSchema.parse({
|
|||||||
accountMembers: `/home/[account]/members`,
|
accountMembers: `/home/[account]/members`,
|
||||||
accountBillingReturn: `/home/[account]/billing/return`,
|
accountBillingReturn: `/home/[account]/billing/return`,
|
||||||
joinTeam: '/join',
|
joinTeam: '/join',
|
||||||
|
// these routes are added as placeholders and can be changed when the pages are added
|
||||||
|
booking: '/booking',
|
||||||
|
myOrders: '/my-orders',
|
||||||
|
analysisResults: '/analysis-results',
|
||||||
|
orderAnalysisPackage: '/order-analysis-package',
|
||||||
|
orderAnalysis: '/order-analysis',
|
||||||
|
orderHealthAnalysis: '/order-health-analysis'
|
||||||
},
|
},
|
||||||
} satisfies z.infer<typeof PathsSchema>);
|
} satisfies z.infer<typeof PathsSchema>);
|
||||||
|
|
||||||
|
|||||||
@@ -1,47 +1,72 @@
|
|||||||
import { CreditCard, Home, User } from 'lucide-react';
|
import {
|
||||||
|
FileLineChart,
|
||||||
|
HeartPulse,
|
||||||
|
LineChart,
|
||||||
|
MousePointerClick,
|
||||||
|
ShoppingCart,
|
||||||
|
Stethoscope,
|
||||||
|
TestTube2,
|
||||||
|
} from 'lucide-react';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { NavigationConfigSchema } from '@kit/ui/navigation-schema';
|
import { NavigationConfigSchema } from '@kit/ui/navigation-schema';
|
||||||
|
|
||||||
import featureFlagsConfig from '~/config/feature-flags.config';
|
|
||||||
import pathsConfig from '~/config/paths.config';
|
import pathsConfig from '~/config/paths.config';
|
||||||
|
|
||||||
const iconClasses = 'w-4';
|
const iconClasses = 'w-4 stroke-[1.5px]';
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
label: 'common:routes.application',
|
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
label: 'common:routes.home',
|
label: 'common:routes.overview',
|
||||||
path: pathsConfig.app.home,
|
path: pathsConfig.app.home,
|
||||||
Icon: <Home className={iconClasses} />,
|
Icon: <LineChart className={iconClasses} />,
|
||||||
|
end: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common:routes.booking',
|
||||||
|
path: pathsConfig.app.booking,
|
||||||
|
Icon: <MousePointerClick className={iconClasses} />,
|
||||||
|
end: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common:routes.myOrders',
|
||||||
|
path: pathsConfig.app.myOrders,
|
||||||
|
Icon: <ShoppingCart className={iconClasses} />,
|
||||||
|
end: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common:routes.analysisResults',
|
||||||
|
path: pathsConfig.app.analysisResults,
|
||||||
|
Icon: <TestTube2 className={iconClasses} />,
|
||||||
|
end: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common:routes.orderAnalysisPackage',
|
||||||
|
path: pathsConfig.app.orderAnalysisPackage,
|
||||||
|
Icon: <HeartPulse className={iconClasses} />,
|
||||||
|
end: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common:routes.orderAnalysis',
|
||||||
|
path: pathsConfig.app.orderAnalysis,
|
||||||
|
Icon: <FileLineChart className={iconClasses} />,
|
||||||
|
end: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common:routes.orderHealthAnalysis',
|
||||||
|
path: pathsConfig.app.orderHealthAnalysis,
|
||||||
|
Icon: <Stethoscope className={iconClasses} />,
|
||||||
end: true,
|
end: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: 'common:routes.settings',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
label: 'common:routes.profile',
|
|
||||||
path: pathsConfig.app.personalAccountSettings,
|
|
||||||
Icon: <User className={iconClasses} />,
|
|
||||||
},
|
|
||||||
featureFlagsConfig.enablePersonalAccountBilling
|
|
||||||
? {
|
|
||||||
label: 'common:routes.billing',
|
|
||||||
path: pathsConfig.app.personalAccountBilling,
|
|
||||||
Icon: <CreditCard className={iconClasses} />,
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
].filter((route) => !!route),
|
|
||||||
},
|
|
||||||
] satisfies z.infer<typeof NavigationConfigSchema>['routes'];
|
] satisfies z.infer<typeof NavigationConfigSchema>['routes'];
|
||||||
|
|
||||||
export const personalAccountNavigationConfig = NavigationConfigSchema.parse({
|
export const personalAccountNavigationConfig = NavigationConfigSchema.parse({
|
||||||
routes,
|
routes,
|
||||||
style: process.env.NEXT_PUBLIC_USER_NAVIGATION_STYLE,
|
style: 'custom',
|
||||||
sidebarCollapsed: process.env.NEXT_PUBLIC_HOME_SIDEBAR_COLLAPSED,
|
sidebarCollapsed: false,
|
||||||
sidebarCollapsedStyle: process.env.NEXT_PUBLIC_SIDEBAR_COLLAPSED_STYLE,
|
sidebarCollapsedStyle: 'icon',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export const defaultI18nNamespaces = [
|
|||||||
'teams',
|
'teams',
|
||||||
'billing',
|
'billing',
|
||||||
'marketing',
|
'marketing',
|
||||||
|
'dashboard',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
13
lib/utils.ts
13
lib/utils.ts
@@ -1,5 +1,5 @@
|
|||||||
import { clsx, type ClassValue } from "clsx";
|
import { type ClassValue, clsx } from 'clsx';
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs));
|
return twMerge(clsx(inputs));
|
||||||
@@ -9,3 +9,12 @@ export function toArray<T>(input?: T | T[] | null): T[] {
|
|||||||
if (!input) return [];
|
if (!input) return [];
|
||||||
return Array.isArray(input) ? input : [input];
|
return Array.isArray(input) ? input : [input];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function toTitleCase(str?: string) {
|
||||||
|
if (!str) return '';
|
||||||
|
return str.replace(
|
||||||
|
/\w\S*/g,
|
||||||
|
(text: string) =>
|
||||||
|
text.charAt(0).toUpperCase() + text.substring(1).toLowerCase(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -121,12 +121,12 @@ export function NotificationsPopover(params: {
|
|||||||
return (
|
return (
|
||||||
<Popover modal open={open} onOpenChange={setOpen}>
|
<Popover modal open={open} onOpenChange={setOpen}>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<Button className={'relative h-9 w-9'} variant={'ghost'}>
|
<Button className="relative w-5" variant={'outline'}>
|
||||||
<Bell className={'min-h-4 min-w-4'} />
|
<Bell className={'min-h-4 min-w-4 stroke-[1.5px]'} />
|
||||||
|
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
`fade-in animate-in zoom-in absolute right-1 top-1 mt-0 flex h-3.5 w-3.5 items-center justify-center rounded-full bg-red-500 text-[0.65rem] text-white`,
|
`fade-in animate-in zoom-in absolute top-1 right-1 mt-0 flex h-3.5 w-3.5 items-center justify-center rounded-full bg-red-500 text-[0.65rem] text-white`,
|
||||||
{
|
{
|
||||||
hidden: !notifications.length,
|
hidden: !notifications.length,
|
||||||
},
|
},
|
||||||
@@ -186,7 +186,7 @@ export function NotificationsPopover(params: {
|
|||||||
<div
|
<div
|
||||||
key={notification.id.toString()}
|
key={notification.id.toString()}
|
||||||
className={cn(
|
className={cn(
|
||||||
'min-h-18 flex flex-col items-start justify-center gap-y-1 px-3 py-2',
|
'flex min-h-18 flex-col items-start justify-center gap-y-1 px-3 py-2',
|
||||||
)}
|
)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (params.onClick) {
|
if (params.onClick) {
|
||||||
|
|||||||
@@ -48,6 +48,48 @@ export type Database = {
|
|||||||
}
|
}
|
||||||
Relationships: []
|
Relationships: []
|
||||||
}
|
}
|
||||||
|
request_entries: {
|
||||||
|
Row: {
|
||||||
|
comment: string | null
|
||||||
|
created_at: string
|
||||||
|
id: number
|
||||||
|
personal_code: number | null
|
||||||
|
request_api: string
|
||||||
|
request_api_method: string
|
||||||
|
requested_end_date: string | null
|
||||||
|
requested_start_date: string | null
|
||||||
|
service_id: number | null
|
||||||
|
service_provider_id: number | null
|
||||||
|
status: Database["audit"]["Enums"]["request_status"]
|
||||||
|
}
|
||||||
|
Insert: {
|
||||||
|
comment?: string | null
|
||||||
|
created_at?: string
|
||||||
|
id?: number
|
||||||
|
personal_code?: number | null
|
||||||
|
request_api: string
|
||||||
|
request_api_method: string
|
||||||
|
requested_end_date?: string | null
|
||||||
|
requested_start_date?: string | null
|
||||||
|
service_id?: number | null
|
||||||
|
service_provider_id?: number | null
|
||||||
|
status: Database["audit"]["Enums"]["request_status"]
|
||||||
|
}
|
||||||
|
Update: {
|
||||||
|
comment?: string | null
|
||||||
|
created_at?: string
|
||||||
|
id?: number
|
||||||
|
personal_code?: number | null
|
||||||
|
request_api?: string
|
||||||
|
request_api_method?: string
|
||||||
|
requested_end_date?: string | null
|
||||||
|
requested_start_date?: string | null
|
||||||
|
service_id?: number | null
|
||||||
|
service_provider_id?: number | null
|
||||||
|
status?: Database["audit"]["Enums"]["request_status"]
|
||||||
|
}
|
||||||
|
Relationships: []
|
||||||
|
}
|
||||||
sync_entries: {
|
sync_entries: {
|
||||||
Row: {
|
Row: {
|
||||||
changed_by_role: string
|
changed_by_role: string
|
||||||
@@ -83,6 +125,7 @@ export type Database = {
|
|||||||
[_ in never]: never
|
[_ in never]: never
|
||||||
}
|
}
|
||||||
Enums: {
|
Enums: {
|
||||||
|
request_status: "SUCCESS" | "FAIL"
|
||||||
sync_status: "SUCCESS" | "FAIL"
|
sync_status: "SUCCESS" | "FAIL"
|
||||||
}
|
}
|
||||||
CompositeTypes: {
|
CompositeTypes: {
|
||||||
@@ -635,6 +678,158 @@ export type Database = {
|
|||||||
}
|
}
|
||||||
Relationships: []
|
Relationships: []
|
||||||
}
|
}
|
||||||
|
connected_online_providers: {
|
||||||
|
Row: {
|
||||||
|
can_select_worker: boolean
|
||||||
|
created_at: string
|
||||||
|
email: string | null
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
personal_code_required: boolean
|
||||||
|
phone_number: string | null
|
||||||
|
updated_at: string | null
|
||||||
|
}
|
||||||
|
Insert: {
|
||||||
|
can_select_worker: boolean
|
||||||
|
created_at?: string
|
||||||
|
email?: string | null
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
personal_code_required: boolean
|
||||||
|
phone_number?: string | null
|
||||||
|
updated_at?: string | null
|
||||||
|
}
|
||||||
|
Update: {
|
||||||
|
can_select_worker?: boolean
|
||||||
|
created_at?: string
|
||||||
|
email?: string | null
|
||||||
|
id?: number
|
||||||
|
name?: string
|
||||||
|
personal_code_required?: boolean
|
||||||
|
phone_number?: string | null
|
||||||
|
updated_at?: string | null
|
||||||
|
}
|
||||||
|
Relationships: []
|
||||||
|
}
|
||||||
|
connected_online_reservation: {
|
||||||
|
Row: {
|
||||||
|
booking_code: string
|
||||||
|
clinic_id: number
|
||||||
|
comments: string | null
|
||||||
|
created_at: string
|
||||||
|
discount_code: string | null
|
||||||
|
id: number
|
||||||
|
lang: string
|
||||||
|
requires_payment: boolean
|
||||||
|
service_id: number
|
||||||
|
service_user_id: number | null
|
||||||
|
start_time: string
|
||||||
|
sync_user_id: number
|
||||||
|
updated_at: string | null
|
||||||
|
user_id: string
|
||||||
|
}
|
||||||
|
Insert: {
|
||||||
|
booking_code: string
|
||||||
|
clinic_id: number
|
||||||
|
comments?: string | null
|
||||||
|
created_at?: string
|
||||||
|
discount_code?: string | null
|
||||||
|
id?: number
|
||||||
|
lang: string
|
||||||
|
requires_payment: boolean
|
||||||
|
service_id: number
|
||||||
|
service_user_id?: number | null
|
||||||
|
start_time: string
|
||||||
|
sync_user_id: number
|
||||||
|
updated_at?: string | null
|
||||||
|
user_id: string
|
||||||
|
}
|
||||||
|
Update: {
|
||||||
|
booking_code?: string
|
||||||
|
clinic_id?: number
|
||||||
|
comments?: string | null
|
||||||
|
created_at?: string
|
||||||
|
discount_code?: string | null
|
||||||
|
id?: number
|
||||||
|
lang?: string
|
||||||
|
requires_payment?: boolean
|
||||||
|
service_id?: number
|
||||||
|
service_user_id?: number | null
|
||||||
|
start_time?: string
|
||||||
|
sync_user_id?: number
|
||||||
|
updated_at?: string | null
|
||||||
|
user_id?: string
|
||||||
|
}
|
||||||
|
Relationships: []
|
||||||
|
}
|
||||||
|
connected_online_services: {
|
||||||
|
Row: {
|
||||||
|
clinic_id: number
|
||||||
|
code: string
|
||||||
|
created_at: string
|
||||||
|
description: string | null
|
||||||
|
display: string | null
|
||||||
|
duration: number
|
||||||
|
has_free_codes: boolean
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
neto_duration: number | null
|
||||||
|
online_hide_duration: number | null
|
||||||
|
online_hide_price: number | null
|
||||||
|
price: number
|
||||||
|
price_periods: string | null
|
||||||
|
requires_payment: boolean
|
||||||
|
sync_id: number
|
||||||
|
updated_at: string | null
|
||||||
|
}
|
||||||
|
Insert: {
|
||||||
|
clinic_id: number
|
||||||
|
code: string
|
||||||
|
created_at?: string
|
||||||
|
description?: string | null
|
||||||
|
display?: string | null
|
||||||
|
duration: number
|
||||||
|
has_free_codes: boolean
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
neto_duration?: number | null
|
||||||
|
online_hide_duration?: number | null
|
||||||
|
online_hide_price?: number | null
|
||||||
|
price: number
|
||||||
|
price_periods?: string | null
|
||||||
|
requires_payment: boolean
|
||||||
|
sync_id: number
|
||||||
|
updated_at?: string | null
|
||||||
|
}
|
||||||
|
Update: {
|
||||||
|
clinic_id?: number
|
||||||
|
code?: string
|
||||||
|
created_at?: string
|
||||||
|
description?: string | null
|
||||||
|
display?: string | null
|
||||||
|
duration?: number
|
||||||
|
has_free_codes?: boolean
|
||||||
|
id?: number
|
||||||
|
name?: string
|
||||||
|
neto_duration?: number | null
|
||||||
|
online_hide_duration?: number | null
|
||||||
|
online_hide_price?: number | null
|
||||||
|
price?: number
|
||||||
|
price_periods?: string | null
|
||||||
|
requires_payment?: boolean
|
||||||
|
sync_id?: number
|
||||||
|
updated_at?: string | null
|
||||||
|
}
|
||||||
|
Relationships: [
|
||||||
|
{
|
||||||
|
foreignKeyName: "connected_online_services_clinic_id_fkey"
|
||||||
|
columns: ["clinic_id"]
|
||||||
|
isOneToOne: false
|
||||||
|
referencedRelation: "connected_online_providers"
|
||||||
|
referencedColumns: ["id"]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
invitations: {
|
invitations: {
|
||||||
Row: {
|
Row: {
|
||||||
account_id: string
|
account_id: string
|
||||||
@@ -700,6 +895,129 @@ export type Database = {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
medreport_product_groups: {
|
||||||
|
Row: {
|
||||||
|
created_at: string
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
updated_at: string | null
|
||||||
|
}
|
||||||
|
Insert: {
|
||||||
|
created_at?: string
|
||||||
|
id?: number
|
||||||
|
name: string
|
||||||
|
updated_at?: string | null
|
||||||
|
}
|
||||||
|
Update: {
|
||||||
|
created_at?: string
|
||||||
|
id?: number
|
||||||
|
name?: string
|
||||||
|
updated_at?: string | null
|
||||||
|
}
|
||||||
|
Relationships: []
|
||||||
|
}
|
||||||
|
medreport_products: {
|
||||||
|
Row: {
|
||||||
|
created_at: string
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
product_group_id: number | null
|
||||||
|
updated_at: string | null
|
||||||
|
}
|
||||||
|
Insert: {
|
||||||
|
created_at?: string
|
||||||
|
id?: number
|
||||||
|
name: string
|
||||||
|
product_group_id?: number | null
|
||||||
|
updated_at?: string | null
|
||||||
|
}
|
||||||
|
Update: {
|
||||||
|
created_at?: string
|
||||||
|
id?: number
|
||||||
|
name?: string
|
||||||
|
product_group_id?: number | null
|
||||||
|
updated_at?: string | null
|
||||||
|
}
|
||||||
|
Relationships: [
|
||||||
|
{
|
||||||
|
foreignKeyName: "medreport_products_product_groups_id_fkey"
|
||||||
|
columns: ["product_group_id"]
|
||||||
|
isOneToOne: false
|
||||||
|
referencedRelation: "medreport_product_groups"
|
||||||
|
referencedColumns: ["id"]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
medreport_products_analyses_relations: {
|
||||||
|
Row: {
|
||||||
|
analysis_element_id: number | null
|
||||||
|
analysis_id: number | null
|
||||||
|
product_id: number
|
||||||
|
}
|
||||||
|
Insert: {
|
||||||
|
analysis_element_id?: number | null
|
||||||
|
analysis_id?: number | null
|
||||||
|
product_id: number
|
||||||
|
}
|
||||||
|
Update: {
|
||||||
|
analysis_element_id?: number | null
|
||||||
|
analysis_id?: number | null
|
||||||
|
product_id?: number
|
||||||
|
}
|
||||||
|
Relationships: [
|
||||||
|
{
|
||||||
|
foreignKeyName: "medreport_products_analyses_analysis_element_id_fkey"
|
||||||
|
columns: ["analysis_element_id"]
|
||||||
|
isOneToOne: true
|
||||||
|
referencedRelation: "analysis_elements"
|
||||||
|
referencedColumns: ["id"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
foreignKeyName: "medreport_products_analyses_analysis_id_fkey"
|
||||||
|
columns: ["analysis_id"]
|
||||||
|
isOneToOne: true
|
||||||
|
referencedRelation: "analyses"
|
||||||
|
referencedColumns: ["id"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
foreignKeyName: "medreport_products_analyses_product_id_fkey"
|
||||||
|
columns: ["product_id"]
|
||||||
|
isOneToOne: true
|
||||||
|
referencedRelation: "medreport_products"
|
||||||
|
referencedColumns: ["id"]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
medreport_products_external_services_relations: {
|
||||||
|
Row: {
|
||||||
|
connected_online_service_id: number
|
||||||
|
product_id: number
|
||||||
|
}
|
||||||
|
Insert: {
|
||||||
|
connected_online_service_id: number
|
||||||
|
product_id: number
|
||||||
|
}
|
||||||
|
Update: {
|
||||||
|
connected_online_service_id?: number
|
||||||
|
product_id?: number
|
||||||
|
}
|
||||||
|
Relationships: [
|
||||||
|
{
|
||||||
|
foreignKeyName: "medreport_products_connected_online_services_id_fkey"
|
||||||
|
columns: ["connected_online_service_id"]
|
||||||
|
isOneToOne: true
|
||||||
|
referencedRelation: "connected_online_services"
|
||||||
|
referencedColumns: ["id"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
foreignKeyName: "medreport_products_connected_online_services_product_id_fkey"
|
||||||
|
columns: ["product_id"]
|
||||||
|
isOneToOne: false
|
||||||
|
referencedRelation: "medreport_products"
|
||||||
|
referencedColumns: ["id"]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
nonces: {
|
nonces: {
|
||||||
Row: {
|
Row: {
|
||||||
client_token: string
|
client_token: string
|
||||||
@@ -1543,6 +1861,7 @@ export type CompositeTypes<
|
|||||||
export const Constants = {
|
export const Constants = {
|
||||||
audit: {
|
audit: {
|
||||||
Enums: {
|
Enums: {
|
||||||
|
request_status: ["SUCCESS", "FAIL"],
|
||||||
sync_status: ["SUCCESS", "FAIL"],
|
sync_status: ["SUCCESS", "FAIL"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ const RouteChild = z.object({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const RouteGroup = z.object({
|
const RouteGroup = z.object({
|
||||||
label: z.string(),
|
label: z.string().optional(),
|
||||||
collapsible: z.boolean().optional(),
|
collapsible: z.boolean().optional(),
|
||||||
collapsed: z.boolean().optional(),
|
collapsed: z.boolean().optional(),
|
||||||
children: z.array(RouteChild),
|
children: z.array(RouteChild),
|
||||||
@@ -37,12 +37,8 @@ const RouteGroup = z.object({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const NavigationConfigSchema = z.object({
|
export const NavigationConfigSchema = z.object({
|
||||||
style: z.enum(['custom', 'sidebar', 'header']).default('sidebar'),
|
style: z.enum(['custom', 'sidebar', 'header']).default('custom'),
|
||||||
sidebarCollapsed: z
|
sidebarCollapsed: z.boolean().optional(),
|
||||||
.enum(['false', 'true'])
|
|
||||||
.default('true')
|
|
||||||
.optional()
|
|
||||||
.transform((value) => value === `true`),
|
|
||||||
sidebarCollapsedStyle: z.enum(['offcanvas', 'icon', 'none']).default('icon'),
|
sidebarCollapsedStyle: z.enum(['offcanvas', 'icon', 'none']).default('icon'),
|
||||||
routes: z.array(z.union([RouteGroup, Divider])),
|
routes: z.array(z.union([RouteGroup, Divider])),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,10 +14,6 @@ type PageProps = React.PropsWithChildren<{
|
|||||||
sticky?: boolean;
|
sticky?: boolean;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
const ENABLE_SIDEBAR_TRIGGER = process.env.NEXT_PUBLIC_ENABLE_SIDEBAR_TRIGGER
|
|
||||||
? process.env.NEXT_PUBLIC_ENABLE_SIDEBAR_TRIGGER === 'true'
|
|
||||||
: true;
|
|
||||||
|
|
||||||
export function Page(props: PageProps) {
|
export function Page(props: PageProps) {
|
||||||
switch (props.style) {
|
switch (props.style) {
|
||||||
case 'header':
|
case 'header':
|
||||||
@@ -79,7 +75,7 @@ function PageWithHeader(props: PageProps) {
|
|||||||
const { Navigation, Children, MobileNavigation } = getSlotsFromPage(props);
|
const { Navigation, Children, MobileNavigation } = getSlotsFromPage(props);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('flex h-screen flex-1 flex-col', props.className)}>
|
<div className={cn('flex h-screen flex-1 flex-col z-1000', props.className)}>
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
props.contentContainerClassName ?? 'flex flex-1 flex-col space-y-4'
|
props.contentContainerClassName ?? 'flex flex-1 flex-col space-y-4'
|
||||||
@@ -87,9 +83,9 @@ function PageWithHeader(props: PageProps) {
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'bg-muted/40 dark:border-border dark:shadow-primary/10 flex h-14 items-center justify-between px-4 lg:justify-start lg:shadow-xs',
|
'bg-muted/40 dark:border-border dark:shadow-primary/10 flex h-14 items-center justify-between px-4 lg:justify-start lg:shadow-xs border-b',
|
||||||
{
|
{
|
||||||
'sticky top-0 z-10 backdrop-blur-md': props.sticky ?? true,
|
'sticky top-0 z-1000 backdrop-blur-md': props.sticky ?? true,
|
||||||
},
|
},
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -113,7 +109,10 @@ export function PageBody(
|
|||||||
className?: string;
|
className?: string;
|
||||||
}>,
|
}>,
|
||||||
) {
|
) {
|
||||||
const className = cn('flex w-full flex-1 flex-col lg:px-4', props.className);
|
const className = cn(
|
||||||
|
'flex w-full flex-1 flex-col space-y-6 lg:px-4',
|
||||||
|
props.className,
|
||||||
|
);
|
||||||
|
|
||||||
return <div className={className}>{props.children}</div>;
|
return <div className={className}>{props.children}</div>;
|
||||||
}
|
}
|
||||||
@@ -125,7 +124,7 @@ export function PageNavigation(props: React.PropsWithChildren) {
|
|||||||
export function PageDescription(props: React.PropsWithChildren) {
|
export function PageDescription(props: React.PropsWithChildren) {
|
||||||
return (
|
return (
|
||||||
<div className={'flex h-6 items-center'}>
|
<div className={'flex h-6 items-center'}>
|
||||||
<div className={'text-muted-foreground text-xs leading-none font-normal'}>
|
<div className={'text-muted-foreground text-sm leading-none font-normal'}>
|
||||||
{props.children}
|
{props.children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -153,7 +152,7 @@ export function PageHeader({
|
|||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
className,
|
className,
|
||||||
displaySidebarTrigger = ENABLE_SIDEBAR_TRIGGER,
|
displaySidebarTrigger = false,
|
||||||
}: React.PropsWithChildren<{
|
}: React.PropsWithChildren<{
|
||||||
className?: string;
|
className?: string;
|
||||||
title?: string | React.ReactNode;
|
title?: string | React.ReactNode;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import type { VariantProps } from 'class-variance-authority';
|
|||||||
import { cn } from '../lib/utils';
|
import { cn } from '../lib/utils';
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
'focus-visible:ring-ring inline-flex items-center justify-center rounded-md text-sm font-medium whitespace-nowrap transition-colors focus-visible:ring-1 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50',
|
'focus-visible:ring-ring gap-1 inline-flex items-center justify-center rounded-md text-sm font-medium whitespace-nowrap transition-colors focus-visible:ring-1 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50',
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
|
|||||||
@@ -1,15 +1,31 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import { cn } from '../lib/utils';
|
import { VariantProps, cva } from 'class-variance-authority';
|
||||||
|
import { cn } from '.';
|
||||||
|
|
||||||
const Card: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({
|
const cardVariants = cva('text-card-foreground rounded-xl border', {
|
||||||
className,
|
variants: {
|
||||||
...props
|
variant: {
|
||||||
}) => (
|
default: 'bg-card',
|
||||||
<div
|
'gradient-warning':
|
||||||
className={cn('bg-card text-card-foreground rounded-xl border', className)}
|
'from-warning/30 via-warning/10 to-background bg-gradient-to-t',
|
||||||
{...props}
|
'gradient-destructive':
|
||||||
/>
|
'from-destructive/30 via-destructive/10 to-background bg-gradient-to-t',
|
||||||
|
'gradient-success':
|
||||||
|
'from-success/30 via-success/10 to-background bg-gradient-to-t',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: 'default',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export interface CardProps
|
||||||
|
extends React.HTMLAttributes<HTMLDivElement>,
|
||||||
|
VariantProps<typeof cardVariants> {}
|
||||||
|
|
||||||
|
const Card: React.FC<CardProps> = ({ className, variant, ...props }) => (
|
||||||
|
<div className={cn(cardVariants({ variant, className }))} {...props} />
|
||||||
);
|
);
|
||||||
Card.displayName = 'Card';
|
Card.displayName = 'Card';
|
||||||
|
|
||||||
|
|||||||
3
packages/ui/src/shadcn/constants.ts
Normal file
3
packages/ui/src/shadcn/constants.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export const SIDEBAR_WIDTH = '16rem';
|
||||||
|
export const SIDEBAR_WIDTH_MOBILE = '18rem';
|
||||||
|
export const SIDEBAR_WIDTH_ICON = '4rem';
|
||||||
@@ -21,6 +21,11 @@ import {
|
|||||||
CollapsibleContent,
|
CollapsibleContent,
|
||||||
CollapsibleTrigger,
|
CollapsibleTrigger,
|
||||||
} from './collapsible';
|
} from './collapsible';
|
||||||
|
import {
|
||||||
|
SIDEBAR_WIDTH,
|
||||||
|
SIDEBAR_WIDTH_ICON,
|
||||||
|
SIDEBAR_WIDTH_MOBILE,
|
||||||
|
} from './constants';
|
||||||
import { Input } from './input';
|
import { Input } from './input';
|
||||||
import { Separator } from './separator';
|
import { Separator } from './separator';
|
||||||
import { Sheet, SheetContent } from './sheet';
|
import { Sheet, SheetContent } from './sheet';
|
||||||
@@ -34,9 +39,6 @@ import {
|
|||||||
|
|
||||||
const SIDEBAR_COOKIE_NAME = 'sidebar:state';
|
const SIDEBAR_COOKIE_NAME = 'sidebar:state';
|
||||||
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
|
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
|
||||||
const SIDEBAR_WIDTH = '16rem';
|
|
||||||
const SIDEBAR_WIDTH_MOBILE = '18rem';
|
|
||||||
const SIDEBAR_WIDTH_ICON = '4rem';
|
|
||||||
const SIDEBAR_KEYBOARD_SHORTCUT = 'b';
|
const SIDEBAR_KEYBOARD_SHORTCUT = 'b';
|
||||||
const SIDEBAR_MINIMIZED_WIDTH = SIDEBAR_WIDTH_ICON;
|
const SIDEBAR_MINIMIZED_WIDTH = SIDEBAR_WIDTH_ICON;
|
||||||
|
|
||||||
@@ -276,7 +278,7 @@ const Sidebar: React.FC<
|
|||||||
<div
|
<div
|
||||||
data-sidebar="sidebar"
|
data-sidebar="sidebar"
|
||||||
className={cn(
|
className={cn(
|
||||||
'bg-sidebar group-data-[variant=floating]:border-sidebar-border flex h-full w-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm',
|
'bg-sidebar group-data-[variant=floating]:border-sidebar-border ml-3 flex h-full w-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm',
|
||||||
{
|
{
|
||||||
'bg-transparent': variant === 'ghost',
|
'bg-transparent': variant === 'ghost',
|
||||||
},
|
},
|
||||||
@@ -908,7 +910,7 @@ export function SidebarNavigation({
|
|||||||
tooltip={child.label}
|
tooltip={child.label}
|
||||||
>
|
>
|
||||||
<Link
|
<Link
|
||||||
className={cn('flex items-center', {
|
className={cn('flex items-center font-medium', {
|
||||||
'mx-auto w-full gap-0! [&>svg]:flex-1': !open,
|
'mx-auto w-full gap-0! [&>svg]:flex-1': !open,
|
||||||
})}
|
})}
|
||||||
href={path}
|
href={path}
|
||||||
@@ -916,7 +918,7 @@ export function SidebarNavigation({
|
|||||||
{child.Icon}
|
{child.Icon}
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-auto transition-opacity duration-300',
|
'text-md w-auto font-medium transition-opacity duration-300',
|
||||||
{
|
{
|
||||||
'w-0 opacity-0': !open,
|
'w-0 opacity-0': !open,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -55,8 +55,19 @@
|
|||||||
"newVersionAvailableDescription": "A new version of the app is available. It is recommended to refresh the page to get the latest updates and avoid any issues.",
|
"newVersionAvailableDescription": "A new version of the app is available. It is recommended to refresh the page to get the latest updates and avoid any issues.",
|
||||||
"newVersionSubmitButton": "Reload and Update",
|
"newVersionSubmitButton": "Reload and Update",
|
||||||
"back": "Back",
|
"back": "Back",
|
||||||
|
"welcome": "Welcome",
|
||||||
|
"shoppingCart": "Shopping cart",
|
||||||
|
"search": "Search{{end}}",
|
||||||
|
"myActions": "My actions",
|
||||||
"routes": {
|
"routes": {
|
||||||
"home": "Home",
|
"home": "Home",
|
||||||
|
"overview": "Overview",
|
||||||
|
"booking": "Booking",
|
||||||
|
"myOrders": "My orders",
|
||||||
|
"analysisResults": "Analysis results",
|
||||||
|
"orderAnalysisPackage": "Telli analüüside pakett",
|
||||||
|
"orderAnalysis": "Order analysis",
|
||||||
|
"orderHealthAnalysis": "Telli terviseuuring",
|
||||||
"account": "Account",
|
"account": "Account",
|
||||||
"members": "Employees",
|
"members": "Employees",
|
||||||
"billing": "Billing",
|
"billing": "Billing",
|
||||||
|
|||||||
16
public/locales/en/dashboard.json
Normal file
16
public/locales/en/dashboard.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"recentlyCheckedDescription": "Super, oled käinud tervist kontrollimas. Siin on sinule olulised näitajad",
|
||||||
|
"respondToQuestion": "Respond",
|
||||||
|
"gender": "Gender",
|
||||||
|
"male": "Male",
|
||||||
|
"female": "Female",
|
||||||
|
"age": "Age",
|
||||||
|
"height": "Height",
|
||||||
|
"weight": "Weight",
|
||||||
|
"bmi": "BMI",
|
||||||
|
"bloodPressure": "Blood pressure",
|
||||||
|
"cholesterol": "Cholesterol",
|
||||||
|
"ldlCholesterol": "LDL Cholesterol",
|
||||||
|
"smoking": "Smoking",
|
||||||
|
"recommendedForYou": "Recommended for you"
|
||||||
|
}
|
||||||
@@ -54,8 +54,20 @@
|
|||||||
"newVersionAvailable": "New version available",
|
"newVersionAvailable": "New version available",
|
||||||
"newVersionAvailableDescription": "A new version of the app is available. It is recommended to refresh the page to get the latest updates and avoid any issues.",
|
"newVersionAvailableDescription": "A new version of the app is available. It is recommended to refresh the page to get the latest updates and avoid any issues.",
|
||||||
"newVersionSubmitButton": "Reload and Update",
|
"newVersionSubmitButton": "Reload and Update",
|
||||||
|
"back": "Back",
|
||||||
|
"welcome": "Tere tulemast",
|
||||||
|
"shoppingCart": "Ostukorv",
|
||||||
|
"search": "Otsi{{end}}",
|
||||||
|
"myActions": "Minu toimingud",
|
||||||
"routes": {
|
"routes": {
|
||||||
"home": "Home",
|
"home": "Home",
|
||||||
|
"overview": "Ülevaade",
|
||||||
|
"booking": "Broneeri aeg",
|
||||||
|
"myOrders": "Minu tellimused",
|
||||||
|
"analysisResults": "Analüüside vastused",
|
||||||
|
"orderAnalysisPackage": "Telli analüüside pakett",
|
||||||
|
"orderAnalysis": "Telli analüüs",
|
||||||
|
"orderHealthAnalysis": "Telli terviseuuring",
|
||||||
"account": "Account",
|
"account": "Account",
|
||||||
"members": "Employees",
|
"members": "Employees",
|
||||||
"billing": "Billing",
|
"billing": "Billing",
|
||||||
|
|||||||
16
public/locales/et/dashboard.json
Normal file
16
public/locales/et/dashboard.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"recentlyCheckedDescription": "Super, oled käinud tervist kontrollimas. Siin on sinule olulised näitajad",
|
||||||
|
"respondToQuestion": "Vasta küsimusele",
|
||||||
|
"gender": "Sugu",
|
||||||
|
"male": "Mees",
|
||||||
|
"female": "Naine",
|
||||||
|
"age": "Vanus",
|
||||||
|
"height": "Pikkus",
|
||||||
|
"weight": "Kaal",
|
||||||
|
"bmi": "KMI",
|
||||||
|
"bloodPressure": "Vererõhk",
|
||||||
|
"cholesterol": "Kolesterool",
|
||||||
|
"ldlCholesterol": "LDL kolesterool",
|
||||||
|
"smoking": "Suitsetamine",
|
||||||
|
"recommendedForYou": "Soovitused sulle"
|
||||||
|
}
|
||||||
@@ -55,8 +55,18 @@
|
|||||||
"newVersionAvailableDescription": "A new version of the app is available. It is recommended to refresh the page to get the latest updates and avoid any issues.",
|
"newVersionAvailableDescription": "A new version of the app is available. It is recommended to refresh the page to get the latest updates and avoid any issues.",
|
||||||
"newVersionSubmitButton": "Reload and Update",
|
"newVersionSubmitButton": "Reload and Update",
|
||||||
"back": "Back",
|
"back": "Back",
|
||||||
|
"welcome": "Welcome",
|
||||||
|
"shoppingCart": "Shopping cart",
|
||||||
|
"search": "Search{{end}}",
|
||||||
|
"myActions": "My actions",
|
||||||
"routes": {
|
"routes": {
|
||||||
"home": "Home",
|
"home": "Home",
|
||||||
|
"overview": "Overview",
|
||||||
|
"booking": "Booking",
|
||||||
|
"myOrders": "My orders",
|
||||||
|
"orderAnalysis": "Order analysis",
|
||||||
|
"orderAnalysisPackage": "Order analysis package",
|
||||||
|
"orderHealthAnalysis": "Order health analysis",
|
||||||
"account": "Account",
|
"account": "Account",
|
||||||
"members": "Employees",
|
"members": "Employees",
|
||||||
"billing": "Billing",
|
"billing": "Billing",
|
||||||
|
|||||||
16
public/locales/ru/dashboard.json
Normal file
16
public/locales/ru/dashboard.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"recentlyCheckedDescription": "Super, oled käinud tervist kontrollimas. Siin on sinule olulised näitajad",
|
||||||
|
"respondToQuestion": "Respond",
|
||||||
|
"gender": "Gender",
|
||||||
|
"male": "Male",
|
||||||
|
"female": "Female",
|
||||||
|
"age": "Age",
|
||||||
|
"height": "Height",
|
||||||
|
"weight": "Weight",
|
||||||
|
"bmi": "BMI",
|
||||||
|
"bloodPressure": "Blood pressure",
|
||||||
|
"cholesterol": "Cholesterol",
|
||||||
|
"ldlCholesterol": "LDL Cholesterol",
|
||||||
|
"smoking": "Smoking",
|
||||||
|
"recommendedForYou": "Recommended for you"
|
||||||
|
}
|
||||||
@@ -31,7 +31,9 @@
|
|||||||
@layer base {
|
@layer base {
|
||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground;
|
||||||
font-feature-settings: "rlig" 1, "calt" 1;
|
font-feature-settings:
|
||||||
|
'rlig' 1,
|
||||||
|
'calt' 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
*,
|
*,
|
||||||
@@ -47,9 +49,44 @@
|
|||||||
color: theme(--color-muted-foreground);
|
color: theme(--color-muted-foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
@apply font-heading text-foreground font-semibold tracking-tight;
|
||||||
|
}
|
||||||
|
|
||||||
h1,h2,h3,h4,h5,h6 {
|
h1 {
|
||||||
@apply font-heading text-foreground text-2xl font-semibold tracking-tight
|
@apply text-5xl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
@apply text-4xl;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
@apply text-3xl;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
@apply text-2xl;
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
@apply text-xl;
|
||||||
|
}
|
||||||
|
|
||||||
|
h6 {
|
||||||
|
@apply text-lg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lucide {
|
||||||
|
stroke-width: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lucide {
|
||||||
|
@apply size-4;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -7,127 +7,130 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
:root {
|
:root {
|
||||||
--font-sans: var(--font-sans) -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
--font-sans:
|
||||||
--font-heading: var(--font-heading);
|
var(--font-sans) -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
||||||
|
Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
|
||||||
|
'Segoe UI Symbol';
|
||||||
|
--font-heading: var(--font-heading);
|
||||||
|
|
||||||
--background: hsla(0, 0%, 100%, 1);
|
--background: hsla(0, 0%, 100%, 1);
|
||||||
--foreground: hsla(240, 10%, 4%, 1);
|
--foreground: hsla(240, 10%, 4%, 1);
|
||||||
--foreground-50: hsla(240, 10%, 4%, 0.5);
|
--foreground-50: hsla(240, 10%, 4%, 0.5);
|
||||||
|
|
||||||
--background-90: hsla(0, 0%, 100%, 0.9);
|
--background-90: hsla(0, 0%, 100%, 0.9);
|
||||||
--background-80: hsla(0, 0%, 100%, 0.8);
|
--background-80: hsla(0, 0%, 100%, 0.8);
|
||||||
|
|
||||||
--card: var(--color-white);
|
--card: var(--color-white);
|
||||||
--card-foreground: var(--color-neutral-950);
|
--card-foreground: var(--color-neutral-950);
|
||||||
|
|
||||||
--popover: hsla(0, 0%, 100%, 1);
|
--popover: hsla(0, 0%, 100%, 1);
|
||||||
--popover-foreground: hsla(240, 10%, 4%, 1);
|
--popover-foreground: hsla(240, 10%, 4%, 1);
|
||||||
|
|
||||||
--primary: hsla(145, 78%, 18%, 1);
|
--primary: hsla(145, 78%, 18%, 1);
|
||||||
--primary-foreground: hsla(356, 100%, 97%, 1);
|
--primary-foreground: hsla(356, 100%, 97%, 1);
|
||||||
|
|
||||||
--primary-90: hsla(145, 78%, 18%, 0.9);
|
--primary-90: hsla(145, 78%, 18%, 0.9);
|
||||||
--primary-80: hsla(145, 78%, 18%, 0.8);
|
--primary-80: hsla(145, 78%, 18%, 0.8);
|
||||||
--primary-50: hsla(145, 78%, 18%, 0.5);
|
--primary-50: hsla(145, 78%, 18%, 0.5);
|
||||||
--primary-20: hsla(145, 78%, 18%, 0.2);
|
--primary-20: hsla(145, 78%, 18%, 0.2);
|
||||||
--primary-10: hsla(145, 78%, 18%, 0.1);
|
--primary-10: hsla(145, 78%, 18%, 0.1);
|
||||||
|
|
||||||
--secondary: hsla(240, 5%, 96%, 1);
|
--secondary: hsla(240, 5%, 96%, 1);
|
||||||
--secondary-foreground: hsla(240, 6%, 10%, 1);
|
--secondary-foreground: hsla(240, 6%, 10%, 1);
|
||||||
|
|
||||||
--secondary-90: hsla(240, 5%, 96%, 0.9);
|
--secondary-90: hsla(240, 5%, 96%, 0.9);
|
||||||
--secondary-80: hsla(240, 5%, 96%, 0.8);
|
--secondary-80: hsla(240, 5%, 96%, 0.8);
|
||||||
|
|
||||||
|
--muted: hsla(240, 5%, 96%, 1);
|
||||||
|
--muted-foreground: hsla(240, 4%, 41%, 1);
|
||||||
|
|
||||||
--muted: hsla(240, 5%, 96%, 1);
|
--muted-90: hsla(240, 5%, 96%, 0.9);
|
||||||
--muted-foreground: hsla(240, 4%, 41%, 1);
|
--muted-80: hsla(240, 5%, 96%, 0.8);
|
||||||
|
--muted-50: hsla(240, 5%, 96%, 0.5);
|
||||||
|
--muted-40: hsla(240, 5%, 96%, 0.4);
|
||||||
|
|
||||||
--muted-90: hsla(240, 5%, 96%, 0.9);
|
--accent: hsla(240, 5%, 96%, 1);
|
||||||
--muted-80: hsla(240, 5%, 96%, 0.8);
|
--accent-foreground: hsla(240, 6%, 10%, 1);
|
||||||
--muted-50: hsla(240, 5%, 96%, 0.5);
|
|
||||||
--muted-40: hsla(240, 5%, 96%, 0.4);
|
|
||||||
|
|
||||||
--accent: hsla(240, 5%, 96%, 1);
|
--accent-90: hsla(240, 5%, 96%, 0.9);
|
||||||
--accent-foreground: hsla(240, 6%, 10%, 1);
|
--accent-80: hsla(240, 5%, 96%, 0.8);
|
||||||
|
--accent-50: hsla(240, 5%, 96%, 0.5);
|
||||||
|
|
||||||
--accent-90: hsla(240, 5%, 96%, 0.9);
|
--success: hsla(142, 76%, 36%, 1);
|
||||||
--accent-80: hsla(240, 5%, 96%, 0.8);
|
|
||||||
--accent-50: hsla(240, 5%, 96%, 0.5);
|
|
||||||
|
|
||||||
--destructive: hsla(0, 84%, 60%, 1);
|
--destructive: hsla(0, 84%, 60%, 1);
|
||||||
--destructive-foreground: hsla(0, 0%, 98%, 1);
|
--destructive-foreground: hsla(0, 0%, 98%, 1);
|
||||||
|
|
||||||
--destructiv-90: hsla(0, 84%, 60%, 0.9);
|
--destructiv-90: hsla(0, 84%, 60%, 0.9);
|
||||||
--destructiv-80: hsla(0, 84%, 60%, 0.8);
|
--destructiv-80: hsla(0, 84%, 60%, 0.8);
|
||||||
--destructiv-50: hsla(0, 84%, 60%, 0.5);
|
--destructiv-50: hsla(0, 84%, 60%, 0.5);
|
||||||
|
|
||||||
|
--border: hsla(240, 6%, 90%, 1);
|
||||||
|
--input: hsla(240, 6%, 90%, 1);
|
||||||
|
--ring: var(--color-neutral-800);
|
||||||
|
|
||||||
--border: hsla(240, 6%, 90%, 1);
|
--radius: calc(1rem);
|
||||||
--input: hsla(240, 6%, 90%, 1);
|
--spacing: 0.25rem;
|
||||||
--ring: var(--color-neutral-800);
|
|
||||||
|
|
||||||
--radius: calc(1rem);
|
--chart-1: var(--color-orange-400);
|
||||||
--spacing: 0.25rem;
|
--chart-2: var(--color-teal-600);
|
||||||
|
--chart-3: var(--color-green-800);
|
||||||
|
--chart-4: var(--color-yellow-200);
|
||||||
|
--chart-5: var(--color-orange-200);
|
||||||
|
|
||||||
--chart-1: var(--color-orange-400);
|
--sidebar-background: var(--background);
|
||||||
--chart-2: var(--color-teal-600);
|
--sidebar-foreground: var(--foreground);
|
||||||
--chart-3: var(--color-green-800);
|
--sidebar-primary: var(--primary);
|
||||||
--chart-4: var(--color-yellow-200);
|
--sidebar-primary-foreground: var(--color-white);
|
||||||
--chart-5: var(--color-orange-200);
|
--sidebar-accent: var(--secondary);
|
||||||
|
--sidebar-accent-foreground: var(--secondary-foreground);
|
||||||
|
--sidebar-border: var(--border);
|
||||||
|
--sidebar-ring: var(--ring);
|
||||||
|
}
|
||||||
|
|
||||||
--sidebar-background: var(--background);
|
.dark {
|
||||||
--sidebar-foreground: var(--foreground);
|
--background: var(--color-neutral-900);
|
||||||
--sidebar-primary: var(--primary);
|
--foreground: var(--color-white);
|
||||||
--sidebar-primary-foreground: var(--color-white);
|
|
||||||
--sidebar-accent: var(--secondary);
|
|
||||||
--sidebar-accent-foreground: var(--secondary-foreground);
|
|
||||||
--sidebar-border: var(--border);
|
|
||||||
--sidebar-ring: var(--ring);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark {
|
--card: var(--color-neutral-900);
|
||||||
--background: var(--color-neutral-900);
|
--card-foreground: var(--color-white);
|
||||||
--foreground: var(--color-white);
|
|
||||||
|
|
||||||
--card: var(--color-neutral-900);
|
--popover: var(--color-neutral-900);
|
||||||
--card-foreground: var(--color-white);
|
--popover-foreground: var(--color-white);
|
||||||
|
|
||||||
--popover: var(--color-neutral-900);
|
--primary: var(--color-white);
|
||||||
--popover-foreground: var(--color-white);
|
--primary-foreground: var(--color-neutral-900);
|
||||||
|
|
||||||
--primary: var(--color-white);
|
--secondary: var(--color-neutral-800);
|
||||||
--primary-foreground: var(--color-neutral-900);
|
--secondary-foreground: oklch(98.43% 0.0017 247.84);
|
||||||
|
|
||||||
--secondary: var(--color-neutral-800);
|
--muted: var(--color-neutral-800);
|
||||||
--secondary-foreground: oklch(98.43% 0.0017 247.84);
|
--muted-foreground: hsla(240, 4%, 41%, 1);
|
||||||
|
|
||||||
--muted: var(--color-neutral-800);
|
--accent: var(--color-neutral-800);
|
||||||
--muted-foreground: hsla(240, 4%, 41%, 1);
|
--accent-foreground: oklch(98.48% 0 0);
|
||||||
|
|
||||||
--accent: var(--color-neutral-800);
|
--destructive: var(--color-red-700);
|
||||||
--accent-foreground: oklch(98.48% 0 0);
|
--destructive-foreground: var(--color-white);
|
||||||
|
|
||||||
--destructive: var(--color-red-700);
|
--border: var(--color-neutral-800);
|
||||||
--destructive-foreground: var(--color-white);
|
--input: var(--color-neutral-700);
|
||||||
|
--ring: oklch(87.09% 0.0055 286.29);
|
||||||
|
|
||||||
--border: var(--color-neutral-800);
|
--chart-1: var(--color-blue-600);
|
||||||
--input: var(--color-neutral-700);
|
--chart-2: var(--color-emerald-400);
|
||||||
--ring: oklch(87.09% 0.0055 286.29);
|
--chart-3: var(--color-orange-400);
|
||||||
|
--chart-4: var(--color-purple-500);
|
||||||
|
--chart-5: var(--color-pink-500);
|
||||||
|
|
||||||
--chart-1: var(--color-blue-600);
|
--sidebar-background: var(--color-neutral-900);
|
||||||
--chart-2: var(--color-emerald-400);
|
--sidebar-foreground: var(--color-white);
|
||||||
--chart-3: var(--color-orange-400);
|
--sidebar-primary: var(--color-blue-500);
|
||||||
--chart-4: var(--color-purple-500);
|
--sidebar-primary-foreground: var(--color-white);
|
||||||
--chart-5: var(--color-pink-500);
|
--sidebar-accent: var(--color-neutral-800);
|
||||||
|
--sidebar-accent-foreground: var(--color-white);
|
||||||
--sidebar-background: var(--color-neutral-900);
|
--sidebar-border: var(--border);
|
||||||
--sidebar-foreground: var(--color-white);
|
--sidebar-ring: var(--color-blue-500);
|
||||||
--sidebar-primary: var(--color-blue-500);
|
}
|
||||||
--sidebar-primary-foreground: var(--color-white);
|
|
||||||
--sidebar-accent: var(--color-neutral-800);
|
|
||||||
--sidebar-accent-foreground: var(--color-white);
|
|
||||||
--sidebar-border: var(--border);
|
|
||||||
--sidebar-ring: var(--color-blue-500);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -43,6 +43,15 @@
|
|||||||
--color-chart-4: var(--chart-4);
|
--color-chart-4: var(--chart-4);
|
||||||
--color-chart-5: var(--chart-5);
|
--color-chart-5: var(--chart-5);
|
||||||
|
|
||||||
|
--success: hsla(142, 76%, 36%, 1);
|
||||||
|
--color-success: var(--success);
|
||||||
|
|
||||||
|
--warning: hsla(25, 95%, 53%, 1);
|
||||||
|
--color-warning: var(--warning);
|
||||||
|
|
||||||
|
--cyan: hsla(189, 94%, 43%, 1);
|
||||||
|
--color-cyan: var(--cyan);
|
||||||
|
|
||||||
/* text colors */
|
/* text colors */
|
||||||
--color-text-foreground: var(--foreground);
|
--color-text-foreground: var(--foreground);
|
||||||
--color-text-primary: var(--primary);
|
--color-text-primary: var(--primary);
|
||||||
|
|||||||
Reference in New Issue
Block a user