@@ -106,9 +106,11 @@ To access admin pages follow these steps:
|
||||
1. Customer adds analysis to cart in **B2B** storefront
|
||||
2. Customer checks out from cart and is redirected to **Montonio**
|
||||
3. Customer pays and is redirected back to **B2B** `GET B2B/home/cart/montonio-callback?order-token=$JWT`
|
||||
|
||||
- **Medusa** order is created and cart is emptied
|
||||
- email is sent to customer
|
||||
- B2B sends order XML as private message to Medipost.
|
||||
|
||||
When **Montonio** has confirmed payment, it will call **Medusa** webhook endpoint and **Medusa** will mark order payment as captured.
|
||||
|
||||
In background a job will call `POST B2B/api/job/sync-analysis-results` every n minutes and sync private messages with responses from **Medipost**.
|
||||
|
||||
@@ -8,8 +8,6 @@ import { ExternalLink } from '@/public/assets/external-link';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
import { onUpdateAccount } from '@kit/auth/actions/update-account-actions';
|
||||
import { UpdateAccountSchema } from '@kit/auth/schemas/update-account.schema';
|
||||
import { Button } from '@kit/ui/button';
|
||||
import { Checkbox } from '@kit/ui/checkbox';
|
||||
import {
|
||||
@@ -23,6 +21,9 @@ import {
|
||||
import { Input } from '@kit/ui/input';
|
||||
import { Trans } from '@kit/ui/trans';
|
||||
|
||||
import { UpdateAccountSchema } from '../_lib/schemas/update-account.schema';
|
||||
import { onUpdateAccount } from '../_lib/server/update-account';
|
||||
|
||||
export function UpdateAccountForm({ user }: { user: User }) {
|
||||
const form = useForm({
|
||||
resolver: zodResolver(UpdateAccountSchema),
|
||||
@@ -30,16 +31,15 @@ export function UpdateAccountForm({ user }: { user: User }) {
|
||||
defaultValues: {
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
personalCode: user.user_metadata.personalCode ?? '',
|
||||
personalCode: '',
|
||||
email: user.email,
|
||||
phone: '',
|
||||
city: '',
|
||||
weight: user.user_metadata.weight ?? undefined,
|
||||
height: user.user_metadata.height ?? undefined,
|
||||
weight: 0,
|
||||
height: 0,
|
||||
userConsent: false,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
|
||||
@@ -4,25 +4,13 @@ import { redirect } from 'next/navigation';
|
||||
|
||||
import { updateCustomer } from '@lib/data/customer';
|
||||
|
||||
import { createAuthApi } from '@kit/auth/api';
|
||||
import { AccountSubmitData, createAuthApi } from '@kit/auth/api';
|
||||
import { enhanceAction } from '@kit/next/actions';
|
||||
import { getSupabaseServerClient } from '@kit/supabase/server-client';
|
||||
|
||||
import pathsConfig from '~/config/paths.config';
|
||||
|
||||
import { UpdateAccountSchema } from '../../schemas/update-account.schema';
|
||||
|
||||
export interface AccountSubmitData {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
personalCode: string;
|
||||
email: string;
|
||||
phone?: string;
|
||||
city?: string;
|
||||
weight: number | null;
|
||||
height: number | null;
|
||||
userConsent: boolean;
|
||||
}
|
||||
import { UpdateAccountSchema } from '../schemas/update-account.schema';
|
||||
|
||||
export const onUpdateAccount = enhanceAction(
|
||||
async (params: AccountSubmitData) => {
|
||||
@@ -42,7 +42,7 @@ const Level = ({
|
||||
|
||||
export const AnalysisLevelBarSkeleton = () => {
|
||||
return (
|
||||
<div className="mt-4 flex h-3 max-w-[360px] w-[35%] gap-1 sm:mt-0">
|
||||
<div className="mt-4 flex h-3 w-[35%] max-w-[360px] gap-1 sm:mt-0">
|
||||
<Level color="gray-200" />
|
||||
</div>
|
||||
);
|
||||
@@ -58,7 +58,7 @@ const AnalysisLevelBar = ({
|
||||
level: AnalysisResultLevel;
|
||||
}) => {
|
||||
return (
|
||||
<div className="mt-4 flex h-3 max-w-[360px] w-[35%] gap-1 sm:mt-0">
|
||||
<div className="mt-4 flex h-3 w-[35%] max-w-[360px] gap-1 sm:mt-0">
|
||||
{normLowerIncluded && (
|
||||
<>
|
||||
<Level
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { format } from 'date-fns';
|
||||
|
||||
import { UserAnalysisElement } from '@/packages/features/accounts/src/types/accounts';
|
||||
import { format } from 'date-fns';
|
||||
import { Info } from 'lucide-react';
|
||||
|
||||
import { Trans } from '@kit/ui/trans';
|
||||
import { cn } from '@kit/ui/utils';
|
||||
|
||||
import AnalysisLevelBar, { AnalysisLevelBarSkeleton, AnalysisResultLevel } from './analysis-level-bar';
|
||||
import { UserAnalysisElement } from '@/packages/features/accounts/src/types/accounts';
|
||||
import { AnalysisElement } from '~/lib/services/analysis-element.service';
|
||||
import { Trans } from '@kit/ui/trans';
|
||||
|
||||
import AnalysisLevelBar, {
|
||||
AnalysisLevelBarSkeleton,
|
||||
AnalysisResultLevel,
|
||||
} from './analysis-level-bar';
|
||||
|
||||
export enum AnalysisStatus {
|
||||
NORMAL = 0,
|
||||
@@ -59,7 +63,7 @@ const Analysis = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="border-border items-center justify-between rounded-lg border px-5 py-3 sm:h-[65px] flex flex-col sm:flex-row px-12 gap-2 sm:gap-0">
|
||||
<div className="border-border flex flex-col items-center justify-between gap-2 rounded-lg border px-5 px-12 py-3 sm:h-[65px] sm:flex-row sm:gap-0">
|
||||
<div className="flex items-center gap-2 font-semibold">
|
||||
{name}
|
||||
{results?.response_time && (
|
||||
@@ -75,7 +79,9 @@ const Analysis = ({
|
||||
{ block: showTooltip },
|
||||
)}
|
||||
>
|
||||
<Trans i18nKey="analysis-results:analysisDate" />{': '}{format(new Date(results.response_time), 'dd.MM.yyyy HH:mm')}
|
||||
<Trans i18nKey="analysis-results:analysisDate" />
|
||||
{': '}
|
||||
{format(new Date(results.response_time), 'dd.MM.yyyy HH:mm')}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -86,7 +92,7 @@ const Analysis = ({
|
||||
<div className="font-semibold">{value}</div>
|
||||
<div className="text-muted-foreground text-sm">{unit}</div>
|
||||
</div>
|
||||
<div className="text-muted-foreground flex flex-col-reverse gap-2 text-center text-sm sm:block sm:gap-0 mx-8">
|
||||
<div className="text-muted-foreground mx-8 flex flex-col-reverse gap-2 text-center text-sm sm:block sm:gap-0">
|
||||
{normLower} - {normUpper}
|
||||
<div>
|
||||
<Trans i18nKey="analysis-results:results.range.normal" />
|
||||
@@ -105,7 +111,7 @@ const Analysis = ({
|
||||
<Trans i18nKey="analysis-results:waitingForResults" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-[60px] mx-8"></div>
|
||||
<div className="mx-8 w-[60px]"></div>
|
||||
<AnalysisLevelBarSkeleton />
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
import { InfoTooltip } from '@/components/ui/info-tooltip';
|
||||
import type { AccountWithParams } from '@/packages/features/accounts/src/server/api';
|
||||
import { BlendingModeIcon, RulerHorizontalIcon } from '@radix-ui/react-icons';
|
||||
import Isikukood from 'isikukood';
|
||||
import {
|
||||
Activity,
|
||||
ChevronRight,
|
||||
@@ -15,7 +17,6 @@ import {
|
||||
TrendingUp,
|
||||
User,
|
||||
} from 'lucide-react';
|
||||
import Isikukood from 'isikukood';
|
||||
|
||||
import { Button } from '@kit/ui/button';
|
||||
import {
|
||||
@@ -28,7 +29,6 @@ import {
|
||||
} from '@kit/ui/card';
|
||||
import { Trans } from '@kit/ui/trans';
|
||||
import { cn } from '@kit/ui/utils';
|
||||
import type { AccountWithParams } from '@/packages/features/accounts/src/server/api';
|
||||
|
||||
const cards = ({
|
||||
gender,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
import { UpdateHealthBenefitSchema } from '@/packages/billing/core/src/schema';
|
||||
import { Database } from '@/packages/supabase/src/database.types';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
@@ -22,32 +24,52 @@ import YearlyExpensesOverview from './yearly-expenses-overview';
|
||||
const HealthBenefitForm = ({
|
||||
account,
|
||||
companyParams,
|
||||
employeeCount,
|
||||
}: {
|
||||
account: Database['medreport']['Tables']['accounts']['Row'];
|
||||
companyParams: Database['medreport']['Tables']['company_params']['Row'];
|
||||
employeeCount: number;
|
||||
}) => {
|
||||
const [currentCompanyParams, setCurrentCompanyParams] =
|
||||
useState<Database['medreport']['Tables']['company_params']['Row']>(
|
||||
companyParams,
|
||||
);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const form = useForm({
|
||||
resolver: zodResolver(UpdateHealthBenefitSchema),
|
||||
mode: 'onChange',
|
||||
defaultValues: {
|
||||
occurance: companyParams.benefit_occurance || 'yearly',
|
||||
amount: companyParams.benefit_amount || 0,
|
||||
occurance: currentCompanyParams.benefit_occurance || 'yearly',
|
||||
amount: currentCompanyParams.benefit_amount || 0,
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = (data: { occurance: string; amount: number }) => {
|
||||
const promise = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await updateHealthBenefit({ ...data, accountId: account.id });
|
||||
setCurrentCompanyParams((prev) => ({
|
||||
...prev,
|
||||
benefit_amount: data.amount,
|
||||
benefit_occurance: data.occurance,
|
||||
}));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
toast.promise(promise, {
|
||||
success: 'Andmed uuendatud',
|
||||
error: 'error',
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
className="flex flex-col gap-6 px-6 text-left"
|
||||
onSubmit={form.handleSubmit((data) => {
|
||||
toast.promise(
|
||||
() => updateHealthBenefit({ ...data, accountId: account.id }),
|
||||
{
|
||||
success: 'success',
|
||||
error: 'error',
|
||||
},
|
||||
);
|
||||
})}
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
>
|
||||
<div className="mt-8 flex items-center justify-between">
|
||||
<div>
|
||||
@@ -61,13 +83,13 @@ const HealthBenefitForm = ({
|
||||
<Trans i18nKey="billing:description" />
|
||||
</p>
|
||||
</div>
|
||||
<Button type="submit" className="relative">
|
||||
{form.formState.isSubmitting && (
|
||||
<Button type="submit" className="relative" disabled={isLoading}>
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
)}
|
||||
<div className={cn({ invisible: form.formState.isSubmitting })}>
|
||||
<div className={cn({ invisible: isLoading })}>
|
||||
<Trans i18nKey="account:saveChanges" />
|
||||
</div>
|
||||
</Button>
|
||||
@@ -82,7 +104,7 @@ const HealthBenefitForm = ({
|
||||
<Trans i18nKey="billing:healthBenefitForm.description" />
|
||||
</p>
|
||||
<p className="pt-2 text-2xl font-semibold">
|
||||
{companyParams.benefit_amount || 0} €
|
||||
{currentCompanyParams.benefit_amount || 0} €
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -95,8 +117,8 @@ const HealthBenefitForm = ({
|
||||
|
||||
<div className="flex-1">
|
||||
<YearlyExpensesOverview
|
||||
employeeCount={22}
|
||||
companyParams={companyParams}
|
||||
employeeCount={employeeCount}
|
||||
companyParams={currentCompanyParams}
|
||||
/>
|
||||
<p className="text-muted-foreground mt-2 text-sm">
|
||||
<Trans i18nKey="billing:healthBenefitForm.info" />
|
||||
|
||||
@@ -21,7 +21,7 @@ const YearlyExpensesOverview = ({
|
||||
case 'yearly':
|
||||
return (companyParams.benefit_amount / 12).toFixed(2);
|
||||
case 'quarterly':
|
||||
return (companyParams.benefit_amount / 4).toFixed(2);
|
||||
return (companyParams.benefit_amount / 3).toFixed(2);
|
||||
case 'monthly':
|
||||
return companyParams.benefit_amount.toFixed(2);
|
||||
default:
|
||||
@@ -38,7 +38,7 @@ const YearlyExpensesOverview = ({
|
||||
case 'yearly':
|
||||
return companyParams.benefit_amount.toFixed(2);
|
||||
case 'quarterly':
|
||||
return (companyParams.benefit_amount * 4).toFixed(2);
|
||||
return (companyParams.benefit_amount * 3).toFixed(2);
|
||||
case 'monthly':
|
||||
return (companyParams.benefit_amount * 12).toFixed(2);
|
||||
default:
|
||||
@@ -80,8 +80,15 @@ const YearlyExpensesOverview = ({
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="mt-4 flex justify-between">
|
||||
<p className="font-semibold">Kokku</p>
|
||||
<span className="font-semibold">13 200,00 €</span>
|
||||
<p className="font-semibold">
|
||||
<Trans i18nKey="billing:expensesOverview.sum" />
|
||||
</p>
|
||||
<span className="font-semibold">
|
||||
{companyParams.benefit_amount
|
||||
? companyParams.benefit_amount * employeeCount
|
||||
: 0}{' '}
|
||||
€
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { ExclamationTriangleIcon } from '@radix-ui/react-icons';
|
||||
|
||||
import { useCaptureException } from '@kit/monitoring/hooks';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
|
||||
import { AppBreadcrumbs } from '@kit/ui/app-breadcrumbs';
|
||||
import { Button } from '@kit/ui/button';
|
||||
import { PageBody, PageHeader } from '@kit/ui/page';
|
||||
import { Trans } from '@kit/ui/trans';
|
||||
|
||||
export default function BillingErrorPage({
|
||||
error,
|
||||
reset,
|
||||
}: {
|
||||
error: Error & { digest?: string };
|
||||
reset: () => void;
|
||||
}) {
|
||||
useCaptureException(error);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHeader description={<AppBreadcrumbs />} />
|
||||
|
||||
<PageBody>
|
||||
<div className={'flex flex-col space-y-4'}>
|
||||
<Alert variant={'destructive'}>
|
||||
<ExclamationTriangleIcon className={'h-4'} />
|
||||
|
||||
<AlertTitle>
|
||||
<Trans i18nKey={'billing:planPickerAlertErrorTitle'} />
|
||||
</AlertTitle>
|
||||
|
||||
<AlertDescription>
|
||||
<Trans i18nKey={'billing:planPickerAlertErrorDescription'} />
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
<div>
|
||||
<Button variant={'outline'} onClick={reset}>
|
||||
<Trans i18nKey={'common:retry'} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</PageBody>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createAccountsApi } from '@/packages/features/accounts/src/server/api';
|
||||
import { createTeamAccountsApi } from '@/packages/features/team-accounts/src/server/api';
|
||||
import { getSupabaseServerClient } from '@/packages/supabase/src/clients/server-client';
|
||||
|
||||
@@ -23,13 +24,20 @@ export const generateMetadata = async () => {
|
||||
|
||||
async function TeamAccountBillingPage({ params }: TeamAccountBillingPageProps) {
|
||||
const accountSlug = (await params).account;
|
||||
const api = createTeamAccountsApi(getSupabaseServerClient());
|
||||
const client = getSupabaseServerClient();
|
||||
const api = createTeamAccountsApi(client);
|
||||
|
||||
const account = await api.getTeamAccount(accountSlug);
|
||||
const companyParams = await api.getTeamAccountParams(account.id);
|
||||
const accounts = await api.getMembers(accountSlug);
|
||||
|
||||
return (
|
||||
<PageBody>
|
||||
<HealthBenefitForm account={account} companyParams={companyParams} />
|
||||
<HealthBenefitForm
|
||||
account={account}
|
||||
companyParams={companyParams}
|
||||
employeeCount={accounts.length}
|
||||
/>
|
||||
</PageBody>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,8 @@ export const generateMetadata = async () => {
|
||||
};
|
||||
|
||||
async function SelectPackagePage() {
|
||||
const { analysisPackageElements, analysisPackages, countryCode } = await loadAnalysisPackages();
|
||||
const { analysisPackageElements, analysisPackages, countryCode } =
|
||||
await loadAnalysisPackages();
|
||||
|
||||
return (
|
||||
<div className="container mx-auto my-24 flex flex-col items-center space-y-12">
|
||||
@@ -44,7 +45,10 @@ async function SelectPackagePage() {
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<SelectAnalysisPackages analysisPackages={analysisPackages} countryCode={countryCode} />
|
||||
<SelectAnalysisPackages
|
||||
analysisPackages={analysisPackages}
|
||||
countryCode={countryCode}
|
||||
/>
|
||||
<div className="flex justify-center">
|
||||
<Link href={pathsConfig.app.home}>
|
||||
<Button variant="secondary" className="align-center">
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import withBundleAnalyzer from '@next/bundle-analyzer';
|
||||
import transpileModules from 'next-transpile-modules';
|
||||
|
||||
const withTM = transpileModules(['lucide-react']);
|
||||
|
||||
const IS_PRODUCTION = process.env.NODE_ENV === 'production';
|
||||
const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL;
|
||||
@@ -25,7 +28,7 @@ const INTERNAL_PACKAGES = [
|
||||
];
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const config = {
|
||||
const config = withTM({
|
||||
reactStrictMode: true,
|
||||
/** Enables hot reloading for local packages without a build step */
|
||||
transpilePackages: INTERNAL_PACKAGES,
|
||||
@@ -68,7 +71,7 @@ const config = {
|
||||
/** We already do linting and typechecking as separate tasks in CI */
|
||||
eslint: { ignoreDuringBuilds: true },
|
||||
typescript: { ignoreBuildErrors: true },
|
||||
};
|
||||
});
|
||||
|
||||
export default withBundleAnalyzer({
|
||||
enabled: process.env.ANALYZE === 'true',
|
||||
|
||||
@@ -3,6 +3,7 @@ import { BadgeX, Ban, ShieldPlus, VenetianMask } from 'lucide-react';
|
||||
import { Tables } from '@kit/supabase/database';
|
||||
import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client';
|
||||
import { getSupabaseServerClient } from '@kit/supabase/server-client';
|
||||
import { createTeamAccountsApi } from '@kit/team-accounts/api';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
|
||||
import { AppBreadcrumbs } from '@kit/ui/app-breadcrumbs';
|
||||
import { Badge } from '@kit/ui/badge';
|
||||
@@ -162,7 +163,8 @@ async function PersonalAccountPage(props: { account: Account }) {
|
||||
async function TeamAccountPage(props: {
|
||||
account: Account & { memberships: Membership[] };
|
||||
}) {
|
||||
const members = await getMembers(props.account.slug ?? '');
|
||||
const api = createTeamAccountsApi(getSupabaseServerClient());
|
||||
const members = await api.getMembers(props.account.slug ?? '');
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -386,17 +388,3 @@ async function getMemberships(userId: string) {
|
||||
|
||||
return memberships.data;
|
||||
}
|
||||
|
||||
async function getMembers(accountSlug: string) {
|
||||
const client = getSupabaseServerClient();
|
||||
|
||||
const members = await client.schema('medreport').rpc('get_account_members', {
|
||||
account_slug: accountSlug,
|
||||
});
|
||||
|
||||
if (members.error) {
|
||||
throw members.error;
|
||||
}
|
||||
|
||||
return members.data;
|
||||
}
|
||||
|
||||
@@ -1,225 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
|
||||
import { User } from '@supabase/supabase-js';
|
||||
|
||||
import { ExternalLink } from '@/public/assets/external-link';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
import { Button } from '@kit/ui/button';
|
||||
import { Checkbox } from '@kit/ui/checkbox';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@kit/ui/form';
|
||||
import { Input } from '@kit/ui/input';
|
||||
import { Trans } from '@kit/ui/trans';
|
||||
|
||||
import { UpdateAccountSchema } from '../schemas/update-account.schema';
|
||||
import { onUpdateAccount } from '../server/actions/update-account-actions';
|
||||
|
||||
export function UpdateAccountForm({ user }: { user: User }) {
|
||||
const form = useForm({
|
||||
resolver: zodResolver(UpdateAccountSchema),
|
||||
mode: 'onChange',
|
||||
defaultValues: {
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
personalCode: '',
|
||||
email: user.email,
|
||||
phone: '',
|
||||
city: '',
|
||||
weight: 0,
|
||||
height: 0,
|
||||
userConsent: false,
|
||||
},
|
||||
});
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
className="flex flex-col gap-6 px-6 pt-10 text-left"
|
||||
onSubmit={form.handleSubmit(onUpdateAccount)}
|
||||
>
|
||||
<FormField
|
||||
name="firstName"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<Trans i18nKey={'common:formField:firstName'} />
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="lastName"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<Trans i18nKey={'common:formField:lastName'} />
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="personalCode"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<Trans i18nKey={'common:formField:personalCode'} />
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<Trans i18nKey={'common:formField:email'} />
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} disabled />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="phone"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<Trans i18nKey={'common:formField:phone'} />
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="city"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<Trans i18nKey={'common:formField:city'} />
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex flex-row justify-between gap-4">
|
||||
<FormField
|
||||
name="weight"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<Trans i18nKey={'common:formField:weight'} />
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="kg"
|
||||
{...field}
|
||||
value={field.value ?? ''}
|
||||
onChange={(e) =>
|
||||
field.onChange(
|
||||
e.target.value === '' ? null : Number(e.target.value),
|
||||
)
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="height"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<Trans i18nKey={'common:formField:height'} />
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="cm"
|
||||
type="number"
|
||||
{...field}
|
||||
value={field.value ?? ''}
|
||||
onChange={(e) =>
|
||||
field.onChange(
|
||||
e.target.value === '' ? null : Number(e.target.value),
|
||||
)
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<FormField
|
||||
name="userConsent"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className="flex flex-row items-center gap-2 pb-1">
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel>
|
||||
<Trans i18nKey={'account:updateAccount:userConsentLabel'} />
|
||||
</FormLabel>
|
||||
</div>
|
||||
|
||||
<Link
|
||||
href={''}
|
||||
className="flex flex-row items-center gap-2 text-sm hover:underline"
|
||||
target="_blank"
|
||||
>
|
||||
<ExternalLink />
|
||||
<Trans i18nKey={'account:updateAccount:userConsentUrlTitle'} />
|
||||
</Link>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button disabled={form.formState.isSubmitting} type="submit">
|
||||
<Trans i18nKey={'account:updateAccount:button'} />
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
@@ -2,7 +2,17 @@ import { SupabaseClient } from '@supabase/supabase-js';
|
||||
|
||||
import { Database } from '@kit/supabase/database';
|
||||
|
||||
import { AccountSubmitData } from './actions/update-account-actions';
|
||||
export interface AccountSubmitData {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
personalCode: string;
|
||||
email: string;
|
||||
phone?: string;
|
||||
city?: string;
|
||||
weight: number | null;
|
||||
height: number | null;
|
||||
userConsent: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class representing an API for interacting with user accounts.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
import { MedReportLogo } from '@/components/med-report-logo';
|
||||
|
||||
@@ -56,10 +56,11 @@ export const SuccessNotification = ({
|
||||
</div>
|
||||
)}
|
||||
{buttonProps && (
|
||||
<Button className="mt-8 w-full">
|
||||
<Link href={buttonProps.href}>
|
||||
<Button
|
||||
className="mt-8 w-full"
|
||||
onClick={() => redirect(buttonProps.href)}
|
||||
>
|
||||
<Trans i18nKey={buttonProps.buttonTitleKey} />
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -295,6 +295,20 @@ export class TeamAccountsApi {
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async getMembers(accountSlug: string) {
|
||||
const members = await this.client
|
||||
.schema('medreport')
|
||||
.rpc('get_account_members', {
|
||||
account_slug: accountSlug,
|
||||
});
|
||||
|
||||
if (members.error) {
|
||||
throw members.error;
|
||||
}
|
||||
|
||||
return members.data;
|
||||
}
|
||||
}
|
||||
|
||||
export function createTeamAccountsApi(client: SupabaseClient<Database>) {
|
||||
|
||||
66
pnpm-lock.yaml
generated
66
pnpm-lock.yaml
generated
@@ -18669,8 +18669,8 @@ snapshots:
|
||||
'@typescript-eslint/parser': 8.33.1(eslint@8.10.0)(typescript@5.8.3)
|
||||
eslint: 8.10.0
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint@8.10.0))(eslint@8.10.0)
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint@8.10.0))(eslint@8.10.0))(eslint@8.10.0)
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@8.10.0)
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint@8.10.0)
|
||||
eslint-plugin-jsx-a11y: 6.10.2(eslint@8.10.0)
|
||||
eslint-plugin-react: 7.37.5(eslint@8.10.0)
|
||||
eslint-plugin-react-hooks: 5.2.0(eslint@8.10.0)
|
||||
@@ -18689,8 +18689,8 @@ snapshots:
|
||||
'@typescript-eslint/parser': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)
|
||||
eslint: 9.28.0(jiti@2.4.2)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.32.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2)))(eslint@9.28.0(jiti@2.4.2))
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.32.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2)))(eslint@9.28.0(jiti@2.4.2)))(eslint@9.28.0(jiti@2.4.2))
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.28.0(jiti@2.4.2))
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.32.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2))
|
||||
eslint-plugin-jsx-a11y: 6.10.2(eslint@9.28.0(jiti@2.4.2))
|
||||
eslint-plugin-react: 7.37.5(eslint@9.28.0(jiti@2.4.2))
|
||||
eslint-plugin-react-hooks: 5.2.0(eslint@9.28.0(jiti@2.4.2))
|
||||
@@ -18715,22 +18715,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.32.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2)))(eslint@9.28.0(jiti@2.4.2)):
|
||||
dependencies:
|
||||
'@nolyfill/is-core-module': 1.0.39
|
||||
debug: 4.4.1
|
||||
eslint: 9.28.0(jiti@2.4.2)
|
||||
get-tsconfig: 4.10.1
|
||||
is-bun-module: 2.0.0
|
||||
stable-hash: 0.0.5
|
||||
tinyglobby: 0.2.14
|
||||
unrs-resolver: 1.7.11
|
||||
optionalDependencies:
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.32.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2)))(eslint@9.28.0(jiti@2.4.2)))(eslint@9.28.0(jiti@2.4.2))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint@8.10.0))(eslint@8.10.0):
|
||||
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@8.10.0):
|
||||
dependencies:
|
||||
'@nolyfill/is-core-module': 1.0.39
|
||||
debug: 4.4.1
|
||||
@@ -18741,33 +18726,48 @@ snapshots:
|
||||
tinyglobby: 0.2.14
|
||||
unrs-resolver: 1.7.11
|
||||
optionalDependencies:
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint@8.10.0))(eslint@8.10.0))(eslint@8.10.0)
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint@8.10.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-module-utils@2.12.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint@8.10.0))(eslint@8.10.0))(eslint@8.10.0):
|
||||
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@9.28.0(jiti@2.4.2)):
|
||||
dependencies:
|
||||
'@nolyfill/is-core-module': 1.0.39
|
||||
debug: 4.4.1
|
||||
eslint: 9.28.0(jiti@2.4.2)
|
||||
get-tsconfig: 4.10.1
|
||||
is-bun-module: 2.0.0
|
||||
stable-hash: 0.0.5
|
||||
tinyglobby: 0.2.14
|
||||
unrs-resolver: 1.7.11
|
||||
optionalDependencies:
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.32.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-module-utils@2.12.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.10.0):
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
optionalDependencies:
|
||||
'@typescript-eslint/parser': 8.33.1(eslint@8.10.0)(typescript@5.8.3)
|
||||
eslint: 8.10.0
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint@8.10.0))(eslint@8.10.0)
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@8.10.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-module-utils@2.12.0(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.32.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2)))(eslint@9.28.0(jiti@2.4.2)))(eslint@9.28.0(jiti@2.4.2)):
|
||||
eslint-module-utils@2.12.0(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.28.0(jiti@2.4.2)):
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
optionalDependencies:
|
||||
'@typescript-eslint/parser': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)
|
||||
eslint: 9.28.0(jiti@2.4.2)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.32.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2)))(eslint@9.28.0(jiti@2.4.2))
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.28.0(jiti@2.4.2))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint@8.10.0))(eslint@8.10.0))(eslint@8.10.0):
|
||||
eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.32.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2)):
|
||||
dependencies:
|
||||
'@rtsao/scc': 1.1.0
|
||||
array-includes: 3.1.9
|
||||
@@ -18776,9 +18776,9 @@ snapshots:
|
||||
array.prototype.flatmap: 1.3.3
|
||||
debug: 3.2.7
|
||||
doctrine: 2.1.0
|
||||
eslint: 8.10.0
|
||||
eslint: 9.28.0(jiti@2.4.2)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint@8.10.0))(eslint@8.10.0))(eslint@8.10.0)
|
||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.28.0(jiti@2.4.2))
|
||||
hasown: 2.0.2
|
||||
is-core-module: 2.16.1
|
||||
is-glob: 4.0.3
|
||||
@@ -18790,13 +18790,13 @@ snapshots:
|
||||
string.prototype.trimend: 1.0.9
|
||||
tsconfig-paths: 3.15.0
|
||||
optionalDependencies:
|
||||
'@typescript-eslint/parser': 8.33.1(eslint@8.10.0)(typescript@5.8.3)
|
||||
'@typescript-eslint/parser': 8.32.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)
|
||||
transitivePeerDependencies:
|
||||
- eslint-import-resolver-typescript
|
||||
- eslint-import-resolver-webpack
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.32.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2)))(eslint@9.28.0(jiti@2.4.2)))(eslint@9.28.0(jiti@2.4.2)):
|
||||
eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint@8.10.0):
|
||||
dependencies:
|
||||
'@rtsao/scc': 1.1.0
|
||||
array-includes: 3.1.9
|
||||
@@ -18805,9 +18805,9 @@ snapshots:
|
||||
array.prototype.flatmap: 1.3.3
|
||||
debug: 3.2.7
|
||||
doctrine: 2.1.0
|
||||
eslint: 9.28.0(jiti@2.4.2)
|
||||
eslint: 8.10.0
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.32.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2)))(eslint@9.28.0(jiti@2.4.2)))(eslint@9.28.0(jiti@2.4.2))
|
||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.10.0)
|
||||
hasown: 2.0.2
|
||||
is-core-module: 2.16.1
|
||||
is-glob: 4.0.3
|
||||
@@ -18819,7 +18819,7 @@ snapshots:
|
||||
string.prototype.trimend: 1.0.9
|
||||
tsconfig-paths: 3.15.0
|
||||
optionalDependencies:
|
||||
'@typescript-eslint/parser': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)
|
||||
'@typescript-eslint/parser': 8.33.1(eslint@8.10.0)(typescript@5.8.3)
|
||||
transitivePeerDependencies:
|
||||
- eslint-import-resolver-typescript
|
||||
- eslint-import-resolver-webpack
|
||||
|
||||
@@ -137,6 +137,7 @@
|
||||
"title": "Kulude ülevaade 2025 aasta raames",
|
||||
"monthly": "Kulu töötaja kohta kuus *",
|
||||
"yearly": "Maksimaalne kulu inimese kohta kokku aastas *",
|
||||
"total": "Maksimaalne kulu {{employeeCount}} töötaja kohta aastas *"
|
||||
"total": "Maksimaalne kulu {{employeeCount}} töötaja kohta aastas *",
|
||||
"sum": "Kokku"
|
||||
}
|
||||
}
|
||||
|
||||
3
supabase/migrations/20250811174200_delete_employee.sql
Normal file
3
supabase/migrations/20250811174200_delete_employee.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
grant
|
||||
execute on function medreport.can_action_account_member (uuid, uuid) to authenticated,
|
||||
service_role;
|
||||
189
supabase/migrations/20250812143800_log_company_params.sql
Normal file
189
supabase/migrations/20250812143800_log_company_params.sql
Normal file
@@ -0,0 +1,189 @@
|
||||
CREATE OR REPLACE FUNCTION medreport.insert_company_params_on_new_company()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $function$
|
||||
BEGIN
|
||||
IF (TG_OP = 'INSERT' AND NEW.slug IS NOT NULL) THEN
|
||||
INSERT INTO medreport.company_params (
|
||||
account_id,
|
||||
slug,
|
||||
benefit_occurance,
|
||||
benefit_amount
|
||||
) VALUES (
|
||||
NEW.id,
|
||||
NEW.slug,
|
||||
NULL,
|
||||
NULL
|
||||
);
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$function$
|
||||
;
|
||||
|
||||
grant execute on function medreport.insert_company_params_on_new_company() to authenticated,
|
||||
service_role;
|
||||
|
||||
CREATE OR REPLACE FUNCTION log_company_params_changes()
|
||||
RETURNS trigger AS $$
|
||||
BEGIN
|
||||
-- For INSERT operation
|
||||
IF (TG_OP = 'INSERT') THEN
|
||||
INSERT INTO audit.log_entries (
|
||||
schema_name,
|
||||
table_name,
|
||||
record_key,
|
||||
operation,
|
||||
row_data,
|
||||
changed_data,
|
||||
changed_by,
|
||||
changed_by_role,
|
||||
changed_at
|
||||
)
|
||||
VALUES (
|
||||
'medreport', -- Schema name
|
||||
'company_params', -- Table name
|
||||
NEW.id, -- The ID of the inserted row
|
||||
'INSERT', -- Operation type
|
||||
NULL, -- No old data for INSERT
|
||||
row_to_json(NEW), -- New data (after the INSERT)
|
||||
auth.uid(), -- The user performing the insert
|
||||
SESSION_USER, -- The role performing the insert
|
||||
CURRENT_TIMESTAMP -- Timestamp of the insert
|
||||
);
|
||||
-- For UPDATE operation
|
||||
ELSIF (TG_OP = 'UPDATE') THEN
|
||||
INSERT INTO audit.log_entries (
|
||||
schema_name,
|
||||
table_name,
|
||||
record_key,
|
||||
operation,
|
||||
row_data,
|
||||
changed_data,
|
||||
changed_by,
|
||||
changed_by_role,
|
||||
changed_at
|
||||
)
|
||||
VALUES (
|
||||
'medreport', -- Schema name
|
||||
'company_params', -- Table name
|
||||
OLD.id, -- The ID of the updated row
|
||||
'UPDATE', -- Operation type
|
||||
row_to_json(OLD), -- Old data (before the update)
|
||||
row_to_json(NEW), -- New data (after the update)
|
||||
auth.uid(), -- The user performing the update
|
||||
SESSION_USER, -- The role performing the update
|
||||
CURRENT_TIMESTAMP -- Timestamp of the update
|
||||
);
|
||||
-- For DELETE operation
|
||||
ELSIF (TG_OP = 'DELETE') THEN
|
||||
INSERT INTO audit.log_entries (
|
||||
schema_name,
|
||||
table_name,
|
||||
record_key,
|
||||
operation,
|
||||
row_data,
|
||||
changed_data,
|
||||
changed_by,
|
||||
changed_by_role,
|
||||
changed_at
|
||||
)
|
||||
VALUES (
|
||||
'medreport', -- Schema name
|
||||
'company_params', -- Table name
|
||||
OLD.id, -- The ID of the deleted row
|
||||
'DELETE', -- Operation type
|
||||
row_to_json(OLD), -- Old data (before the delete)
|
||||
NULL, -- No new data for DELETE
|
||||
auth.uid(), -- The user performing the delete
|
||||
SESSION_USER, -- The role performing the delete
|
||||
CURRENT_TIMESTAMP -- Timestamp of the delete
|
||||
);
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER company_params_audit_trigger
|
||||
AFTER INSERT OR UPDATE OR DELETE ON medreport.company_params
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION log_company_params_changes();
|
||||
|
||||
create or replace function medreport.create_team_account (
|
||||
account_name text,
|
||||
new_personal_code text
|
||||
) returns medreport.accounts
|
||||
security definer
|
||||
set search_path = ''
|
||||
as $$
|
||||
declare
|
||||
existing_account medreport.accounts;
|
||||
current_user uuid := (select auth.uid())::uuid;
|
||||
new_account medreport.accounts;
|
||||
begin
|
||||
if not medreport.is_set('enable_team_accounts') then
|
||||
raise exception 'Team accounts are not enabled';
|
||||
end if;
|
||||
|
||||
-- Try to find existing account
|
||||
select *
|
||||
into existing_account
|
||||
from medreport.accounts
|
||||
where personal_code = new_personal_code
|
||||
limit 1;
|
||||
|
||||
-- If not found, fail
|
||||
if not found then
|
||||
raise exception 'No account found with personal_code = %', new_personal_code;
|
||||
end if;
|
||||
|
||||
insert into medreport.accounts(
|
||||
name,
|
||||
is_personal_account,
|
||||
primary_owner_user_id)
|
||||
values (
|
||||
account_name,
|
||||
false,
|
||||
existing_account.id)
|
||||
returning
|
||||
* into new_account;
|
||||
|
||||
-- Insert membership
|
||||
insert into medreport.accounts_memberships (
|
||||
user_id,
|
||||
account_id,
|
||||
account_role,
|
||||
created_by,
|
||||
updated_by,
|
||||
created_at,
|
||||
updated_at,
|
||||
has_seen_confirmation
|
||||
)
|
||||
values (
|
||||
existing_account.id,
|
||||
new_account.id,
|
||||
'owner',
|
||||
null,
|
||||
null,
|
||||
now(),
|
||||
now(),
|
||||
false
|
||||
)
|
||||
on conflict do nothing;
|
||||
|
||||
return new_account;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
||||
grant execute on function medreport.create_team_account (text, text) to authenticated, service_role;
|
||||
|
||||
ALTER TABLE "medreport"."accounts"
|
||||
DROP CONSTRAINT "accounts_primary_owner_user_id_fkey";
|
||||
|
||||
ALTER TABLE "medreport"."accounts"
|
||||
ADD CONSTRAINT "accounts_primary_owner_user_id_fkey"
|
||||
FOREIGN KEY (primary_owner_user_id)
|
||||
REFERENCES auth.users(id)
|
||||
ON DELETE CASCADE;
|
||||
Reference in New Issue
Block a user