diff --git a/frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/Maskinporten/ScopeListContainer/SaveStatus/SaveStatus.module.css b/frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/Maskinporten/ScopeListContainer/SaveStatus/SaveStatus.module.css
new file mode 100644
index 00000000000..afca290346f
--- /dev/null
+++ b/frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/Maskinporten/ScopeListContainer/SaveStatus/SaveStatus.module.css
@@ -0,0 +1,11 @@
+.savingContainer {
+ display: flex;
+ align-items: center;
+ gap: var(--fds-spacing-1);
+ margin-top: var(--fds-spacing-4);
+}
+
+.savedIcon {
+ font-size: var(--fds-sizing-8);
+ color: var(--fds-semantic-text-neutral-subtle);
+}
diff --git a/frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/Maskinporten/ScopeListContainer/SaveStatus/SaveStatus.test.tsx b/frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/Maskinporten/ScopeListContainer/SaveStatus/SaveStatus.test.tsx
new file mode 100644
index 00000000000..786272e101f
--- /dev/null
+++ b/frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/Maskinporten/ScopeListContainer/SaveStatus/SaveStatus.test.tsx
@@ -0,0 +1,32 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import { SaveStatus } from './SaveStatus';
+import { textMock } from '@studio/testing/mocks/i18nMock';
+
+describe('SaveStatus', () => {
+ it('should display a spinner while pending', () => {
+ render();
+
+ expect(
+ screen.getByText(textMock('settings_modal.maskinporten_tab_save_scopes_pending')),
+ ).toBeInTheDocument();
+
+ expect(
+ screen.getByTitle(textMock('settings_modal.maskinporten_tab_save_scopes_pending_spinner')),
+ ).toBeInTheDocument();
+ });
+
+ it('should render saved status with checkmark icon', () => {
+ render();
+
+ expect(
+ screen.getByText(textMock('settings_modal.maskinporten_tab_save_scopes_complete')),
+ ).toBeInTheDocument();
+ });
+
+ it('should render nothing when neither pending nor saved', () => {
+ const { container } = render();
+
+ expect(container).toBeEmptyDOMElement();
+ });
+});
diff --git a/frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/Maskinporten/ScopeListContainer/SaveStatus/SaveStatus.tsx b/frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/Maskinporten/ScopeListContainer/SaveStatus/SaveStatus.tsx
new file mode 100644
index 00000000000..99047940f7c
--- /dev/null
+++ b/frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/Maskinporten/ScopeListContainer/SaveStatus/SaveStatus.tsx
@@ -0,0 +1,50 @@
+import React, { type ReactElement } from 'react';
+import classes from './SaveStatus.module.css';
+import { StudioParagraph, StudioSpinner } from '@studio/components';
+import { useTranslation } from 'react-i18next';
+import { CheckmarkIcon } from '@studio/icons';
+
+type SaveStatusProps = {
+ isPending: boolean;
+ isSaved: boolean;
+};
+
+export const SaveStatus = ({ isPending, isSaved }: SaveStatusProps): ReactElement => {
+ const { t } = useTranslation();
+
+ if (isPending) {
+ return (
+
+ );
+ }
+ if (isSaved) {
+ return ;
+ }
+ return null;
+};
+
+type SaveStatusContentProps = {
+ text: string;
+ isPending?: boolean;
+};
+
+const SaveStatusContent = ({ text, isPending = false }: SaveStatusContentProps): ReactElement => {
+ const { t } = useTranslation();
+
+ return (
+
+ {text}
+ {isPending ? (
+
+ ) : (
+
+ )}
+
+ );
+};
diff --git a/frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/Maskinporten/ScopeListContainer/SaveStatus/index.ts b/frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/Maskinporten/ScopeListContainer/SaveStatus/index.ts
new file mode 100644
index 00000000000..e7dcaec9432
--- /dev/null
+++ b/frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/Maskinporten/ScopeListContainer/SaveStatus/index.ts
@@ -0,0 +1 @@
+export { SaveStatus } from './SaveStatus';
diff --git a/frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/Maskinporten/ScopeListContainer/ScopeList/ScopeList.tsx b/frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/Maskinporten/ScopeListContainer/ScopeList/ScopeList.tsx
index 2860c815503..3f150ac7a52 100644
--- a/frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/Maskinporten/ScopeListContainer/ScopeList/ScopeList.tsx
+++ b/frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/Maskinporten/ScopeListContainer/ScopeList/ScopeList.tsx
@@ -1,6 +1,5 @@
import React, { type ChangeEvent, type ReactElement } from 'react';
import classes from './ScopeList.module.css';
-
import {
StudioCheckboxTable,
type StudioCheckboxTableRowElement,
@@ -25,6 +24,7 @@ import { GetInTouchWith } from 'app-shared/getInTouch';
import { EmailContactProvider } from 'app-shared/getInTouch/providers';
import { LoggedInTitle } from '../LoggedInTitle';
import { useUpdateSelectedMaskinportenScopesMutation } from 'app-development/hooks/mutations/useUpdateSelectedMaskinportenScopesMutation';
+import { SaveStatus } from '../SaveStatus';
export type ScopeListProps = {
maskinPortenScopes: MaskinportenScope[];
@@ -33,8 +33,11 @@ export type ScopeListProps = {
export const ScopeList = ({ maskinPortenScopes, selectedScopes }: ScopeListProps): ReactElement => {
const { t } = useTranslation();
- const { mutate: mutateSelectedMaskinportenScopes } =
- useUpdateSelectedMaskinportenScopesMutation();
+ const {
+ mutate: mutateSelectedMaskinportenScopes,
+ isPending: isPendingSaveScopes,
+ isSuccess: scopesSaved,
+ } = useUpdateSelectedMaskinportenScopesMutation();
const checkboxTableRowElements: StudioCheckboxTableRowElement[] = mapScopesToRowElements(
maskinPortenScopes,
@@ -103,6 +106,7 @@ export const ScopeList = ({ maskinPortenScopes, selectedScopes }: ScopeListProps
))}
+
);
};
diff --git a/frontend/language/src/nb.json b/frontend/language/src/nb.json
index e836948c1f2..bfd5859b960 100644
--- a/frontend/language/src/nb.json
+++ b/frontend/language/src/nb.json
@@ -1023,9 +1023,12 @@
"settings_modal.maskinporten_tab_available_scopes_description_help": "Hvis du trenger tilgang til flere scopes i denne listen, <0> tar du kontakt med Altinn servicedesk.0>",
"settings_modal.maskinporten_tab_available_scopes_description_help_link": "tar du kontakt med Altinn servicedesk.",
"settings_modal.maskinporten_tab_available_scopes_title": "Scopes for denne virksomheten",
- "settings_modal.maskinporten_tab_description": "Maskinporten autentiserer og autoriserer API-er som skal brukes i apper. I Maskinporten kan du lage hvilke scopes en app skal ha fra og til andre systemer, for eksempel tilgang til persondata.",
+ "settings_modal.maskinporten_tab_description": "Maskinporten autentiserer og autoriserer API-er som skal brukes i apper. I Maskinporten kan du sette opp hvilke scopes en app skal ha fra og til andre systemer, for eksempel tilgang til persondata.",
"settings_modal.maskinporten_tab_login_with_ansattporten": "Logg inn med Ansattporten",
"settings_modal.maskinporten_tab_login_with_description": "Med Ansattporten logger du inn på vegne av virksomheten. Her kan du se og velge scopes.",
+ "settings_modal.maskinporten_tab_save_scopes_complete": "Lagret",
+ "settings_modal.maskinporten_tab_save_scopes_pending": "Lagrer...",
+ "settings_modal.maskinporten_tab_save_scopes_pending_spinner": "Lagrer scopes",
"settings_modal.maskinporten_tab_title": "Velg scopes fra Maskinporten",
"settings_modal.policy_tab_heading": "Tilganger",
"settings_modal.setup_tab_heading": "Oppsett",