MED-151: add profile view and working smoking dashboard card (#71)
* MED-151: add profile view and working smoking dashboard card * update zod * move some components to shared * move some components to shared * remove console.logs * remove unused password form components * only check null for variant * use pathsconfig
This commit is contained in:
@@ -21,39 +21,25 @@ export const PaymentTypeSchema = z.enum(['one-time', 'recurring']);
|
||||
export const LineItemSchema = z
|
||||
.object({
|
||||
id: z
|
||||
.string({
|
||||
description:
|
||||
'Unique identifier for the line item. Defined by the Provider.',
|
||||
})
|
||||
.string()
|
||||
.describe('Unique identifier for the line item. Defined by the Provider.')
|
||||
.min(1),
|
||||
name: z
|
||||
.string({
|
||||
description: 'Name of the line item. Displayed to the user.',
|
||||
})
|
||||
.string().describe('Name of the line item. Displayed to the user.')
|
||||
.min(1),
|
||||
description: z
|
||||
.string({
|
||||
description:
|
||||
'Description of the line item. Displayed to the user and will replace the auto-generated description inferred' +
|
||||
' from the line item. This is useful if you want to provide a more detailed description to the user.',
|
||||
})
|
||||
.string().describe('Description of the line item. Displayed to the user and will replace the auto-generated description inferred' +
|
||||
' from the line item. This is useful if you want to provide a more detailed description to the user.')
|
||||
.optional(),
|
||||
cost: z
|
||||
.number({
|
||||
description: 'Cost of the line item. Displayed to the user.',
|
||||
})
|
||||
.number().describe('Cost of the line item. Displayed to the user.')
|
||||
.min(0),
|
||||
type: LineItemTypeSchema,
|
||||
unit: z
|
||||
.string({
|
||||
description:
|
||||
'Unit of the line item. Displayed to the user. Example "seat" or "GB"',
|
||||
})
|
||||
.string().describe('Unit of the line item. Displayed to the user. Example "seat" or "GB"')
|
||||
.optional(),
|
||||
setupFee: z
|
||||
.number({
|
||||
description: `Lemon Squeezy only: If true, in addition to the cost, a setup fee will be charged.`,
|
||||
})
|
||||
.number().describe(`Lemon Squeezy only: If true, in addition to the cost, a setup fee will be charged.`)
|
||||
.positive()
|
||||
.optional(),
|
||||
tiers: z
|
||||
@@ -92,14 +78,10 @@ export const LineItemSchema = z
|
||||
export const PlanSchema = z
|
||||
.object({
|
||||
id: z
|
||||
.string({
|
||||
description: 'Unique identifier for the plan. Defined by yourself.',
|
||||
})
|
||||
.string().describe('Unique identifier for the plan. Defined by yourself.')
|
||||
.min(1),
|
||||
name: z
|
||||
.string({
|
||||
description: 'Name of the plan. Displayed to the user.',
|
||||
})
|
||||
.string().describe('Name of the plan. Displayed to the user.')
|
||||
.min(1),
|
||||
interval: BillingIntervalSchema.optional(),
|
||||
custom: z.boolean().default(false).optional(),
|
||||
@@ -124,10 +106,7 @@ export const PlanSchema = z
|
||||
},
|
||||
),
|
||||
trialDays: z
|
||||
.number({
|
||||
description:
|
||||
'Number of days for the trial period. Leave empty for no trial.',
|
||||
})
|
||||
.number().describe('Number of days for the trial period. Leave empty for no trial.')
|
||||
.positive()
|
||||
.optional(),
|
||||
paymentType: PaymentTypeSchema,
|
||||
@@ -209,54 +188,34 @@ export const PlanSchema = z
|
||||
const ProductSchema = z
|
||||
.object({
|
||||
id: z
|
||||
.string({
|
||||
description:
|
||||
'Unique identifier for the product. Defined by th Provider.',
|
||||
})
|
||||
.string().describe('Unique identifier for the product. Defined by th Provider.')
|
||||
.min(1),
|
||||
name: z
|
||||
.string({
|
||||
description: 'Name of the product. Displayed to the user.',
|
||||
})
|
||||
.string().describe('Name of the product. Displayed to the user.')
|
||||
.min(1),
|
||||
description: z
|
||||
.string({
|
||||
description: 'Description of the product. Displayed to the user.',
|
||||
})
|
||||
.string().describe('Description of the product. Displayed to the user.')
|
||||
.min(1),
|
||||
currency: z
|
||||
.string({
|
||||
description: 'Currency code for the product. Displayed to the user.',
|
||||
})
|
||||
.string().describe('Currency code for the product. Displayed to the user.')
|
||||
.min(3)
|
||||
.max(3),
|
||||
badge: z
|
||||
.string({
|
||||
description:
|
||||
'Badge for the product. Displayed to the user. Example: "Popular"',
|
||||
})
|
||||
.string().describe('Badge for the product. Displayed to the user. Example: "Popular"')
|
||||
.optional(),
|
||||
features: z
|
||||
.array(
|
||||
z.string({
|
||||
description: 'Features of the product. Displayed to the user.',
|
||||
}),
|
||||
)
|
||||
z.string(),
|
||||
).describe('Features of the product. Displayed to the user.')
|
||||
.nonempty(),
|
||||
enableDiscountField: z
|
||||
.boolean({
|
||||
description: 'Enable discount field for the product in the checkout.',
|
||||
})
|
||||
.boolean().describe('Enable discount field for the product in the checkout.')
|
||||
.optional(),
|
||||
highlighted: z
|
||||
.boolean({
|
||||
description: 'Highlight this product. Displayed to the user.',
|
||||
})
|
||||
.boolean().describe('Highlight this product. Displayed to the user.')
|
||||
.optional(),
|
||||
hidden: z
|
||||
.boolean({
|
||||
description: 'Hide this product from being displayed to users.',
|
||||
})
|
||||
.boolean().describe('Hide this product from being displayed to users.')
|
||||
.optional(),
|
||||
plans: z.array(PlanSchema),
|
||||
})
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ReportBillingUsageSchema = z.object({
|
||||
id: z.string({
|
||||
description:
|
||||
'The id of the usage record. For Stripe a customer ID, for LS a subscription item ID.',
|
||||
}),
|
||||
id: z.string().describe('The id of the usage record. For Stripe a customer ID, for LS a subscription item ID.'),
|
||||
eventName: z
|
||||
.string({
|
||||
description: 'The name of the event that triggered the usage',
|
||||
})
|
||||
.string()
|
||||
.describe('The name of the event that triggered the usage')
|
||||
.optional(),
|
||||
usage: z.object({
|
||||
quantity: z.number(),
|
||||
|
||||
@@ -3,8 +3,8 @@ import { z } from 'zod';
|
||||
export const UpdateHealthBenefitSchema = z.object({
|
||||
occurance: z
|
||||
.string({
|
||||
required_error: 'Occurance is required',
|
||||
error: 'Occurance is required',
|
||||
})
|
||||
.nonempty(),
|
||||
amount: z.number({ required_error: 'Amount is required' }),
|
||||
amount: z.number({ error: 'Amount is required' }),
|
||||
});
|
||||
|
||||
@@ -4,12 +4,12 @@ export const MontonioServerEnvSchema = z
|
||||
.object({
|
||||
secretKey: z
|
||||
.string({
|
||||
required_error: `Please provide the variable MONTONIO_SECRET_KEY`,
|
||||
error: `Please provide the variable MONTONIO_SECRET_KEY`,
|
||||
})
|
||||
.min(1),
|
||||
apiUrl: z
|
||||
.string({
|
||||
required_error: `Please provide the variable MONTONIO_API_URL`,
|
||||
error: `Please provide the variable MONTONIO_API_URL`,
|
||||
})
|
||||
.min(1),
|
||||
});
|
||||
|
||||
@@ -4,12 +4,12 @@ export const StripeServerEnvSchema = z
|
||||
.object({
|
||||
secretKey: z
|
||||
.string({
|
||||
required_error: `Please provide the variable STRIPE_SECRET_KEY`,
|
||||
error: `Please provide the variable STRIPE_SECRET_KEY`,
|
||||
})
|
||||
.min(1),
|
||||
webhooksSecret: z
|
||||
.string({
|
||||
required_error: `Please provide the variable STRIPE_WEBHOOK_SECRET`,
|
||||
error: `Please provide the variable STRIPE_WEBHOOK_SECRET`,
|
||||
})
|
||||
.min(1),
|
||||
})
|
||||
|
||||
@@ -4,9 +4,9 @@ import { DatabaseWebhookVerifierService } from './database-webhook-verifier.serv
|
||||
|
||||
const webhooksSecret = z
|
||||
.string({
|
||||
description: `The secret used to verify the webhook signature`,
|
||||
required_error: `Provide the variable SUPABASE_DB_WEBHOOK_SECRET. This is used to authenticate the webhook event from Supabase.`,
|
||||
error: `Provide the variable SUPABASE_DB_WEBHOOK_SECRET. This is used to authenticate the webhook event from Supabase.`,
|
||||
})
|
||||
.describe(`The secret used to verify the webhook signature`,)
|
||||
.min(1)
|
||||
.parse(process.env.SUPABASE_DB_WEBHOOK_SECRET);
|
||||
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
export * from './user-workspace-context';
|
||||
export * from './personal-account-settings/mfa/multi-factor-auth-list'
|
||||
export * from './personal-account-settings/mfa/multi-factor-auth-setup-dialog'
|
||||
|
||||
@@ -101,14 +101,14 @@ export function PersonalAccountDropdown({
|
||||
personalAccountData?.application_role === ApplicationRoleEnum.SuperAdmin;
|
||||
|
||||
return hasAdminRole && hasTotpFactor;
|
||||
}, [user, personalAccountData, hasTotpFactor]);
|
||||
}, [personalAccountData, hasTotpFactor]);
|
||||
|
||||
const isDoctor = useMemo(() => {
|
||||
const hasDoctorRole =
|
||||
personalAccountData?.application_role === ApplicationRoleEnum.Doctor;
|
||||
|
||||
return hasDoctorRole && hasTotpFactor;
|
||||
}, [user, personalAccountData, hasTotpFactor]);
|
||||
}, [personalAccountData, hasTotpFactor]);
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
|
||||
@@ -1,208 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@kit/ui/card';
|
||||
import { If } from '@kit/ui/if';
|
||||
import { LanguageSelector } from '@kit/ui/language-selector';
|
||||
import { LoadingOverlay } from '@kit/ui/loading-overlay';
|
||||
import { Trans } from '@kit/ui/trans';
|
||||
|
||||
import { usePersonalAccountData } from '../../hooks/use-personal-account-data';
|
||||
import { AccountDangerZone } from './account-danger-zone';
|
||||
import ConsentToggle from './consent/consent-toggle';
|
||||
import { UpdateEmailFormContainer } from './email/update-email-form-container';
|
||||
import { MultiFactorAuthFactorsList } from './mfa/multi-factor-auth-list';
|
||||
import { UpdatePasswordFormContainer } from './password/update-password-container';
|
||||
import { UpdateAccountDetailsFormContainer } from './update-account-details-form-container';
|
||||
import { UpdateAccountImageContainer } from './update-account-image-container';
|
||||
|
||||
export function PersonalAccountSettingsContainer(
|
||||
props: React.PropsWithChildren<{
|
||||
userId: string;
|
||||
|
||||
features: {
|
||||
enableAccountDeletion: boolean;
|
||||
enablePasswordUpdate: boolean;
|
||||
};
|
||||
|
||||
paths: {
|
||||
callback: string;
|
||||
};
|
||||
}>,
|
||||
) {
|
||||
const supportsLanguageSelection = useSupportMultiLanguage();
|
||||
const user = usePersonalAccountData(props.userId);
|
||||
|
||||
if (!user.data || user.isPending) {
|
||||
return <LoadingOverlay fullPage />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={'flex w-full flex-col space-y-4 pb-32'}>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
<Trans i18nKey={'account:accountImage'} />
|
||||
</CardTitle>
|
||||
|
||||
<CardDescription>
|
||||
<Trans i18nKey={'account:accountImageDescription'} />
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent>
|
||||
<UpdateAccountImageContainer
|
||||
user={{
|
||||
pictureUrl: user.data.picture_url,
|
||||
id: user.data.id,
|
||||
}}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
<Trans i18nKey={'account:name'} />
|
||||
</CardTitle>
|
||||
|
||||
<CardDescription>
|
||||
<Trans i18nKey={'account:nameDescription'} />
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent>
|
||||
<UpdateAccountDetailsFormContainer user={user.data} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<If condition={supportsLanguageSelection}>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
<Trans i18nKey={'account:language'} />
|
||||
</CardTitle>
|
||||
|
||||
<CardDescription>
|
||||
<Trans i18nKey={'account:languageDescription'} />
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent>
|
||||
<LanguageSelector />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</If>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
<Trans i18nKey={'account:updateEmailCardTitle'} />
|
||||
</CardTitle>
|
||||
|
||||
<CardDescription>
|
||||
<Trans i18nKey={'account:updateEmailCardDescription'} />
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent>
|
||||
<UpdateEmailFormContainer callbackPath={props.paths.callback} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<If condition={props.features.enablePasswordUpdate}>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
<Trans i18nKey={'account:updatePasswordCardTitle'} />
|
||||
</CardTitle>
|
||||
|
||||
<CardDescription>
|
||||
<Trans i18nKey={'account:updatePasswordCardDescription'} />
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent>
|
||||
<UpdatePasswordFormContainer callbackPath={props.paths.callback} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</If>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
<Trans i18nKey={'account:multiFactorAuth'} />
|
||||
</CardTitle>
|
||||
|
||||
<CardDescription>
|
||||
<Trans i18nKey={'account:multiFactorAuthDescription'} />
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent>
|
||||
<MultiFactorAuthFactorsList userId={props.userId} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
<p className="font-medium">
|
||||
<Trans
|
||||
i18nKey={'account:consentToAnonymizedCompanyData.label'}
|
||||
/>
|
||||
</p>
|
||||
|
||||
<CardDescription>
|
||||
<Trans
|
||||
i18nKey={'account:consentToAnonymizedCompanyData.description'}
|
||||
/>
|
||||
</CardDescription>
|
||||
</div>
|
||||
<ConsentToggle
|
||||
userId={props.userId}
|
||||
initialState={
|
||||
!!user.data.has_consent_anonymized_company_statistics
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
|
||||
<If condition={props.features.enableAccountDeletion}>
|
||||
<Card className={'border-destructive'}>
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
<Trans i18nKey={'account:dangerZone'} />
|
||||
</CardTitle>
|
||||
|
||||
<CardDescription>
|
||||
<Trans i18nKey={'account:dangerZoneDescription'} />
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent>
|
||||
<AccountDangerZone />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</If>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function useSupportMultiLanguage() {
|
||||
const { i18n } = useTranslation();
|
||||
const langs = (i18n?.options?.supportedLngs as string[]) ?? [];
|
||||
|
||||
const supportedLangs = langs.filter((lang) => lang !== 'cimode');
|
||||
|
||||
return supportedLangs.length > 1;
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import { Switch } from '@kit/ui/switch';
|
||||
import { Trans } from '@kit/ui/trans';
|
||||
|
||||
import { useRevalidatePersonalAccountDataQuery } from '../../../hooks/use-personal-account-data';
|
||||
import { useUpdateAccountData } from '../../../hooks/use-update-account';
|
||||
|
||||
// This is temporary. When the profile views are ready, all account values included in the form will be updated together on form submit.
|
||||
export default function ConsentToggle({
|
||||
userId,
|
||||
initialState,
|
||||
}: {
|
||||
userId: string;
|
||||
initialState: boolean;
|
||||
}) {
|
||||
const [isConsent, setIsConsent] = useState(initialState);
|
||||
const updateAccountMutation = useUpdateAccountData(userId);
|
||||
const revalidateUserDataQuery = useRevalidatePersonalAccountDataQuery();
|
||||
|
||||
const updateConsent = (consent: boolean) => {
|
||||
const promise = updateAccountMutation
|
||||
.mutateAsync({
|
||||
has_consent_anonymized_company_statistics: consent,
|
||||
})
|
||||
.then(() => {
|
||||
revalidateUserDataQuery(userId);
|
||||
});
|
||||
|
||||
return toast.promise(() => promise, {
|
||||
success: <Trans i18nKey={'account:updateConsentSuccess'} />,
|
||||
error: <Trans i18nKey={'account:updateConsentError'} />,
|
||||
loading: <Trans i18nKey={'account:updateConsentLoading'} />,
|
||||
});
|
||||
};
|
||||
return (
|
||||
<Switch
|
||||
checked={isConsent}
|
||||
onCheckedChange={setIsConsent}
|
||||
onClick={() => updateConsent(!isConsent)}
|
||||
disabled={updateAccountMutation.isPending}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import { toast } from 'sonner';
|
||||
|
||||
import { useFetchAuthFactors } from '@kit/supabase/hooks/use-fetch-mfa-factors';
|
||||
import { useSupabase } from '@kit/supabase/hooks/use-supabase';
|
||||
import { useUser } from '@kit/supabase/hooks/use-user';
|
||||
import { useFactorsMutationKey } from '@kit/supabase/hooks/use-user-factors-mutation-key';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
|
||||
import {
|
||||
@@ -46,13 +47,18 @@ import { Trans } from '@kit/ui/trans';
|
||||
|
||||
import { MultiFactorAuthSetupDialog } from './multi-factor-auth-setup-dialog';
|
||||
|
||||
export function MultiFactorAuthFactorsList(props: { userId: string }) {
|
||||
export function MultiFactorAuthFactorsList() {
|
||||
const { data: user } = useUser();
|
||||
if (!user?.id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={'flex flex-col space-y-4'}>
|
||||
<FactorsTableContainer userId={props.userId} />
|
||||
<FactorsTableContainer userId={user?.id} />
|
||||
|
||||
<div>
|
||||
<MultiFactorAuthSetupDialog userId={props.userId} />
|
||||
<MultiFactorAuthSetupDialog userId={user?.id} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { useUser } from '@kit/supabase/hooks/use-user';
|
||||
import { Alert } from '@kit/ui/alert';
|
||||
import { LoadingOverlay } from '@kit/ui/loading-overlay';
|
||||
import { Trans } from '@kit/ui/trans';
|
||||
|
||||
import { UpdatePasswordForm } from './update-password-form';
|
||||
|
||||
export function UpdatePasswordFormContainer(
|
||||
props: React.PropsWithChildren<{
|
||||
callbackPath: string;
|
||||
}>,
|
||||
) {
|
||||
const { data: user, isPending } = useUser();
|
||||
|
||||
if (isPending) {
|
||||
return <LoadingOverlay fullPage={false} />;
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const canUpdatePassword = user.identities?.some(
|
||||
(item) => item.provider === `email`,
|
||||
);
|
||||
|
||||
if (!canUpdatePassword) {
|
||||
return <WarnCannotUpdatePasswordAlert />;
|
||||
}
|
||||
|
||||
return <UpdatePasswordForm callbackPath={props.callbackPath} user={user} />;
|
||||
}
|
||||
|
||||
function WarnCannotUpdatePasswordAlert() {
|
||||
return (
|
||||
<Alert variant={'warning'}>
|
||||
<Trans i18nKey={'account:cannotUpdatePassword'} />
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
@@ -1,206 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
import type { User } from '@supabase/supabase-js';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { ExclamationTriangleIcon } from '@radix-ui/react-icons';
|
||||
import { Check } from 'lucide-react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import { useUpdateUser } from '@kit/supabase/hooks/use-update-user-mutation';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
|
||||
import { Button } from '@kit/ui/button';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@kit/ui/form';
|
||||
import { If } from '@kit/ui/if';
|
||||
import { Input } from '@kit/ui/input';
|
||||
import { Label } from '@kit/ui/label';
|
||||
import { Trans } from '@kit/ui/trans';
|
||||
|
||||
import { PasswordUpdateSchema } from '../../../schema/update-password.schema';
|
||||
|
||||
export const UpdatePasswordForm = ({
|
||||
user,
|
||||
callbackPath,
|
||||
}: {
|
||||
user: User;
|
||||
callbackPath: string;
|
||||
}) => {
|
||||
const { t } = useTranslation('account');
|
||||
const updateUserMutation = useUpdateUser();
|
||||
const [needsReauthentication, setNeedsReauthentication] = useState(false);
|
||||
|
||||
const updatePasswordFromCredential = (password: string) => {
|
||||
const redirectTo = [window.location.origin, callbackPath].join('');
|
||||
|
||||
const promise = updateUserMutation
|
||||
.mutateAsync({ password, redirectTo })
|
||||
.catch((error) => {
|
||||
if (
|
||||
typeof error === 'string' &&
|
||||
error?.includes('Password update requires reauthentication')
|
||||
) {
|
||||
setNeedsReauthentication(true);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
toast.promise(() => promise, {
|
||||
success: t(`updatePasswordSuccess`),
|
||||
error: t(`updatePasswordError`),
|
||||
loading: t(`updatePasswordLoading`),
|
||||
});
|
||||
};
|
||||
|
||||
const updatePasswordCallback = async ({
|
||||
newPassword,
|
||||
}: {
|
||||
newPassword: string;
|
||||
}) => {
|
||||
const email = user.email;
|
||||
|
||||
// if the user does not have an email assigned, it's possible they
|
||||
// don't have an email/password factor linked, and the UI is out of sync
|
||||
if (!email) {
|
||||
return Promise.reject(t(`cannotUpdatePassword`));
|
||||
}
|
||||
|
||||
updatePasswordFromCredential(newPassword);
|
||||
};
|
||||
|
||||
const form = useForm({
|
||||
resolver: zodResolver(
|
||||
PasswordUpdateSchema.withTranslation(t('passwordNotMatching')),
|
||||
),
|
||||
defaultValues: {
|
||||
newPassword: '',
|
||||
repeatPassword: '',
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
data-test={'account-password-form'}
|
||||
onSubmit={form.handleSubmit(updatePasswordCallback)}
|
||||
>
|
||||
<div className={'flex flex-col space-y-4'}>
|
||||
<If condition={updateUserMutation.data}>
|
||||
<SuccessAlert />
|
||||
</If>
|
||||
|
||||
<If condition={needsReauthentication}>
|
||||
<NeedsReauthenticationAlert />
|
||||
</If>
|
||||
|
||||
<FormField
|
||||
name={'newPassword'}
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<Label>
|
||||
<Trans i18nKey={'account:newPassword'} />
|
||||
</Label>
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
<Input
|
||||
data-test={'account-password-form-password-input'}
|
||||
autoComplete={'new-password'}
|
||||
required
|
||||
type={'password'}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name={'repeatPassword'}
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<Label>
|
||||
<Trans i18nKey={'account:repeatPassword'} />
|
||||
</Label>
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
<Input
|
||||
data-test={'account-password-form-repeat-password-input'}
|
||||
required
|
||||
type={'password'}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormDescription>
|
||||
<Trans i18nKey={'account:repeatPasswordDescription'} />
|
||||
</FormDescription>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<Button disabled={updateUserMutation.isPending}>
|
||||
<Trans i18nKey={'account:updatePasswordSubmitLabel'} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
function SuccessAlert() {
|
||||
return (
|
||||
<Alert variant={'success'}>
|
||||
<Check className={'h-4'} />
|
||||
|
||||
<AlertTitle>
|
||||
<Trans i18nKey={'account:updatePasswordSuccess'} />
|
||||
</AlertTitle>
|
||||
|
||||
<AlertDescription>
|
||||
<Trans i18nKey={'account:updatePasswordSuccessMessage'} />
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
function NeedsReauthenticationAlert() {
|
||||
return (
|
||||
<Alert variant={'warning'}>
|
||||
<ExclamationTriangleIcon className={'h-4'} />
|
||||
|
||||
<AlertTitle>
|
||||
<Trans i18nKey={'account:needsReauthentication'} />
|
||||
</AlertTitle>
|
||||
|
||||
<AlertDescription>
|
||||
<Trans i18nKey={'account:needsReauthenticationDescription'} />
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { useRevalidatePersonalAccountDataQuery } from '../../hooks/use-personal-account-data';
|
||||
import { UpdateAccountDetailsForm } from './update-account-details-form';
|
||||
|
||||
export function UpdateAccountDetailsFormContainer({
|
||||
user,
|
||||
}: {
|
||||
user: {
|
||||
name: string | null;
|
||||
id: string;
|
||||
};
|
||||
}) {
|
||||
const revalidateUserDataQuery = useRevalidatePersonalAccountDataQuery();
|
||||
|
||||
return (
|
||||
<UpdateAccountDetailsForm
|
||||
displayName={user.name ?? ''}
|
||||
userId={user.id}
|
||||
onUpdate={() => revalidateUserDataQuery(user.id)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import { Database } from '@kit/supabase/database';
|
||||
import { Button } from '@kit/ui/button';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@kit/ui/form';
|
||||
import { Input } from '@kit/ui/input';
|
||||
import { Trans } from '@kit/ui/trans';
|
||||
|
||||
import { useUpdateAccountData } from '../../hooks/use-update-account';
|
||||
import { AccountDetailsSchema } from '../../schema/account-details.schema';
|
||||
|
||||
type UpdateUserDataParams =
|
||||
Database['medreport']['Tables']['accounts']['Update'];
|
||||
|
||||
export function UpdateAccountDetailsForm({
|
||||
displayName,
|
||||
onUpdate,
|
||||
userId,
|
||||
}: {
|
||||
displayName: string;
|
||||
userId: string;
|
||||
onUpdate: (user: Partial<UpdateUserDataParams>) => void;
|
||||
}) {
|
||||
const updateAccountMutation = useUpdateAccountData(userId);
|
||||
const { t } = useTranslation('account');
|
||||
|
||||
const form = useForm({
|
||||
resolver: zodResolver(AccountDetailsSchema),
|
||||
defaultValues: {
|
||||
displayName,
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = ({ displayName }: { displayName: string }) => {
|
||||
const data = { name: displayName };
|
||||
|
||||
const promise = updateAccountMutation.mutateAsync(data).then(() => {
|
||||
onUpdate(data);
|
||||
});
|
||||
|
||||
return toast.promise(() => promise, {
|
||||
success: t(`updateProfileSuccess`),
|
||||
error: t(`updateProfileError`),
|
||||
loading: t(`updateProfileLoading`),
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={'flex flex-col space-y-8'}>
|
||||
<Form {...form}>
|
||||
<form
|
||||
data-test={'update-account-name-form'}
|
||||
className={'flex flex-col space-y-4'}
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
>
|
||||
<FormField
|
||||
name={'displayName'}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<Trans i18nKey={'account:name'} />
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
<Input
|
||||
data-test={'account-display-name'}
|
||||
minLength={2}
|
||||
placeholder={''}
|
||||
maxLength={100}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<Button disabled={updateAccountMutation.isPending}>
|
||||
<Trans i18nKey={'account:updateProfileSubmitLabel'} />
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import type { SupabaseClient } from '@supabase/supabase-js';
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import { Database } from '@kit/supabase/database';
|
||||
import { useSupabase } from '@kit/supabase/hooks/use-supabase';
|
||||
import { ImageUploader } from '@kit/ui/image-uploader';
|
||||
import { Trans } from '@kit/ui/trans';
|
||||
|
||||
import { useRevalidatePersonalAccountDataQuery } from '../../hooks/use-personal-account-data';
|
||||
|
||||
const AVATARS_BUCKET = 'account_image';
|
||||
|
||||
export function UpdateAccountImageContainer({
|
||||
user,
|
||||
}: {
|
||||
user: {
|
||||
pictureUrl: string | null;
|
||||
id: string;
|
||||
};
|
||||
}) {
|
||||
const revalidateUserDataQuery = useRevalidatePersonalAccountDataQuery();
|
||||
|
||||
return (
|
||||
<UploadProfileAvatarForm
|
||||
pictureUrl={user.pictureUrl ?? null}
|
||||
userId={user.id}
|
||||
onAvatarUpdated={() => revalidateUserDataQuery(user.id)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function UploadProfileAvatarForm(props: {
|
||||
pictureUrl: string | null;
|
||||
userId: string;
|
||||
onAvatarUpdated: () => void;
|
||||
}) {
|
||||
const client = useSupabase();
|
||||
const { t } = useTranslation('account');
|
||||
|
||||
const createToaster = useCallback(
|
||||
(promise: () => Promise<unknown>) => {
|
||||
return toast.promise(promise, {
|
||||
success: t(`updateProfileSuccess`),
|
||||
error: t(`updateProfileError`),
|
||||
loading: t(`updateProfileLoading`),
|
||||
});
|
||||
},
|
||||
[t],
|
||||
);
|
||||
|
||||
const onValueChange = useCallback(
|
||||
(file: File | null) => {
|
||||
const removeExistingStorageFile = () => {
|
||||
if (props.pictureUrl) {
|
||||
return (
|
||||
deleteProfilePhoto(client, props.pictureUrl) ?? Promise.resolve()
|
||||
);
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
if (file) {
|
||||
const promise = () =>
|
||||
removeExistingStorageFile().then(() =>
|
||||
uploadUserProfilePhoto(client, file, props.userId)
|
||||
.then((pictureUrl) => {
|
||||
return client
|
||||
.schema('medreport')
|
||||
.from('accounts')
|
||||
.update({
|
||||
picture_url: pictureUrl,
|
||||
})
|
||||
.eq('id', props.userId)
|
||||
.throwOnError();
|
||||
})
|
||||
.then(() => {
|
||||
props.onAvatarUpdated();
|
||||
}),
|
||||
);
|
||||
|
||||
createToaster(promise);
|
||||
} else {
|
||||
const promise = () =>
|
||||
removeExistingStorageFile()
|
||||
.then(() => {
|
||||
return client
|
||||
.schema('medreport')
|
||||
.from('accounts')
|
||||
.update({
|
||||
picture_url: null,
|
||||
})
|
||||
.eq('id', props.userId)
|
||||
.throwOnError();
|
||||
})
|
||||
.then(() => {
|
||||
props.onAvatarUpdated();
|
||||
});
|
||||
|
||||
createToaster(promise);
|
||||
}
|
||||
},
|
||||
[client, createToaster, props],
|
||||
);
|
||||
|
||||
return (
|
||||
<ImageUploader value={props.pictureUrl} onValueChange={onValueChange}>
|
||||
<div className={'flex flex-col space-y-1'}>
|
||||
<span className={'text-sm'}>
|
||||
<Trans i18nKey={'account:profilePictureHeading'} />
|
||||
</span>
|
||||
|
||||
<span className={'text-xs'}>
|
||||
<Trans i18nKey={'account:profilePictureSubheading'} />
|
||||
</span>
|
||||
</div>
|
||||
</ImageUploader>
|
||||
);
|
||||
}
|
||||
|
||||
function deleteProfilePhoto(client: SupabaseClient<Database>, url: string) {
|
||||
const bucket = client.storage.from(AVATARS_BUCKET);
|
||||
const fileName = url.split('/').pop()?.split('?')[0];
|
||||
|
||||
if (!fileName) {
|
||||
return;
|
||||
}
|
||||
|
||||
return bucket.remove([fileName]);
|
||||
}
|
||||
|
||||
async function uploadUserProfilePhoto(
|
||||
client: SupabaseClient<Database>,
|
||||
photoFile: File,
|
||||
userId: string,
|
||||
) {
|
||||
const bytes = await photoFile.arrayBuffer();
|
||||
const bucket = client.storage.from(AVATARS_BUCKET);
|
||||
const extension = photoFile.name.split('.').pop();
|
||||
const fileName = await getAvatarFileName(userId, extension);
|
||||
|
||||
const result = await bucket.upload(fileName, bytes);
|
||||
|
||||
if (!result.error) {
|
||||
return bucket.getPublicUrl(fileName).data.publicUrl;
|
||||
}
|
||||
|
||||
throw result.error;
|
||||
}
|
||||
|
||||
async function getAvatarFileName(
|
||||
userId: string,
|
||||
extension: string | undefined,
|
||||
) {
|
||||
const { nanoid } = await import('nanoid');
|
||||
|
||||
// we add a version to the URL to ensure
|
||||
// the browser always fetches the latest image
|
||||
const uniqueId = nanoid(16);
|
||||
|
||||
return `${userId}.${extension}?v=${uniqueId}`;
|
||||
}
|
||||
@@ -2,19 +2,19 @@ import { SupabaseClient } from '@supabase/supabase-js';
|
||||
|
||||
import { Database } from '@kit/supabase/database';
|
||||
|
||||
import {
|
||||
AnalysisResultDetails,
|
||||
UserAnalysis,
|
||||
UserAnalysisResponse,
|
||||
} from '../types/accounts';
|
||||
import { AnalysisResultDetails, UserAnalysis } from '../types/accounts';
|
||||
|
||||
export type AccountWithParams =
|
||||
Database['medreport']['Tables']['accounts']['Row'] & {
|
||||
account_params:
|
||||
| Pick<
|
||||
accountParams:
|
||||
| (Pick<
|
||||
Database['medreport']['Tables']['account_params']['Row'],
|
||||
'weight' | 'height'
|
||||
>[]
|
||||
> & {
|
||||
isSmoker:
|
||||
| Database['medreport']['Tables']['account_params']['Row']['is_smoker']
|
||||
| null;
|
||||
})
|
||||
| null;
|
||||
};
|
||||
|
||||
@@ -35,7 +35,9 @@ class AccountsApi {
|
||||
const { data, error } = await this.client
|
||||
.schema('medreport')
|
||||
.from('accounts')
|
||||
.select('*, account_params: account_params (weight, height)')
|
||||
.select(
|
||||
'*, accountParams: account_params (weight, height, isSmoker:is_smoker)',
|
||||
)
|
||||
.eq('id', id)
|
||||
.single();
|
||||
|
||||
|
||||
@@ -32,9 +32,8 @@ const SPECIAL_CHARACTERS_REGEX = /[!@#$%^&*()+=[\]{};':"\\|,.<>/?]/;
|
||||
* @name CompanyNameSchema
|
||||
*/
|
||||
export const CompanyNameSchema = z
|
||||
.string({
|
||||
description: 'The name of the company account',
|
||||
})
|
||||
.string()
|
||||
.describe('The name of the company account')
|
||||
.min(2)
|
||||
.max(50)
|
||||
.refine(
|
||||
|
||||
@@ -410,7 +410,7 @@ export async function getAnalysisResultsForDoctor(
|
||||
.from('accounts')
|
||||
.select(
|
||||
`primary_owner_user_id, id, name, last_name, personal_code, phone, email, preferred_locale,
|
||||
account_params(height,weight)`,
|
||||
accountParams:account_params(height,weight)`,
|
||||
)
|
||||
.eq('is_personal_account', true)
|
||||
.eq('primary_owner_user_id', userId)
|
||||
@@ -472,7 +472,7 @@ export async function getAnalysisResultsForDoctor(
|
||||
last_name,
|
||||
personal_code,
|
||||
phone,
|
||||
account_params,
|
||||
accountParams,
|
||||
preferred_locale,
|
||||
} = accountWithParams[0];
|
||||
|
||||
@@ -513,8 +513,8 @@ export async function getAnalysisResultsForDoctor(
|
||||
personalCode: personal_code,
|
||||
phone,
|
||||
email,
|
||||
height: account_params?.[0]?.height,
|
||||
weight: account_params?.[0]?.weight,
|
||||
height: accountParams?.height,
|
||||
weight: accountParams?.weight,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -17,22 +17,22 @@ const env = z
|
||||
.object({
|
||||
invitePath: z
|
||||
.string({
|
||||
required_error: 'The property invitePath is required',
|
||||
error: 'The property invitePath is required',
|
||||
})
|
||||
.min(1),
|
||||
siteURL: z
|
||||
.string({
|
||||
required_error: 'NEXT_PUBLIC_SITE_URL is required',
|
||||
error: 'NEXT_PUBLIC_SITE_URL is required',
|
||||
})
|
||||
.min(1),
|
||||
productName: z
|
||||
.string({
|
||||
required_error: 'NEXT_PUBLIC_PRODUCT_NAME is required',
|
||||
error: 'NEXT_PUBLIC_PRODUCT_NAME is required',
|
||||
})
|
||||
.min(1),
|
||||
emailSender: z
|
||||
.string({
|
||||
required_error: 'EMAIL_SENDER is required',
|
||||
error: 'EMAIL_SENDER is required',
|
||||
})
|
||||
.min(1),
|
||||
})
|
||||
|
||||
@@ -78,7 +78,7 @@ class AccountWebhooksService {
|
||||
productName: z.string(),
|
||||
fromEmail: z
|
||||
.string({
|
||||
required_error: 'EMAIL_SENDER is required',
|
||||
error: 'EMAIL_SENDER is required',
|
||||
})
|
||||
.min(1),
|
||||
})
|
||||
|
||||
@@ -8,9 +8,9 @@ type Config = z.infer<typeof MailerSchema>;
|
||||
|
||||
const RESEND_API_KEY = z
|
||||
.string({
|
||||
description: 'The API key for the Resend API',
|
||||
required_error: 'Please provide the API key for the Resend API',
|
||||
error: 'Please provide the API key for the Resend API',
|
||||
})
|
||||
.describe('The API key for the Resend API')
|
||||
.parse(process.env.RESEND_API_KEY);
|
||||
|
||||
export function createResendMailer() {
|
||||
|
||||
@@ -4,25 +4,19 @@ import { z } from 'zod';
|
||||
|
||||
export const SmtpConfigSchema = z.object({
|
||||
user: z.string({
|
||||
description:
|
||||
'This is the email account to send emails from. This is specific to the email provider.',
|
||||
required_error: `Please provide the variable EMAIL_USER`,
|
||||
}),
|
||||
error: `Please provide the variable EMAIL_USER`,
|
||||
})
|
||||
.describe('This is the email account to send emails from. This is specific to the email provider.'),
|
||||
pass: z.string({
|
||||
description: 'This is the password for the email account',
|
||||
required_error: `Please provide the variable EMAIL_PASSWORD`,
|
||||
}),
|
||||
error: `Please provide the variable EMAIL_PASSWORD`,
|
||||
}).describe('This is the password for the email account'),
|
||||
host: z.string({
|
||||
description: 'This is the SMTP host for the email provider',
|
||||
required_error: `Please provide the variable EMAIL_HOST`,
|
||||
}),
|
||||
error: `Please provide the variable EMAIL_HOST`,
|
||||
}).describe('This is the SMTP host for the email provider'),
|
||||
port: z.number({
|
||||
description:
|
||||
'This is the port for the email provider. Normally 587 or 465.',
|
||||
required_error: `Please provide the variable EMAIL_PORT`,
|
||||
}),
|
||||
error: `Please provide the variable EMAIL_PORT`,
|
||||
}).describe('This is the port for the email provider. Normally 587 or 465.'),
|
||||
secure: z.boolean({
|
||||
description: 'This is whether the connection is secure or not',
|
||||
required_error: `Please provide the variable EMAIL_TLS`,
|
||||
}),
|
||||
error: `Please provide the variable EMAIL_TLS`,
|
||||
}).describe('This is whether the connection is secure or not'),
|
||||
});
|
||||
|
||||
@@ -4,9 +4,9 @@ import { MonitoringService } from '@kit/monitoring-core';
|
||||
|
||||
const apiKey = z
|
||||
.string({
|
||||
required_error: 'NEXT_PUBLIC_BASELIME_KEY is required',
|
||||
description: 'The Baseline API key',
|
||||
error: 'NEXT_PUBLIC_BASELIME_KEY is required',
|
||||
})
|
||||
.describe('The Baseline API key')
|
||||
.parse(process.env.NEXT_PUBLIC_BASELIME_KEY);
|
||||
|
||||
export class BaselimeServerMonitoringService implements MonitoringService {
|
||||
|
||||
@@ -6,14 +6,14 @@ import { getLogger } from '@kit/shared/logger';
|
||||
|
||||
const EMAIL_SENDER = z
|
||||
.string({
|
||||
required_error: 'EMAIL_SENDER is required',
|
||||
error: 'EMAIL_SENDER is required',
|
||||
})
|
||||
.min(1)
|
||||
.parse(process.env.EMAIL_SENDER);
|
||||
|
||||
const PRODUCT_NAME = z
|
||||
.string({
|
||||
required_error: 'PRODUCT_NAME is required',
|
||||
error: 'PRODUCT_NAME is required',
|
||||
})
|
||||
.min(1)
|
||||
.parse(process.env.NEXT_PUBLIC_PRODUCT_NAME);
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
import { Trans } from '@kit/ui/trans';
|
||||
import { ButtonTooltip } from './ui/button-tooltip';
|
||||
import { PackageHeader } from './package-header';
|
||||
import { pathsConfig } from '../config';
|
||||
|
||||
export type AnalysisPackageWithVariant = Pick<StoreProduct, 'title' | 'description' | 'subtitle' | 'metadata'> & {
|
||||
variantId: string;
|
||||
@@ -57,7 +58,7 @@ export default function SelectAnalysisPackage({
|
||||
});
|
||||
setIsAddingToCart(false);
|
||||
toast.success(<Trans i18nKey={'order-analysis-package:analysisPackageAddedToCart'} />);
|
||||
router.push('/home/cart');
|
||||
router.push(pathsConfig.app.cart);
|
||||
} catch (e) {
|
||||
toast.error(<Trans i18nKey={'order-analysis-package:analysisPackageAddToCartError'} />);
|
||||
setIsAddingToCart(false);
|
||||
|
||||
24
packages/shared/src/components/sign-out-dropdown-item.tsx
Normal file
24
packages/shared/src/components/sign-out-dropdown-item.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
'use client'
|
||||
|
||||
import { DropdownMenuItem } from "@kit/ui/dropdown-menu";
|
||||
import { Trans } from "@kit/ui/trans";
|
||||
import { LogOut } from "lucide-react";
|
||||
|
||||
export default function SignOutDropdownItem(
|
||||
props: React.PropsWithChildren<{
|
||||
onSignOut: () => unknown;
|
||||
}>,
|
||||
) {
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
className={'flex h-12 w-full items-center space-x-4'}
|
||||
onClick={props.onSignOut}
|
||||
>
|
||||
<LogOut className={'h-6'} />
|
||||
|
||||
<span>
|
||||
<Trans i18nKey={'common:signOut'} defaults={'Sign out'} />
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
}
|
||||
33
packages/shared/src/components/ui/dropdown-link.tsx
Normal file
33
packages/shared/src/components/ui/dropdown-link.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
'use client'
|
||||
|
||||
import { DropdownMenuItem } from "@kit/ui/dropdown-menu";
|
||||
import { Trans } from "@kit/ui/trans";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function DropdownLink(
|
||||
props: React.PropsWithChildren<{
|
||||
path: string;
|
||||
label: string;
|
||||
labelOptions?: Record<string, any>;
|
||||
Icon?: React.ReactNode;
|
||||
}>,
|
||||
) {
|
||||
return (
|
||||
<DropdownMenuItem asChild key={props.path}>
|
||||
<Link
|
||||
href={props.path}
|
||||
className={'flex h-12 w-full items-center space-x-4'}
|
||||
>
|
||||
{props.Icon}
|
||||
|
||||
<span>
|
||||
<Trans
|
||||
i18nKey={props.label}
|
||||
defaults={props.label}
|
||||
values={props.labelOptions}
|
||||
/>
|
||||
</span>
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
}
|
||||
@@ -6,32 +6,30 @@ const AppConfigSchema = z
|
||||
.object({
|
||||
name: z
|
||||
.string({
|
||||
description: `This is the name of your SaaS. Ex. "Makerkit"`,
|
||||
required_error: `Please provide the variable NEXT_PUBLIC_PRODUCT_NAME`,
|
||||
error: `Please provide the variable NEXT_PUBLIC_PRODUCT_NAME`,
|
||||
})
|
||||
.describe(`This is the name of your SaaS. Ex. "Makerkit"`)
|
||||
.min(1),
|
||||
title: z
|
||||
.string({
|
||||
description: `This is the default title tag of your SaaS.`,
|
||||
required_error: `Please provide the variable NEXT_PUBLIC_SITE_TITLE`,
|
||||
error: `Please provide the variable NEXT_PUBLIC_SITE_TITLE`,
|
||||
})
|
||||
.describe(`This is the default title tag of your SaaS.`)
|
||||
.min(1),
|
||||
description: z.string({
|
||||
description: `This is the default description of your SaaS.`,
|
||||
required_error: `Please provide the variable NEXT_PUBLIC_SITE_DESCRIPTION`,
|
||||
error: `Please provide the variable NEXT_PUBLIC_SITE_DESCRIPTION`,
|
||||
})
|
||||
.describe(`This is the default description of your SaaS.`),
|
||||
url: z.url({
|
||||
error: (issue) => issue.input === undefined
|
||||
? "Please provide the variable NEXT_PUBLIC_SITE_URL"
|
||||
: `You are deploying a production build but have entered a NEXT_PUBLIC_SITE_URL variable using http instead of https. It is very likely that you have set the incorrect URL. The build will now fail to prevent you from from deploying a faulty configuration. Please provide the variable NEXT_PUBLIC_SITE_URL with a valid URL, such as: 'https://example.com'`
|
||||
}),
|
||||
url: z
|
||||
.string({
|
||||
required_error: `Please provide the variable NEXT_PUBLIC_SITE_URL`,
|
||||
})
|
||||
.url({
|
||||
message: `You are deploying a production build but have entered a NEXT_PUBLIC_SITE_URL variable using http instead of https. It is very likely that you have set the incorrect URL. The build will now fail to prevent you from from deploying a faulty configuration. Please provide the variable NEXT_PUBLIC_SITE_URL with a valid URL, such as: 'https://example.com'`,
|
||||
}),
|
||||
locale: z
|
||||
.string({
|
||||
description: `This is the default locale of your SaaS.`,
|
||||
required_error: `Please provide the variable NEXT_PUBLIC_DEFAULT_LOCALE`,
|
||||
error: `Please provide the variable NEXT_PUBLIC_DEFAULT_LOCALE`,
|
||||
})
|
||||
.describe(`This is the default locale of your SaaS.`)
|
||||
.default('en'),
|
||||
theme: z.enum(['light', 'dark', 'system']),
|
||||
production: z.boolean(),
|
||||
|
||||
@@ -6,22 +6,14 @@ const providers: z.ZodType<Provider> = getProviders();
|
||||
|
||||
const AuthConfigSchema = z.object({
|
||||
captchaTokenSiteKey: z
|
||||
.string({
|
||||
description: 'The reCAPTCHA site key.',
|
||||
})
|
||||
.string().describe('The reCAPTCHA site key.')
|
||||
.optional(),
|
||||
displayTermsCheckbox: z
|
||||
.boolean({
|
||||
description: 'Whether to display the terms checkbox during sign-up.',
|
||||
})
|
||||
.boolean().describe('Whether to display the terms checkbox during sign-up.')
|
||||
.optional(),
|
||||
providers: z.object({
|
||||
password: z.boolean({
|
||||
description: 'Enable password authentication.',
|
||||
}),
|
||||
magicLink: z.boolean({
|
||||
description: 'Enable magic link authentication.',
|
||||
}),
|
||||
password: z.boolean().describe('Enable password authentication.'),
|
||||
magicLink: z.boolean().describe('Enable magic link authentication.'),
|
||||
oAuth: providers.array(),
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -4,56 +4,56 @@ type LanguagePriority = 'user' | 'application';
|
||||
|
||||
const FeatureFlagsSchema = z.object({
|
||||
enableThemeToggle: z.boolean({
|
||||
description: 'Enable theme toggle in the user interface.',
|
||||
required_error: 'Provide the variable NEXT_PUBLIC_ENABLE_THEME_TOGGLE',
|
||||
}),
|
||||
error: 'Provide the variable NEXT_PUBLIC_ENABLE_THEME_TOGGLE',
|
||||
})
|
||||
.describe( 'Enable theme toggle in the user interface.'),
|
||||
enableAccountDeletion: z.boolean({
|
||||
description: 'Enable personal account deletion.',
|
||||
required_error:
|
||||
error:
|
||||
'Provide the variable NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_DELETION',
|
||||
}),
|
||||
})
|
||||
.describe('Enable personal account deletion.'),
|
||||
enableTeamDeletion: z.boolean({
|
||||
description: 'Enable team deletion.',
|
||||
required_error:
|
||||
error:
|
||||
'Provide the variable NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_DELETION',
|
||||
}),
|
||||
})
|
||||
.describe('Enable team deletion.'),
|
||||
enableTeamAccounts: z.boolean({
|
||||
description: 'Enable team accounts.',
|
||||
required_error: 'Provide the variable NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS',
|
||||
}),
|
||||
error: 'Provide the variable NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS',
|
||||
})
|
||||
.describe('Enable team accounts.'),
|
||||
enableTeamCreation: z.boolean({
|
||||
description: 'Enable team creation.',
|
||||
required_error:
|
||||
error:
|
||||
'Provide the variable NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_CREATION',
|
||||
}),
|
||||
})
|
||||
.describe('Enable team creation.'),
|
||||
enablePersonalAccountBilling: z.boolean({
|
||||
description: 'Enable personal account billing.',
|
||||
required_error:
|
||||
error:
|
||||
'Provide the variable NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_BILLING',
|
||||
}),
|
||||
})
|
||||
.describe('Enable personal account billing.'),
|
||||
enableTeamAccountBilling: z.boolean({
|
||||
description: 'Enable team account billing.',
|
||||
required_error:
|
||||
error:
|
||||
'Provide the variable NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_BILLING',
|
||||
}),
|
||||
})
|
||||
.describe('Enable team account billing.'),
|
||||
languagePriority: z
|
||||
.enum(['user', 'application'], {
|
||||
required_error: 'Provide the variable NEXT_PUBLIC_LANGUAGE_PRIORITY',
|
||||
description: `If set to user, use the user's preferred language. If set to application, use the application's default language.`,
|
||||
error: 'Provide the variable NEXT_PUBLIC_LANGUAGE_PRIORITY',
|
||||
})
|
||||
.describe(`If set to user, use the user's preferred language. If set to application, use the application's default language.`)
|
||||
.default('application'),
|
||||
enableNotifications: z.boolean({
|
||||
description: 'Enable notifications functionality',
|
||||
required_error: 'Provide the variable NEXT_PUBLIC_ENABLE_NOTIFICATIONS',
|
||||
}),
|
||||
error: 'Provide the variable NEXT_PUBLIC_ENABLE_NOTIFICATIONS',
|
||||
})
|
||||
.describe('Enable notifications functionality'),
|
||||
realtimeNotifications: z.boolean({
|
||||
description: 'Enable realtime for the notifications functionality',
|
||||
required_error: 'Provide the variable NEXT_PUBLIC_REALTIME_NOTIFICATIONS',
|
||||
}),
|
||||
error: 'Provide the variable NEXT_PUBLIC_REALTIME_NOTIFICATIONS',
|
||||
})
|
||||
.describe('Enable realtime for the notifications functionality'),
|
||||
enableVersionUpdater: z.boolean({
|
||||
description: 'Enable version updater',
|
||||
required_error: 'Provide the variable NEXT_PUBLIC_ENABLE_VERSION_UPDATER',
|
||||
}),
|
||||
error: 'Provide the variable NEXT_PUBLIC_ENABLE_VERSION_UPDATER',
|
||||
})
|
||||
.describe('Enable version updater'),
|
||||
});
|
||||
|
||||
const featureFlagsConfig = FeatureFlagsSchema.parse({
|
||||
|
||||
@@ -14,6 +14,7 @@ const PathsSchema = z.object({
|
||||
}),
|
||||
app: z.object({
|
||||
home: z.string().min(1),
|
||||
cart: z.string().min(1),
|
||||
selectPackage: z.string().min(1),
|
||||
booking: z.string().min(1),
|
||||
bookingHandle: z.string().min(1),
|
||||
@@ -23,6 +24,8 @@ const PathsSchema = z.object({
|
||||
orderAnalysis: z.string().min(1),
|
||||
orderHealthAnalysis: z.string().min(1),
|
||||
personalAccountSettings: z.string().min(1),
|
||||
personalAccountPreferences: z.string().min(1),
|
||||
personalAccountSecurity: z.string().min(1),
|
||||
personalAccountBilling: z.string().min(1),
|
||||
personalAccountBillingReturn: z.string().min(1),
|
||||
accountHome: z.string().min(1),
|
||||
@@ -54,7 +57,10 @@ const pathsConfig = PathsSchema.parse({
|
||||
},
|
||||
app: {
|
||||
home: '/home',
|
||||
cart: '/home/cart',
|
||||
personalAccountSettings: '/home/settings',
|
||||
personalAccountPreferences: '/home/settings/preferences',
|
||||
personalAccountSecurity: '/home/settings/security',
|
||||
personalAccountBilling: '/home/billing',
|
||||
personalAccountBillingReturn: '/home/billing/return',
|
||||
accountHome: '/home/[account]',
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
MousePointerClick,
|
||||
ShoppingCart,
|
||||
Stethoscope,
|
||||
TestTube2,
|
||||
} from 'lucide-react';
|
||||
import { z } from 'zod';
|
||||
|
||||
|
||||
@@ -199,7 +199,6 @@ export type Database = {
|
||||
changed_by: string
|
||||
created_at: string
|
||||
id: number
|
||||
extra_data?: Json | null
|
||||
}
|
||||
Insert: {
|
||||
account_id: string
|
||||
@@ -207,7 +206,6 @@ export type Database = {
|
||||
changed_by: string
|
||||
created_at?: string
|
||||
id?: number
|
||||
extra_data?: Json | null
|
||||
}
|
||||
Update: {
|
||||
account_id?: string
|
||||
@@ -215,7 +213,6 @@ export type Database = {
|
||||
changed_by?: string
|
||||
created_at?: string
|
||||
id?: number
|
||||
extra_data?: Json | null
|
||||
}
|
||||
Relationships: []
|
||||
}
|
||||
@@ -320,10 +317,10 @@ export type Database = {
|
||||
Functions: {
|
||||
graphql: {
|
||||
Args: {
|
||||
extensions?: Json
|
||||
operationName?: string
|
||||
query?: string
|
||||
variables?: Json
|
||||
extensions?: Json
|
||||
}
|
||||
Returns: Json
|
||||
}
|
||||
@@ -342,6 +339,7 @@ export type Database = {
|
||||
account_id: string
|
||||
height: number | null
|
||||
id: string
|
||||
is_smoker: boolean | null
|
||||
recorded_at: string
|
||||
weight: number | null
|
||||
}
|
||||
@@ -349,6 +347,7 @@ export type Database = {
|
||||
account_id?: string
|
||||
height?: number | null
|
||||
id?: string
|
||||
is_smoker?: boolean | null
|
||||
recorded_at?: string
|
||||
weight?: number | null
|
||||
}
|
||||
@@ -356,6 +355,7 @@ export type Database = {
|
||||
account_id?: string
|
||||
height?: number | null
|
||||
id?: string
|
||||
is_smoker?: boolean | null
|
||||
recorded_at?: string
|
||||
weight?: number | null
|
||||
}
|
||||
@@ -363,21 +363,21 @@ export type Database = {
|
||||
{
|
||||
foreignKeyName: "account_params_account_id_fkey"
|
||||
columns: ["account_id"]
|
||||
isOneToOne: false
|
||||
isOneToOne: true
|
||||
referencedRelation: "accounts"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
{
|
||||
foreignKeyName: "account_params_account_id_fkey"
|
||||
columns: ["account_id"]
|
||||
isOneToOne: false
|
||||
isOneToOne: true
|
||||
referencedRelation: "user_account_workspace"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
{
|
||||
foreignKeyName: "account_params_account_id_fkey"
|
||||
columns: ["account_id"]
|
||||
isOneToOne: false
|
||||
isOneToOne: true
|
||||
referencedRelation: "user_accounts"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
@@ -1091,7 +1091,7 @@ export type Database = {
|
||||
price: number
|
||||
price_periods: string | null
|
||||
requires_payment: boolean
|
||||
sync_id: string | null
|
||||
sync_id: string
|
||||
updated_at: string | null
|
||||
}
|
||||
Insert: {
|
||||
@@ -1110,7 +1110,7 @@ export type Database = {
|
||||
price: number
|
||||
price_periods?: string | null
|
||||
requires_payment: boolean
|
||||
sync_id?: string | null
|
||||
sync_id: string
|
||||
updated_at?: string | null
|
||||
}
|
||||
Update: {
|
||||
@@ -1129,7 +1129,7 @@ export type Database = {
|
||||
price?: number
|
||||
price_periods?: string | null
|
||||
requires_payment?: boolean
|
||||
sync_id?: string | null
|
||||
sync_id?: string
|
||||
updated_at?: string | null
|
||||
}
|
||||
Relationships: [
|
||||
@@ -1150,7 +1150,7 @@ export type Database = {
|
||||
doctor_user_id: string | null
|
||||
id: number
|
||||
status: Database["medreport"]["Enums"]["analysis_feedback_status"]
|
||||
updated_at: string | null
|
||||
updated_at: string
|
||||
updated_by: string | null
|
||||
user_id: string
|
||||
value: string | null
|
||||
@@ -1162,7 +1162,7 @@ export type Database = {
|
||||
doctor_user_id?: string | null
|
||||
id?: number
|
||||
status?: Database["medreport"]["Enums"]["analysis_feedback_status"]
|
||||
updated_at?: string | null
|
||||
updated_at?: string
|
||||
updated_by?: string | null
|
||||
user_id: string
|
||||
value?: string | null
|
||||
@@ -1174,7 +1174,7 @@ export type Database = {
|
||||
doctor_user_id?: string | null
|
||||
id?: number
|
||||
status?: Database["medreport"]["Enums"]["analysis_feedback_status"]
|
||||
updated_at?: string | null
|
||||
updated_at?: string
|
||||
updated_by?: string | null
|
||||
user_id?: string
|
||||
value?: string | null
|
||||
@@ -1257,34 +1257,6 @@ export type Database = {
|
||||
},
|
||||
]
|
||||
}
|
||||
medipost_actions: {
|
||||
Row: {
|
||||
id: string
|
||||
action: string
|
||||
xml: string
|
||||
has_analysis_results: boolean
|
||||
created_at: string
|
||||
medusa_order_id: string
|
||||
response_xml: string
|
||||
has_error: boolean
|
||||
}
|
||||
Insert: {
|
||||
action: string
|
||||
xml: string
|
||||
has_analysis_results: boolean
|
||||
medusa_order_id: string
|
||||
response_xml: string
|
||||
has_error: boolean
|
||||
}
|
||||
Update: {
|
||||
action?: string
|
||||
xml?: string
|
||||
has_analysis_results?: boolean
|
||||
medusa_order_id?: string
|
||||
response_xml?: string
|
||||
has_error?: boolean
|
||||
}
|
||||
}
|
||||
medreport_product_groups: {
|
||||
Row: {
|
||||
created_at: string
|
||||
@@ -1871,17 +1843,19 @@ export type Database = {
|
||||
}
|
||||
create_nonce: {
|
||||
Args: {
|
||||
p_user_id?: string
|
||||
p_purpose?: string
|
||||
p_expires_in_seconds?: number
|
||||
p_metadata?: Json
|
||||
p_scopes?: string[]
|
||||
p_purpose?: string
|
||||
p_revoke_previous?: boolean
|
||||
p_scopes?: string[]
|
||||
p_user_id?: string
|
||||
}
|
||||
Returns: Json
|
||||
}
|
||||
create_team_account: {
|
||||
Args: { account_name: string; new_personal_code: string }
|
||||
Args:
|
||||
| { account_name: string }
|
||||
| { account_name: string; new_personal_code: string }
|
||||
Returns: {
|
||||
application_role: Database["medreport"]["Enums"]["application_role"]
|
||||
city: string | null
|
||||
@@ -1908,34 +1882,34 @@ export type Database = {
|
||||
get_account_invitations: {
|
||||
Args: { account_slug: string }
|
||||
Returns: {
|
||||
id: number
|
||||
email: string
|
||||
account_id: string
|
||||
invited_by: string
|
||||
role: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
email: string
|
||||
expires_at: string
|
||||
personal_code: string
|
||||
inviter_name: string
|
||||
id: number
|
||||
invited_by: string
|
||||
inviter_email: string
|
||||
inviter_name: string
|
||||
personal_code: string
|
||||
role: string
|
||||
updated_at: string
|
||||
}[]
|
||||
}
|
||||
get_account_members: {
|
||||
Args: { account_slug: string }
|
||||
Returns: {
|
||||
id: string
|
||||
user_id: string
|
||||
account_id: string
|
||||
role: string
|
||||
role_hierarchy_level: number
|
||||
primary_owner_user_id: string
|
||||
name: string
|
||||
created_at: string
|
||||
email: string
|
||||
id: string
|
||||
name: string
|
||||
personal_code: string
|
||||
picture_url: string
|
||||
created_at: string
|
||||
primary_owner_user_id: string
|
||||
role: string
|
||||
role_hierarchy_level: number
|
||||
updated_at: string
|
||||
user_id: string
|
||||
}[]
|
||||
}
|
||||
get_config: {
|
||||
@@ -1945,20 +1919,11 @@ export type Database = {
|
||||
get_invitations_with_account_ids: {
|
||||
Args: { company_id: string; personal_codes: string[] }
|
||||
Returns: {
|
||||
account_id: string
|
||||
invite_token: string
|
||||
personal_code: string
|
||||
account_id: string
|
||||
}[]
|
||||
}
|
||||
get_latest_medipost_dispatch_state_for_order: {
|
||||
Args: {
|
||||
medusa_order_id: string
|
||||
}
|
||||
Returns: {
|
||||
has_success: boolean
|
||||
action_date: string
|
||||
}
|
||||
}
|
||||
get_medipost_dispatch_tries: {
|
||||
Args: { p_medusa_order_id: string }
|
||||
Returns: number
|
||||
@@ -1985,17 +1950,17 @@ export type Database = {
|
||||
}
|
||||
has_more_elevated_role: {
|
||||
Args: {
|
||||
target_user_id: string
|
||||
target_account_id: string
|
||||
role_name: string
|
||||
target_account_id: string
|
||||
target_user_id: string
|
||||
}
|
||||
Returns: boolean
|
||||
}
|
||||
has_permission: {
|
||||
Args: {
|
||||
user_id: string
|
||||
account_id: string
|
||||
permission_name: Database["medreport"]["Enums"]["app_permissions"]
|
||||
user_id: string
|
||||
}
|
||||
Returns: boolean
|
||||
}
|
||||
@@ -2005,9 +1970,9 @@ export type Database = {
|
||||
}
|
||||
has_same_role_hierarchy_level: {
|
||||
Args: {
|
||||
target_user_id: string
|
||||
target_account_id: string
|
||||
role_name: string
|
||||
target_account_id: string
|
||||
target_user_id: string
|
||||
}
|
||||
Returns: boolean
|
||||
}
|
||||
@@ -2062,39 +2027,39 @@ export type Database = {
|
||||
team_account_workspace: {
|
||||
Args: { account_slug: string }
|
||||
Returns: {
|
||||
id: string
|
||||
name: string
|
||||
picture_url: string
|
||||
slug: string
|
||||
role: string
|
||||
role_hierarchy_level: number
|
||||
primary_owner_user_id: string
|
||||
subscription_status: Database["medreport"]["Enums"]["subscription_status"]
|
||||
permissions: Database["medreport"]["Enums"]["app_permissions"][]
|
||||
account_role: string
|
||||
application_role: Database["medreport"]["Enums"]["application_role"]
|
||||
id: string
|
||||
name: string
|
||||
permissions: Database["medreport"]["Enums"]["app_permissions"][]
|
||||
picture_url: string
|
||||
primary_owner_user_id: string
|
||||
role: string
|
||||
role_hierarchy_level: number
|
||||
slug: string
|
||||
subscription_status: Database["medreport"]["Enums"]["subscription_status"]
|
||||
}[]
|
||||
}
|
||||
transfer_team_account_ownership: {
|
||||
Args: { target_account_id: string; new_owner_id: string }
|
||||
Args: { new_owner_id: string; target_account_id: string }
|
||||
Returns: undefined
|
||||
}
|
||||
update_account: {
|
||||
Args: {
|
||||
p_name: string
|
||||
p_last_name: string
|
||||
p_personal_code: string
|
||||
p_phone: string
|
||||
p_city: string
|
||||
p_has_consent_personal_data: boolean
|
||||
p_last_name: string
|
||||
p_name: string
|
||||
p_personal_code: string
|
||||
p_phone: string
|
||||
p_uid: string
|
||||
}
|
||||
Returns: undefined
|
||||
}
|
||||
update_analysis_order_status: {
|
||||
Args: {
|
||||
order_id: number
|
||||
medusa_order_id_param: string
|
||||
order_id: number
|
||||
status_param: Database["medreport"]["Enums"]["analysis_order_status"]
|
||||
}
|
||||
Returns: {
|
||||
@@ -2109,14 +2074,14 @@ export type Database = {
|
||||
}
|
||||
upsert_order: {
|
||||
Args: {
|
||||
billing_provider: Database["medreport"]["Enums"]["billing_provider"]
|
||||
currency: string
|
||||
line_items: Json
|
||||
status: Database["medreport"]["Enums"]["payment_status"]
|
||||
target_account_id: string
|
||||
target_customer_id: string
|
||||
target_order_id: string
|
||||
status: Database["medreport"]["Enums"]["payment_status"]
|
||||
billing_provider: Database["medreport"]["Enums"]["billing_provider"]
|
||||
total_amount: number
|
||||
currency: string
|
||||
line_items: Json
|
||||
}
|
||||
Returns: {
|
||||
account_id: string
|
||||
@@ -2132,19 +2097,19 @@ export type Database = {
|
||||
}
|
||||
upsert_subscription: {
|
||||
Args: {
|
||||
target_account_id: string
|
||||
target_customer_id: string
|
||||
target_subscription_id: string
|
||||
active: boolean
|
||||
status: Database["medreport"]["Enums"]["subscription_status"]
|
||||
billing_provider: Database["medreport"]["Enums"]["billing_provider"]
|
||||
cancel_at_period_end: boolean
|
||||
currency: string
|
||||
period_starts_at: string
|
||||
period_ends_at: string
|
||||
line_items: Json
|
||||
trial_starts_at?: string
|
||||
period_ends_at: string
|
||||
period_starts_at: string
|
||||
status: Database["medreport"]["Enums"]["subscription_status"]
|
||||
target_account_id: string
|
||||
target_customer_id: string
|
||||
target_subscription_id: string
|
||||
trial_ends_at?: string
|
||||
trial_starts_at?: string
|
||||
}
|
||||
Returns: {
|
||||
account_id: string
|
||||
@@ -2165,31 +2130,16 @@ export type Database = {
|
||||
}
|
||||
verify_nonce: {
|
||||
Args: {
|
||||
p_token: string
|
||||
p_purpose: string
|
||||
p_user_id?: string
|
||||
p_required_scopes?: string[]
|
||||
p_max_verification_attempts?: number
|
||||
p_ip?: unknown
|
||||
p_max_verification_attempts?: number
|
||||
p_purpose: string
|
||||
p_required_scopes?: string[]
|
||||
p_token: string
|
||||
p_user_agent?: string
|
||||
p_user_id?: string
|
||||
}
|
||||
Returns: Json
|
||||
}
|
||||
sync_analysis_results: {
|
||||
}
|
||||
send_medipost_test_response_for_order: {
|
||||
Args: {
|
||||
medusa_order_id: string
|
||||
}
|
||||
}
|
||||
order_has_medipost_dispatch_error: {
|
||||
Args: {
|
||||
medusa_order_id: string
|
||||
}
|
||||
Returns: {
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
}
|
||||
Enums: {
|
||||
analysis_feedback_status: "STARTED" | "DRAFT" | "COMPLETED"
|
||||
@@ -7918,9 +7868,9 @@ export type Database = {
|
||||
Functions: {
|
||||
has_permission: {
|
||||
Args: {
|
||||
user_id: string
|
||||
account_id: string
|
||||
permission_name: Database["public"]["Enums"]["app_permissions"]
|
||||
user_id: string
|
||||
}
|
||||
Returns: boolean
|
||||
}
|
||||
@@ -7970,21 +7920,25 @@ export type Database = {
|
||||
}
|
||||
}
|
||||
|
||||
type DefaultSchema = Database[Extract<keyof Database, "public">]
|
||||
type DatabaseWithoutInternals = Omit<Database, "__InternalSupabase">
|
||||
|
||||
type DefaultSchema = DatabaseWithoutInternals[Extract<keyof Database, "public">]
|
||||
|
||||
export type Tables<
|
||||
DefaultSchemaTableNameOrOptions extends
|
||||
| keyof (DefaultSchema["Tables"] & DefaultSchema["Views"])
|
||||
| { schema: keyof Database },
|
||||
| { schema: keyof DatabaseWithoutInternals },
|
||||
TableName extends DefaultSchemaTableNameOrOptions extends {
|
||||
schema: keyof Database
|
||||
schema: keyof DatabaseWithoutInternals
|
||||
}
|
||||
? keyof (Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] &
|
||||
Database[DefaultSchemaTableNameOrOptions["schema"]]["Views"])
|
||||
? keyof (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] &
|
||||
DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Views"])
|
||||
: never = never,
|
||||
> = DefaultSchemaTableNameOrOptions extends { schema: keyof Database }
|
||||
? (Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] &
|
||||
Database[DefaultSchemaTableNameOrOptions["schema"]]["Views"])[TableName] extends {
|
||||
> = DefaultSchemaTableNameOrOptions extends {
|
||||
schema: keyof DatabaseWithoutInternals
|
||||
}
|
||||
? (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] &
|
||||
DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Views"])[TableName] extends {
|
||||
Row: infer R
|
||||
}
|
||||
? R
|
||||
@@ -8002,14 +7956,16 @@ export type Tables<
|
||||
export type TablesInsert<
|
||||
DefaultSchemaTableNameOrOptions extends
|
||||
| keyof DefaultSchema["Tables"]
|
||||
| { schema: keyof Database },
|
||||
| { schema: keyof DatabaseWithoutInternals },
|
||||
TableName extends DefaultSchemaTableNameOrOptions extends {
|
||||
schema: keyof Database
|
||||
schema: keyof DatabaseWithoutInternals
|
||||
}
|
||||
? keyof Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"]
|
||||
? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"]
|
||||
: never = never,
|
||||
> = DefaultSchemaTableNameOrOptions extends { schema: keyof Database }
|
||||
? Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends {
|
||||
> = DefaultSchemaTableNameOrOptions extends {
|
||||
schema: keyof DatabaseWithoutInternals
|
||||
}
|
||||
? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends {
|
||||
Insert: infer I
|
||||
}
|
||||
? I
|
||||
@@ -8025,14 +7981,16 @@ export type TablesInsert<
|
||||
export type TablesUpdate<
|
||||
DefaultSchemaTableNameOrOptions extends
|
||||
| keyof DefaultSchema["Tables"]
|
||||
| { schema: keyof Database },
|
||||
| { schema: keyof DatabaseWithoutInternals },
|
||||
TableName extends DefaultSchemaTableNameOrOptions extends {
|
||||
schema: keyof Database
|
||||
schema: keyof DatabaseWithoutInternals
|
||||
}
|
||||
? keyof Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"]
|
||||
? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"]
|
||||
: never = never,
|
||||
> = DefaultSchemaTableNameOrOptions extends { schema: keyof Database }
|
||||
? Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends {
|
||||
> = DefaultSchemaTableNameOrOptions extends {
|
||||
schema: keyof DatabaseWithoutInternals
|
||||
}
|
||||
? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends {
|
||||
Update: infer U
|
||||
}
|
||||
? U
|
||||
@@ -8048,14 +8006,16 @@ export type TablesUpdate<
|
||||
export type Enums<
|
||||
DefaultSchemaEnumNameOrOptions extends
|
||||
| keyof DefaultSchema["Enums"]
|
||||
| { schema: keyof Database },
|
||||
| { schema: keyof DatabaseWithoutInternals },
|
||||
EnumName extends DefaultSchemaEnumNameOrOptions extends {
|
||||
schema: keyof Database
|
||||
schema: keyof DatabaseWithoutInternals
|
||||
}
|
||||
? keyof Database[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"]
|
||||
? keyof DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"]
|
||||
: never = never,
|
||||
> = DefaultSchemaEnumNameOrOptions extends { schema: keyof Database }
|
||||
? Database[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"][EnumName]
|
||||
> = DefaultSchemaEnumNameOrOptions extends {
|
||||
schema: keyof DatabaseWithoutInternals
|
||||
}
|
||||
? DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"][EnumName]
|
||||
: DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema["Enums"]
|
||||
? DefaultSchema["Enums"][DefaultSchemaEnumNameOrOptions]
|
||||
: never
|
||||
@@ -8063,14 +8023,16 @@ export type Enums<
|
||||
export type CompositeTypes<
|
||||
PublicCompositeTypeNameOrOptions extends
|
||||
| keyof DefaultSchema["CompositeTypes"]
|
||||
| { schema: keyof Database },
|
||||
| { schema: keyof DatabaseWithoutInternals },
|
||||
CompositeTypeName extends PublicCompositeTypeNameOrOptions extends {
|
||||
schema: keyof Database
|
||||
schema: keyof DatabaseWithoutInternals
|
||||
}
|
||||
? keyof Database[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"]
|
||||
? keyof DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"]
|
||||
: never = never,
|
||||
> = PublicCompositeTypeNameOrOptions extends { schema: keyof Database }
|
||||
? Database[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"][CompositeTypeName]
|
||||
> = PublicCompositeTypeNameOrOptions extends {
|
||||
schema: keyof DatabaseWithoutInternals
|
||||
}
|
||||
? DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"][CompositeTypeName]
|
||||
: PublicCompositeTypeNameOrOptions extends keyof DefaultSchema["CompositeTypes"]
|
||||
? DefaultSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions]
|
||||
: never
|
||||
|
||||
@@ -13,7 +13,7 @@ const message =
|
||||
export function getServiceRoleKey() {
|
||||
return z
|
||||
.string({
|
||||
required_error: message,
|
||||
error: message,
|
||||
})
|
||||
.min(1, {
|
||||
message: message,
|
||||
|
||||
@@ -7,14 +7,14 @@ export function getSupabaseClientKeys() {
|
||||
return z
|
||||
.object({
|
||||
url: z.string({
|
||||
description: `This is the URL of your hosted Supabase instance. Please provide the variable NEXT_PUBLIC_SUPABASE_URL.`,
|
||||
required_error: `Please provide the variable NEXT_PUBLIC_SUPABASE_URL`,
|
||||
}),
|
||||
error: `Please provide the variable NEXT_PUBLIC_SUPABASE_URL`,
|
||||
})
|
||||
.describe(`This is the URL of your hosted Supabase instance. Please provide the variable NEXT_PUBLIC_SUPABASE_URL.`),
|
||||
anonKey: z
|
||||
.string({
|
||||
description: `This is the anon key provided by Supabase. It is a public key used client-side. Please provide the variable NEXT_PUBLIC_SUPABASE_ANON_KEY.`,
|
||||
required_error: `Please provide the variable NEXT_PUBLIC_SUPABASE_ANON_KEY`,
|
||||
error: `Please provide the variable NEXT_PUBLIC_SUPABASE_ANON_KEY`,
|
||||
})
|
||||
.describe(`This is the anon key provided by Supabase. It is a public key used client-side. Please provide the variable NEXT_PUBLIC_SUPABASE_ANON_KEY.`)
|
||||
.min(1),
|
||||
})
|
||||
.parse({
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
const RouteMatchingEnd = z
|
||||
.union([z.boolean(), z.function().args(z.string()).returns(z.boolean())])
|
||||
.union([
|
||||
z.boolean(),
|
||||
z.function({ input: [z.string()], output: z.boolean() }),
|
||||
])
|
||||
.default(false)
|
||||
.optional();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user