From b4b75438d2b88f2e63aba3111083468a5350841f Mon Sep 17 00:00:00 2001
From: Helena <37183360+helenarebane@users.noreply.github.com>
Date: Thu, 14 Aug 2025 11:40:53 +0300
Subject: [PATCH] Fix: add application_role to account-related fields (#48)
* Fix: fix accounts view, menu
* add migration
* add application_role to account-related fields
---
.../team-account-layout-sidebar.tsx | 7 +-
.../team-account-navigation-menu.tsx | 23 +++---
app/home/[account]/layout.tsx | 26 ++++---
.../personal-account-dropdown-container.tsx | 5 +-
.../components/personal-account-dropdown.tsx | 10 ++-
.../features/accounts/src/types/accounts.ts | 18 ++++-
.../features/team-accounts/src/server/api.ts | 3 +-
packages/supabase/src/database.types.ts | 11 ++-
.../20250814071257_update_accounts_view.sql | 76 +++++++++++++++++++
9 files changed, 146 insertions(+), 33 deletions(-)
create mode 100644 supabase/migrations/20250814071257_update_accounts_view.sql
diff --git a/app/home/[account]/_components/team-account-layout-sidebar.tsx b/app/home/[account]/_components/team-account-layout-sidebar.tsx
index 8b3c385..398fb9a 100644
--- a/app/home/[account]/_components/team-account-layout-sidebar.tsx
+++ b/app/home/[account]/_components/team-account-layout-sidebar.tsx
@@ -1,5 +1,6 @@
import type { User } from '@supabase/supabase-js';
+import { ApplicationRole } from '@kit/accounts/types/accounts';
import {
Sidebar,
SidebarContent,
@@ -18,6 +19,7 @@ type AccountModel = {
label: string | null;
value: string | null;
image: string | null;
+ application_role: ApplicationRole | null;
};
export function TeamAccountLayoutSidebar(props: {
@@ -73,7 +75,10 @@ function SidebarContainer(props: {
-
+
diff --git a/app/home/[account]/_components/team-account-navigation-menu.tsx b/app/home/[account]/_components/team-account-navigation-menu.tsx
index c09a19a..80c1bb9 100644
--- a/app/home/[account]/_components/team-account-navigation-menu.tsx
+++ b/app/home/[account]/_components/team-account-navigation-menu.tsx
@@ -1,7 +1,4 @@
-import {
- BorderedNavigationMenu,
- BorderedNavigationMenuItem,
-} from '@kit/ui/bordered-navigation-menu';
+import { useMemo } from 'react';
import { AppLogo } from '~/components/app-logo';
import { ProfileAccountDropdownContainer } from '~/components/personal-account-dropdown-container';
@@ -10,18 +7,22 @@ import { getTeamAccountSidebarConfig } from '~/config/team-account-navigation.co
// local imports
import { TeamAccountWorkspace } from '../_lib/server/team-account-workspace.loader';
import { TeamAccountNotifications } from './team-account-notifications';
-import { useMemo } from 'react';
export function TeamAccountNavigationMenu(props: {
workspace: TeamAccountWorkspace;
}) {
const { account, user, accounts: rawAccounts } = props.workspace;
- const accounts = useMemo(() => rawAccounts.map((account) => ({
- label: account.name,
- value: account.slug,
- image: account.picture_url,
- })),[rawAccounts])
+ const accounts = useMemo(
+ () =>
+ rawAccounts.map((account) => ({
+ label: account.name,
+ value: account.slug,
+ image: account.picture_url,
+ application_role: account.application_role,
+ })),
+ [rawAccounts],
+ );
const routes = getTeamAccountSidebarConfig(account.slug).routes.reduce<
Array<{
@@ -48,7 +49,7 @@ export function TeamAccountNavigationMenu(props: {
-
+
({
- label: name,
- value: slug,
- image: picture_url,
- }));
+ const accounts = data.accounts.map(
+ ({ name, slug, picture_url, application_role }) => ({
+ label: name,
+ value: slug,
+ image: picture_url,
+ application_role,
+ }),
+ );
return (
@@ -91,11 +94,14 @@ function HeaderLayout({
}>) {
const data = use(loadTeamWorkspace(account));
- const accounts = data.accounts.map(({ name, slug, picture_url }) => ({
- label: name,
- value: slug,
- image: picture_url,
- }));
+ const accounts = data.accounts.map(
+ ({ name, slug, picture_url, application_role }) => ({
+ label: name,
+ value: slug,
+ image: picture_url,
+ application_role,
+ }),
+ );
return (
diff --git a/components/personal-account-dropdown-container.tsx b/components/personal-account-dropdown-container.tsx
index 52e8e5c..fe68027 100644
--- a/components/personal-account-dropdown-container.tsx
+++ b/components/personal-account-dropdown-container.tsx
@@ -3,6 +3,7 @@
import type { User } from '@supabase/supabase-js';
import { PersonalAccountDropdown } from '@kit/accounts/personal-account-dropdown';
+import { ApplicationRole } from '@kit/accounts/types/accounts';
import { useSignOut } from '@kit/supabase/hooks/use-sign-out';
import { useUser } from '@kit/supabase/hooks/use-user';
@@ -13,7 +14,7 @@ const paths = {
home: pathsConfig.app.home,
admin: pathsConfig.app.admin,
doctor: pathsConfig.app.doctor,
- personalAccountSettings: pathsConfig.app.personalAccountSettings
+ personalAccountSettings: pathsConfig.app.personalAccountSettings,
};
const features = {
@@ -28,11 +29,13 @@ export function ProfileAccountDropdownContainer(props: {
id: string | null;
name: string | null;
picture_url: string | null;
+ application_role: ApplicationRole | null;
};
accounts: {
label: string | null;
value: string | null;
image?: string | null;
+ application_role: ApplicationRole | null;
}[];
}) {
const signOut = useSignOut();
diff --git a/packages/features/accounts/src/components/personal-account-dropdown.tsx b/packages/features/accounts/src/components/personal-account-dropdown.tsx
index 9141ff1..8f21130 100644
--- a/packages/features/accounts/src/components/personal-account-dropdown.tsx
+++ b/packages/features/accounts/src/components/personal-account-dropdown.tsx
@@ -32,6 +32,7 @@ import { cn } from '@kit/ui/utils';
import { toTitleCase } from '~/lib/utils';
import { usePersonalAccountData } from '../hooks/use-personal-account-data';
+import { ApplicationRole, ApplicationRoleEnum } from '../types/accounts';
const PERSONAL_ACCOUNT_SLUG = 'personal';
@@ -51,13 +52,13 @@ export function PersonalAccountDropdown({
id: string | null;
name: string | null;
picture_url: string | null;
- application_role: string;
+ application_role: ApplicationRole | null;
};
accounts: {
label: string | null;
value: string | null;
image?: string | null;
- application_role: string;
+ application_role: ApplicationRole | null;
}[];
signOutRequested: () => unknown;
@@ -97,13 +98,14 @@ export function PersonalAccountDropdown({
const isSuperAdmin = useMemo(() => {
const hasAdminRole =
- personalAccountData?.application_role === 'super_admin';
+ personalAccountData?.application_role === ApplicationRoleEnum.SuperAdmin;
return hasAdminRole && hasTotpFactor;
}, [user, personalAccountData, hasTotpFactor]);
const isDoctor = useMemo(() => {
- const hasDoctorRole = personalAccountData?.application_role === 'doctor';
+ const hasDoctorRole =
+ personalAccountData?.application_role === ApplicationRoleEnum.Doctor;
return hasDoctorRole && hasTotpFactor;
}, [user, personalAccountData, hasTotpFactor]);
diff --git a/packages/features/accounts/src/types/accounts.ts b/packages/features/accounts/src/types/accounts.ts
index bbc4ddb..eb9cf8e 100644
--- a/packages/features/accounts/src/types/accounts.ts
+++ b/packages/features/accounts/src/types/accounts.ts
@@ -1,7 +1,17 @@
import { Database } from '@kit/supabase/database';
-export type UserAnalysisElement = Database['medreport']['Tables']['analysis_response_elements']['Row'];
-export type UserAnalysisResponse = Database['medreport']['Tables']['analysis_responses']['Row'] & {
- elements: UserAnalysisElement[];
-};
+export type UserAnalysisElement =
+ Database['medreport']['Tables']['analysis_response_elements']['Row'];
+export type UserAnalysisResponse =
+ Database['medreport']['Tables']['analysis_responses']['Row'] & {
+ elements: UserAnalysisElement[];
+ };
export type UserAnalysis = UserAnalysisResponse[];
+
+export type ApplicationRole =
+ Database['medreport']['Tables']['accounts']['Row']['application_role'];
+export enum ApplicationRoleEnum {
+ User = 'user',
+ Doctor = 'doctor',
+ SuperAdmin = 'super_admin',
+}
\ No newline at end of file
diff --git a/packages/features/team-accounts/src/server/api.ts b/packages/features/team-accounts/src/server/api.ts
index 60339ff..a57b68d 100644
--- a/packages/features/team-accounts/src/server/api.ts
+++ b/packages/features/team-accounts/src/server/api.ts
@@ -114,7 +114,8 @@ export class TeamAccountsApi {
role,
name,
slug,
- picture_url
+ picture_url,
+ application_role
)
`,
)
diff --git a/packages/supabase/src/database.types.ts b/packages/supabase/src/database.types.ts
index 8398b9d..2c8c64b 100644
--- a/packages/supabase/src/database.types.ts
+++ b/packages/supabase/src/database.types.ts
@@ -1563,6 +1563,9 @@ export type Database = {
Views: {
user_account_workspace: {
Row: {
+ application_role:
+ | Database["medreport"]["Enums"]["application_role"]
+ | null
id: string | null
name: string | null
picture_url: string | null
@@ -1574,6 +1577,9 @@ export type Database = {
}
user_accounts: {
Row: {
+ application_role:
+ | Database["medreport"]["Enums"]["application_role"]
+ | null
id: string | null
name: string | null
picture_url: string | null
@@ -1634,7 +1640,9 @@ export type Database = {
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
@@ -1801,6 +1809,7 @@ export type Database = {
primary_owner_user_id: string
subscription_status: Database["medreport"]["Enums"]["subscription_status"]
permissions: Database["medreport"]["Enums"]["app_permissions"][]
+ application_role: Database["medreport"]["Enums"]["application_role"]
}[]
}
transfer_team_account_ownership: {
diff --git a/supabase/migrations/20250814071257_update_accounts_view.sql b/supabase/migrations/20250814071257_update_accounts_view.sql
new file mode 100644
index 0000000..0ad5bd9
--- /dev/null
+++ b/supabase/migrations/20250814071257_update_accounts_view.sql
@@ -0,0 +1,76 @@
+CREATE OR REPLACE VIEW medreport.user_accounts AS
+SELECT
+ account.id,
+ account.name,
+ account.picture_url,
+ account.slug,
+ membership.account_role AS role,
+ COALESCE(account.application_role, 'user') AS application_role
+FROM medreport.accounts account
+JOIN medreport.accounts_memberships membership ON (account.id = membership.account_id)
+WHERE (
+ membership.user_id = (SELECT auth.uid())
+ AND account.is_personal_account = false
+ AND account.id IN (
+ SELECT accounts_memberships.account_id
+ FROM medreport.accounts_memberships
+ WHERE accounts_memberships.user_id = (SELECT auth.uid())
+ )
+);
+
+GRANT SELECT ON medreport.user_accounts TO authenticated, service_role;
+
+
+DROP FUNCTION IF EXISTS medreport.team_account_workspace(text);
+
+CREATE 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[], application_role medreport.application_role)
+ LANGUAGE plpgsql
+ SET search_path TO ''
+AS $function$begin
+ return QUERY
+ select
+ accounts.id,
+ accounts.name,
+ accounts.picture_url,
+ accounts.slug,
+ accounts.application_role,
+ accounts_memberships.account_role,
+ roles.hierarchy_level,
+ accounts.primary_owner_user_id,
+ subscriptions.status,
+ array_agg(role_permissions.permission)
+ from
+ medreport.accounts
+ join medreport.accounts_memberships on accounts.id = accounts_memberships.account_id
+ left join medreport.subscriptions on accounts.id = subscriptions.account_id
+ join medreport.roles on accounts_memberships.account_role = roles.name
+ left join medreport.role_permissions on accounts_memberships.account_role = role_permissions.role
+ where
+ accounts.slug = account_slug
+ and medreport.accounts_memberships.user_id = (select auth.uid())
+ group by
+ accounts.id,
+ accounts_memberships.account_role,
+ subscriptions.status,
+ roles.hierarchy_level;
+end;$function$;
+
+GRANT EXECUTE ON FUNCTION medreport.team_account_workspace(text) TO authenticated, service_role;
+
+create or replace view medreport.user_account_workspace as SELECT accounts.id,
+ accounts.name,
+ accounts.picture_url,
+ ( SELECT subscriptions.status
+ FROM medreport.subscriptions
+ WHERE (subscriptions.account_id = accounts.id)
+ LIMIT 1) AS subscription_status,
+ accounts.application_role
+ FROM medreport.accounts
+ WHERE ((accounts.primary_owner_user_id = ( SELECT auth.uid() AS uid)) AND (accounts.is_personal_account = true))
+ LIMIT 1;
+
+grant
+ select
+ on medreport.user_account_workspace to authenticated,
+ service_role;