diff --git a/frontend/resourceadm/components/ResourceAdmHeader/ResourceAdmHeader.test.tsx b/frontend/resourceadm/components/ResourceAdmHeader/ResourceAdmHeader.test.tsx
new file mode 100644
index 00000000000..69989ead373
--- /dev/null
+++ b/frontend/resourceadm/components/ResourceAdmHeader/ResourceAdmHeader.test.tsx
@@ -0,0 +1,84 @@
+import React from 'react';
+import { MemoryRouter } from 'react-router-dom';
+import { render, screen } from '@testing-library/react';
+import { textMock } from '@studio/testing/mocks/i18nMock';
+import userEvent from '@testing-library/user-event';
+import { queriesMock } from 'app-shared/mocks/queriesMock';
+import { createQueryClientMock } from 'app-shared/mocks/queryClientMock';
+import { ServicesContextProvider } from 'app-shared/contexts/ServicesContext';
+import { ResourceAdmHeader } from './ResourceAdmHeader';
+
+const mainOrganization = {
+ avatar_url: '',
+ id: 1,
+ username: 'ttd',
+ full_name: 'Testdepartementet',
+};
+const otherOrganization = {
+ avatar_url: '',
+ id: 2,
+ username: 'skd',
+ full_name: 'Skatteetaten',
+};
+const organizations = [mainOrganization, otherOrganization];
+
+const testUser = {
+ avatar_url: '',
+ email: 'test@test.no',
+ full_name: 'Test Testersen',
+ id: 11,
+ login: 'test',
+ userType: 1,
+};
+
+const resourceId = 'res-id';
+
+const navigateMock = jest.fn();
+jest.mock('react-router-dom', () => ({
+ ...jest.requireActual('react-router-dom'),
+ useNavigate: () => navigateMock,
+ useParams: () => ({
+ org: mainOrganization.username,
+ resourceId: resourceId,
+ }),
+}));
+
+describe('ResourceAdmHeader', () => {
+ afterEach(jest.clearAllMocks);
+
+ it('should show org name and resource id in header', () => {
+ renderResourceAdmHeader();
+
+ expect(screen.getByText(`${mainOrganization.full_name} / ${resourceId}`)).toBeInTheDocument();
+ });
+
+ it('should navigate to new org when another org is chosen in menu', async () => {
+ const user = userEvent.setup();
+ renderResourceAdmHeader();
+
+ const menuTrigger = screen.getByRole('button', {
+ name: textMock('shared.header_user_for_org', {
+ user: testUser.full_name,
+ org: mainOrganization.full_name,
+ }),
+ });
+ await user.click(menuTrigger);
+
+ const otherOrgButton = screen.getByRole('menuitemradio', {
+ name: otherOrganization.full_name,
+ });
+ await user.click(otherOrgButton);
+
+ expect(navigateMock).toHaveBeenCalled();
+ });
+});
+
+const renderResourceAdmHeader = () => {
+ return render(
+
+
+
+
+ ,
+ );
+};
diff --git a/frontend/resourceadm/components/ResourceAdmHeader/ResourceAdmHeader.tsx b/frontend/resourceadm/components/ResourceAdmHeader/ResourceAdmHeader.tsx
new file mode 100644
index 00000000000..ff7c2ae54e6
--- /dev/null
+++ b/frontend/resourceadm/components/ResourceAdmHeader/ResourceAdmHeader.tsx
@@ -0,0 +1,98 @@
+import React from 'react';
+import { useNavigate } from 'react-router-dom';
+import { useTranslation } from 'react-i18next';
+import {
+ StudioAvatar,
+ StudioPageHeader,
+ type StudioProfileMenuGroup,
+ useMediaQuery,
+ type StudioProfileMenuItem,
+} from '@studio/components';
+import { getOrgNameByUsername } from '../../utils/userUtils';
+import { type Organization } from 'app-shared/types/Organization';
+import { MEDIA_QUERY_MAX_WIDTH } from 'app-shared/constants';
+import { useLogoutMutation } from 'app-shared/hooks/mutations/useLogoutMutation';
+import type { User } from 'app-shared/types/Repository';
+import { useUrlParams } from '../../hooks/useUrlParams';
+import { getAppName } from '../../utils/stringUtils';
+
+interface ResourceAdmHeaderProps {
+ organizations: Organization[];
+ user: User;
+}
+
+export const ResourceAdmHeader = ({ organizations, user }: ResourceAdmHeaderProps) => {
+ const { org, resourceId } = useUrlParams();
+ const resourcePath = resourceId ? ` / ${resourceId}` : '';
+ const pageHeaderTitle: string = `${getOrgNameByUsername(org, organizations)}${resourcePath}`;
+
+ return (
+
+
+
+
+
+
+
+
+ );
+};
+
+const DashboardHeaderMenu = ({ organizations, user }: ResourceAdmHeaderProps) => {
+ const { t } = useTranslation();
+ const showButtonText = !useMediaQuery(MEDIA_QUERY_MAX_WIDTH);
+ const { org, app } = useUrlParams();
+ const { mutate: logout } = useLogoutMutation();
+ const navigate = useNavigate();
+ const selectableOrgs = organizations;
+
+ const triggerButtonText = t('shared.header_user_for_org', {
+ user: user?.full_name || user?.login,
+ org: getOrgNameByUsername(org, selectableOrgs),
+ });
+ const repoPath = `/repos/${org}/${app}`;
+
+ const handleSetSelectedContext = (context: string) => {
+ navigate(`/${context}/${getAppName(context)}${location.search}`);
+ };
+
+ const selectableOrgMenuItems: StudioProfileMenuItem[] = selectableOrgs.map(
+ (selectableOrg: Organization) => ({
+ action: { type: 'button', onClick: () => handleSetSelectedContext(selectableOrg.username) },
+ itemName: selectableOrg?.full_name || selectableOrg.username,
+ isActive: org === selectableOrg.username,
+ }),
+ );
+
+ const giteaMenuItem: StudioProfileMenuItem = {
+ action: { type: 'link', href: repoPath },
+ itemName: t('shared.header_go_to_gitea'),
+ };
+
+ const logOutMenuItem: StudioProfileMenuItem = {
+ action: { type: 'button', onClick: logout },
+ itemName: t('shared.header_logout'),
+ };
+
+ const profileMenuGroups: StudioProfileMenuGroup[] = [
+ { items: selectableOrgMenuItems },
+ { items: [giteaMenuItem, logOutMenuItem] },
+ ];
+
+ return (
+
+ }
+ profileMenuGroups={profileMenuGroups}
+ />
+ );
+};
diff --git a/frontend/resourceadm/components/ResourceAdmHeader/index.ts b/frontend/resourceadm/components/ResourceAdmHeader/index.ts
new file mode 100644
index 00000000000..c10ac33b2c8
--- /dev/null
+++ b/frontend/resourceadm/components/ResourceAdmHeader/index.ts
@@ -0,0 +1 @@
+export { ResourceAdmHeader } from './ResourceAdmHeader';
diff --git a/frontend/resourceadm/hooks/useUrlParams/useUrlParams.ts b/frontend/resourceadm/hooks/useUrlParams/useUrlParams.ts
index 70a3147557e..2170785d108 100644
--- a/frontend/resourceadm/hooks/useUrlParams/useUrlParams.ts
+++ b/frontend/resourceadm/hooks/useUrlParams/useUrlParams.ts
@@ -1,4 +1,5 @@
import { useParams } from 'react-router-dom';
+import { getAppName } from '../../utils/stringUtils';
interface ResourceAdminUrlParams {
org: string;
@@ -14,7 +15,7 @@ export const useUrlParams = (): Readonly => {
return {
org: params.org,
- app: `${params.org}-resources`,
+ app: getAppName(params.org),
env: params.env,
resourceId: params.resourceId,
accessListId: params.accessListId,
diff --git a/frontend/resourceadm/pages/PageLayout/PageLayout.tsx b/frontend/resourceadm/pages/PageLayout/PageLayout.tsx
index 2751be58d65..76600ed2b19 100644
--- a/frontend/resourceadm/pages/PageLayout/PageLayout.tsx
+++ b/frontend/resourceadm/pages/PageLayout/PageLayout.tsx
@@ -1,11 +1,6 @@
-import React, { useEffect, useMemo, useRef } from 'react';
+import React, { useEffect, useRef } from 'react';
import classes from './PageLayout.module.css';
import { Outlet, useLocation, useNavigate } from 'react-router-dom';
-import AppHeader, {
- HeaderContext,
- SelectedContextType,
-} from 'app-shared/navigation/main-header/Header';
-import type { IHeaderContext } from 'app-shared/navigation/main-header/Header';
import { userHasAccessToOrganization } from '../../utils/userUtils';
import { useOrganizationsQuery } from '../../hooks/queries';
import { useRepoStatusQuery, useUserQuery } from 'app-shared/hooks/queries';
@@ -13,6 +8,7 @@ import { GiteaHeader } from 'app-shared/components/GiteaHeader';
import { useUrlParams } from '../../hooks/useUrlParams';
import postMessages from 'app-shared/utils/postMessages';
import { MergeConflictModal } from '../../components/MergeConflictModal';
+import { ResourceAdmHeader } from '../../components/ResourceAdmHeader';
/**
* @component
@@ -26,7 +22,7 @@ export const PageLayout = (): React.JSX.Element => {
const { data: organizations } = useOrganizationsQuery();
const mergeConflictModalRef = useRef(null);
- const { org = SelectedContextType.Self, app } = useUrlParams();
+ const { org, app } = useUrlParams();
const { data: repoStatus } = useRepoStatusQuery(org, app);
const navigate = useNavigate();
@@ -63,22 +59,11 @@ export const PageLayout = (): React.JSX.Element => {
};
}, [mergeConflictModalRef]);
- const headerContextValue: IHeaderContext = useMemo(
- () => ({
- selectableOrgs: organizations,
- user,
- }),
- [organizations, user],
- );
-
return (
<>
-
-
- {/* TODO - Find out if should be replaced to be the same as studio */}
-
-
-
+
+ {organizations && user && }
+
>
);
diff --git a/frontend/resourceadm/pages/RedirectPage/RedirectPage.tsx b/frontend/resourceadm/pages/RedirectPage/RedirectPage.tsx
index 4f73093d85d..a7e44e2efaf 100644
--- a/frontend/resourceadm/pages/RedirectPage/RedirectPage.tsx
+++ b/frontend/resourceadm/pages/RedirectPage/RedirectPage.tsx
@@ -3,6 +3,7 @@ import { Navigate } from 'react-router-dom';
import classes from './RedirectPage.module.css';
import { ErrorPage } from '../ErrorPage';
import { useUrlParams } from '../../hooks/useUrlParams';
+import { getAppName } from '../../utils/stringUtils';
/**
* @component
@@ -19,7 +20,7 @@ export const RedirectPage = (): React.JSX.Element => {
// Error page if user has chosen "Alle"
) : (
-
+
)}
);
diff --git a/frontend/resourceadm/utils/stringUtils/index.ts b/frontend/resourceadm/utils/stringUtils/index.ts
index a8b3f0dc76f..4326770255e 100644
--- a/frontend/resourceadm/utils/stringUtils/index.ts
+++ b/frontend/resourceadm/utils/stringUtils/index.ts
@@ -4,4 +4,5 @@ export {
isSePrefix,
stringNumberToAriaLabel,
isOrgNrString,
+ getAppName,
} from './stringUtils';
diff --git a/frontend/resourceadm/utils/stringUtils/stringUtils.ts b/frontend/resourceadm/utils/stringUtils/stringUtils.ts
index eb9f7be6f85..8d319fb4fbc 100644
--- a/frontend/resourceadm/utils/stringUtils/stringUtils.ts
+++ b/frontend/resourceadm/utils/stringUtils/stringUtils.ts
@@ -30,3 +30,7 @@ export const stringNumberToAriaLabel = (s: string): string => {
export const isOrgNrString = (s: string): boolean => {
return /^\d{9}$/.test(s); // regex for search string is exactly 9 digits
};
+
+export const getAppName = (org: string): string => {
+ return `${org}-resources`;
+};
diff --git a/frontend/resourceadm/utils/userUtils/index.ts b/frontend/resourceadm/utils/userUtils/index.ts
index 20865c094f2..c1b0eaffa3b 100644
--- a/frontend/resourceadm/utils/userUtils/index.ts
+++ b/frontend/resourceadm/utils/userUtils/index.ts
@@ -1 +1 @@
-export { userHasAccessToOrganization } from './userUtils';
+export { userHasAccessToOrganization, getOrgNameByUsername } from './userUtils';
diff --git a/frontend/resourceadm/utils/userUtils/userUtils.ts b/frontend/resourceadm/utils/userUtils/userUtils.ts
index 57ee2512e92..9373106c2b9 100644
--- a/frontend/resourceadm/utils/userUtils/userUtils.ts
+++ b/frontend/resourceadm/utils/userUtils/userUtils.ts
@@ -14,3 +14,8 @@ export const userHasAccessToOrganization = ({
return Boolean(orgs.find((x) => x.username === org));
};
+
+export const getOrgNameByUsername = (username: string, orgs: Organization[]) => {
+ const org = orgs?.find((o) => o.username === username);
+ return org?.full_name || org?.username;
+};