main <- develop

main <- develop
This commit is contained in:
danelkungla
2025-10-07 18:44:59 +03:00
committed by GitHub
52 changed files with 511 additions and 387 deletions

View File

@@ -41,7 +41,7 @@ export default async function syncAnalysisGroups() {
try { try {
console.info('Getting latest public message id'); console.info('Getting latest public message id');
const lastCheckedDate = await getLastCheckedDate(); // const lastCheckedDate = await getLastCheckedDate(); never used?
const latestMessage = await getLatestPublicMessageListItem(); const latestMessage = await getLatestPublicMessageListItem();
if (!latestMessage) { if (!latestMessage) {

View File

@@ -81,21 +81,19 @@ export default async function syncConnectedOnline() {
}); });
} }
let clinics;
let services;
let serviceProviders;
let jobTitleTranslations;
// Filter out "Dentas Demo OÜ" in prod or only sync "Dentas Demo OÜ" in any other environment // Filter out "Dentas Demo OÜ" in prod or only sync "Dentas Demo OÜ" in any other environment
const isDemoClinic = (clinicId: number) => const isDemoClinic = (clinicId: number) =>
isProd ? clinicId !== 2 : clinicId === 2; isProd ? clinicId !== 2 : clinicId === 2;
clinics = responseData.Data.T_Lic.filter(({ ID }) => isDemoClinic(ID)); const clinics = responseData.Data.T_Lic.filter(({ ID }) =>
services = responseData.Data.T_Service.filter(({ ClinicID }) => isDemoClinic(ID),
);
const services = responseData.Data.T_Service.filter(({ ClinicID }) =>
isDemoClinic(ClinicID), isDemoClinic(ClinicID),
); );
serviceProviders = responseData.Data.T_Doctor.filter(({ ClinicID }) => const serviceProviders = responseData.Data.T_Doctor.filter(({ ClinicID }) =>
isDemoClinic(ClinicID), isDemoClinic(ClinicID),
); );
jobTitleTranslations = createTranslationMap( const jobTitleTranslations = createTranslationMap(
responseData.Data.P_JobTitleTranslations.filter(({ ClinicID }) => responseData.Data.P_JobTitleTranslations.filter(({ ClinicID }) =>
isDemoClinic(ClinicID), isDemoClinic(ClinicID),
), ),

View File

@@ -21,7 +21,7 @@ export const POST = async (request: NextRequest) => {
try { try {
validateApiKey(request); validateApiKey(request);
} catch (e) { } catch {
return NextResponse.json({}, { status: 401, statusText: 'Unauthorized' }); return NextResponse.json({}, { status: 401, statusText: 'Unauthorized' });
} }

View File

@@ -14,7 +14,7 @@ export const POST = async (request: NextRequest) => {
try { try {
validateApiKey(request); validateApiKey(request);
} catch (e) { } catch {
return NextResponse.json({}, { status: 401, statusText: 'Unauthorized' }); return NextResponse.json({}, { status: 401, statusText: 'Unauthorized' });
} }

View File

@@ -9,7 +9,7 @@ export const POST = async (request: NextRequest) => {
try { try {
validateApiKey(request); validateApiKey(request);
} catch (e) { } catch {
return NextResponse.json({}, { status: 401, statusText: 'Unauthorized' }); return NextResponse.json({}, { status: 401, statusText: 'Unauthorized' });
} }

View File

@@ -9,7 +9,7 @@ export const POST = async (request: NextRequest) => {
try { try {
validateApiKey(request); validateApiKey(request);
} catch (e) { } catch {
return NextResponse.json({}, { status: 401, statusText: 'Unauthorized' }); return NextResponse.json({}, { status: 401, statusText: 'Unauthorized' });
} }

View File

@@ -9,7 +9,7 @@ export const POST = async (request: NextRequest) => {
try { try {
validateApiKey(request); validateApiKey(request);
} catch (e) { } catch {
return NextResponse.json({}, { status: 401, statusText: 'Unauthorized' }); return NextResponse.json({}, { status: 401, statusText: 'Unauthorized' });
} }

View File

@@ -9,7 +9,7 @@ export const POST = async (request: NextRequest) => {
try { try {
validateApiKey(request); validateApiKey(request);
} catch (e) { } catch {
return NextResponse.json({}, { status: 401, statusText: 'Unauthorized' }); return NextResponse.json({}, { status: 401, statusText: 'Unauthorized' });
} }

View File

@@ -18,7 +18,7 @@ export async function POST(request: NextRequest) {
try { try {
validateApiKey(request); validateApiKey(request);
} catch (e) { } catch {
return NextResponse.json({}, { status: 401, statusText: 'Unauthorized' }); return NextResponse.json({}, { status: 401, statusText: 'Unauthorized' });
} }

View File

@@ -1,5 +1,3 @@
import { withI18n } from '~/lib/i18n/with-i18n';
async function SiteLayout(props: React.PropsWithChildren) { async function SiteLayout(props: React.PropsWithChildren) {
return ( return (
<div className={'flex min-h-[100vh] flex-col items-center justify-center'}> <div className={'flex min-h-[100vh] flex-col items-center justify-center'}>

View File

@@ -4,7 +4,6 @@ import { updateCustomer } from '@lib/data/customer';
import { AccountSubmitData, createAuthApi } from '@kit/auth/api'; import { AccountSubmitData, createAuthApi } from '@kit/auth/api';
import { enhanceAction } from '@kit/next/actions'; import { enhanceAction } from '@kit/next/actions';
import { pathsConfig } from '@kit/shared/config';
import { getSupabaseServerClient } from '@kit/supabase/server-client'; import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { UpdateAccountSchemaServer } from '../schemas/update-account.schema'; import { UpdateAccountSchemaServer } from '../schemas/update-account.schema';

View File

@@ -0,0 +1,156 @@
import React, { useState } from 'react';
import { giveFeedbackAction } from '@/packages/features/doctor/src/lib/server/actions/doctor-server-actions';
import {
DoctorFeedback,
Order,
Patient,
} from '@/packages/features/doctor/src/lib/server/schema/doctor-analysis-detail-view.schema';
import {
DoctorAnalysisFeedbackForm,
doctorAnalysisFeedbackFormSchema,
} from '@/packages/features/doctor/src/lib/server/schema/doctor-analysis.schema';
import ConfirmationModal from '@/packages/shared/src/components/confirmation-modal';
import { useUser } from '@/packages/supabase/src/hooks/use-user';
import { zodResolver } from '@hookform/resolvers/zod';
import { useQueryClient } from '@tanstack/react-query';
import { useForm } from 'react-hook-form';
import { Trans } from '@kit/ui/makerkit/trans';
import { Button } from '@kit/ui/shadcn/button';
import {
Form,
FormControl,
FormField,
FormItem,
FormMessage,
} from '@kit/ui/shadcn/form';
import { toast } from '@kit/ui/shadcn/sonner';
import { Textarea } from '@kit/ui/shadcn/textarea';
const AnalysisFeedback = ({
feedback,
patient,
order,
}: {
feedback?: DoctorFeedback;
patient: Patient;
order: Order;
}) => {
const [isDraftSubmitting, setIsDraftSubmitting] = useState(false);
const [isConfirmOpen, setIsConfirmOpen] = useState(false);
const { data: user } = useUser();
const queryClient = useQueryClient();
const form = useForm({
resolver: zodResolver(doctorAnalysisFeedbackFormSchema),
reValidateMode: 'onChange',
defaultValues: {
feedbackValue: feedback?.value ?? '',
userId: patient.userId,
},
});
const isReadOnly =
!!feedback?.doctor_user_id && feedback?.doctor_user_id !== user?.id;
const handleDraftSubmit = async (e: React.FormEvent) => {
setIsDraftSubmitting(true);
e.preventDefault();
form.formState.errors.feedbackValue = undefined;
const formData = form.getValues();
await onSubmit(formData, 'DRAFT');
setIsDraftSubmitting(false);
};
const handleCompleteSubmit = form.handleSubmit(async () => {
setIsConfirmOpen(true);
});
const onSubmit = async (
data: DoctorAnalysisFeedbackForm,
status: 'DRAFT' | 'COMPLETED',
) => {
const result = await giveFeedbackAction({
...data,
analysisOrderId: order.analysisOrderId,
status,
});
if (!result.success) {
return toast.error(<Trans i18nKey="common:genericServerError" />);
}
queryClient.invalidateQueries({
predicate: (query) => query.queryKey.includes('doctor-jobs'),
});
toast.success(<Trans i18nKey={'doctor:updateFeedbackSuccess'} />);
return setIsConfirmOpen(false);
};
const confirmComplete = form.handleSubmit(async (data) => {
await onSubmit(data, 'COMPLETED');
});
return (
<>
<h3>
<Trans i18nKey="doctor:feedback" />
</h3>
<p>{feedback?.value ?? '-'}</p>
{!isReadOnly && (
<Form {...form}>
<form className="space-y-4 lg:w-1/2">
<FormField
control={form.control}
name="feedbackValue"
render={({ field }) => (
<FormItem>
<FormControl>
<Textarea {...field} disabled={isReadOnly} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="xs:flex block justify-end gap-2 space-y-2">
<Button
type="button"
variant="outline"
onClick={handleDraftSubmit}
disabled={
isReadOnly || isDraftSubmitting || form.formState.isSubmitting
}
className="xs:w-auto w-full text-xs"
>
<Trans i18nKey="common:saveAsDraft" />
</Button>
<Button
type="button"
onClick={handleCompleteSubmit}
disabled={
isReadOnly || isDraftSubmitting || form.formState.isSubmitting
}
className="xs:w-1/4 w-full"
>
<Trans i18nKey="common:save" />
</Button>
</div>
</form>
</Form>
)}
<ConfirmationModal
isOpen={isConfirmOpen}
onClose={() => setIsConfirmOpen(false)}
onConfirm={confirmComplete}
titleKey="doctor:confirmFeedbackModal.title"
descriptionKey="doctor:confirmFeedbackModal.description"
/>
</>
);
};
export default AnalysisFeedback;

View File

@@ -1,13 +1,8 @@
'use client'; 'use client';
import { useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import { capitalize } from 'lodash'; import { capitalize } from 'lodash';
import { useForm } from 'react-hook-form';
import { giveFeedbackAction } from '@kit/doctor/actions/doctor-server-actions';
import { import {
getDOBWithAgeStringFromPersonalCode, getDOBWithAgeStringFromPersonalCode,
getResultSetName, getResultSetName,
@@ -18,28 +13,14 @@ import {
Order, Order,
Patient, Patient,
} from '@kit/doctor/schema/doctor-analysis-detail-view.schema'; } from '@kit/doctor/schema/doctor-analysis-detail-view.schema';
import {
DoctorAnalysisFeedbackForm,
doctorAnalysisFeedbackFormSchema,
} from '@kit/doctor/schema/doctor-analysis.schema';
import ConfirmationModal from '@kit/shared/components/confirmation-modal';
import { useCurrentLocaleLanguageNames } from '@kit/shared/hooks'; import { useCurrentLocaleLanguageNames } from '@kit/shared/hooks';
import { getFullName } from '@kit/shared/utils'; import { getFullName } from '@kit/shared/utils';
import { useUser } from '@kit/supabase/hooks/use-user'; import { useUser } from '@kit/supabase/hooks/use-user';
import { Button } from '@kit/ui/button';
import {
Form,
FormControl,
FormField,
FormItem,
FormMessage,
} from '@kit/ui/form';
import { toast } from '@kit/ui/sonner';
import { Textarea } from '@kit/ui/textarea';
import { Trans } from '@kit/ui/trans'; import { Trans } from '@kit/ui/trans';
import { bmiFromMetric } from '~/lib/utils'; import { bmiFromMetric } from '~/lib/utils';
import AnalysisFeedback from './analysis-feedback';
import DoctorAnalysisWrapper from './doctor-analysis-wrapper'; import DoctorAnalysisWrapper from './doctor-analysis-wrapper';
import DoctorJobSelect from './doctor-job-select'; import DoctorJobSelect from './doctor-job-select';
@@ -54,10 +35,8 @@ export default function AnalysisView({
analyses: AnalysisResponse[]; analyses: AnalysisResponse[];
feedback?: DoctorFeedback; feedback?: DoctorFeedback;
}) { }) {
const [isConfirmOpen, setIsConfirmOpen] = useState(false);
const [isDraftSubmitting, setIsDraftSubmitting] = useState(false);
const { data: user } = useUser(); const { data: user } = useUser();
const queryClient = useQueryClient();
const languageNames = useCurrentLocaleLanguageNames(); const languageNames = useCurrentLocaleLanguageNames();
@@ -68,65 +47,11 @@ export default function AnalysisView({
); );
const isCurrentDoctorJob = const isCurrentDoctorJob =
!!feedback?.doctor_user_id && feedback?.doctor_user_id === user?.id; !!feedback?.doctor_user_id && feedback?.doctor_user_id === user?.id;
const isReadOnly =
!!feedback?.doctor_user_id && feedback?.doctor_user_id !== user?.id;
const form = useForm({
resolver: zodResolver(doctorAnalysisFeedbackFormSchema),
reValidateMode: 'onChange',
defaultValues: {
feedbackValue: feedback?.value ?? '',
userId: patient.userId,
},
});
const queryClient = useQueryClient();
if (!patient || !order || !analyses) { if (!patient || !order || !analyses) {
return null; return null;
} }
const onSubmit = async (
data: DoctorAnalysisFeedbackForm,
status: 'DRAFT' | 'COMPLETED',
) => {
const result = await giveFeedbackAction({
...data,
analysisOrderId: order.analysisOrderId,
status,
});
if (!result.success) {
return toast.error(<Trans i18nKey="common:genericServerError" />);
}
queryClient.invalidateQueries({
predicate: (query) => query.queryKey.includes('doctor-jobs'),
});
toast.success(<Trans i18nKey={'doctor:updateFeedbackSuccess'} />);
return setIsConfirmOpen(false);
};
const handleDraftSubmit = async (e: React.FormEvent) => {
setIsDraftSubmitting(true);
e.preventDefault();
form.formState.errors.feedbackValue = undefined;
const formData = form.getValues();
await onSubmit(formData, 'DRAFT');
setIsDraftSubmitting(false);
};
const handleCompleteSubmit = form.handleSubmit(async () => {
setIsConfirmOpen(true);
});
const confirmComplete = form.handleSubmit(async (data) => {
await onSubmit(data, 'COMPLETED');
});
return ( return (
<> <>
<div className="xs:flex xs:justify-between"> <div className="xs:flex xs:justify-between">
@@ -228,59 +153,9 @@ export default function AnalysisView({
); );
})} })}
</div> </div>
<h3> {order.isPackage && (
<Trans i18nKey="doctor:feedback" /> <AnalysisFeedback order={order} patient={patient} feedback={feedback} />
</h3>
<p>{feedback?.value ?? '-'}</p>
{!isReadOnly && (
<Form {...form}>
<form className="space-y-4 lg:w-1/2">
<FormField
control={form.control}
name="feedbackValue"
render={({ field }) => (
<FormItem>
<FormControl>
<Textarea {...field} disabled={isReadOnly} />
</FormControl>
<FormMessage />
</FormItem>
)} )}
/>
<div className="xs:flex block justify-end gap-2 space-y-2">
<Button
type="button"
variant="outline"
onClick={handleDraftSubmit}
disabled={
isReadOnly || isDraftSubmitting || form.formState.isSubmitting
}
className="xs:w-1/4 w-full"
>
<Trans i18nKey="common:saveAsDraft" />
</Button>
<Button
type="button"
onClick={handleCompleteSubmit}
disabled={
isReadOnly || isDraftSubmitting || form.formState.isSubmitting
}
className="xs:w-1/4 w-full"
>
<Trans i18nKey="common:save" />
</Button>
</div>
</form>
</Form>
)}
<ConfirmationModal
isOpen={isConfirmOpen}
onClose={() => setIsConfirmOpen(false)}
onConfirm={confirmComplete}
titleKey="doctor:confirmFeedbackModal.title"
descriptionKey="doctor:confirmFeedbackModal.description"
/>
</> </>
); );
} }

View File

@@ -108,7 +108,7 @@ export default async function AnalysisResultsPage({
)} )}
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
{orderedAnalysisElements ? ( {orderedAnalysisElements ? (
orderedAnalysisElements.map((element, index) => ( orderedAnalysisElements.map((element) => (
<React.Fragment key={element.analysisIdOriginal}> <React.Fragment key={element.analysisIdOriginal}>
<Analysis element={element} /> <Analysis element={element} />
{element.results?.nestedElements?.map( {element.results?.nestedElements?.map(

View File

@@ -1,5 +1,3 @@
import { use } from 'react';
import Link from 'next/link'; import Link from 'next/link';
import { createI18nServerInstance } from '@/lib/i18n/i18n.server'; import { createI18nServerInstance } from '@/lib/i18n/i18n.server';

View File

@@ -28,17 +28,18 @@ async function OrderAnalysisPackagePage() {
<PageBody> <PageBody>
<div className="space-y-3 text-center"> <div className="space-y-3 text-center">
<h3> <h3>
<Trans i18nKey={'marketing:selectPackage'} /> <Trans i18nKey="order-analysis-package:selectPackage" />
</h3> </h3>
<ComparePackagesModal <ComparePackagesModal
analysisPackages={analysisPackages} analysisPackages={analysisPackages}
analysisPackageElements={analysisPackageElements} analysisPackageElements={analysisPackageElements}
triggerElement={ triggerElement={
<Button variant="secondary" className="gap-2"> <Button variant="secondary" className="gap-2">
<Trans i18nKey={'marketing:comparePackages'} /> <Trans i18nKey="order-analysis-package:comparePackages" />
<Scale className="size-4 stroke-[1.5px]" /> <Scale className="size-4 stroke-[1.5px]" />
</Button> </Button>
} }
countryCode={countryCode}
/> />
</div> </div>
<SelectAnalysisPackages <SelectAnalysisPackages

View File

@@ -51,7 +51,7 @@ export const BookingProvider: React.FC<{
); );
setTimeSlots(response.timeSlots); setTimeSlots(response.timeSlots);
setLocations(response.locations); setLocations(response.locations);
} catch (error) { } catch {
setTimeSlots(null); setTimeSlots(null);
} finally { } finally {
setIsLoadingTimeSlots(false); setIsLoadingTimeSlots(false);

View File

@@ -0,0 +1,115 @@
'use client';
import React, { useState } from 'react';
import { useRouter } from 'next/navigation';
import { AnalysisPackageWithVariant } from '@/packages/shared/src/components/select-analysis-package';
import { pathsConfig } from '@/packages/shared/src/config';
import { Spinner } from '@kit/ui/makerkit/spinner';
import { Trans } from '@kit/ui/makerkit/trans';
import { Button } from '@kit/ui/shadcn/button';
import { toast } from '@kit/ui/shadcn/sonner';
import { Table, TableBody, TableCell, TableRow } from '@kit/ui/shadcn/table';
import { handleAddToCart } from '~/lib/services/medusaCart.service';
import { cn } from '~/lib/utils';
const AddToCartButton = ({
onClick,
disabled,
isLoading,
}: {
onClick: () => void;
disabled: boolean;
isLoading: boolean;
}) => {
return (
<TableCell align="center" className="xs:px-2 px-1 py-6">
<Button
onClick={onClick}
disabled={disabled}
className="xs:p-6 xs:text-sm relative p-2 text-[10px]"
>
{isLoading && (
<div className="absolute inset-0 flex items-center justify-center">
<Spinner />
</div>
)}
<div
className={cn({
invisible: isLoading,
})}
>
<Trans i18nKey="compare-packages-modal:selectThisPackage" />
</div>
</Button>
</TableCell>
);
};
const ComparePackagesAddToCartButtons = ({
countryCode,
standardPackage,
standardPlusPackage,
premiumPackage,
}: {
countryCode: string;
standardPackage: AnalysisPackageWithVariant;
standardPlusPackage: AnalysisPackageWithVariant;
premiumPackage: AnalysisPackageWithVariant;
}) => {
const [addedPackage, setAddedPackage] = useState<string | null>(null);
const router = useRouter();
const handleSelect = async ({ variantId }: AnalysisPackageWithVariant) => {
setAddedPackage(variantId);
try {
await handleAddToCart({
selectedVariant: { id: variantId },
countryCode,
});
setAddedPackage(null);
toast.success(
<Trans i18nKey={'order-analysis-package:analysisPackageAddedToCart'} />,
);
router.push(pathsConfig.app.cart);
} catch (e) {
toast.error(
<Trans
i18nKey={'order-analysis-package:analysisPackageAddToCartError'}
/>,
);
setAddedPackage(null);
console.error(e);
}
};
return (
<Table>
<TableBody>
<TableRow>
<TableCell className="w-[30vw] py-6" />
<AddToCartButton
onClick={() => handleSelect(standardPackage)}
disabled={!!addedPackage}
isLoading={addedPackage === standardPackage.variantId}
/>
<AddToCartButton
onClick={() => handleSelect(standardPlusPackage)}
disabled={!!addedPackage}
isLoading={addedPackage === standardPlusPackage.variantId}
/>
<AddToCartButton
onClick={() => handleSelect(premiumPackage)}
disabled={!!addedPackage}
isLoading={addedPackage === premiumPackage.variantId}
/>
</TableRow>
</TableBody>
</Table>
);
};
export default ComparePackagesAddToCartButtons;

View File

@@ -26,6 +26,9 @@ import {
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 ComparePackagesAddToCartButtons from './compare-packages-add-to-cart-buttons';
import DefaultPackageFeaturesRows from './default-package-features-rows';
export type AnalysisPackageElement = Pick< export type AnalysisPackageElement = Pick<
StoreProduct, StoreProduct,
'title' | 'id' | 'description' 'title' | 'id' | 'description'
@@ -35,7 +38,7 @@ export type AnalysisPackageElement = Pick<
isIncludedInPremium: boolean; isIncludedInPremium: boolean;
}; };
const CheckWithBackground = () => { export const CheckWithBackground = () => {
return ( return (
<div className="bg-primary w-min rounded-full p-1 text-white"> <div className="bg-primary w-min rounded-full p-1 text-white">
<Check className="size-3 stroke-2" /> <Check className="size-3 stroke-2" />
@@ -53,7 +56,7 @@ const PackageTableHead = async ({
const { title, price, nrOfAnalyses } = product; const { title, price, nrOfAnalyses } = product;
return ( return (
<TableHead className="py-2"> <TableHead className="xs:content-normal content-start py-2">
<PackageHeader <PackageHeader
title={t(title)} title={t(title)}
tagColor="bg-cyan" tagColor="bg-cyan"
@@ -69,10 +72,12 @@ const ComparePackagesModal = async ({
analysisPackages, analysisPackages,
analysisPackageElements, analysisPackageElements,
triggerElement, triggerElement,
countryCode,
}: { }: {
analysisPackages: AnalysisPackageWithVariant[]; analysisPackages: AnalysisPackageWithVariant[];
analysisPackageElements: AnalysisPackageElement[]; analysisPackageElements: AnalysisPackageElement[];
triggerElement: JSX.Element; triggerElement: JSX.Element;
countryCode: string;
}) => { }) => {
const { t } = await createI18nServerInstance(); const { t } = await createI18nServerInstance();
@@ -110,7 +115,7 @@ const ComparePackagesModal = async ({
<p className="text-muted-foreground mx-auto w-3/5 text-sm"> <p className="text-muted-foreground mx-auto w-3/5 text-sm">
{t('product:healthPackageComparison.description')} {t('product:healthPackageComparison.description')}
</p> </p>
<div className="max-h-[80vh] overflow-y-auto rounded-md border"> <div className="max-h-[50vh] overflow-y-auto rounded-md border sm:max-h-[70vh]">
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow> <TableRow>
@@ -121,6 +126,8 @@ const ComparePackagesModal = async ({
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
<DefaultPackageFeaturesRows />
{analysisPackageElements.map( {analysisPackageElements.map(
({ ({
title, title,
@@ -136,7 +143,7 @@ const ComparePackagesModal = async ({
return ( return (
<TableRow key={id}> <TableRow key={id}>
<TableCell className="py-6 sm:max-w-[30vw]"> <TableCell className="py-6 sm:w-[30vw]">
{title}{' '} {title}{' '}
{description && ( {description && (
<InfoTooltip <InfoTooltip
@@ -164,6 +171,12 @@ const ComparePackagesModal = async ({
</Table> </Table>
</div> </div>
</div> </div>
<ComparePackagesAddToCartButtons
countryCode={countryCode}
standardPackage={standardPackage}
premiumPackage={premiumPackage}
standardPlusPackage={standardPlusPackage}
/>
</div> </div>
</DialogContent> </DialogContent>
</Dialog> </Dialog>

View File

@@ -0,0 +1,46 @@
import React from 'react';
import { Trans } from '@kit/ui/makerkit/trans';
import { TableCell, TableRow } from '@kit/ui/shadcn/table';
import { withI18n } from '~/lib/i18n/with-i18n';
import { CheckWithBackground } from './compare-packages-modal';
const DefaultPackageFeaturesRows = () => {
return (
<>
<TableRow key="digital-doctor-feedback">
<TableCell className="max-w-[30vw] py-6">
<Trans i18nKey="order-analysis-package:digitalDoctorFeedback" />
</TableCell>
<TableCell align="center" className="py-6">
<CheckWithBackground />
</TableCell>
<TableCell align="center" className="py-6">
<CheckWithBackground />
</TableCell>
<TableCell align="center" className="py-6">
<CheckWithBackground />
</TableCell>
</TableRow>
<TableRow key="give-analyses">
<TableCell className="py-6 sm:max-w-[30vw]">
<Trans i18nKey="order-analysis-package:giveAnalyses" />
</TableCell>
<TableCell align="center" className="py-6">
<CheckWithBackground />
</TableCell>
<TableCell align="center" className="py-6">
<CheckWithBackground />
</TableCell>
<TableCell align="center" className="py-6">
<CheckWithBackground />
</TableCell>
</TableRow>
</>
);
};
export default withI18n(DefaultPackageFeaturesRows);

View File

@@ -26,13 +26,6 @@ export async function HomeMenuNavigation(props: {
const balanceSummary = workspace?.id const balanceSummary = workspace?.id
? await getAccountBalanceSummary(workspace.id) ? await getAccountBalanceSummary(workspace.id)
: null; : null;
const totalValue = props.cart?.total
? formatCurrency({
currencyCode: props.cart.currency_code,
locale: language,
value: props.cart.total,
})
: 0;
const cartQuantityTotal = const cartQuantityTotal =
props.cart?.items?.reduce((acc, item) => acc + item.quantity, 0) ?? 0; props.cart?.items?.reduce((acc, item) => acc + item.quantity, 0) ?? 0;

View File

@@ -25,16 +25,21 @@ import {
DropdownMenuTrigger, DropdownMenuTrigger,
} from '@kit/ui/dropdown-menu'; } from '@kit/ui/dropdown-menu';
import { If } from '@kit/ui/if'; import { If } from '@kit/ui/if';
import { cn } from '@kit/ui/shadcn';
import { Avatar, AvatarFallback, AvatarImage } from '@kit/ui/shadcn/avatar';
import { Button } from '@kit/ui/shadcn/button';
import { Trans } from '@kit/ui/trans'; import { Trans } from '@kit/ui/trans';
// home imports // home imports
import type { UserWorkspace } from '../_lib/server/load-user-workspace'; import type { UserWorkspace } from '../_lib/server/load-user-workspace';
const PERSONAL_ACCOUNT_SLUG = 'personal';
export function HomeMobileNavigation(props: { export function HomeMobileNavigation(props: {
workspace: UserWorkspace; workspace: UserWorkspace;
cart: StoreCart | null; cart: StoreCart | null;
}) { }) {
const user = props.workspace.user; const { user, accounts } = props.workspace;
const signOut = useSignOut(); const signOut = useSignOut();
const { data: personalAccountData } = usePersonalAccountData(user.id); const { data: personalAccountData } = usePersonalAccountData(user.id);
@@ -85,10 +90,28 @@ export function HomeMobileNavigation(props: {
return ( return (
<DropdownMenu> <DropdownMenu>
<div className="flex justify-between gap-4">
<Link href={pathsConfig.app.cart}>
<Button
variant="ghost"
className="relative mr-0 h-10 cursor-pointer border-1 px-4 py-2"
>
<ShoppingCart className="stroke-[1.5px]" />
{hasCartItems && (
<>
(
<span className="text-success font-bold">
{cartQuantityTotal}
</span>
)
</>
)}
</Button>
</Link>
<DropdownMenuTrigger> <DropdownMenuTrigger>
<Menu className={'h-9'} /> <Menu className="h-6 w-6" />
</DropdownMenuTrigger> </DropdownMenuTrigger>
</div>
<DropdownMenuContent sideOffset={10} className={'w-screen rounded-none'}> <DropdownMenuContent sideOffset={10} className={'w-screen rounded-none'}>
<If condition={props.cart && hasCartItems}> <If condition={props.cart && hasCartItems}>
<DropdownMenuGroup> <DropdownMenuGroup>
@@ -148,6 +171,46 @@ export function HomeMobileNavigation(props: {
</If> </If>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<If condition={accounts.length > 0}>
<span className="text-muted-foreground px-2 text-xs">
<Trans
i18nKey={'teams:yourTeams'}
values={{ teamsCount: accounts.length }}
/>
</span>
{accounts.map((account) => (
<DropdownMenuItem key={account.value} asChild>
<Link
className={'s-full flex cursor-pointer items-center space-x-2'}
href={`${pathsConfig.app.home}/${account.value}`}
>
<div className={'flex items-center'}>
<Avatar className={'h-5 w-5 rounded-xs ' + account.image}>
<AvatarImage
{...(account.image && { src: account.image })}
/>
<AvatarFallback
className={cn('rounded-md', {
['bg-background']:
PERSONAL_ACCOUNT_SLUG === account.value,
['group-hover:bg-background']:
PERSONAL_ACCOUNT_SLUG !== account.value,
})}
>
{account.label ? account.label[0] : ''}
</AvatarFallback>
</Avatar>
<span className={'pl-3'}>{account.label}</span>
</div>
</Link>
</DropdownMenuItem>
))}
<DropdownMenuSeparator />
</If>
<SignOutDropdownItem onSignOut={() => signOut.mutateAsync()} /> <SignOutDropdownItem onSignOut={() => signOut.mutateAsync()} />
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>

View File

@@ -5,7 +5,7 @@ import Link from 'next/link';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { pathsConfig } from '@/packages/shared/src/config'; import { pathsConfig } from '@/packages/shared/src/config';
import { ComponentInstanceIcon } from '@radix-ui/react-icons'; import { ComponentInstanceIcon } from '@radix-ui/react-icons';
import { ChevronRight, HeartPulse } from 'lucide-react'; import { ChevronRight } from 'lucide-react';
import { Button } from '@kit/ui/button'; import { Button } from '@kit/ui/button';
import { import {

View File

@@ -1,7 +1,6 @@
import React from 'react'; import React from 'react';
import { InfoTooltip } from '@/packages/shared/src/components/ui/info-tooltip'; import { InfoTooltip } from '@/packages/shared/src/components/ui/info-tooltip';
import { HeartPulse } from 'lucide-react';
import { Button } from '@kit/ui/shadcn/button'; import { Button } from '@kit/ui/shadcn/button';
import { import {

View File

@@ -48,7 +48,7 @@ export async function createInitialReservationAction(
export async function cancelTtoBooking(bookingCode: string, clinicId: number) { export async function cancelTtoBooking(bookingCode: string, clinicId: number) {
try { try {
const response = await fetch( await fetch(
`${process.env.CONNECTED_ONLINE_URL}/${ConnectedOnlineMethodName.ConfirmedCancel}`, `${process.env.CONNECTED_ONLINE_URL}/${ConnectedOnlineMethodName.ConfirmedCancel}`,
{ {
headers: { headers: {

View File

@@ -5,7 +5,7 @@ export const isValidOpenAiEnv = async () => {
const client = new OpenAI(); const client = new OpenAI();
await client.models.list(); await client.models.list();
return true; return true;
} catch (e) { } catch {
return false; return false;
} }
}; };

View File

@@ -1,9 +1,6 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { import { pathsConfig } from '@/packages/shared/src/config';
getTeamAccountSidebarConfig,
pathsConfig,
} from '@/packages/shared/src/config';
import { AppLogo } from '@kit/shared/components/app-logo'; import { AppLogo } from '@kit/shared/components/app-logo';
import { ProfileAccountDropdownContainer } from '@kit/shared/components/personal-account-dropdown-container'; import { ProfileAccountDropdownContainer } from '@kit/shared/components/personal-account-dropdown-container';
@@ -28,25 +25,6 @@ export function TeamAccountNavigationMenu(props: {
[rawAccounts], [rawAccounts],
); );
const routes = getTeamAccountSidebarConfig(account.slug).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 justify-between'}>
<div className={'flex items-center space-x-8'}> <div className={'flex items-center space-x-8'}>

View File

@@ -1,6 +1,6 @@
'use client'; 'use client';
import { useState } from 'react'; import { useMemo, useState } from 'react';
import { redirect } from 'next/navigation'; import { redirect } from 'next/navigation';
@@ -43,11 +43,13 @@ export default function TeamAccountStatistics({
accountBenefitStatistics, accountBenefitStatistics,
expensesOverview, expensesOverview,
}: TeamAccountStatisticsProps) { }: TeamAccountStatisticsProps) {
const date = useMemo<DateRange | undefined>(() => {
const currentDate = new Date(); const currentDate = new Date();
const [date, setDate] = useState<DateRange | undefined>({ return {
from: new Date(currentDate.getFullYear(), currentDate.getMonth(), 1), from: new Date(currentDate.getFullYear(), currentDate.getMonth(), 1),
to: new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0), to: new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0),
}); };
}, []);
const { const {
i18n: { language }, i18n: { language },
} = useTranslation(); } = useTranslation();

View File

@@ -397,7 +397,7 @@ export async function readPrivateMessageResponse({
try { try {
analysisOrder = await getAnalysisOrder({ analysisOrderId }); analysisOrder = await getAnalysisOrder({ analysisOrderId });
medusaOrderId = analysisOrder.medusa_order_id; medusaOrderId = analysisOrder.medusa_order_id;
} catch (e) { } catch {
if (IS_ENABLED_DELETE_PRIVATE_MESSAGE) { if (IS_ENABLED_DELETE_PRIVATE_MESSAGE) {
await deletePrivateMessage(privateMessageId); await deletePrivateMessage(privateMessageId);
} }
@@ -571,7 +571,6 @@ export async function sendOrderToMedipost({
phone: account.phone ?? '', phone: account.phone ?? '',
}, },
orderId: medreportOrder.id, orderId: medreportOrder.id,
orderCreatedAt: new Date(medreportOrder.created_at),
comment: '', comment: '',
}); });

View File

@@ -33,7 +33,6 @@ export async function composeOrderXML({
analysisElements, analysisElements,
person, person,
orderId, orderId,
orderCreatedAt,
comment, comment,
}: { }: {
analyses: AnalysesWithGroupsAndElements; analyses: AnalysesWithGroupsAndElements;
@@ -45,7 +44,6 @@ export async function composeOrderXML({
phone: string; phone: string;
}; };
orderId: number; orderId: number;
orderCreatedAt: Date;
comment?: string; comment?: string;
}) { }) {
const analysisGroups: Tables<{ schema: 'medreport' }, 'analysis_groups'>[] = const analysisGroups: Tables<{ schema: 'medreport' }, 'analysis_groups'>[] =

View File

@@ -2,6 +2,7 @@
import { loadCurrentUserAccount } from '@/app/home/(user)/_lib/server/load-user-account'; import { loadCurrentUserAccount } from '@/app/home/(user)/_lib/server/load-user-account';
import { MontonioOrderHandlerService } from '@/packages/billing/montonio/src'; import { MontonioOrderHandlerService } from '@/packages/billing/montonio/src';
import { getLogger } from '@/packages/shared/src/logger';
import { addToCart, deleteLineItem, retrieveCart } from '@lib/data/cart'; import { addToCart, deleteLineItem, retrieveCart } from '@lib/data/cart';
import { getCartId } from '@lib/data/cookies'; import { getCartId } from '@lib/data/cookies';
import { StoreCartLineItem, StoreProductVariant } from '@medusajs/types'; import { StoreCartLineItem, StoreProductVariant } from '@medusajs/types';
@@ -44,12 +45,17 @@ export async function handleAddToCart({
selectedVariant: Pick<StoreProductVariant, 'id'>; selectedVariant: Pick<StoreProductVariant, 'id'>;
countryCode: string; countryCode: string;
}) { }) {
try { const logger = await getLogger();
} catch (e) { const ctx = {
console.error('medusa card error: ', e); countryCode,
} selectedVariant,
};
logger.info(ctx, 'Adding to cart...');
const { account } = await loadCurrentUserAccount(); const { account } = await loadCurrentUserAccount();
if (!account) { if (!account) {
logger.error(ctx, 'Account not found');
throw new Error('Account not found'); throw new Error('Account not found');
} }

View File

@@ -43,7 +43,7 @@ export async function renderBookTimeFailedEmail({
</Text> </Text>
<Text> <Text>
Broneeringu {reservationId} Connected Online'i saatmine ei Broneeringu {reservationId} Connected Online&apos;i saatmine ei
õnnestunud, kliendile tuleb teha tagasimakse. õnnestunud, kliendile tuleb teha tagasimakse.
</Text> </Text>
<Text>Saadud error: {error}</Text> <Text>Saadud error: {error}</Text>

View File

@@ -69,7 +69,7 @@ export async function renderNewJobsAvailableEmail({
</Text> </Text>
<ul className="list-none text-[16px] leading-[24px]"> <ul className="list-none text-[16px] leading-[24px]">
{analysisResponseIds.map((analysisResponseId, index) => ( {analysisResponseIds.map((analysisResponseId, index) => (
<li> <li key={index}>
<Link <Link
key={analysisResponseId} key={analysisResponseId}
href={`${process.env.NEXT_PUBLIC_SITE_URL}/doctor/analysis/${analysisResponseId}`} href={`${process.env.NEXT_PUBLIC_SITE_URL}/doctor/analysis/${analysisResponseId}`}

View File

@@ -43,17 +43,9 @@ export function PersonalAccountDropdown({
showProfileName = true, showProfileName = true,
paths, paths,
features, features,
account,
accounts = [], accounts = [],
}: { }: {
user: User; user: User;
account?: {
id: string | null;
name: string | null;
picture_url: string | null;
application_role: ApplicationRole | null;
};
accounts: { accounts: {
label: string | null; label: string | null;
value: string | null; value: string | null;

View File

@@ -4,7 +4,6 @@ import { Tables } from '@kit/supabase/database';
import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client'; import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client';
import { getSupabaseServerClient } from '@kit/supabase/server-client'; import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { createTeamAccountsApi } from '@kit/team-accounts/api'; import { createTeamAccountsApi } from '@kit/team-accounts/api';
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
import { AppBreadcrumbs } from '@kit/ui/app-breadcrumbs'; import { AppBreadcrumbs } from '@kit/ui/app-breadcrumbs';
import { Badge } from '@kit/ui/badge'; import { Badge } from '@kit/ui/badge';
import { Button } from '@kit/ui/button'; import { Button } from '@kit/ui/button';
@@ -12,14 +11,6 @@ import { Heading } from '@kit/ui/heading';
import { If } from '@kit/ui/if'; import { If } from '@kit/ui/if';
import { PageBody, PageHeader } from '@kit/ui/page'; import { PageBody, PageHeader } from '@kit/ui/page';
import { ProfileAvatar } from '@kit/ui/profile-avatar'; import { ProfileAvatar } from '@kit/ui/profile-avatar';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@kit/ui/table';
import { AdminBanUserDialog } from './admin-ban-user-dialog'; import { AdminBanUserDialog } from './admin-ban-user-dialog';
import { AdminDeleteAccountDialog } from './admin-delete-account-dialog'; import { AdminDeleteAccountDialog } from './admin-delete-account-dialog';
@@ -224,148 +215,6 @@ async function TeamAccountPage(props: {
); );
} }
async function SubscriptionsTable(props: { accountId: string }) {
const client = getSupabaseServerClient();
const { data: subscription, error } = await client
.schema('medreport')
.from('subscriptions')
.select('*, subscription_items !inner (*)')
.eq('account_id', props.accountId)
.maybeSingle();
if (error) {
return (
<Alert variant={'destructive'}>
<AlertTitle>There was an error loading subscription.</AlertTitle>
<AlertDescription>
Please check the logs for more information or try again later.
</AlertDescription>
</Alert>
);
}
return (
<div className={'flex flex-col gap-y-1'}>
<Heading level={6}>Subscription</Heading>
<If
condition={subscription}
fallback={
<span className={'text-muted-foreground text-sm'}>
This account does not currently have a subscription.
</span>
}
>
{(subscription) => {
return (
<div className={'flex flex-col space-y-4'}>
<Table>
<TableHeader>
<TableHead>Subscription ID</TableHead>
<TableHead>Provider</TableHead>
<TableHead>Customer ID</TableHead>
<TableHead>Status</TableHead>
<TableHead>Created At</TableHead>
<TableHead>Period Starts At</TableHead>
<TableHead>Ends At</TableHead>
</TableHeader>
<TableBody>
<TableRow>
<TableCell>
<span>{subscription.id}</span>
</TableCell>
<TableCell>
<span>{subscription.billing_provider}</span>
</TableCell>
<TableCell>
<span>{subscription.billing_customer_id}</span>
</TableCell>
<TableCell>
<span>{subscription.status}</span>
</TableCell>
<TableCell>
<span>{subscription.created_at}</span>
</TableCell>
<TableCell>
<span>{subscription.period_starts_at}</span>
</TableCell>
<TableCell>
<span>{subscription.period_ends_at}</span>
</TableCell>
</TableRow>
</TableBody>
</Table>
<Table>
<TableHeader>
<TableHead>Product ID</TableHead>
<TableHead>Variant ID</TableHead>
<TableHead>Quantity</TableHead>
<TableHead>Price</TableHead>
<TableHead>Interval</TableHead>
<TableHead>Type</TableHead>
</TableHeader>
<TableBody>
{subscription.subscription_items.map((item) => {
return (
<TableRow key={item.variant_id}>
<TableCell>
<span>{item.product_id}</span>
</TableCell>
<TableCell>
<span>{item.variant_id}</span>
</TableCell>
<TableCell>
<span>{item.quantity}</span>
</TableCell>
<TableCell>
<span>{item.price_amount}</span>
</TableCell>
<TableCell>
<span>{item.interval}</span>
</TableCell>
<TableCell>
<span>{item.type}</span>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</div>
);
}}
</If>
</div>
);
}
async function getMemberships(userId: string) { async function getMemberships(userId: string) {
const client = getSupabaseServerClient(); const client = getSupabaseServerClient();

View File

@@ -50,7 +50,7 @@ export function MultiFactorChallengeContainer({
try { try {
await fetch('/api/after-mfa', { method: 'POST' }); await fetch('/api/after-mfa', { method: 'POST' });
router.replace(paths.redirectPath); router.replace(paths.redirectPath);
} catch (err) { } catch {
// ignore // ignore
} }
}, },

View File

@@ -92,7 +92,7 @@ export const AnalysisResponseSchema = z.object({
export type AnalysisResponse = z.infer<typeof AnalysisResponseSchema>; export type AnalysisResponse = z.infer<typeof AnalysisResponseSchema>;
export const AnalysisResultDetailsSchema = z.object({ export const AnalysisResultDetailsSchema = z.object({
analysisResponse: z.array(AnalysisResponseSchema).nullable(), analysisResponse: z.array(AnalysisResponseSchema),
order: OrderSchema, order: OrderSchema,
doctorFeedback: DoctorFeedbackSchema, doctorFeedback: DoctorFeedbackSchema,
patient: PatientSchema, patient: PatientSchema,

View File

@@ -7,6 +7,8 @@ import { sdk } from '@lib/config';
import medusaError from '@lib/util/medusa-error'; import medusaError from '@lib/util/medusa-error';
import { HttpTypes, StoreCart } from '@medusajs/types'; import { HttpTypes, StoreCart } from '@medusajs/types';
import { getLogger } from '@kit/shared/logger';
import { import {
getAuthHeaders, getAuthHeaders,
getCacheOptions, getCacheOptions,
@@ -135,13 +137,21 @@ export async function addToCart({
quantity: number; quantity: number;
countryCode: string; countryCode: string;
}) { }) {
const logger = await getLogger();
const ctx = {
variantId,
quantity,
countryCode,
};
if (!variantId) { if (!variantId) {
logger.error(ctx, 'Missing variant ID when adding to cart');
throw new Error('Missing variant ID when adding to cart'); throw new Error('Missing variant ID when adding to cart');
} }
const cart = await getOrSetCart(countryCode); const cart = await getOrSetCart(countryCode);
if (!cart) { if (!cart) {
logger.error(ctx, 'Error retrieving or creating cart');
throw new Error('Error retrieving or creating cart'); throw new Error('Error retrieving or creating cart');
} }

View File

@@ -16,7 +16,7 @@ export function CompanyGuard<Params extends object>(
Component: LayoutOrPageComponent<Params>, Component: LayoutOrPageComponent<Params>,
) { ) {
return async function AdminGuardServerComponentWrapper(params: Params) { return async function AdminGuardServerComponentWrapper(params: Params) {
//@ts-ignore // @ts-expect-error incorrectly typed params
const { account } = await params.params; const { account } = await params.params;
const client = getSupabaseServerClient(); const client = getSupabaseServerClient();
const [isUserSuperAdmin, isUserCompanyAdmin] = await Promise.all([ const [isUserSuperAdmin, isUserCompanyAdmin] = await Promise.all([

View File

@@ -149,7 +149,6 @@ export const updateInvitationAction = enhanceAction(
export const acceptInvitationAction = enhanceAction( export const acceptInvitationAction = enhanceAction(
async (data: FormData, user) => { async (data: FormData, user) => {
const client = getSupabaseServerClient(); const client = getSupabaseServerClient();
const accountBalanceService = new AccountBalanceService();
const { inviteToken, nextPath } = AcceptInvitationSchema.parse( const { inviteToken, nextPath } = AcceptInvitationSchema.parse(
Object.fromEntries(data), Object.fromEntries(data),

View File

@@ -18,14 +18,16 @@ export const PackageHeader = ({
return ( return (
<div className="space-y-1 text-center"> <div className="space-y-1 text-center">
<p className="text-sm sm:text-lg sm:font-medium">{title}</p> <p className="text-sm sm:text-lg sm:font-medium">{title}</p>
<h2 className="text-xl sm:text-4xl"> <h2 className="xs:text-xl text-lg sm:text-4xl">
{formatCurrency({ {formatCurrency({
currencyCode: 'eur', currencyCode: 'eur',
locale: language, locale: language,
value: price, value: price,
})} })}
</h2> </h2>
<Badge className={cn('text-xs', tagColor)}>{analysesNr}</Badge> <Badge className={cn('xs:text-xs text-[10px]', tagColor)}>
{analysesNr}
</Badge>
</div> </div>
); );
}; };

View File

@@ -46,11 +46,10 @@ export function ProfileAccountDropdownContainer(props: {
return ( return (
<PersonalAccountDropdown <PersonalAccountDropdown
className={'w-full'} className="w-full"
paths={paths} paths={paths}
features={features} features={features}
user={userData} user={userData}
account={props.account}
accounts={props.accounts} accounts={props.accounts}
signOutRequested={() => signOut.mutateAsync()} signOutRequested={() => signOut.mutateAsync()}
showProfileName={props.showProfileName} showProfileName={props.showProfileName}

View File

@@ -117,6 +117,7 @@ export default function SelectAnalysisPackage({
<Button <Button
className="w-full text-[10px] sm:text-sm" className="w-full text-[10px] sm:text-sm"
onClick={handleSelect} onClick={handleSelect}
disabled={isAddingToCart}
> >
{isAddingToCart ? ( {isAddingToCart ? (
<Spinner /> <Spinner />

View File

@@ -1,4 +1,4 @@
import React, { JSX, ReactNode } from 'react'; import React, { JSX } from 'react';
import { cn } from '@kit/ui/utils'; import { cn } from '@kit/ui/utils';

View File

@@ -72,7 +72,7 @@
"myOrders": "My orders", "myOrders": "My orders",
"analysisResults": "Analysis results", "analysisResults": "Analysis results",
"orderAnalysisPackage": "Order analysis package", "orderAnalysisPackage": "Order analysis package",
"orderAnalysis": "Order analysis", "orderAnalysis": "Order single analysis",
"orderHealthAnalysis": "Order health check", "orderHealthAnalysis": "Order health check",
"account": "Account", "account": "Account",
"companyMembers": "Manage employees", "companyMembers": "Manage employees",

View File

@@ -5,5 +5,7 @@
"selectPackage": "Select package", "selectPackage": "Select package",
"comparePackages": "Compare packages", "comparePackages": "Compare packages",
"analysisPackageAddedToCart": "Analysis package added to cart", "analysisPackageAddedToCart": "Analysis package added to cart",
"analysisPackageAddToCartError": "Adding analysis package to cart failed" "analysisPackageAddToCartError": "Adding analysis package to cart failed",
"digitalDoctorFeedback": "Digital doctor feedback",
"giveAnalyses": "Analyses"
} }

View File

@@ -72,7 +72,7 @@
"myOrders": "Minu tellimused", "myOrders": "Minu tellimused",
"analysisResults": "Analüüside vastused", "analysisResults": "Analüüside vastused",
"orderAnalysisPackage": "Telli analüüside pakett", "orderAnalysisPackage": "Telli analüüside pakett",
"orderAnalysis": "Telli analüüs", "orderAnalysis": "Telli üksikanalüüs",
"orderHealthAnalysis": "Telli terviseuuring", "orderHealthAnalysis": "Telli terviseuuring",
"account": "Konto", "account": "Konto",
"companyMembers": "Töötajate haldamine", "companyMembers": "Töötajate haldamine",

View File

@@ -15,7 +15,7 @@
"recommendedForYou": "Soovitused sulle", "recommendedForYou": "Soovitused sulle",
"heroCard": { "heroCard": {
"orderAnalysis": { "orderAnalysis": {
"title": "Telli analüüs", "title": "Telli üksikanalüüs",
"description": "Telli endale sobiv analüüs" "description": "Telli endale sobiv analüüs"
}, },
"benefits": { "benefits": {

View File

@@ -5,5 +5,7 @@
"selectPackage": "Vali pakett", "selectPackage": "Vali pakett",
"comparePackages": "Võrdle pakette", "comparePackages": "Võrdle pakette",
"analysisPackageAddedToCart": "Analüüsi pakett lisatud ostukorvi", "analysisPackageAddedToCart": "Analüüsi pakett lisatud ostukorvi",
"analysisPackageAddToCartError": "Analüüsi paketi lisamine ostukorvi ebaõnnestus" "analysisPackageAddToCartError": "Analüüsi paketi lisamine ostukorvi ebaõnnestus",
"digitalDoctorFeedback": "Digitaalne arsti kokkuvõte",
"giveAnalyses": "Analüüside võtmine"
} }

View File

@@ -5,5 +5,7 @@
"selectPackage": "Выберите пакет", "selectPackage": "Выберите пакет",
"comparePackages": "Сравнить пакеты", "comparePackages": "Сравнить пакеты",
"analysisPackageAddedToCart": "Пакет анализов добавлен в корзину", "analysisPackageAddedToCart": "Пакет анализов добавлен в корзину",
"analysisPackageAddToCartError": "Не удалось добавить пакет анализов в корзину" "analysisPackageAddToCartError": "Не удалось добавить пакет анализов в корзину",
"digitalDoctorFeedback": "Digital doctor feedback",
"giveAnalyses": "Analyses"
} }

View File

@@ -0,0 +1,12 @@
grant execute on function medreport.update_analysis_order_status (
bigint,
text,
medreport.analysis_order_status
) to authenticated, service_role;
create policy "doctor_update_analysis_order"
on "medreport"."analysis_orders"
as PERMISSIVE
for UPDATE
to authenticated
using (medreport.is_doctor());

View File

@@ -0,0 +1,17 @@
DO $$
DECLARE
target_schema text := 'public';
table_name text;
BEGIN
FOR table_name IN
SELECT c.relname
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname = target_schema
AND c.relkind = 'r'
LOOP
EXECUTE format('ALTER TABLE %I.%I ENABLE ROW LEVEL SECURITY;', target_schema, table_name);
END LOOP;
END $$;
REVOKE USAGE ON SCHEMA public FROM anon, authenticated;