MED-140: ui fixes (#69)

* MED-140: ui fixes

* make accountid optional in hook
This commit is contained in:
Helena
2025-09-02 12:14:24 +03:00
committed by GitHub
parent 3498406a0c
commit 9d62a2d86f
13 changed files with 142 additions and 64 deletions

View File

@@ -69,7 +69,7 @@ function AuthButtons() {
</div> </div>
<div className={'flex gap-x-2.5'}> <div className={'flex gap-x-2.5'}>
<Button className={'hidden md:block'} asChild variant={'ghost'}> <Button className={'block'} asChild variant={'ghost'}>
<Link href={pathsConfig.auth.signIn}> <Link href={pathsConfig.auth.signIn}>
<Trans i18nKey={'auth:signIn'} /> <Trans i18nKey={'auth:signIn'} />
</Link> </Link>

View File

@@ -1,30 +1,28 @@
import { Trans } from '@kit/ui/trans';
import {
Card,
CardHeader,
CardDescription,
CardFooter,
} from '@kit/ui/card';
import Link from 'next/link'; import Link from 'next/link';
import { Button } from '@kit/ui/button';
import { ChevronRight, HeartPulse } from 'lucide-react'; import { ChevronRight, HeartPulse } from 'lucide-react';
import { Button } from '@kit/ui/button';
import { Card, CardDescription, CardFooter, CardHeader } from '@kit/ui/card';
import { Trans } from '@kit/ui/trans';
export default function DashboardCards() { export default function DashboardCards() {
return ( return (
<div className='flex gap-4 lg:px-4'> <div className="flex gap-4 lg:px-4">
<Card <Card
variant="gradient-success" variant="gradient-success"
className="flex flex-col justify-between" className="xs:w-1/2 sm:w-auto flex w-full flex-col justify-between"
> >
<CardHeader className="flex-row"> <CardHeader className="flex-row">
<div <div
className={'flex size-8 items-center-safe justify-center-safe rounded-full text-white bg-primary\/10 mb-6'} className={
'bg-primary/10 mb-6 flex size-8 items-center-safe justify-center-safe rounded-full text-white'
}
> >
<HeartPulse className="size-4 fill-green-500" /> <HeartPulse className="size-4 fill-green-500" />
</div> </div>
<div className='ml-auto flex size-8 items-center-safe justify-center-safe rounded-full text-white bg-warning'> <div className="bg-warning ml-auto flex size-8 items-center-safe justify-center-safe rounded-full text-white">
<Link href='/home/order-analysis'> <Link href="/home/order-analysis">
<Button size="icon" variant="outline" className="px-2 text-black"> <Button size="icon" variant="outline" className="px-2 text-black">
<ChevronRight className="size-4 stroke-2" /> <ChevronRight className="size-4 stroke-2" />
</Button> </Button>
@@ -33,10 +31,10 @@ export default function DashboardCards() {
</CardHeader> </CardHeader>
<CardFooter className="flex flex-col items-start gap-2"> <CardFooter className="flex flex-col items-start gap-2">
<h5> <h5>
<Trans i18nKey='dashboard:heroCard.orderAnalysis.title' /> <Trans i18nKey="dashboard:heroCard.orderAnalysis.title" />
</h5> </h5>
<CardDescription className="text-primary"> <CardDescription className="text-primary">
<Trans i18nKey='dashboard:heroCard.orderAnalysis.description' /> <Trans i18nKey="dashboard:heroCard.orderAnalysis.description" />
</CardDescription> </CardDescription>
</CardFooter> </CardFooter>
</Card> </Card>

View File

@@ -166,7 +166,7 @@ export default function Dashboard({
return ( return (
<> <>
<div className="grid auto-rows-fr grid-cols-2 gap-3 sm:grid-cols-4 lg:grid-cols-5"> <div className="xs:grid-cols-2 grid auto-rows-fr gap-3 sm:grid-cols-4 lg:grid-cols-5">
{cards({ {cards({
gender: params?.gender, gender: params?.gender,
age: params?.age, age: params?.age,
@@ -233,8 +233,11 @@ export default function Dashboard({
index, index,
) => { ) => {
return ( return (
<div className="flex justify-between" key={index}> <div
<div className="mr-4 flex flex-row items-center gap-4"> className="flex w-full justify-between gap-3 overflow-scroll"
key={index}
>
<div className="mr-4 flex min-w-fit flex-row items-center gap-4">
<div <div
className={cn( className={cn(
'flex size-8 items-center-safe justify-center-safe rounded-full text-white', 'flex size-8 items-center-safe justify-center-safe rounded-full text-white',
@@ -243,7 +246,7 @@ export default function Dashboard({
> >
{icon} {icon}
</div> </div>
<div> <div className="min-w-fit">
<div className="inline-flex items-center gap-1 align-baseline text-sm font-medium"> <div className="inline-flex items-center gap-1 align-baseline text-sm font-medium">
{title} {title}
<InfoTooltip content={tooltipContent} /> <InfoTooltip content={tooltipContent} />
@@ -253,16 +256,24 @@ export default function Dashboard({
</p> </p>
</div> </div>
</div> </div>
<div className="grid w-36 auto-rows-fr grid-cols-2 items-center gap-4"> <div className="grid w-36 auto-rows-fr grid-cols-2 items-center gap-4 min-w-fit">
<p className="text-sm font-medium"> {price}</p> <p className="text-sm font-medium"> {price}</p>
{href ? ( {href ? (
<Link href={href}> <Link href={href}>
<Button size="sm" variant="secondary"> <Button
size="sm"
variant="secondary"
className="w-full min-w-fit"
>
{buttonText} {buttonText}
</Button> </Button>
</Link> </Link>
) : ( ) : (
<Button size="sm" variant="secondary"> <Button
size="sm"
variant="secondary"
className="w-full min-w-fit"
>
{buttonText} {buttonText}
</Button> </Button>
)} )}

View File

@@ -1,12 +1,16 @@
'use client'; 'use client';
import { useMemo } from 'react';
import Link from 'next/link'; import Link from 'next/link';
import { StoreCart } from '@medusajs/types'; import { StoreCart } from '@medusajs/types';
import { LogOut, Menu, ShoppingCart } from 'lucide-react'; import { Cross, LogOut, Menu, Shield, ShoppingCart } from 'lucide-react';
import { usePersonalAccountData } from '@kit/accounts/hooks/use-personal-account-data';
import { ApplicationRoleEnum } from '@kit/accounts/types/accounts';
import { import {
featureFlagsConfig, pathsConfig,
personalAccountNavigationConfig, personalAccountNavigationConfig,
} from '@kit/shared/config'; } from '@kit/shared/config';
import { useSignOut } from '@kit/supabase/hooks/use-sign-out'; import { useSignOut } from '@kit/supabase/hooks/use-sign-out';
@@ -15,7 +19,6 @@ import {
DropdownMenuContent, DropdownMenuContent,
DropdownMenuGroup, DropdownMenuGroup,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuTrigger, DropdownMenuTrigger,
} from '@kit/ui/dropdown-menu'; } from '@kit/ui/dropdown-menu';
@@ -23,14 +26,16 @@ import { If } from '@kit/ui/if';
import { Trans } from '@kit/ui/trans'; import { Trans } from '@kit/ui/trans';
// home imports // home imports
import { HomeAccountSelector } from '../_components/home-account-selector';
import type { UserWorkspace } from '../_lib/server/load-user-workspace'; import type { UserWorkspace } from '../_lib/server/load-user-workspace';
export function HomeMobileNavigation(props: { export function HomeMobileNavigation(props: {
workspace: UserWorkspace; workspace: UserWorkspace;
cart: StoreCart | null; cart: StoreCart | null;
}) { }) {
const user = props.workspace.user;
const signOut = useSignOut(); const signOut = useSignOut();
const { data: personalAccountData } = usePersonalAccountData(user.id);
const Links = personalAccountNavigationConfig.routes.map((item, index) => { const Links = personalAccountNavigationConfig.routes.map((item, index) => {
if ('children' in item) { if ('children' in item) {
@@ -51,7 +56,29 @@ export function HomeMobileNavigation(props: {
} }
}); });
const cartQuantityTotal = props.cart?.items?.reduce((acc, item) => acc + item.quantity, 0) ?? 0; const hasTotpFactor = useMemo(() => {
const factors = user?.factors ?? [];
return factors.some(
(factor) => factor.factor_type === 'totp' && factor.status === 'verified',
);
}, [user?.factors]);
const isSuperAdmin = useMemo(() => {
const hasAdminRole =
personalAccountData?.application_role === ApplicationRoleEnum.SuperAdmin;
return hasAdminRole && hasTotpFactor;
}, [user, personalAccountData, hasTotpFactor]);
const isDoctor = useMemo(() => {
const hasDoctorRole =
personalAccountData?.application_role === ApplicationRoleEnum.Doctor;
return hasDoctorRole && hasTotpFactor;
}, [user, personalAccountData, hasTotpFactor]);
const cartQuantityTotal =
props.cart?.items?.reduce((acc, item) => acc + item.quantity, 0) ?? 0;
const hasCartItems = cartQuantityTotal > 0; const hasCartItems = cartQuantityTotal > 0;
return ( return (
@@ -61,22 +88,6 @@ export function HomeMobileNavigation(props: {
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent sideOffset={10} className={'w-screen rounded-none'}> <DropdownMenuContent sideOffset={10} className={'w-screen rounded-none'}>
<If condition={featureFlagsConfig.enableTeamAccounts}>
<DropdownMenuGroup>
<DropdownMenuLabel>
<Trans i18nKey={'common:yourAccounts'} />
</DropdownMenuLabel>
<HomeAccountSelector
userId={props.workspace.user.id}
accounts={props.workspace.accounts}
collisionPadding={0}
/>
</DropdownMenuGroup>
<DropdownMenuSeparator />
</If>
<If condition={props.cart && hasCartItems}> <If condition={props.cart && hasCartItems}>
<DropdownMenuGroup> <DropdownMenuGroup>
<DropdownLink <DropdownLink
@@ -91,6 +102,41 @@ export function HomeMobileNavigation(props: {
<DropdownMenuGroup>{Links}</DropdownMenuGroup> <DropdownMenuGroup>{Links}</DropdownMenuGroup>
<If condition={isSuperAdmin}>
<DropdownMenuSeparator />
<DropdownMenuItem asChild>
<Link
className={
's-full flex cursor-pointer items-center space-x-2 text-yellow-700 dark:text-yellow-500'
}
href={pathsConfig.app.admin}
>
<Shield className={'h-5'} />
<span>Super Admin</span>
</Link>
</DropdownMenuItem>
</If>
<If condition={isDoctor}>
<DropdownMenuSeparator />
<DropdownMenuItem asChild>
<Link
className={
'flex h-full cursor-pointer items-center space-x-2 text-yellow-700 dark:text-yellow-500'
}
href={pathsConfig.app.doctor}
>
<Cross className={'h-5'} />
<span>
<Trans i18nKey="common:doctor" />
</span>
</Link>
</DropdownMenuItem>
</If>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<SignOutDropdownItem onSignOut={() => signOut.mutateAsync()} /> <SignOutDropdownItem onSignOut={() => signOut.mutateAsync()} />

View File

@@ -52,7 +52,7 @@ export default function OrderAnalysesCards({
} }
return ( return (
<div className="grid grid-cols-3 gap-6 mt-4"> <div className="grid 2xs:grid-cols-3 gap-6 mt-4">
{analyses.map(({ {analyses.map(({
title, title,
variant, variant,

View File

@@ -6,8 +6,8 @@ import { listRegions } from '@lib/data/regions';
import { getAnalysisElementMedusaProductIds } from '@/utils/medusa-product'; import { getAnalysisElementMedusaProductIds } from '@/utils/medusa-product';
import type { StoreProduct } from '@medusajs/types'; import type { StoreProduct } from '@medusajs/types';
import { loadCurrentUserAccount } from './load-user-account'; import { loadCurrentUserAccount } from './load-user-account';
import { AnalysisPackageWithVariant } from '~/components/select-analysis-package';
import { AccountWithParams } from '@/packages/features/accounts/src/server/api'; import { AccountWithParams } from '@/packages/features/accounts/src/server/api';
import { AnalysisPackageWithVariant } from '@kit/shared/components/select-analysis-package';
async function countryCodesLoader() { async function countryCodesLoader() {
const countryCodes = await listRegions().then((regions) => const countryCodes = await listRegions().then((regions) =>

View File

@@ -5,12 +5,16 @@ import { useSupabase } from '@kit/supabase/hooks/use-supabase';
type UpdateData = Database['medreport']['Tables']['accounts']['Update']; type UpdateData = Database['medreport']['Tables']['accounts']['Update'];
export function useUpdateAccountData(accountId: string) { export function useUpdateAccountData(accountId?: string) {
const client = useSupabase(); const client = useSupabase();
const mutationKey = ['account:data', accountId]; const mutationKey = ['account:data', accountId];
const mutationFn = async (data: UpdateData) => { const mutationFn = async (data: UpdateData) => {
if (!accountId) {
return null;
}
const response = await client const response = await client
.schema('medreport') .schema('medreport')
.from('accounts') .from('accounts')

View File

@@ -7,7 +7,7 @@ export function AuthLayoutShell({
return ( return (
<div <div
className={ className={
'flex h-screen flex-col items-center justify-center' + 'sm:py-auto flex flex-col items-center justify-center py-6' +
' bg-background lg:bg-muted/30 gap-y-10 lg:gap-y-8' + ' bg-background lg:bg-muted/30 gap-y-10 lg:gap-y-8' +
' animate-in fade-in slide-in-from-top-16 zoom-in-95 duration-1000' ' animate-in fade-in slide-in-from-top-16 zoom-in-95 duration-1000'
} }

View File

@@ -1,6 +1,8 @@
import { Trans } from '@kit/ui/trans'; import { Trans } from '@kit/ui/trans';
import SelectAnalysisPackage, { AnalysisPackageWithVariant } from './select-analysis-package'; import SelectAnalysisPackage, {
AnalysisPackageWithVariant,
} from './select-analysis-package';
export default function SelectAnalysisPackages({ export default function SelectAnalysisPackages({
analysisPackages, analysisPackages,
@@ -10,11 +12,16 @@ export default function SelectAnalysisPackages({
countryCode: string; countryCode: string;
}) { }) {
return ( return (
<div className="grid grid-cols-3 gap-6"> <div className="grid gap-6 sm:grid-cols-3">
{analysisPackages.length > 0 ? analysisPackages.map( {analysisPackages.length > 0 ? (
(analysisPackage) => ( analysisPackages.map((analysisPackage) => (
<SelectAnalysisPackage key={analysisPackage.title} analysisPackage={analysisPackage} countryCode={countryCode} /> <SelectAnalysisPackage
)) : ( key={analysisPackage.title}
analysisPackage={analysisPackage}
countryCode={countryCode}
/>
))
) : (
<h4> <h4>
<Trans i18nKey="order-analysis-package:noPackagesAvailable" /> <Trans i18nKey="order-analysis-package:noPackagesAvailable" />
</h4> </h4>

View File

@@ -40,7 +40,7 @@ export function LanguageSelector({
}, [currentLanguage]); }, [currentLanguage]);
const userId = user?.id; const userId = user?.id;
const updateAccountMutation = useUpdateAccountData(userId!); const updateAccountMutation = useUpdateAccountData(userId);
const revalidateUserDataQuery = useRevalidatePersonalAccountDataQuery(); const revalidateUserDataQuery = useRevalidatePersonalAccountDataQuery();
const updateLanguagePreference = async ( const updateLanguagePreference = async (
@@ -52,6 +52,10 @@ export function LanguageSelector({
onChange(locale); onChange(locale);
} }
if (!userId) {
return i18n.changeLanguage(locale);
}
const promise = updateAccountMutation const promise = updateAccountMutation
.mutateAsync({ .mutateAsync({
preferred_locale: locale, preferred_locale: locale,

View File

@@ -1,5 +1,7 @@
import { cn } from '../../lib/utils';
import { LanguageSelector } from '@kit/ui/language-selector'; import { LanguageSelector } from '@kit/ui/language-selector';
import { cn } from '../../lib/utils';
interface HeaderProps extends React.HTMLAttributes<HTMLDivElement> { interface HeaderProps extends React.HTMLAttributes<HTMLDivElement> {
logo?: React.ReactNode; logo?: React.ReactNode;
navigation?: React.ReactNode; navigation?: React.ReactNode;
@@ -22,11 +24,16 @@ export const Header: React.FC<HeaderProps> = function ({
{...props} {...props}
> >
<div className="container"> <div className="container">
<div className="grid h-14 grid-cols-3 items-center"> <div className="2xs:h-14 2xs:grid-cols-3 grid h-24 w-full items-center">
<div className={'mx-auto md:mx-0'}>{logo}</div> <div className={'mx-auto flex'}>{logo}</div>
<div className="order-first md:order-none">{navigation}</div> <div className="2xs:order-none order-first">{navigation}</div>
<div className="flex items-center justify-end gap-x-2"><div className="max-w-[100px]"><LanguageSelector /></div>{actions}</div> <div className="2xs:justify-end 2xs:gap-x-2 flex items-center justify-evenly">
<div className="max-w-[100px]">
<LanguageSelector />
</div>
{actions}
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -42,7 +42,7 @@ function PageWithSidebar(props: PageProps) {
> >
{MobileNavigation} {MobileNavigation}
<div className={'bg-background flex flex-1 flex-col px-4 lg:px-0 pb-8'}> <div className={'bg-background flex flex-1 flex-col px-4 pb-8 lg:px-0'}>
{Children} {Children}
</div> </div>
</div> </div>
@@ -58,7 +58,7 @@ export function PageMobileNavigation(
return ( return (
<div <div
className={cn( className={cn(
'flex w-full items-center border-b px-4 py-2 lg:hidden lg:px-0', 'flex w-full items-center px-4 py-2 lg:hidden lg:px-0',
props.className, props.className,
)} )}
> >

View File

@@ -135,6 +135,7 @@
--animate-accordion-down: accordion-down 0.2s ease-out; --animate-accordion-down: accordion-down 0.2s ease-out;
--animate-accordion-up: accordion-up 0.2s ease-out; --animate-accordion-up: accordion-up 0.2s ease-out;
--breakpoint-2xs: 36rem;
--breakpoint-xs: 48rem; --breakpoint-xs: 48rem;
--breakpoint-sm: 64rem; --breakpoint-sm: 64rem;
--breakpoint-md: 70rem; --breakpoint-md: 70rem;