add medreport schema

This commit is contained in:
Danel Kungla
2025-07-09 13:31:37 +03:00
parent 9371ff7710
commit d9198a8a12
73 changed files with 420 additions and 270 deletions

View File

@@ -6,6 +6,7 @@ import { useRouter } from 'next/navigation';
import { MedReportLogo } from '@/components/med-report-logo'; import { MedReportLogo } from '@/components/med-report-logo';
import { SubmitButton } from '@/components/ui/submit-button'; import { SubmitButton } from '@/components/ui/submit-button';
import { withI18n } from '@/lib/i18n/with-i18n';
import { sendCompanyOfferEmail } from '@/lib/services/mailer.service'; import { sendCompanyOfferEmail } from '@/lib/services/mailer.service';
import { CompanySubmitData } from '@/lib/types/company'; import { CompanySubmitData } from '@/lib/types/company';
import { companyOfferSchema } from '@/lib/validations/company-offer.schema'; import { companyOfferSchema } from '@/lib/validations/company-offer.schema';
@@ -18,7 +19,7 @@ import { Input } from '@kit/ui/input';
import { Label } from '@kit/ui/label'; import { Label } from '@kit/ui/label';
import { Trans } from '@kit/ui/trans'; import { Trans } from '@kit/ui/trans';
export default function CompanyOffer() { function CompanyOffer() {
const router = useRouter(); const router = useRouter();
const { const {
register, register,
@@ -100,3 +101,5 @@ export default function CompanyOffer() {
</div> </div>
); );
} }
export default withI18n(CompanyOffer);

View File

@@ -34,6 +34,7 @@ async function accountLoader(id: string) {
const client = getSupabaseServerClient(); const client = getSupabaseServerClient();
const { data, error } = await client const { data, error } = await client
.schema('medreport')
.from('accounts') .from('accounts')
.select('*, memberships: accounts_memberships (*)') .select('*, memberships: accounts_memberships (*)')
.eq('id', id) .eq('id', id)

View File

@@ -0,0 +1,34 @@
'use client';
import React from 'react';
import pathsConfig from '@/config/paths.config';
import { useTranslation } from 'react-i18next';
import { usePersonalAccountData } from '@kit/accounts/hooks/use-personal-account-data';
import { SuccessNotification } from '@kit/notifications/components';
const MembershipConfirmationNotification: React.FC<{
userId: string;
}> = ({ userId }) => {
const { t } = useTranslation();
const { data: accountData } = usePersonalAccountData(userId);
return (
<SuccessNotification
showLogo={false}
title={t('account:membershipConfirmation:successTitle', {
firstName: accountData?.name,
lastName: accountData?.last_name,
})}
descriptionKey="account:membershipConfirmation:successDescription"
buttonProps={{
buttonTitleKey: 'account:membershipConfirmation:successButton',
href: pathsConfig.app.selectPackage,
}}
/>
);
};
export default MembershipConfirmationNotification;

View File

@@ -8,4 +8,4 @@ async function SiteLayout(props: React.PropsWithChildren) {
); );
} }
export default withI18n(SiteLayout); export default SiteLayout;

View File

@@ -1,16 +1,14 @@
import { redirect } from 'next/navigation'; import { redirect } from 'next/navigation';
import pathsConfig from '@/config/paths.config'; import pathsConfig from '@/config/paths.config';
import { useTranslation } from 'react-i18next';
import { usePersonalAccountData } from '@kit/accounts/hooks/use-personal-account-data';
import { SuccessNotification } from '@kit/notifications/components';
import { getSupabaseServerClient } from '@kit/supabase/server-client'; import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { withI18n } from '~/lib/i18n/with-i18n'; import { withI18n } from '~/lib/i18n/with-i18n';
import MembershipConfirmationNotification from './_components/membership-confirmation-notification';
async function UpdateAccountSuccess() { async function UpdateAccountSuccess() {
const { t } = useTranslation('account');
const client = getSupabaseServerClient(); const client = getSupabaseServerClient();
const { const {
@@ -21,26 +19,7 @@ async function UpdateAccountSuccess() {
redirect(pathsConfig.app.home); redirect(pathsConfig.app.home);
} }
const { data: accountData } = usePersonalAccountData(user.id); return <MembershipConfirmationNotification userId={user.id} />;
if (!accountData?.id) {
redirect(pathsConfig.app.home);
}
return (
<SuccessNotification
showLogo={false}
title={t('account:membershipConfirmation:successTitle', {
firstName: accountData?.name,
lastName: accountData?.last_name,
})}
descriptionKey="account:membershipConfirmation:successDescription"
buttonProps={{
buttonTitleKey: 'account:membershipConfirmation:successButton',
href: pathsConfig.app.selectPackage,
}}
/>
);
} }
export default withI18n(UpdateAccountSuccess); export default withI18n(UpdateAccountSuccess);

View File

@@ -26,7 +26,7 @@ async function getSupabaseHealthCheck() {
try { try {
const client = getSupabaseServerAdminClient(); const client = getSupabaseServerAdminClient();
const { error } = await client.rpc('is_set', { const { error } = await client.schema('medreport').rpc('is_set', {
field_name: 'billing_provider', field_name: 'billing_provider',
}); });

View File

@@ -46,9 +46,11 @@ async function loadAccountMembers(
client: SupabaseClient<Database>, client: SupabaseClient<Database>,
account: string, account: string,
) { ) {
const { data, error } = await client.rpc('get_account_members', { const { data, error } = await client
account_slug: account, .schema('medreport')
}); .rpc('get_account_members', {
account_slug: account,
});
if (error) { if (error) {
console.error(error); console.error(error);
@@ -67,9 +69,11 @@ async function loadInvitations(
client: SupabaseClient<Database>, client: SupabaseClient<Database>,
account: string, account: string,
) { ) {
const { data, error } = await client.rpc('get_account_invitations', { const { data, error } = await client
account_slug: account, .schema('medreport')
}); .rpc('get_account_invitations', {
account_slug: account,
});
if (error) { if (error) {
console.error(error); console.error(error);

View File

@@ -79,12 +79,11 @@ async function JoinTeamAccountPage(props: JoinTeamAccountPageProps) {
// we need to verify the user isn't already in the account // we need to verify the user isn't already in the account
// we do so by checking if the user can read the account // we do so by checking if the user can read the account
// if the user can read the account, then they are already in the account // if the user can read the account, then they are already in the account
const { data: isAlreadyTeamMember } = await client.rpc( const { data: isAlreadyTeamMember } = await client
'is_account_team_member', .schema('medreport')
{ .rpc('is_account_team_member', {
target_account_id: invitation.account.id, target_account_id: invitation.account.id,
}, });
);
// if the user is already in the account redirect to the home page // if the user is already in the account redirect to the home page
if (isAlreadyTeamMember) { if (isAlreadyTeamMember) {

View File

@@ -137,6 +137,7 @@ async function syncData() {
for (const analysisGroup of analysisGroups) { for (const analysisGroup of analysisGroups) {
// SAVE ANALYSIS GROUP // SAVE ANALYSIS GROUP
const { data: insertedAnalysisGroup, error } = await supabase const { data: insertedAnalysisGroup, error } = await supabase
.schema('medreport')
.from('analysis_groups') .from('analysis_groups')
.upsert( .upsert(
{ {
@@ -174,6 +175,7 @@ async function syncData() {
const analysisElement = item.UuringuElement; const analysisElement = item.UuringuElement;
const { data: insertedAnalysisElement, error } = await supabase const { data: insertedAnalysisElement, error } = await supabase
.schema('medreport')
.from('analysis_elements') .from('analysis_elements')
.upsert( .upsert(
{ {
@@ -217,6 +219,7 @@ async function syncData() {
if (analyses?.length) { if (analyses?.length) {
for (const analysis of analyses) { for (const analysis of analyses) {
const { data: insertedAnalysis, error } = await supabase const { data: insertedAnalysis, error } = await supabase
.schema('medreport')
.from('analyses') .from('analyses')
.upsert( .upsert(
{ {
@@ -259,7 +262,7 @@ async function syncData() {
} }
} }
await supabase.from('codes').upsert(codes); await supabase.schema('medreport').from('codes').upsert(codes);
await supabase.schema('audit').from('sync_entries').insert({ await supabase.schema('audit').from('sync_entries').insert({
operation: 'ANALYSES_SYNC', operation: 'ANALYSES_SYNC',

View File

@@ -105,10 +105,12 @@ async function syncData() {
}); });
const { error: providersError } = await supabase const { error: providersError } = await supabase
.schema('medreport')
.from('connected_online_providers') .from('connected_online_providers')
.upsert(mappedClinics); .upsert(mappedClinics);
const { error: servicesError } = await supabase const { error: servicesError } = await supabase
.schema('medreport')
.from('connected_online_services') .from('connected_online_services')
.upsert(mappedServices, { onConflict: 'id', ignoreDuplicates: false }); .upsert(mappedServices, { onConflict: 'id', ignoreDuplicates: false });

View File

@@ -1,4 +1,4 @@
'use server' 'use server';
import logRequestResult from '@/lib/services/audit.service'; import logRequestResult from '@/lib/services/audit.service';
import { RequestStatus } from '@/lib/types/audit'; import { RequestStatus } from '@/lib/types/audit';
@@ -9,7 +9,7 @@ import {
ConnectedOnlineMethodName, ConnectedOnlineMethodName,
} from '@/lib/types/connected-online'; } from '@/lib/types/connected-online';
import { ExternalApi } from '@/lib/types/external'; import { ExternalApi } from '@/lib/types/external';
import { Tables } from '@/supabase/database.types'; import { Tables } from '@/packages/supabase/src/database.types';
import { createClient } from '@/utils/supabase/server'; import { createClient } from '@/utils/supabase/server';
import axios from 'axios'; import axios from 'axios';
@@ -106,11 +106,13 @@ export async function bookAppointment(
{ data: dbService, error: serviceError }, { data: dbService, error: serviceError },
] = await Promise.all([ ] = await Promise.all([
supabase supabase
.schema('medreport')
.from('connected_online_providers') .from('connected_online_providers')
.select('*') .select('*')
.eq('id', clinicId) .eq('id', clinicId)
.limit(1), .limit(1),
supabase supabase
.schema('medreport')
.from('connected_online_services') .from('connected_online_services')
.select('*') .select('*')
.eq('sync_id', serviceSyncId) .eq('sync_id', serviceSyncId)
@@ -132,8 +134,14 @@ export async function bookAppointment(
); );
} }
const clinic: Tables<'connected_online_providers'> = dbClinic![0]; const clinic: Tables<
const service: Tables<'connected_online_services'> = dbService![0]; { schema: 'medreport' },
'connected_online_providers'
> = dbClinic![0];
const service: Tables<
{ schema: 'medreport' },
'connected_online_services'
> = dbService![0];
// TODO the dummy data needs to be replaced with real values once they're present on the user/account // TODO the dummy data needs to be replaced with real values once they're present on the user/account
const response = await axios.post( const response = await axios.post(
@@ -183,6 +191,7 @@ export async function bookAppointment(
const responseParts = responseData.Value.split(','); const responseParts = responseData.Value.split(',');
const { error } = await supabase const { error } = await supabase
.schema('medreport')
.from('connected_online_reservation') .from('connected_online_reservation')
.insert({ .insert({
booking_code: responseParts[1], booking_code: responseParts[1],

View File

@@ -32,6 +32,7 @@ import { toArray } from '@/lib/utils';
import axios from 'axios'; import axios from 'axios';
import { XMLParser } from 'fast-xml-parser'; import { XMLParser } from 'fast-xml-parser';
import { uniqBy } from 'lodash'; import { uniqBy } from 'lodash';
import { Tables } from '@kit/supabase/database'; import { Tables } from '@kit/supabase/database';
const BASE_URL = process.env.MEDIPOST_URL!; const BASE_URL = process.env.MEDIPOST_URL!;
@@ -196,6 +197,7 @@ async function saveAnalysisGroup(
supabase: SupabaseClient, supabase: SupabaseClient,
) { ) {
const { data: insertedAnalysisGroup, error } = await supabase const { data: insertedAnalysisGroup, error } = await supabase
.schema('medreport')
.from('analysis_groups') .from('analysis_groups')
.upsert( .upsert(
{ {
@@ -215,13 +217,14 @@ async function saveAnalysisGroup(
const analysisGroupId = insertedAnalysisGroup[0].id; const analysisGroupId = insertedAnalysisGroup[0].id;
const analysisGroupCodes = toArray(analysisGroup.Kood); const analysisGroupCodes = toArray(analysisGroup.Kood);
const codes: Partial<Tables<'codes'>>[] = analysisGroupCodes.map((kood) => ({ const codes: Partial<Tables<{ schema: 'medreport' }, 'codes'>>[] =
hk_code: kood.HkKood, analysisGroupCodes.map((kood) => ({
hk_code_multiplier: kood.HkKoodiKordaja, hk_code: kood.HkKood,
coefficient: kood.Koefitsient, hk_code_multiplier: kood.HkKoodiKordaja,
price: kood.Hind, coefficient: kood.Koefitsient,
analysis_group_id: analysisGroupId, price: kood.Hind,
})); analysis_group_id: analysisGroupId,
}));
const analysisGroupItems = toArray(analysisGroup.Uuring); const analysisGroupItems = toArray(analysisGroup.Uuring);
@@ -229,6 +232,7 @@ async function saveAnalysisGroup(
const analysisElement = item.UuringuElement; const analysisElement = item.UuringuElement;
const { data: insertedAnalysisElement, error } = await supabase const { data: insertedAnalysisElement, error } = await supabase
.schema('medreport')
.from('analysis_elements') .from('analysis_elements')
.upsert( .upsert(
{ {
@@ -270,6 +274,7 @@ async function saveAnalysisGroup(
if (analyses?.length) { if (analyses?.length) {
for (const analysis of analyses) { for (const analysis of analyses) {
const { data: insertedAnalysis, error } = await supabase const { data: insertedAnalysis, error } = await supabase
.schema('medreport')
.from('analyses') .from('analyses')
.upsert( .upsert(
{ {
@@ -310,6 +315,7 @@ async function saveAnalysisGroup(
} }
const { error: codesError } = await supabase const { error: codesError } = await supabase
.schema('medreport')
.from('codes') .from('codes')
.upsert(codes, { ignoreDuplicates: false }); .upsert(codes, { ignoreDuplicates: false });
@@ -404,34 +410,41 @@ export async function composeOrderXML(
}; };
const { data: analysisElements } = (await supabase const { data: analysisElements } = (await supabase
.schema('medreport')
.from('analysis_elements') .from('analysis_elements')
.select(`*, analysis_groups(*)`) .select(`*, analysis_groups(*)`)
.in('id', orderedElements)) as { .in('id', orderedElements)) as {
data: ({ data: ({
analysis_groups: Tables<'analysis_groups'>; analysis_groups: Tables<{ schema: 'medreport' }, 'analysis_groups'>;
} & Tables<'analysis_elements'>)[]; } & Tables<{ schema: 'medreport' }, 'analysis_elements'>)[];
}; };
const { data: analyses } = (await supabase const { data: analyses } = (await supabase
.schema('medreport')
.from('analyses') .from('analyses')
.select(`*, analysis_elements(*, analysis_groups(*))`) .select(`*, analysis_elements(*, analysis_groups(*))`)
.in('id', orderedAnalyses)) as { .in('id', orderedAnalyses)) as {
data: ({ data: ({
analysis_elements: Tables<'analysis_elements'> & { analysis_elements: Tables<
analysis_groups: Tables<'analysis_groups'>; { schema: 'medreport' },
'analysis_elements'
> & {
analysis_groups: Tables<{ schema: 'medreport' }, 'analysis_groups'>;
}; };
} & Tables<'analyses'>)[]; } & Tables<{ schema: 'medreport' }, 'analyses'>)[];
}; };
const analysisGroups: Tables<'analysis_groups'>[] = uniqBy( const analysisGroups: Tables<{ schema: 'medreport' }, 'analysis_groups'>[] =
( uniqBy(
analysisElements?.flatMap(({ analysis_groups }) => analysis_groups) ?? [] (
).concat( analysisElements?.flatMap(({ analysis_groups }) => analysis_groups) ??
analyses?.flatMap( []
({ analysis_elements }) => analysis_elements.analysis_groups, ).concat(
) ?? [], analyses?.flatMap(
), ({ analysis_elements }) => analysis_elements.analysis_groups,
'id', ) ?? [],
); ),
'id',
);
const specimenSection = []; const specimenSection = [];
const analysisSection = []; const analysisSection = [];
@@ -545,6 +558,7 @@ export async function syncPrivateMessage(
const status = response.TellimuseOlek; const status = response.TellimuseOlek;
const { data: analysisOrder, error: analysisOrderError } = await supabase const { data: analysisOrder, error: analysisOrderError } = await supabase
.schema('medreport')
.from('analysis_orders') .from('analysis_orders')
.select('user_id') .select('user_id')
.eq('id', response.ValisTellimuseId); .eq('id', response.ValisTellimuseId);
@@ -556,6 +570,7 @@ export async function syncPrivateMessage(
} }
const { data: analysisResponse, error } = await supabase const { data: analysisResponse, error } = await supabase
.schema('medreport')
.from('analysis_responses') .from('analysis_responses')
.upsert( .upsert(
{ {
@@ -576,7 +591,7 @@ export async function syncPrivateMessage(
const analysisGroups = toArray(response.UuringuGrupp); const analysisGroups = toArray(response.UuringuGrupp);
const responses: Omit< const responses: Omit<
Tables<'analysis_response_elements'>, Tables<{ schema: 'medreport' }, 'analysis_response_elements'>,
'id' | 'created_at' | 'updated_at' 'id' | 'created_at' | 'updated_at'
>[] = []; >[] = [];
for (const analysisGroup of analysisGroups) { for (const analysisGroup of analysisGroups) {
@@ -608,6 +623,7 @@ export async function syncPrivateMessage(
} }
const { error: deleteError } = await supabase const { error: deleteError } = await supabase
.schema('medreport')
.from('analysis_response_elements') .from('analysis_response_elements')
.delete() .delete()
.eq('analysis_response_id', analysisResponse[0].id); .eq('analysis_response_id', analysisResponse[0].id);
@@ -619,6 +635,7 @@ export async function syncPrivateMessage(
} }
const { error: elementInsertError } = await supabase const { error: elementInsertError } = await supabase
.schema('medreport')
.from('analysis_response_elements') .from('analysis_response_elements')
.insert(responses); .insert(responses);

View File

@@ -1,5 +1,5 @@
import { DATE_TIME_FORMAT } from '@/lib/constants'; import { DATE_TIME_FORMAT } from '@/lib/constants';
import { Tables } from '@/supabase/database.types'; import { Tables } from '@/packages/supabase/src/database.types';
import { format } from 'date-fns'; import { format } from 'date-fns';
const isProd = process.env.NODE_ENV === 'production'; const isProd = process.env.NODE_ENV === 'production';
@@ -160,7 +160,7 @@ export const getAnalysisGroup = (
analysisGroupOriginalId: string, analysisGroupOriginalId: string,
analysisGroupName: string, analysisGroupName: string,
specimenOrderNr: number, specimenOrderNr: number,
analysisElement: Tables<'analysis_elements'>, analysisElement: Tables<{ schema: 'medreport' }, 'analysis_elements'>,
) => ) =>
`<UuringuGrupp> `<UuringuGrupp>
<UuringuGruppId>${analysisGroupOriginalId}</UuringuGruppId> <UuringuGruppId>${analysisGroupOriginalId}</UuringuGruppId>

View File

@@ -139,7 +139,7 @@ function getPatterns() {
handler: adminMiddleware, handler: adminMiddleware,
}, },
{ {
pattern: new URLPattern({ pathname: '/auth/update-account' }), pattern: new URLPattern({ pathname: '/auth/*?' }),
handler: async (req: NextRequest, res: NextResponse) => { handler: async (req: NextRequest, res: NextResponse) => {
const { const {
data: { user }, data: { user },

View File

@@ -1,7 +1,7 @@
import { Database } from '@kit/supabase/database'; import { Database } from '@kit/supabase/database';
export type UpsertSubscriptionParams = export type UpsertSubscriptionParams =
Database['public']['Functions']['upsert_subscription']['Args'] & { Database['medreport']['Functions']['upsert_subscription']['Args'] & {
line_items: Array<LineItem>; line_items: Array<LineItem>;
}; };
@@ -19,4 +19,4 @@ interface LineItem {
} }
export type UpsertOrderParams = export type UpsertOrderParams =
Database['public']['Functions']['upsert_order']['Args']; Database['medreport']['Functions']['upsert_order']['Args'];

View File

@@ -32,8 +32,7 @@
"lucide-react": "^0.510.0", "lucide-react": "^0.510.0",
"next": "15.3.2", "next": "15.3.2",
"react": "19.1.0", "react": "19.1.0",
"react-hook-form": "^7.56.3", "react-hook-form": "^7.56.3"
"react-i18next": "^15.5.1"
}, },
"typesVersions": { "typesVersions": {
"*": { "*": {

View File

@@ -14,8 +14,8 @@ import { Trans } from '@kit/ui/trans';
import { CurrentPlanBadge } from './current-plan-badge'; import { CurrentPlanBadge } from './current-plan-badge';
import { LineItemDetails } from './line-item-details'; import { LineItemDetails } from './line-item-details';
type Order = Tables<'orders'>; type Order = Tables<{ schema: 'medreport' }, 'orders'>;
type LineItem = Tables<'order_items'>; type LineItem = Tables<{ schema: 'medreport' }, 'order_items'>;
interface Props { interface Props {
order: Order & { order: Order & {

View File

@@ -18,8 +18,8 @@ import { CurrentPlanAlert } from './current-plan-alert';
import { CurrentPlanBadge } from './current-plan-badge'; import { CurrentPlanBadge } from './current-plan-badge';
import { LineItemDetails } from './line-item-details'; import { LineItemDetails } from './line-item-details';
type Subscription = Tables<'subscriptions'>; type Subscription = Tables<{ schema: 'medreport' }, 'subscriptions'>;
type LineItem = Tables<'subscription_items'>; type LineItem = Tables<{ schema: 'medreport' }, 'subscription_items'>;
interface Props { interface Props {
subscription: Subscription & { subscription: Subscription & {

View File

@@ -86,6 +86,7 @@ class BillingEventHandlerService {
logger.info(ctx, 'Processing subscription deleted event...'); logger.info(ctx, 'Processing subscription deleted event...');
const { error } = await client const { error } = await client
.schema('medreport')
.from('subscriptions') .from('subscriptions')
.delete() .delete()
.match({ id: subscriptionId }); .match({ id: subscriptionId });
@@ -109,7 +110,7 @@ class BillingEventHandlerService {
logger.info(ctx, 'Successfully deleted subscription'); logger.info(ctx, 'Successfully deleted subscription');
}, },
onSubscriptionUpdated: async (subscription) => { onSubscriptionUpdated: async (subscription) => {
const client = this.clientProvider(); const client = this.clientProvider().schema('medreport');
const logger = await getLogger(); const logger = await getLogger();
const ctx = { const ctx = {
@@ -147,7 +148,7 @@ class BillingEventHandlerService {
onCheckoutSessionCompleted: async (payload) => { onCheckoutSessionCompleted: async (payload) => {
// Handle the checkout session completed event // Handle the checkout session completed event
// here we add the subscription to the database // here we add the subscription to the database
const client = this.clientProvider(); const client = this.clientProvider().schema('medreport');
const logger = await getLogger(); const logger = await getLogger();
// Check if the payload contains an order_id // Check if the payload contains an order_id
@@ -212,7 +213,7 @@ class BillingEventHandlerService {
} }
}, },
onPaymentSucceeded: async (sessionId: string) => { onPaymentSucceeded: async (sessionId: string) => {
const client = this.clientProvider(); const client = this.clientProvider().schema('medreport');
const logger = await getLogger(); const logger = await getLogger();
const ctx = { const ctx = {
@@ -244,7 +245,7 @@ class BillingEventHandlerService {
logger.info(ctx, 'Successfully updated payment status'); logger.info(ctx, 'Successfully updated payment status');
}, },
onPaymentFailed: async (sessionId: string) => { onPaymentFailed: async (sessionId: string) => {
const client = this.clientProvider(); const client = this.clientProvider().schema('medreport');
const logger = await getLogger(); const logger = await getLogger();
const ctx = { const ctx = {

View File

@@ -21,6 +21,7 @@ export async function getBillingGatewayProvider(
async function getBillingProvider(client: SupabaseClient<Database>) { async function getBillingProvider(client: SupabaseClient<Database>) {
const { data, error } = await client const { data, error } = await client
.schema('medreport')
.from('config') .from('config')
.select('billing_provider') .select('billing_provider')
.single(); .single();

View File

@@ -4,7 +4,7 @@ import { Tables } from '@kit/supabase/database';
import { createBillingGatewayService } from '../billing-gateway/billing-gateway.service'; import { createBillingGatewayService } from '../billing-gateway/billing-gateway.service';
type Subscription = Tables<'subscriptions'>; type Subscription = Tables<{ schema: 'medreport' }, 'subscriptions'>;
export function createBillingWebhooksService() { export function createBillingWebhooksService() {
return new BillingWebhooksService(); return new BillingWebhooksService();

View File

@@ -17,14 +17,14 @@ import { createLemonSqueezySubscriptionPayloadBuilderService } from './lemon-squ
import { createHmac } from './verify-hmac'; import { createHmac } from './verify-hmac';
type UpsertSubscriptionParams = type UpsertSubscriptionParams =
Database['public']['Functions']['upsert_subscription']['Args'] & { Database['medreport']['Functions']['upsert_subscription']['Args'] & {
line_items: Array<LineItem>; line_items: Array<LineItem>;
}; };
type UpsertOrderParams = type UpsertOrderParams =
Database['public']['Functions']['upsert_order']['Args']; Database['medreport']['Functions']['upsert_order']['Args'];
type BillingProvider = Enums<'billing_provider'>; type BillingProvider = Enums<{ schema: 'medreport' }, 'billing_provider'>;
interface LineItem { interface LineItem {
id: string; id: string;

View File

@@ -9,7 +9,7 @@ import { createStripeClient } from './stripe-sdk';
import { createStripeSubscriptionPayloadBuilderService } from './stripe-subscription-payload-builder.service'; import { createStripeSubscriptionPayloadBuilderService } from './stripe-subscription-payload-builder.service';
type UpsertSubscriptionParams = type UpsertSubscriptionParams =
Database['public']['Functions']['upsert_subscription']['Args'] & { Database['medreport']['Functions']['upsert_subscription']['Args'] & {
line_items: Array<LineItem>; line_items: Array<LineItem>;
}; };
@@ -27,9 +27,9 @@ interface LineItem {
} }
type UpsertOrderParams = type UpsertOrderParams =
Database['public']['Functions']['upsert_order']['Args']; Database['medreport']['Functions']['upsert_order']['Args'];
type BillingProvider = Enums<'billing_provider'>; type BillingProvider = Enums<{ schema: 'medreport' }, 'billing_provider'>;
export class StripeWebhookHandlerService export class StripeWebhookHandlerService
implements BillingWebhookHandlerService implements BillingWebhookHandlerService

View File

@@ -1,6 +1,6 @@
import { Database } from '@kit/supabase/database'; import { Database } from '@kit/supabase/database';
export type Tables = Database['public']['Tables']; export type Tables = Database['medreport']['Tables'];
export type TableChangeType = 'INSERT' | 'UPDATE' | 'DELETE'; export type TableChangeType = 'INSERT' | 'UPDATE' | 'DELETE';

View File

@@ -44,7 +44,6 @@
"react": "19.1.0", "react": "19.1.0",
"react-dom": "19.1.0", "react-dom": "19.1.0",
"react-hook-form": "^7.56.3", "react-hook-form": "^7.56.3",
"react-i18next": "^15.5.1",
"sonner": "^2.0.3" "sonner": "^2.0.3"
}, },
"prettier": "@kit/prettier-config", "prettier": "@kit/prettier-config",

View File

@@ -19,7 +19,8 @@ import { Trans } from '@kit/ui/trans';
import { useUpdateAccountData } from '../../hooks/use-update-account'; import { useUpdateAccountData } from '../../hooks/use-update-account';
import { AccountDetailsSchema } from '../../schema/account-details.schema'; import { AccountDetailsSchema } from '../../schema/account-details.schema';
type UpdateUserDataParams = Database['public']['Tables']['accounts']['Update']; type UpdateUserDataParams =
Database['medreport']['Tables']['accounts']['Update'];
export function UpdateAccountDetailsForm({ export function UpdateAccountDetailsForm({
displayName, displayName,

View File

@@ -72,6 +72,7 @@ function UploadProfileAvatarForm(props: {
uploadUserProfilePhoto(client, file, props.userId) uploadUserProfilePhoto(client, file, props.userId)
.then((pictureUrl) => { .then((pictureUrl) => {
return client return client
.schema('medreport')
.from('accounts') .from('accounts')
.update({ .update({
picture_url: pictureUrl, picture_url: pictureUrl,
@@ -90,6 +91,7 @@ function UploadProfileAvatarForm(props: {
removeExistingStorageFile() removeExistingStorageFile()
.then(() => { .then(() => {
return client return client
.schema('medreport')
.from('accounts') .from('accounts')
.update({ .update({
picture_url: null, picture_url: null,

View File

@@ -17,7 +17,9 @@ interface UserWorkspace {
id: string | null; id: string | null;
name: string | null; name: string | null;
picture_url: string | null; picture_url: string | null;
subscription_status: Tables<'subscriptions'>['status'] | null; subscription_status:
| Tables<{ schema: 'medreport' }, 'subscriptions'>['status']
| null;
}; };
user: User; user: User;

View File

@@ -21,6 +21,7 @@ export function usePersonalAccountData(
} }
const response = await client const response = await client
.schema('medreport')
.from('accounts') .from('accounts')
.select() .select()
.eq('primary_owner_user_id', userId) .eq('primary_owner_user_id', userId)

View File

@@ -3,7 +3,7 @@ import { useMutation } from '@tanstack/react-query';
import { Database } from '@kit/supabase/database'; import { Database } from '@kit/supabase/database';
import { useSupabase } from '@kit/supabase/hooks/use-supabase'; import { useSupabase } from '@kit/supabase/hooks/use-supabase';
type UpdateData = Database['public']['Tables']['accounts']['Update']; type UpdateData = Database['medreport']['Tables']['accounts']['Update'];
export function useUpdateAccountData(accountId: string) { export function useUpdateAccountData(accountId: string) {
const client = useSupabase(); const client = useSupabase();
@@ -11,9 +11,13 @@ export function useUpdateAccountData(accountId: string) {
const mutationKey = ['account:data', accountId]; const mutationKey = ['account:data', accountId];
const mutationFn = async (data: UpdateData) => { const mutationFn = async (data: UpdateData) => {
const response = await client.from('accounts').update(data).match({ const response = await client
id: accountId, .schema('medreport')
}); .from('accounts')
.update(data)
.match({
id: accountId,
});
if (response.error) { if (response.error) {
throw response.error; throw response.error;

View File

@@ -17,6 +17,7 @@ class AccountsApi {
*/ */
async getAccount(id: string) { async getAccount(id: string) {
const { data, error } = await this.client const { data, error } = await this.client
.schema('medreport')
.from('accounts') .from('accounts')
.select('*') .select('*')
.eq('id', id) .eq('id', id)
@@ -35,6 +36,7 @@ class AccountsApi {
*/ */
async getAccountWorkspace() { async getAccountWorkspace() {
const { data, error } = await this.client const { data, error } = await this.client
.schema('medreport')
.from('user_account_workspace') .from('user_account_workspace')
.select(`*`) .select(`*`)
.single(); .single();
@@ -63,6 +65,7 @@ class AccountsApi {
const { user } = data; const { user } = data;
const { data: accounts, error } = await this.client const { data: accounts, error } = await this.client
.schema('medreport')
.from('accounts_memberships') .from('accounts_memberships')
.select( .select(
` `
@@ -91,6 +94,7 @@ class AccountsApi {
async loadTempUserAccounts() { async loadTempUserAccounts() {
const { data: accounts, error } = await this.client const { data: accounts, error } = await this.client
.schema('medreport')
.from('user_accounts') .from('user_accounts')
.select(`name, slug`); .select(`name, slug`);
@@ -131,6 +135,7 @@ class AccountsApi {
*/ */
async getOrder(accountId: string) { async getOrder(accountId: string) {
const response = await this.client const response = await this.client
.schema('medreport')
.from('orders') .from('orders')
.select('*, items: order_items !inner (*)') .select('*, items: order_items !inner (*)')
.eq('account_id', accountId) .eq('account_id', accountId)
@@ -151,6 +156,7 @@ class AccountsApi {
*/ */
async getCustomerId(accountId: string) { async getCustomerId(accountId: string) {
const response = await this.client const response = await this.client
.schema('medreport')
.from('billing_customers') .from('billing_customers')
.select('customer_id') .select('customer_id')
.eq('account_id', accountId) .eq('account_id', accountId)

View File

@@ -3,6 +3,11 @@ import { BadgeX, Ban, ShieldPlus, VenetianMask } from 'lucide-react';
import { Tables } from '@kit/supabase/database'; import { Tables } from '@kit/supabase/database';
import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client'; import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client';
import { getSupabaseServerClient } from '@kit/supabase/server-client'; import { getSupabaseServerClient } from '@kit/supabase/server-client';
import {
AccountInvitationsTable,
AccountMembersTable,
InviteMembersDialogContainer,
} from '@kit/team-accounts/components';
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert'; import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
import { AppBreadcrumbs } from '@kit/ui/app-breadcrumbs'; import { AppBreadcrumbs } from '@kit/ui/app-breadcrumbs';
import { Badge } from '@kit/ui/badge'; import { Badge } from '@kit/ui/badge';
@@ -28,14 +33,8 @@ import { AdminMembersTable } from './admin-members-table';
import { AdminMembershipsTable } from './admin-memberships-table'; import { AdminMembershipsTable } from './admin-memberships-table';
import { AdminReactivateUserDialog } from './admin-reactivate-user-dialog'; import { AdminReactivateUserDialog } from './admin-reactivate-user-dialog';
import { type Account = Tables<{ schema: 'medreport' }, 'accounts'>;
AccountInvitationsTable, type Membership = Tables<{ schema: 'medreport' }, 'accounts_memberships'>;
AccountMembersTable,
InviteMembersDialogContainer,
} from '@kit/team-accounts/components';
type Account = Tables<'accounts'>;
type Membership = Tables<'accounts_memberships'>;
export function AdminAccountPage(props: { export function AdminAccountPage(props: {
account: Account & { memberships: Membership[] }; account: Account & { memberships: Membership[] };
@@ -231,6 +230,7 @@ async function SubscriptionsTable(props: { accountId: string }) {
const client = getSupabaseServerClient(); const client = getSupabaseServerClient();
const { data: subscription, error } = await client const { data: subscription, error } = await client
.schema('medreport')
.from('subscriptions') .from('subscriptions')
.select('*, subscription_items !inner (*)') .select('*, subscription_items !inner (*)')
.eq('account_id', props.accountId) .eq('account_id', props.accountId)
@@ -372,6 +372,7 @@ async function getMemberships(userId: string) {
const client = getSupabaseServerClient(); const client = getSupabaseServerClient();
const memberships = await client const memberships = await client
.schema('medreport')
.from('accounts_memberships') .from('accounts_memberships')
.select< .select<
string, string,
@@ -394,7 +395,7 @@ async function getMemberships(userId: string) {
async function getMembers(accountSlug: string) { async function getMembers(accountSlug: string) {
const client = getSupabaseServerClient(); const client = getSupabaseServerClient();
const members = await client.rpc('get_account_members', { const members = await client.schema('medreport').rpc('get_account_members', {
account_slug: accountSlug, account_slug: accountSlug,
}); });

View File

@@ -38,7 +38,7 @@ import { AdminDeleteUserDialog } from './admin-delete-user-dialog';
import { AdminImpersonateUserDialog } from './admin-impersonate-user-dialog'; import { AdminImpersonateUserDialog } from './admin-impersonate-user-dialog';
import { AdminResetPasswordDialog } from './admin-reset-password-dialog'; import { AdminResetPasswordDialog } from './admin-reset-password-dialog';
type Account = Database['public']['Tables']['accounts']['Row']; type Account = Database['medreport']['Tables']['accounts']['Row'];
const FiltersSchema = z.object({ const FiltersSchema = z.object({
type: z.enum(['all', 'team', 'personal']), type: z.enum(['all', 'team', 'personal']),

View File

@@ -9,7 +9,7 @@ import { DataTable } from '@kit/ui/enhanced-data-table';
import { ProfileAvatar } from '@kit/ui/profile-avatar'; import { ProfileAvatar } from '@kit/ui/profile-avatar';
type Memberships = type Memberships =
Database['public']['Functions']['get_account_members']['Returns'][number]; Database['medreport']['Functions']['get_account_members']['Returns'][number];
export function AdminMembersTable(props: { members: Memberships[] }) { export function AdminMembersTable(props: { members: Memberships[] }) {
return <DataTable data={props.members} columns={getColumns()} />; return <DataTable data={props.members} columns={getColumns()} />;

View File

@@ -7,7 +7,7 @@ import { ColumnDef } from '@tanstack/react-table';
import { Tables } from '@kit/supabase/database'; import { Tables } from '@kit/supabase/database';
import { DataTable } from '@kit/ui/enhanced-data-table'; import { DataTable } from '@kit/ui/enhanced-data-table';
type Membership = Tables<'accounts_memberships'> & { type Membership = Tables<{ schema: 'medreport' }, 'accounts_memberships'> & {
account: { account: {
id: string; id: string;
name: string; name: string;

View File

@@ -15,13 +15,13 @@ import {
ImpersonateUserSchema, ImpersonateUserSchema,
ReactivateUserSchema, ReactivateUserSchema,
} from './schema/admin-actions.schema'; } from './schema/admin-actions.schema';
import { CreateCompanySchema } from './schema/create-company.schema';
import { CreateUserSchema } from './schema/create-user.schema'; import { CreateUserSchema } from './schema/create-user.schema';
import { ResetPasswordSchema } from './schema/reset-password.schema'; import { ResetPasswordSchema } from './schema/reset-password.schema';
import { createAdminAccountsService } from './services/admin-accounts.service'; import { createAdminAccountsService } from './services/admin-accounts.service';
import { createAdminAuthUserService } from './services/admin-auth-user.service'; import { createAdminAuthUserService } from './services/admin-auth-user.service';
import { adminAction } from './utils/admin-action';
import { CreateCompanySchema } from './schema/create-company.schema';
import { createCreateCompanyAccountService } from './services/admin-create-company-account.service'; import { createCreateCompanyAccountService } from './services/admin-create-company-account.service';
import { adminAction } from './utils/admin-action';
/** /**
* @name banUserAction * @name banUserAction
@@ -183,12 +183,16 @@ export const createUserAction = adminAction(
); );
const { error: accountError } = await adminClient const { error: accountError } = await adminClient
.schema('medreport')
.from('accounts') .from('accounts')
.update({ personal_code: personalCode }) .update({ personal_code: personalCode })
.eq('id', data.user.id); .eq('id', data.user.id);
if (accountError) { if (accountError) {
logger.error({ accountError }, 'Error inserting personal code to accounts'); logger.error(
{ accountError },
'Error inserting personal code to accounts',
);
throw new Error(`Error saving personal code: ${accountError.message}`); throw new Error(`Error saving personal code: ${accountError.message}`);
} }

View File

@@ -13,6 +13,7 @@ class AdminAccountsService {
async deleteAccount(accountId: string) { async deleteAccount(accountId: string) {
const { error } = await this.adminClient const { error } = await this.adminClient
.schema('medreport')
.from('accounts') .from('accounts')
.delete() .delete()
.eq('id', accountId); .eq('id', accountId);

View File

@@ -30,6 +30,7 @@ export class AdminDashboardService {
}; };
const subscriptionsPromise = this.client const subscriptionsPromise = this.client
.schema('medreport')
.from('subscriptions') .from('subscriptions')
.select('*', selectParams) .select('*', selectParams)
.eq('status', 'active') .eq('status', 'active')
@@ -47,6 +48,7 @@ export class AdminDashboardService {
}); });
const trialsPromise = this.client const trialsPromise = this.client
.schema('medreport')
.from('subscriptions') .from('subscriptions')
.select('*', selectParams) .select('*', selectParams)
.eq('status', 'trialing') .eq('status', 'trialing')
@@ -64,6 +66,7 @@ export class AdminDashboardService {
}); });
const accountsPromise = this.client const accountsPromise = this.client
.schema('medreport')
.from('accounts') .from('accounts')
.select('*', selectParams) .select('*', selectParams)
.eq('is_personal_account', true) .eq('is_personal_account', true)
@@ -81,6 +84,7 @@ export class AdminDashboardService {
}); });
const teamAccountsPromise = this.client const teamAccountsPromise = this.client
.schema('medreport')
.from('accounts') .from('accounts')
.select('*', selectParams) .select('*', selectParams)
.eq('is_personal_account', false) .eq('is_personal_account', false)

View File

@@ -35,7 +35,6 @@
"lucide-react": "^0.510.0", "lucide-react": "^0.510.0",
"next": "15.3.2", "next": "15.3.2",
"react-hook-form": "^7.56.3", "react-hook-form": "^7.56.3",
"react-i18next": "^15.5.1",
"sonner": "^2.0.3" "sonner": "^2.0.3"
}, },
"prettier": "@kit/prettier-config", "prettier": "@kit/prettier-config",

View File

@@ -55,12 +55,11 @@ export function SignInMethodsContainer(props: {
} }
try { try {
const { data: hasConsentPersonalData } = await client.rpc( const { data: hasConsentPersonalData } = await client
'has_consent_personal_data', .schema('medreport')
{ .rpc('has_consent_personal_data', {
account_id: userId, account_id: userId,
}, });
);
if (hasConsentPersonalData) { if (hasConsentPersonalData) {
router.replace(props.paths.returnPath); router.replace(props.paths.returnPath);

View File

@@ -86,7 +86,7 @@ class AuthApi {
if (!user) { if (!user) {
throw new Error('User not authenticated'); throw new Error('User not authenticated');
} }
console.log('test', user, data);
const response = await this.client const response = await this.client
.schema('medreport') .schema('medreport')
.from('account_params') .from('account_params')

View File

@@ -1,29 +1,28 @@
import { HttpTypes } from "@medusajs/types" import { HttpTypes } from "@medusajs/types";
import { NextRequest, NextResponse } from "next/server" import { NextRequest, NextResponse } from "next/server";
const BACKEND_URL = process.env.MEDUSA_BACKEND_URL const BACKEND_URL = process.env.MEDUSA_BACKEND_URL;
const PUBLISHABLE_API_KEY = process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY const PUBLISHABLE_API_KEY = process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY;
const DEFAULT_REGION = process.env.NEXT_PUBLIC_DEFAULT_REGION || "us" const DEFAULT_REGION = process.env.NEXT_PUBLIC_DEFAULT_REGION || "us";
const regionMapCache = { const regionMapCache = {
regionMap: new Map<string, HttpTypes.StoreRegion>(), regionMap: new Map<string, HttpTypes.StoreRegion>(),
regionMapUpdated: Date.now(), regionMapUpdated: Date.now(),
} };
async function getRegionMap(cacheId: string) { async function getRegionMap(cacheId: string) {
const { regionMap, regionMapUpdated } = regionMapCache const { regionMap, regionMapUpdated } = regionMapCache;
if (!BACKEND_URL) { if (!BACKEND_URL) {
throw new Error( throw new Error(
"Middleware.ts: Error fetching regions. Did you set up regions in your Medusa Admin and define a MEDUSA_BACKEND_URL environment variable? Note that the variable is no longer named NEXT_PUBLIC_MEDUSA_BACKEND_URL." "Middleware.ts: Error fetching regions. Did you set up regions in your Medusa Admin and define a MEDUSA_BACKEND_URL environment variable? Note that the variable is no longer named NEXT_PUBLIC_MEDUSA_BACKEND_URL."
) );
} }
if ( if (
!regionMap.keys().next().value || !regionMap.keys().next().value ||
regionMapUpdated < Date.now() - 3600 * 1000 regionMapUpdated < Date.now() - 3600 * 1000
) { ) {
console.log("PUBLISHABLE_API_KEY", PUBLISHABLE_API_KEY)
// Fetch regions from Medusa. We can't use the JS client here because middleware is running on Edge and the client needs a Node environment. // Fetch regions from Medusa. We can't use the JS client here because middleware is running on Edge and the client needs a Node environment.
const { regions } = await fetch(`${BACKEND_URL}/store/regions`, { const { regions } = await fetch(`${BACKEND_URL}/store/regions`, {
headers: { headers: {
@@ -35,32 +34,32 @@ async function getRegionMap(cacheId: string) {
}, },
cache: "force-cache", cache: "force-cache",
}).then(async (response) => { }).then(async (response) => {
const json = await response.json() const json = await response.json();
if (!response.ok) { if (!response.ok) {
throw new Error(json.message) throw new Error(json.message);
} }
return json return json;
}) });
if (!regions?.length) { if (!regions?.length) {
throw new Error( throw new Error(
"No regions found. Please set up regions in your Medusa Admin." "No regions found. Please set up regions in your Medusa Admin."
) );
} }
// Create a map of country codes to regions. // Create a map of country codes to regions.
regions.forEach((region: HttpTypes.StoreRegion) => { regions.forEach((region: HttpTypes.StoreRegion) => {
region.countries?.forEach((c) => { region.countries?.forEach((c) => {
regionMapCache.regionMap.set(c.iso_2 ?? "", region) regionMapCache.regionMap.set(c.iso_2 ?? "", region);
}) });
}) });
regionMapCache.regionMapUpdated = Date.now() regionMapCache.regionMapUpdated = Date.now();
} }
return regionMapCache.regionMap return regionMapCache.regionMap;
} }
/** /**
@@ -73,30 +72,32 @@ async function getCountryCode(
regionMap: Map<string, HttpTypes.StoreRegion | number> regionMap: Map<string, HttpTypes.StoreRegion | number>
) { ) {
try { try {
let countryCode let countryCode;
const vercelCountryCode = request.headers const vercelCountryCode = request.headers
.get("x-vercel-ip-country") .get("x-vercel-ip-country")
?.toLowerCase() ?.toLowerCase();
const urlCountryCode = request.nextUrl.pathname.split("/")[1]?.toLowerCase() const urlCountryCode = request.nextUrl.pathname
.split("/")[1]
?.toLowerCase();
if (urlCountryCode && regionMap.has(urlCountryCode)) { if (urlCountryCode && regionMap.has(urlCountryCode)) {
countryCode = urlCountryCode countryCode = urlCountryCode;
} else if (vercelCountryCode && regionMap.has(vercelCountryCode)) { } else if (vercelCountryCode && regionMap.has(vercelCountryCode)) {
countryCode = vercelCountryCode countryCode = vercelCountryCode;
} else if (regionMap.has(DEFAULT_REGION)) { } else if (regionMap.has(DEFAULT_REGION)) {
countryCode = DEFAULT_REGION countryCode = DEFAULT_REGION;
} else if (regionMap.keys().next().value) { } else if (regionMap.keys().next().value) {
countryCode = regionMap.keys().next().value countryCode = regionMap.keys().next().value;
} }
return countryCode return countryCode;
} catch (error) { } catch (error) {
if (process.env.NODE_ENV === "development") { if (process.env.NODE_ENV === "development") {
console.error( console.error(
"Middleware.ts: Error getting the country code. Did you set up regions in your Medusa Admin and define a MEDUSA_BACKEND_URL environment variable? Note that the variable is no longer named NEXT_PUBLIC_MEDUSA_BACKEND_URL." "Middleware.ts: Error getting the country code. Did you set up regions in your Medusa Admin and define a MEDUSA_BACKEND_URL environment variable? Note that the variable is no longer named NEXT_PUBLIC_MEDUSA_BACKEND_URL."
) );
} }
} }
} }
@@ -105,56 +106,56 @@ async function getCountryCode(
* Middleware to handle region selection and onboarding status. * Middleware to handle region selection and onboarding status.
*/ */
export async function middleware(request: NextRequest) { export async function middleware(request: NextRequest) {
let redirectUrl = request.nextUrl.href let redirectUrl = request.nextUrl.href;
let response = NextResponse.redirect(redirectUrl, 307) let response = NextResponse.redirect(redirectUrl, 307);
let cacheIdCookie = request.cookies.get("_medusa_cache_id") let cacheIdCookie = request.cookies.get("_medusa_cache_id");
let cacheId = cacheIdCookie?.value || crypto.randomUUID() let cacheId = cacheIdCookie?.value || crypto.randomUUID();
const regionMap = await getRegionMap(cacheId) const regionMap = await getRegionMap(cacheId);
const countryCode = regionMap && (await getCountryCode(request, regionMap)) const countryCode = regionMap && (await getCountryCode(request, regionMap));
const urlHasCountryCode = const urlHasCountryCode =
countryCode && request.nextUrl.pathname.split("/")[1].includes(countryCode) countryCode && request.nextUrl.pathname.split("/")[1].includes(countryCode);
// if one of the country codes is in the url and the cache id is set, return next // if one of the country codes is in the url and the cache id is set, return next
if (urlHasCountryCode && cacheIdCookie) { if (urlHasCountryCode && cacheIdCookie) {
return NextResponse.next() return NextResponse.next();
} }
// if one of the country codes is in the url and the cache id is not set, set the cache id and redirect // if one of the country codes is in the url and the cache id is not set, set the cache id and redirect
if (urlHasCountryCode && !cacheIdCookie) { if (urlHasCountryCode && !cacheIdCookie) {
response.cookies.set("_medusa_cache_id", cacheId, { response.cookies.set("_medusa_cache_id", cacheId, {
maxAge: 60 * 60 * 24, maxAge: 60 * 60 * 24,
}) });
return response return response;
} }
// check if the url is a static asset // check if the url is a static asset
if (request.nextUrl.pathname.includes(".")) { if (request.nextUrl.pathname.includes(".")) {
return NextResponse.next() return NextResponse.next();
} }
const redirectPath = const redirectPath =
request.nextUrl.pathname === "/" ? "" : request.nextUrl.pathname request.nextUrl.pathname === "/" ? "" : request.nextUrl.pathname;
const queryString = request.nextUrl.search ? request.nextUrl.search : "" const queryString = request.nextUrl.search ? request.nextUrl.search : "";
// If no country code is set, we redirect to the relevant region. // If no country code is set, we redirect to the relevant region.
if (!urlHasCountryCode && countryCode) { if (!urlHasCountryCode && countryCode) {
redirectUrl = `${request.nextUrl.origin}/${countryCode}${redirectPath}${queryString}` redirectUrl = `${request.nextUrl.origin}/${countryCode}${redirectPath}${queryString}`;
response = NextResponse.redirect(`${redirectUrl}`, 307) response = NextResponse.redirect(`${redirectUrl}`, 307);
} }
return response return response;
} }
export const config = { export const config = {
matcher: [ matcher: [
"/((?!api|_next/static|_next/image|favicon.ico|images|assets|png|svg|jpg|jpeg|gif|webp).*)", "/((?!api|_next/static|_next/image|favicon.ico|images|assets|png|svg|jpg|jpeg|gif|webp).*)",
], ],
} };

View File

@@ -24,8 +24,7 @@
"@types/react": "19.1.4", "@types/react": "19.1.4",
"lucide-react": "^0.510.0", "lucide-react": "^0.510.0",
"react": "19.1.0", "react": "19.1.0",
"react-dom": "19.1.0", "react-dom": "19.1.0"
"react-i18next": "^15.5.1"
}, },
"prettier": "@kit/prettier-config", "prettier": "@kit/prettier-config",
"typesVersions": { "typesVersions": {

View File

@@ -14,7 +14,7 @@ import { cn } from '@kit/ui/utils';
import { useDismissNotification, useFetchNotifications } from '../hooks'; import { useDismissNotification, useFetchNotifications } from '../hooks';
type Notification = Database['public']['Tables']['notifications']['Row']; type Notification = Database['medreport']['Tables']['notifications']['Row'];
type PartialNotification = Pick< type PartialNotification = Pick<
Notification, Notification,
@@ -121,7 +121,10 @@ export function NotificationsPopover(params: {
return ( return (
<Popover modal open={open} onOpenChange={setOpen}> <Popover modal open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button className={'relative px-4 py-2 h-10 border-1 mr-0'} variant="ghost"> <Button
className={'relative mr-0 h-10 border-1 px-4 py-2'}
variant="ghost"
>
<Bell className={'size-4'} /> <Bell className={'size-4'} />
<span <span

View File

@@ -8,6 +8,7 @@ export function useDismissNotification() {
return useCallback( return useCallback(
async (notification: number) => { async (notification: number) => {
const { error } = await client const { error } = await client
.schema('medreport')
.from('notifications') .from('notifications')
.update({ dismissed: true }) .update({ dismissed: true })
.eq('id', notification); .eq('id', notification);

View File

@@ -49,6 +49,7 @@ function useFetchInitialNotifications(props: { accountIds: string[] }) {
queryKey: ['notifications', ...props.accountIds], queryKey: ['notifications', ...props.accountIds],
queryFn: async () => { queryFn: async () => {
const { data } = await client const { data } = await client
.schema('medreport')
.from('notifications') .from('notifications')
.select( .select(
`id, `id,

View File

@@ -22,7 +22,7 @@ import { Database } from '@kit/supabase/database';
import { createNotificationsService } from './notifications.service'; import { createNotificationsService } from './notifications.service';
type Notification = Database['public']['Tables']['notifications']; type Notification = Database['medreport']['Tables']['notifications'];
/** /**
* @name createNotificationsApi * @name createNotificationsApi

View File

@@ -4,7 +4,7 @@ import { SupabaseClient } from '@supabase/supabase-js';
import { Database } from '@kit/supabase/database'; import { Database } from '@kit/supabase/database';
type Notification = Database['public']['Tables']['notifications']; type Notification = Database['medreport']['Tables']['notifications'];
export function createNotificationsService(client: SupabaseClient<Database>) { export function createNotificationsService(client: SupabaseClient<Database>) {
return new NotificationsService(client); return new NotificationsService(client);
@@ -14,7 +14,10 @@ class NotificationsService {
constructor(private readonly client: SupabaseClient<Database>) {} constructor(private readonly client: SupabaseClient<Database>) {}
async createNotification(params: Notification['Insert']) { async createNotification(params: Notification['Insert']) {
const { error } = await this.client.from('notifications').insert(params); const { error } = await this.client
.schema('medreport')
.from('notifications')
.insert(params);
if (error) { if (error) {
throw error; throw error;

View File

@@ -45,7 +45,6 @@
"react": "19.1.0", "react": "19.1.0",
"react-dom": "19.1.0", "react-dom": "19.1.0",
"react-hook-form": "^7.56.3", "react-hook-form": "^7.56.3",
"react-i18next": "^15.5.1",
"sonner": "^2.0.3" "sonner": "^2.0.3"
}, },
"prettier": "@kit/prettier-config", "prettier": "@kit/prettier-config",

View File

@@ -27,7 +27,7 @@ import { RenewInvitationDialog } from './renew-invitation-dialog';
import { UpdateInvitationDialog } from './update-invitation-dialog'; import { UpdateInvitationDialog } from './update-invitation-dialog';
type Invitations = type Invitations =
Database['public']['Functions']['get_account_invitations']['Returns']; Database['medreport']['Functions']['get_account_invitations']['Returns'];
type AccountInvitationsTableProps = { type AccountInvitationsTableProps = {
invitations: Invitations; invitations: Invitations;

View File

@@ -27,7 +27,7 @@ import { TransferOwnershipDialog } from './transfer-ownership-dialog';
import { UpdateMemberRoleDialog } from './update-member-role-dialog'; import { UpdateMemberRoleDialog } from './update-member-role-dialog';
type Members = type Members =
Database['public']['Functions']['get_account_members']['Returns']; Database['medreport']['Functions']['get_account_members']['Returns'];
interface Permissions { interface Permissions {
canUpdateRole: (roleHierarchy: number) => boolean; canUpdateRole: (roleHierarchy: number) => boolean;

View File

@@ -21,7 +21,7 @@ export function RolesDataProvider(props: {
} }
function useFetchRoles(props: { maxRoleHierarchy: number }) { function useFetchRoles(props: { maxRoleHierarchy: number }) {
const supabase = useSupabase(); const supabase = useSupabase().schema('medreport');
return useQuery({ return useQuery({
queryKey: ['roles', props.maxRoleHierarchy], queryKey: ['roles', props.maxRoleHierarchy],

View File

@@ -53,6 +53,7 @@ export function UpdateTeamAccountImage(props: {
uploadUserProfilePhoto(client, file, props.account.id).then( uploadUserProfilePhoto(client, file, props.account.id).then(
(pictureUrl) => { (pictureUrl) => {
return client return client
.schema('medreport')
.from('accounts') .from('accounts')
.update({ .update({
picture_url: pictureUrl, picture_url: pictureUrl,
@@ -68,6 +69,7 @@ export function UpdateTeamAccountImage(props: {
const promise = () => const promise = () =>
removeExistingStorageFile().then(() => { removeExistingStorageFile().then(() => {
return client return client
.schema('medreport')
.from('accounts') .from('accounts')
.update({ .update({
picture_url: null, picture_url: null,

View File

@@ -7,8 +7,8 @@ import { User } from '@supabase/supabase-js';
import { Database } from '@kit/supabase/database'; import { Database } from '@kit/supabase/database';
interface AccountWorkspace { interface AccountWorkspace {
accounts: Database['public']['Views']['user_accounts']['Row'][]; accounts: Database['medreport']['Views']['user_accounts']['Row'][];
account: Database['public']['Functions']['team_account_workspace']['Returns'][0]; account: Database['medreport']['Functions']['team_account_workspace']['Returns'][0];
user: User; user: User;
} }

View File

@@ -25,7 +25,6 @@ export const TeamNameSchema = z
.max(50) .max(50)
.refine( .refine(
(name) => { (name) => {
console.log(name);
return !SPECIAL_CHARACTERS_REGEX.test(name); return !SPECIAL_CHARACTERS_REGEX.test(name);
}, },
{ {

View File

@@ -22,6 +22,7 @@ export const updateTeamAccountName = enhanceAction(
logger.info(ctx, `Updating team name...`); logger.info(ctx, `Updating team name...`);
const { error, data } = await client const { error, data } = await client
.schema('medreport')
.from('accounts') .from('accounts')
.update({ .update({
name, name,

View File

@@ -41,6 +41,7 @@ export const createInvitationsAction = enhanceAction(
); );
const { data: company, error: companyError } = await client const { data: company, error: companyError } = await client
.schema('medreport')
.from('accounts') .from('accounts')
.select('id') .select('id')
.eq('slug', accountSlug); .eq('slug', accountSlug);

View File

@@ -79,9 +79,11 @@ export const transferOwnershipAction = enhanceAction(
logger.info(ctx, 'Processing team ownership transfer request...'); logger.info(ctx, 'Processing team ownership transfer request...');
// assert that the user is the owner of the account // assert that the user is the owner of the account
const { data: isOwner, error } = await client.rpc('is_account_owner', { const { data: isOwner, error } = await client
account_id: data.accountId, .schema('medreport')
}); .rpc('is_account_owner', {
account_id: data.accountId,
});
if (error || !isOwner) { if (error || !isOwner) {
logger.error(ctx, 'User is not the owner of this account'); logger.error(ctx, 'User is not the owner of this account');

View File

@@ -17,6 +17,7 @@ export class TeamAccountsApi {
*/ */
async getTeamAccount(slug: string) { async getTeamAccount(slug: string) {
const { data, error } = await this.client const { data, error } = await this.client
.schema('medreport')
.from('accounts') .from('accounts')
.select('*') .select('*')
.eq('slug', slug) .eq('slug', slug)
@@ -36,6 +37,7 @@ export class TeamAccountsApi {
*/ */
async getTeamAccountById(accountId: string) { async getTeamAccountById(accountId: string) {
const { data, error } = await this.client const { data, error } = await this.client
.schema('medreport')
.from('accounts') .from('accounts')
.select('*') .select('*')
.eq('id', accountId) .eq('id', accountId)
@@ -55,6 +57,7 @@ export class TeamAccountsApi {
*/ */
async getSubscription(accountId: string) { async getSubscription(accountId: string) {
const { data, error } = await this.client const { data, error } = await this.client
.schema('medreport')
.from('subscriptions') .from('subscriptions')
.select('*, items: subscription_items !inner (*)') .select('*, items: subscription_items !inner (*)')
.eq('account_id', accountId) .eq('account_id', accountId)
@@ -73,6 +76,7 @@ export class TeamAccountsApi {
*/ */
async getOrder(accountId: string) { async getOrder(accountId: string) {
const response = await this.client const response = await this.client
.schema('medreport')
.from('orders') .from('orders')
.select('*, items: order_items !inner (*)') .select('*, items: order_items !inner (*)')
.eq('account_id', accountId) .eq('account_id', accountId)
@@ -91,13 +95,17 @@ export class TeamAccountsApi {
* @param slug * @param slug
*/ */
async getAccountWorkspace(slug: string, userId: string) { async getAccountWorkspace(slug: string, userId: string) {
const accountPromise = this.client.rpc('team_account_workspace', { const accountPromise = this.client
account_slug: slug, .schema('medreport')
}); .rpc('team_account_workspace', {
account_slug: slug,
});
const accountsPromise = this.client const accountsPromise = this.client
.schema('medreport')
.from('accounts_memberships') .from('accounts_memberships')
.select(` .select(
`
account_id, account_id,
user_accounts ( user_accounts (
id, id,
@@ -106,7 +114,8 @@ export class TeamAccountsApi {
slug, slug,
picture_url picture_url
) )
`) `,
)
.eq('user_id', userId) .eq('user_id', userId)
.eq('account_role', 'owner'); .eq('account_role', 'owner');
@@ -154,7 +163,7 @@ export class TeamAccountsApi {
async hasPermission(params: { async hasPermission(params: {
accountId: string; accountId: string;
userId: string; userId: string;
permission: Database['public']['Enums']['app_permissions']; permission: Database['medreport']['Enums']['app_permissions'];
}) { }) {
const { data, error } = await this.client.rpc('has_permission', { const { data, error } = await this.client.rpc('has_permission', {
account_id: params.accountId, account_id: params.accountId,
@@ -176,6 +185,7 @@ export class TeamAccountsApi {
*/ */
async getMembersCount(accountId: string) { async getMembersCount(accountId: string) {
const { count, error } = await this.client const { count, error } = await this.client
.schema('medreport')
.from('accounts_memberships') .from('accounts_memberships')
.select('*', { .select('*', {
head: true, head: true,
@@ -197,6 +207,7 @@ export class TeamAccountsApi {
*/ */
async getCustomerId(accountId: string) { async getCustomerId(accountId: string) {
const { data, error } = await this.client const { data, error } = await this.client
.schema('medreport')
.from('billing_customers') .from('billing_customers')
.select('customer_id') .select('customer_id')
.eq('account_id', accountId) .eq('account_id', accountId)
@@ -217,6 +228,7 @@ export class TeamAccountsApi {
*/ */
async getInvitation(adminClient: SupabaseClient<Database>, token: string) { async getInvitation(adminClient: SupabaseClient<Database>, token: string) {
const { data: invitation, error } = await adminClient const { data: invitation, error } = await adminClient
.schema('medreport')
.from('invitations') .from('invitations')
.select< .select<
string, string,

View File

@@ -43,6 +43,7 @@ class AccountInvitationsService {
logger.info(ctx, 'Removing invitation...'); logger.info(ctx, 'Removing invitation...');
const { data, error } = await this.client const { data, error } = await this.client
.schema('medreport')
.from('invitations') .from('invitations')
.delete() .delete()
.match({ .match({
@@ -76,6 +77,7 @@ class AccountInvitationsService {
logger.info(ctx, 'Updating invitation...'); logger.info(ctx, 'Updating invitation...');
const { data, error } = await this.client const { data, error } = await this.client
.schema('medreport')
.from('invitations') .from('invitations')
.update({ .update({
role: params.role, role: params.role,
@@ -105,12 +107,11 @@ class AccountInvitationsService {
invitation: z.infer<typeof InviteMembersSchema>['invitations'][number], invitation: z.infer<typeof InviteMembersSchema>['invitations'][number],
accountSlug: string, accountSlug: string,
) { ) {
const { data: members, error } = await this.client.rpc( const { data: members, error } = await this.client
'get_account_members', .schema('medreport')
{ .rpc('get_account_members', {
account_slug: accountSlug, account_slug: accountSlug,
}, });
);
if (error) { if (error) {
throw error; throw error;
@@ -169,6 +170,7 @@ class AccountInvitationsService {
} }
const accountResponse = await this.client const accountResponse = await this.client
.schema('medreport')
.from('accounts') .from('accounts')
.select('name') .select('name')
.eq('slug', accountSlug) .eq('slug', accountSlug)
@@ -183,10 +185,12 @@ class AccountInvitationsService {
throw new Error('Account not found'); throw new Error('Account not found');
} }
const response = await this.client.rpc('add_invitations_to_account', { const response = await this.client
invitations, .schema('medreport')
account_slug: accountSlug, .rpc('add_invitations_to_account', {
}); invitations,
account_slug: accountSlug,
});
if (response.error) { if (response.error) {
logger.error( logger.error(
@@ -232,10 +236,12 @@ class AccountInvitationsService {
logger.info(ctx, 'Accepting invitation to team'); logger.info(ctx, 'Accepting invitation to team');
const { error, data } = await adminClient.rpc('accept_invitation', { const { error, data } = await adminClient
token: params.inviteToken, .schema('medreport')
user_id: params.userId, .rpc('accept_invitation', {
}); token: params.inviteToken,
user_id: params.userId,
});
if (error) { if (error) {
logger.error( logger.error(
@@ -272,6 +278,7 @@ class AccountInvitationsService {
const sevenDaysFromNow = formatISO(addDays(new Date(), 7)); const sevenDaysFromNow = formatISO(addDays(new Date(), 7));
const { data, error } = await this.client const { data, error } = await this.client
.schema('medreport')
.from('invitations') .from('invitations')
.update({ .update({
expires_at: sevenDaysFromNow, expires_at: sevenDaysFromNow,

View File

@@ -37,6 +37,7 @@ class AccountMembersService {
logger.info(ctx, `Removing member from account...`); logger.info(ctx, `Removing member from account...`);
const { data, error } = await this.client const { data, error } = await this.client
.schema('medreport')
.from('accounts_memberships') .from('accounts_memberships')
.delete() .delete()
.match({ .match({
@@ -88,7 +89,7 @@ class AccountMembersService {
logger.info(ctx, `Validating permissions to update member role...`); logger.info(ctx, `Validating permissions to update member role...`);
const { data: canActionAccountMember, error: accountError } = const { data: canActionAccountMember, error: accountError } =
await this.client.rpc('can_action_account_member', { await this.client.schema('medreport').rpc('can_action_account_member', {
target_user_id: params.userId, target_user_id: params.userId,
target_team_account_id: params.accountId, target_team_account_id: params.accountId,
}); });
@@ -112,6 +113,7 @@ class AccountMembersService {
// for updating accounts_memberships. Instead, we use the can_action_account_member // for updating accounts_memberships. Instead, we use the can_action_account_member
// RPC to validate permissions to update the role // RPC to validate permissions to update the role
const { data, error } = await adminClient const { data, error } = await adminClient
.schema('medreport')
.from('accounts_memberships') .from('accounts_memberships')
.update({ .update({
account_role: params.role, account_role: params.role,
@@ -157,13 +159,12 @@ class AccountMembersService {
logger.info(ctx, `Transferring ownership of account...`); logger.info(ctx, `Transferring ownership of account...`);
const { data, error } = await adminClient.rpc( const { data, error } = await adminClient
'transfer_team_account_ownership', .schema('medreport')
{ .rpc('transfer_team_account_ownership', {
target_account_id: params.accountId, target_account_id: params.accountId,
new_owner_id: params.userId, new_owner_id: params.userId,
}, });
);
if (error) { if (error) {
logger.error( logger.error(

View File

@@ -36,6 +36,7 @@ class AccountPerSeatBillingService {
); );
const { data, error } = await this.client const { data, error } = await this.client
.schema('medreport')
.from('subscriptions') .from('subscriptions')
.select( .select(
` `

View File

@@ -22,9 +22,11 @@ class CreateTeamAccountService {
logger.info(ctx, `Creating new team account...`); logger.info(ctx, `Creating new team account...`);
const { error, data } = await this.client.rpc('create_team_account', { const { error, data } = await this.client
account_name: params.name, .schema('medreport')
}); .rpc('create_team_account', {
account_name: params.name,
});
if (error) { if (error) {
logger.error( logger.error(

View File

@@ -40,6 +40,7 @@ class DeleteTeamAccountService {
// we can use the admin client to delete the account. // we can use the admin client to delete the account.
const { error } = await adminClient const { error } = await adminClient
.schema('medreport')
.from('accounts') .from('accounts')
.delete() .delete()
.eq('id', params.accountId); .eq('id', params.accountId);

View File

@@ -45,6 +45,7 @@ class LeaveTeamAccountService {
const { accountId, userId } = Schema.parse(params); const { accountId, userId } = Schema.parse(params);
const { error } = await this.adminClient const { error } = await this.adminClient
.schema('medreport')
.from('accounts_memberships') .from('accounts_memberships')
.delete() .delete()
.match({ .match({

View File

@@ -5,7 +5,7 @@ import { z } from 'zod';
import { getLogger } from '@kit/shared/logger'; import { getLogger } from '@kit/shared/logger';
import { Database } from '@kit/supabase/database'; import { Database } from '@kit/supabase/database';
type Invitation = Database['public']['Tables']['invitations']['Row']; type Invitation = Database['medreport']['Tables']['invitations']['Row'];
const invitePath = '/join'; const invitePath = '/join';
@@ -72,6 +72,7 @@ class AccountInvitationsWebhookService {
); );
const inviter = await this.adminClient const inviter = await this.adminClient
.schema('medreport')
.from('accounts') .from('accounts')
.select('email, name') .select('email, name')
.eq('id', invitation.invited_by) .eq('id', invitation.invited_by)
@@ -90,6 +91,7 @@ class AccountInvitationsWebhookService {
} }
const team = await this.adminClient const team = await this.adminClient
.schema('medreport')
.from('accounts') .from('accounts')
.select('name') .select('name')
.eq('id', invitation.account_id) .eq('id', invitation.account_id)

View File

@@ -3,7 +3,7 @@ import { z } from 'zod';
import { getLogger } from '@kit/shared/logger'; import { getLogger } from '@kit/shared/logger';
import { Database } from '@kit/supabase/database'; import { Database } from '@kit/supabase/database';
type Account = Database['public']['Tables']['accounts']['Row']; type Account = Database['medreport']['Tables']['accounts']['Row'];
export function createAccountWebhooksService() { export function createAccountWebhooksService() {
return new AccountWebhooksService(); return new AccountWebhooksService();

View File

@@ -24,7 +24,7 @@
"next": "15.3.2", "next": "15.3.2",
"react": "19.1.0", "react": "19.1.0",
"react-dom": "19.1.0", "react-dom": "19.1.0",
"react-i18next": "^15.5.1" "react-i18next": "^15.5.3"
}, },
"dependencies": { "dependencies": {
"i18next": "25.1.3", "i18next": "25.1.3",

View File

@@ -74,7 +74,7 @@ class OtpService {
logger.info(ctx, 'Creating one-time token'); logger.info(ctx, 'Creating one-time token');
try { try {
const result = await this.client.rpc('create_nonce', { const result = await this.client.schema('medreport').rpc('create_nonce', {
p_user_id: userId, p_user_id: userId,
p_purpose: purpose, p_purpose: purpose,
p_expires_in_seconds: expiresInSeconds, p_expires_in_seconds: expiresInSeconds,
@@ -135,7 +135,7 @@ class OtpService {
logger.info(ctx, 'Verifying one-time token'); logger.info(ctx, 'Verifying one-time token');
try { try {
const result = await this.client.rpc('verify_nonce', { const result = await this.client.schema('medreport').rpc('verify_nonce', {
p_token: token, p_token: token,
p_user_id: params.userId, p_user_id: params.userId,
p_purpose: purpose, p_purpose: purpose,
@@ -184,10 +184,12 @@ class OtpService {
logger.info(ctx, 'Revoking one-time token'); logger.info(ctx, 'Revoking one-time token');
try { try {
const { data, error } = await this.client.rpc('revoke_nonce', { const { data, error } = await this.client
p_id: id, .schema('medreport')
p_reason: reason, .rpc('revoke_nonce', {
}); p_id: id,
p_reason: reason,
});
if (error) { if (error) {
logger.error( logger.error(
@@ -225,9 +227,11 @@ class OtpService {
logger.info(ctx, 'Getting one-time token status'); logger.info(ctx, 'Getting one-time token status');
try { try {
const result = await this.client.rpc('get_nonce_status', { const result = await this.client
p_id: id, .schema('medreport')
}); .rpc('get_nonce_status', {
p_id: id,
});
if (result.error) { if (result.error) {
logger.error( logger.error(

View File

@@ -55,7 +55,6 @@
"prettier": "^3.5.3", "prettier": "^3.5.3",
"react-day-picker": "^8.10.1", "react-day-picker": "^8.10.1",
"react-hook-form": "^7.56.3", "react-hook-form": "^7.56.3",
"react-i18next": "^15.5.1",
"sonner": "^2.0.3", "sonner": "^2.0.3",
"tailwindcss": "4.1.7", "tailwindcss": "4.1.7",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",

48
pnpm-lock.yaml generated
View File

@@ -312,9 +312,6 @@ importers:
react-hook-form: react-hook-form:
specifier: ^7.56.3 specifier: ^7.56.3
version: 7.57.0(react@19.1.0) version: 7.57.0(react@19.1.0)
react-i18next:
specifier: ^15.5.1
version: 15.5.2(i18next@25.1.3(typescript@5.8.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3)
packages/billing/lemon-squeezy: packages/billing/lemon-squeezy:
dependencies: dependencies:
@@ -632,9 +629,6 @@ importers:
react-hook-form: react-hook-form:
specifier: ^7.56.3 specifier: ^7.56.3
version: 7.57.0(react@19.1.0) version: 7.57.0(react@19.1.0)
react-i18next:
specifier: ^15.5.1
version: 15.5.2(i18next@25.1.3(typescript@5.8.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3)
sonner: sonner:
specifier: ^2.0.3 specifier: ^2.0.3
version: 2.0.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) version: 2.0.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -746,9 +740,6 @@ importers:
react-hook-form: react-hook-form:
specifier: ^7.56.3 specifier: ^7.56.3
version: 7.57.0(react@19.1.0) version: 7.57.0(react@19.1.0)
react-i18next:
specifier: ^15.5.1
version: 15.5.2(i18next@25.1.3(typescript@5.8.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3)
sonner: sonner:
specifier: ^2.0.3 specifier: ^2.0.3
version: 2.0.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) version: 2.0.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -894,9 +885,6 @@ importers:
react-dom: react-dom:
specifier: 19.1.0 specifier: 19.1.0
version: 19.1.0(react@19.1.0) version: 19.1.0(react@19.1.0)
react-i18next:
specifier: ^15.5.1
version: 15.5.2(i18next@25.1.3(typescript@5.8.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3)
packages/features/team-accounts: packages/features/team-accounts:
dependencies: dependencies:
@@ -982,9 +970,6 @@ importers:
react-hook-form: react-hook-form:
specifier: ^7.56.3 specifier: ^7.56.3
version: 7.57.0(react@19.1.0) version: 7.57.0(react@19.1.0)
react-i18next:
specifier: ^15.5.1
version: 15.5.2(i18next@25.1.3(typescript@5.8.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3)
sonner: sonner:
specifier: ^2.0.3 specifier: ^2.0.3
version: 2.0.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) version: 2.0.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -1026,8 +1011,8 @@ importers:
specifier: 19.1.0 specifier: 19.1.0
version: 19.1.0(react@19.1.0) version: 19.1.0(react@19.1.0)
react-i18next: react-i18next:
specifier: ^15.5.1 specifier: ^15.5.3
version: 15.5.2(i18next@25.1.3(typescript@5.8.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3) version: 15.5.3(i18next@25.1.3(typescript@5.8.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3)
packages/mailers/core: packages/mailers/core:
devDependencies: devDependencies:
@@ -1482,9 +1467,6 @@ importers:
react-hook-form: react-hook-form:
specifier: ^7.56.3 specifier: ^7.56.3
version: 7.57.0(react@19.1.0) version: 7.57.0(react@19.1.0)
react-i18next:
specifier: ^15.5.1
version: 15.5.2(i18next@25.1.3(typescript@5.8.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3)
sonner: sonner:
specifier: ^2.0.3 specifier: ^2.0.3
version: 2.0.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) version: 2.0.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -8384,22 +8366,6 @@ packages:
peerDependencies: peerDependencies:
react: ^16.8.0 || ^17 || ^18 || ^19 react: ^16.8.0 || ^17 || ^18 || ^19
react-i18next@15.5.2:
resolution: {integrity: sha512-ePODyXgmZQAOYTbZXQn5rRsSBu3Gszo69jxW6aKmlSgxKAI1fOhDwSu6bT4EKHciWPKQ7v7lPrjeiadR6Gi+1A==}
peerDependencies:
i18next: '>= 23.2.3'
react: '>= 16.8.0'
react-dom: '*'
react-native: '*'
typescript: ^5
peerDependenciesMeta:
react-dom:
optional: true
react-native:
optional: true
typescript:
optional: true
react-i18next@15.5.3: react-i18next@15.5.3:
resolution: {integrity: sha512-ypYmOKOnjqPEJZO4m1BI0kS8kWqkBNsKYyhVUfij0gvjy9xJNoG/VcGkxq5dRlVwzmrmY1BQMAmpbbUBLwC4Kw==} resolution: {integrity: sha512-ypYmOKOnjqPEJZO4m1BI0kS8kWqkBNsKYyhVUfij0gvjy9xJNoG/VcGkxq5dRlVwzmrmY1BQMAmpbbUBLwC4Kw==}
peerDependencies: peerDependencies:
@@ -19948,16 +19914,6 @@ snapshots:
dependencies: dependencies:
react: 19.1.0 react: 19.1.0
react-i18next@15.5.2(i18next@25.1.3(typescript@5.8.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3):
dependencies:
'@babel/runtime': 7.27.6
html-parse-stringify: 3.0.1
i18next: 25.1.3(typescript@5.8.3)
react: 19.1.0
optionalDependencies:
react-dom: 19.1.0(react@19.1.0)
typescript: 5.8.3
react-i18next@15.5.3(i18next@25.1.3(typescript@5.8.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3): react-i18next@15.5.3(i18next@25.1.3(typescript@5.8.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3):
dependencies: dependencies:
'@babel/runtime': 7.27.6 '@babel/runtime': 7.27.6

View File

@@ -2157,6 +2157,9 @@ AS $function$select
or has_role_on_account.account_role is null)));$function$ or has_role_on_account.account_role is null)));$function$
; ;
grant
execute on function medreport.has_role_on_account (uuid, varchar) to authenticated;
CREATE OR REPLACE FUNCTION medreport.has_same_role_hierarchy_level(target_user_id uuid, target_account_id uuid, role_name character varying) CREATE OR REPLACE FUNCTION medreport.has_same_role_hierarchy_level(target_user_id uuid, target_account_id uuid, role_name character varying)
RETURNS boolean RETURNS boolean
LANGUAGE plpgsql LANGUAGE plpgsql
@@ -2221,6 +2224,10 @@ begin
end;$function$ end;$function$
; ;
grant
execute on function medreport.has_same_role_hierarchy_level (uuid, uuid, varchar) to authenticated,
service_role;
CREATE OR REPLACE FUNCTION medreport.is_aal2() CREATE OR REPLACE FUNCTION medreport.is_aal2()
RETURNS boolean RETURNS boolean
LANGUAGE plpgsql LANGUAGE plpgsql
@@ -2236,6 +2243,8 @@ end
$function$ $function$
; ;
grant execute on function medreport.is_aal2() to authenticated;
CREATE OR REPLACE FUNCTION medreport.is_account_owner(account_id uuid) CREATE OR REPLACE FUNCTION medreport.is_account_owner(account_id uuid)
RETURNS boolean RETURNS boolean
LANGUAGE sql LANGUAGE sql
@@ -2251,6 +2260,10 @@ AS $function$select
and primary_owner_user_id = auth.uid());$function$ and primary_owner_user_id = auth.uid());$function$
; ;
grant
execute on function medreport.is_account_owner (uuid) to authenticated,
service_role;
CREATE OR REPLACE FUNCTION medreport.is_account_team_member(target_account_id uuid) CREATE OR REPLACE FUNCTION medreport.is_account_team_member(target_account_id uuid)
RETURNS boolean RETURNS boolean
LANGUAGE sql LANGUAGE sql
@@ -2262,6 +2275,10 @@ AS $function$select exists(
);$function$ );$function$
; ;
grant
execute on function medreport.is_account_team_member (uuid) to authenticated,
service_role;
CREATE OR REPLACE FUNCTION medreport.is_mfa_compliant() CREATE OR REPLACE FUNCTION medreport.is_mfa_compliant()
RETURNS boolean RETURNS boolean
LANGUAGE plpgsql LANGUAGE plpgsql
@@ -2280,6 +2297,8 @@ AS $function$begin
end$function$ end$function$
; ;
grant execute on function medreport.is_mfa_compliant() to authenticated;
CREATE OR REPLACE FUNCTION medreport.is_set(field_name text) CREATE OR REPLACE FUNCTION medreport.is_set(field_name text)
RETURNS boolean RETURNS boolean
LANGUAGE plpgsql LANGUAGE plpgsql
@@ -2294,6 +2313,9 @@ begin
end;$function$ end;$function$
; ;
grant
execute on function medreport.is_set (text) to authenticated;
CREATE OR REPLACE FUNCTION medreport.is_super_admin() CREATE OR REPLACE FUNCTION medreport.is_super_admin()
RETURNS boolean RETURNS boolean
LANGUAGE plpgsql LANGUAGE plpgsql
@@ -2311,6 +2333,8 @@ begin
end$function$ end$function$
; ;
grant execute on function medreport.is_super_admin() to authenticated;
CREATE OR REPLACE FUNCTION medreport.is_team_member(account_id uuid, user_id uuid) CREATE OR REPLACE FUNCTION medreport.is_team_member(account_id uuid, user_id uuid)
RETURNS boolean RETURNS boolean
LANGUAGE sql LANGUAGE sql
@@ -2328,6 +2352,10 @@ AS $function$select
and membership.account_id = is_team_member.account_id);$function$ and membership.account_id = is_team_member.account_id);$function$
; ;
grant
execute on function medreport.is_team_member (uuid, uuid) to authenticated,
service_role;
CREATE OR REPLACE FUNCTION medreport.revoke_nonce(p_id uuid, p_reason text DEFAULT NULL::text) CREATE OR REPLACE FUNCTION medreport.revoke_nonce(p_id uuid, p_reason text DEFAULT NULL::text)
RETURNS boolean RETURNS boolean
LANGUAGE plpgsql LANGUAGE plpgsql
@@ -2350,6 +2378,8 @@ BEGIN
END;$function$ END;$function$
; ;
grant execute on function medreport.revoke_nonce to service_role;
CREATE OR REPLACE FUNCTION medreport.team_account_workspace(account_slug text) CREATE OR REPLACE FUNCTION medreport.team_account_workspace(account_slug text)
RETURNS TABLE(id uuid, name character varying, picture_url character varying, slug text, role character varying, role_hierarchy_level integer, primary_owner_user_id uuid, subscription_status medreport.subscription_status, permissions medreport.app_permissions[]) RETURNS TABLE(id uuid, name character varying, picture_url character varying, slug text, role character varying, role_hierarchy_level integer, primary_owner_user_id uuid, subscription_status medreport.subscription_status, permissions medreport.app_permissions[])
LANGUAGE plpgsql LANGUAGE plpgsql
@@ -2489,6 +2519,10 @@ AS $function$begin
end;$function$ end;$function$
; ;
grant
execute on function medreport.update_account(p_name character varying, p_last_name text, p_personal_code text, p_phone text, p_city text, p_has_consent_personal_data boolean, p_uid uuid) to authenticated,
service_role;
CREATE OR REPLACE FUNCTION medreport.upsert_order(target_account_id uuid, target_customer_id character varying, target_order_id text, status medreport.payment_status, billing_provider medreport.billing_provider, total_amount numeric, currency character varying, line_items jsonb) CREATE OR REPLACE FUNCTION medreport.upsert_order(target_account_id uuid, target_customer_id character varying, target_order_id text, status medreport.payment_status, billing_provider medreport.billing_provider, total_amount numeric, currency character varying, line_items jsonb)
RETURNS medreport.orders RETURNS medreport.orders
LANGUAGE plpgsql LANGUAGE plpgsql
@@ -2589,6 +2623,18 @@ begin
end;$function$ end;$function$
; ;
grant
execute on function medreport.upsert_order (
uuid,
varchar,
text,
medreport.payment_status,
medreport.billing_provider,
numeric,
varchar,
jsonb
) to service_role;
CREATE OR REPLACE FUNCTION medreport.upsert_subscription(target_account_id uuid, target_customer_id character varying, target_subscription_id text, active boolean, status medreport.subscription_status, billing_provider medreport.billing_provider, cancel_at_period_end boolean, currency character varying, period_starts_at timestamp with time zone, period_ends_at timestamp with time zone, line_items jsonb, trial_starts_at timestamp with time zone DEFAULT NULL::timestamp with time zone, trial_ends_at timestamp with time zone DEFAULT NULL::timestamp with time zone) CREATE OR REPLACE FUNCTION medreport.upsert_subscription(target_account_id uuid, target_customer_id character varying, target_subscription_id text, active boolean, status medreport.subscription_status, billing_provider medreport.billing_provider, cancel_at_period_end boolean, currency character varying, period_starts_at timestamp with time zone, period_ends_at timestamp with time zone, line_items jsonb, trial_starts_at timestamp with time zone DEFAULT NULL::timestamp with time zone, trial_ends_at timestamp with time zone DEFAULT NULL::timestamp with time zone)
RETURNS medreport.subscriptions RETURNS medreport.subscriptions
LANGUAGE plpgsql LANGUAGE plpgsql
@@ -2716,6 +2762,23 @@ begin
end;$function$ end;$function$
; ;
grant
execute on function medreport.upsert_subscription (
uuid,
varchar,
text,
bool,
medreport.subscription_status,
medreport.billing_provider,
bool,
varchar,
timestamptz,
timestamptz,
jsonb,
timestamptz,
timestamptz
) to service_role;
create or replace view "medreport"."user_account_workspace" as SELECT accounts.id, create or replace view "medreport"."user_account_workspace" as SELECT accounts.id,
accounts.name, accounts.name,
accounts.picture_url, accounts.picture_url,
@@ -2727,6 +2790,10 @@ create or replace view "medreport"."user_account_workspace" as SELECT accounts.
WHERE ((accounts.primary_owner_user_id = ( SELECT auth.uid() AS uid)) AND (accounts.is_personal_account = true)) WHERE ((accounts.primary_owner_user_id = ( SELECT auth.uid() AS uid)) AND (accounts.is_personal_account = true))
LIMIT 1; LIMIT 1;
grant
select
on medreport.user_account_workspace to authenticated,
service_role;
create or replace view "medreport"."user_accounts" as SELECT account.id, create or replace view "medreport"."user_accounts" as SELECT account.id,
account.name, account.name,
@@ -2739,6 +2806,10 @@ create or replace view "medreport"."user_accounts" as SELECT account.id,
FROM medreport.accounts_memberships FROM medreport.accounts_memberships
WHERE (accounts_memberships.user_id = ( SELECT auth.uid() AS uid))))); WHERE (accounts_memberships.user_id = ( SELECT auth.uid() AS uid)))));
grant
select
on medreport.user_accounts to authenticated,
service_role;
CREATE OR REPLACE FUNCTION medreport.verify_nonce(p_token text, p_purpose text, p_user_id uuid DEFAULT NULL::uuid, p_required_scopes text[] DEFAULT NULL::text[], p_max_verification_attempts integer DEFAULT 5, p_ip inet DEFAULT NULL::inet, p_user_agent text DEFAULT NULL::text) CREATE OR REPLACE FUNCTION medreport.verify_nonce(p_token text, p_purpose text, p_user_id uuid DEFAULT NULL::uuid, p_required_scopes text[] DEFAULT NULL::text[], p_max_verification_attempts integer DEFAULT 5, p_ip inet DEFAULT NULL::inet, p_user_agent text DEFAULT NULL::text)
RETURNS jsonb RETURNS jsonb
@@ -2836,6 +2907,10 @@ BEGIN
END;$function$ END;$function$
; ;
grant
execute on function medreport.verify_nonce to authenticated,
service_role;
grant delete on table "medreport"."account_params" to "anon"; grant delete on table "medreport"."account_params" to "anon";
grant insert on table "medreport"."account_params" to "anon"; grant insert on table "medreport"."account_params" to "anon";

View File

@@ -39,3 +39,5 @@ END;$function$
grant execute on function medreport.has_consent_personal_data(uuid) grant execute on function medreport.has_consent_personal_data(uuid)
to authenticated, anon; to authenticated, anon;
grant usage on schema medreport to authenticated;