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;