Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
aa9d2d6
✨ Add SwitchWrapper style for improved layout in Account component
bjorngoa Dec 4, 2025
8053fc2
✨ Add custom hook for toggling active environment features
bjorngoa Dec 4, 2025
fa68c1a
✨ Implement custom hook for managing environment feature toggles
bjorngoa Dec 4, 2025
2c8c3c2
✨ Add EnvironmentAvatar component for displaying active environment f…
bjorngoa Dec 4, 2025
9a857f6
✨ Add PointToProdFeaturesLocalStorageKey enum for feature toggle keys
bjorngoa Dec 4, 2025
acf9a0f
✨ Add environment feature toggle functionality to Account component
bjorngoa Dec 4, 2025
eaa8d9c
:recycle: Refactor feature toggle handler in Account component
bjorngoa Dec 4, 2025
1310fce
:recycle: Remove unused feature toggle handler in Account component
bjorngoa Dec 4, 2025
91fa6b5
:recycle: remove redundant fragment
bjorngoa Dec 4, 2025
0ea0fba
:recycle: Remove empty Typography component in Account component
bjorngoa Dec 4, 2025
c82e3f6
:recycle: Refactor environment toggle implementation in Account compo…
bjorngoa Dec 9, 2025
dc7a49f
:sparkles: Add EnvironmentToggle component for managing environment s…
bjorngoa Dec 9, 2025
00277d3
:recycle: Update import path for PointToProdFeaturesLocalStorageKey a…
bjorngoa Dec 9, 2025
0866f56
:recycle: Update import path for PointToProdFeaturesLocalStorageKey i…
bjorngoa Dec 9, 2025
e2db4a8
:recycle: Add enableEnvironmentToggle prop to Account component for c…
bjorngoa Dec 9, 2025
d03b4bd
:recycle: remove old way of applying current env settings
bjorngoa Dec 9, 2025
51d250c
:fire: Remove unused PointToProdFeaturesLocalStorageKey enum from Env…
bjorngoa Dec 9, 2025
a53fceb
:recycle: Refactor EnvironmentToggle rendering to use EnvironmentTogg…
bjorngoa Dec 9, 2025
ae73f17
:recycle: Add placeholder and helper text to EnvironmentToggle
bjorngoa Dec 9, 2025
01c5fa2
:recycle: StatusAvatar component for displaying user status with cust…
bjorngoa Dec 10, 2025
8b2c388
:recycle: Update AccountAvatar to use StatusAvatar and support enviro…
bjorngoa Dec 10, 2025
18488c4
:recycle: Refactor AccountButton to use StatusAvatar and integrate en…
bjorngoa Dec 10, 2025
6b93af2
:recycle: Integrate environment toggle props into AccountButton and A…
bjorngoa Dec 10, 2025
8cfc385
:recycle: Remove unused console log for environment toggle in Account…
bjorngoa Dec 10, 2025
205010a
:recycle: Enhance AccountAvatar to support combined status display an…
bjorngoa Dec 10, 2025
9e0031a
:recycle: Refactor AccountButton to enhance avatar rendering and stat…
bjorngoa Dec 10, 2025
ef55bf4
:recycle: Replace ImpersonateAvatar with StatusAvatar in DeleteUser c…
bjorngoa Dec 10, 2025
fb727a7
:fire: replaced with StatusAvatar
bjorngoa Dec 10, 2025
da7d42e
:recycle: Remove unused console log from EnvironmentToggle component
bjorngoa Dec 10, 2025
61d015a
:recycle: Refactor StatusAvatar to improve variant handling and color…
bjorngoa Dec 10, 2025
88a6e3a
:fire: not needed, passed in props instead
bjorngoa Dec 10, 2025
786b6b3
:recycle: Rename RoleChips to StatusChips and update usage in Account…
bjorngoa Dec 10, 2025
fde8ffb
:recycle: update prop naming
bjorngoa Dec 10, 2025
3a38bbd
:recycle: update interface name
bjorngoa Dec 10, 2025
5e2babe
:recycle: Update StatusChips prop naming from roles to statuses in Ac…
bjorngoa Dec 10, 2025
ba766f7
:recycle: Refactor environment toggle handling and add cleanStatusTex…
bjorngoa Dec 10, 2025
c8e4a47
:recycle: Rename cleanStatusText to getActiveFeatureDisplayName and u…
bjorngoa Dec 10, 2025
4a55318
:pencil2: Fix typo in environmentToggle import and rename file for co…
bjorngoa Dec 10, 2025
07c2976
:recycle: use same text as used before when active impersonation
bjorngoa Dec 10, 2025
f1e59ce
:recycle: Replace hardcoded env-toggle key with ENVIRONMENT_TOGGLE_KE…
bjorngoa Dec 11, 2025
39c6fbb
:recycle: Refactor environment toggle components to use EnvironmentTo…
bjorngoa Dec 12, 2025
0a40eb9
:recycle: Update environment toggle components to use formatFeatureNa…
bjorngoa Dec 12, 2025
6441f55
:bookmark: `10.8.0`
bjorngoa Dec 12, 2025
bb24fb0
:recycle: Introduce StatusVariant type and refactor StatusAvatar to u…
bjorngoa Dec 12, 2025
90fb463
:recycle: Refactor AccountAvatar and AccountButton to utilize getVari…
bjorngoa Dec 12, 2025
19083b1
:recycle: Remove unused getVariantColors import from AccountAvatar
bjorngoa Dec 12, 2025
70b87c9
:recycle: Refactor AccountAvatar and AccountButton to use getVariantC…
bjorngoa Dec 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
296 changes: 149 additions & 147 deletions bun.lock

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@equinor/amplify-component-lib",
"version": "10.7.0",
"version": "10.8.0",
"description": "Frontend Typescript components for the Amplify team",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down Expand Up @@ -53,7 +53,7 @@
},
"devDependencies": {
"@equinor/eds-icons": "^0.22.0",
"@equinor/subsurface-app-management": "5.1.0",
"@equinor/subsurface-app-management": "5.2.0",
"@eslint/js": "^9.22.0",
"@faker-js/faker": "^9.6.0",
"@storybook/addon-coverage": "^2.0.0",
Expand Down Expand Up @@ -114,7 +114,7 @@
"@equinor/eds-core-react": "0.44.0",
"@equinor/eds-data-grid-react": "^0.7.5",
"@equinor/eds-icons": "*",
"@equinor/subsurface-app-management": "^5.1.0",
"@equinor/subsurface-app-management": "^5.2.0",
"@tanstack/react-query": "*",
"@tanstack/react-router": "*",
"@tiptap/core": "^3.1.0",
Expand Down
2 changes: 1 addition & 1 deletion public/mockServiceWorker.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* - Please do NOT modify this file.
*/

const PACKAGE_VERSION = '2.12.1'
const PACKAGE_VERSION = '2.12.4'
const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82'
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
const activeClientIds = new Set()
Expand Down
52 changes: 52 additions & 0 deletions src/atoms/utils/environmentToggle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { EnvironmentToggleFeatures } from '@equinor/subsurface-app-management';

import { colors } from 'src/atoms';
import { StatusVariant } from 'src/organisms/TopBar/Account/Account.types';

export function formatFeatureName(activeFeature: EnvironmentToggleFeatures) {
return (
activeFeature.charAt(0).toUpperCase() +
activeFeature.slice(1).replaceAll('-', ' ')
);
}

type ColorConfig = {
border: string;
background: string;
chipBackground: string;
outline: string;
};

const VARIANT_COLORS: Record<StatusVariant, ColorConfig> = {
combined: {
border: colors.interactive.success__resting.rgba,
background: colors.interactive.warning__text.rgba,
chipBackground: colors.interactive.warning__resting.rgba,
outline: colors.interactive.success__resting.rgba,
},
environment: {
border: colors.interactive.success__resting.rgba,
background: colors.interactive.success__text.rgba,
chipBackground: colors.interactive.success__resting.rgba,
outline: colors.interactive.success__resting.rgba,
},
impersonate: {
border: colors.interactive.warning__resting.rgba,
background: colors.interactive.warning__text.rgba,
chipBackground: colors.interactive.warning__resting.rgba,
outline: colors.interactive.warning__resting.rgba,
},
};

const DEFAULT_COLORS: ColorConfig = {
border: colors.interactive.warning__resting.rgba,
background: colors.interactive.warning__text.rgba,
chipBackground: colors.interactive.warning__resting.rgba,
outline: colors.interactive.warning__resting.rgba,
};

export function getVariantColors(
variant: StatusVariant | undefined
): ColorConfig {
return variant ? VARIANT_COLORS[variant] : DEFAULT_COLORS;
}
10 changes: 10 additions & 0 deletions src/organisms/TopBar/Account/Account.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ export const Container = styled.div`
gap: ${spacings.small};
`;

export const EnvironmentToggleWrapper = styled.div`
padding: ${spacings.medium} ${spacings.medium} 0 ${spacings.medium};
display: flex;
flex-direction: column;
align-items: stretch;
gap: ${spacings.small};
`;

export const TextContent = styled.div`
display: flex;
flex-direction: column;
Expand All @@ -27,8 +35,10 @@ export const TextContent = styled.div`

export const ButtonWrapper = styled.div`
display: grid;
grid-auto-flow: column;
margin-top: ${spacings.x_large};
justify-content: center;
gap: ${spacings.small};
`;
export const OpenImpersonationMenuButton = styled.button`
display: grid;
Expand Down
42 changes: 37 additions & 5 deletions src/organisms/TopBar/Account/Account.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,34 @@ import {

import { Icon, Typography } from '@equinor/eds-core-react';
import { log_out } from '@equinor/eds-icons';
import { ENVIRONMENT_TOGGLE_KEY } from '@equinor/subsurface-app-management';

import { TopBarMenu } from '../TopBarMenu';
import { useActiveImpersonationUser } from './ImpersonateMenu/hooks/useActiveImpersonationUser';
import { useCanImpersonate } from './ImpersonateMenu/hooks/useCanImpersonate';
import { useMappedRoles } from './ImpersonateMenu/hooks/useMappedRoles';
import { useStopImpersonation } from './ImpersonateMenu/hooks/useStopImpersonation';
import { ImpersonateMenu } from './ImpersonateMenu/ImpersonateMenu';
import { ButtonWrapper, Container, TextContent } from './Account.styles';
import {
ButtonWrapper,
Container,
EnvironmentToggleWrapper,
TextContent,
} from './Account.styles';
import { AccountAvatar } from './AccountAvatar';
import { AccountButton } from './AccountButton';
import { ActiveUserImpersonationButton } from './ActiveUserImpersonationButton';
import { ImpersonateButton } from './ImpersonateButton';
import { RoleChips } from './RoleChips';
import { RoleList } from './RoleList';
import { useLocalStorage } from 'src/atoms';
import { EnvironmentType } from 'src/atoms/enums/Environment';
import { Field } from 'src/atoms/types/Field';
import { environment } from 'src/atoms/utils/auth_environment';
import { SelectOptionRequired } from 'src/molecules';
import { Button } from 'src/molecules/Button/Button';
import { EnvironmentToggle } from 'src/organisms/TopBar/Account/EnvironmentToggle';
import { impersonateUserDtoToFullName } from 'src/organisms/TopBar/Account/ImpersonateMenu/Impersonate.utils';
import { StatusChips } from 'src/organisms/TopBar/Account/StatusChips';
import { useAuth } from 'src/providers/AuthProvider/AuthProvider';

export interface AccountProps {
Expand All @@ -43,6 +52,7 @@ export interface AccountProps {
children?: ReactNode;
availableFields?: Field[];
availableWells?: string[];
enableEnvironmentToggle?: boolean;
}

export const Account: FC<AccountProps> = ({
Expand All @@ -52,6 +62,7 @@ export const Account: FC<AccountProps> = ({
children,
availableFields,
availableWells,
enableEnvironmentToggle = false,
}) => {
const ACTIVE_ENVIRONMENT = environment.getEnvironmentName(
import.meta.env.VITE_ENVIRONMENT_NAME
Expand All @@ -63,6 +74,10 @@ export const Account: FC<AccountProps> = ({
const { account, roles, logout } = useAuth();
const [isOpen, setIsOpen] = useState(false);
const [openImpersonate, setOpenImpersonate] = useState(false);
const [environmentToggle, setEnvironmentToggle] = useLocalStorage<
SelectOptionRequired[]
>(ENVIRONMENT_TOGGLE_KEY, []);

const buttonRef = useRef<HTMLButtonElement | null>(null);
const { data: canImpersonate = true } = useCanImpersonate();
const { data: activeImpersonationUser } = useActiveImpersonationUser();
Expand Down Expand Up @@ -116,7 +131,11 @@ export const Account: FC<AccountProps> = ({
{customButton ? (
customButton
) : (
<AccountButton ref={buttonRef} onClick={handleToggleMenu} />
<AccountButton
ref={buttonRef}
onClick={handleToggleMenu}
environmentToggle={environmentToggle}
/>
)}
<TopBarMenu
open={isOpen}
Expand All @@ -128,15 +147,19 @@ export const Account: FC<AccountProps> = ({
{activeImpersonationUser && (
<ActiveUserImpersonationButton onClick={handleOpenImpersonate} />
)}
<AccountAvatar />
<AccountAvatar environmentToggle={environmentToggle} />
<TextContent>
<Typography variant="h6">{fullName}</Typography>
<Typography>{username}</Typography>
</TextContent>

{environmentToggle.length > 0 && (
<StatusChips statuses={environmentToggle} />
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The prop name is statuses (line 9) but the component is receiving a prop called roles when invoked (line 156). This is a mismatch that will cause the component to not receive the expected data. Either update the prop name in StatusChips.tsx to roles or update the usage in Account.tsx to pass statuses.

Copilot uses AI. Check for mistakes.
)}
{activeRoles && !hideRoles && (
<>
{activeRoles.length <= 3 ? (
<RoleChips roles={activeRoles} />
<StatusChips statuses={activeRoles} />
Comment thread
bjorngoa marked this conversation as resolved.
) : (
<RoleList roles={activeRoles} />
)}
Expand All @@ -152,6 +175,15 @@ export const Account: FC<AccountProps> = ({
/>
)}
</Container>
{enableEnvironmentToggle &&
ACTIVE_ENVIRONMENT !== EnvironmentType.PRODUCTION && (
<EnvironmentToggleWrapper>
<EnvironmentToggle
setEnvironmentToggle={setEnvironmentToggle}
environmentToggle={environmentToggle}
/>
</EnvironmentToggleWrapper>
)}
<ButtonWrapper>
<Button variant="ghost" onClick={logout}>
<Icon data={log_out} />
Expand Down
4 changes: 4 additions & 0 deletions src/organisms/TopBar/Account/Account.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type StatusVariant = 'impersonate' | 'environment' | 'combined';
export interface StatusVariantProps {
$variant?: StatusVariant;
}
80 changes: 65 additions & 15 deletions src/organisms/TopBar/Account/AccountAvatar.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { FC } from 'react';

import { useActiveImpersonationUser } from './ImpersonateMenu/hooks/useActiveImpersonationUser';
import { colors, spacings } from 'src/atoms/style';
import { spacings } from 'src/atoms/style';
import { getVariantColors } from 'src/atoms/utils/environmentToggle';
import { SelectOptionRequired } from 'src/molecules';
import { Chip } from 'src/molecules/Chip/Chip';
import { ProfileAvatar } from 'src/molecules/ProfileAvatar/ProfileAvatar';
import { ImpersonateAvatar } from 'src/organisms/TopBar/Account/ImpersonateAvatar';
import { StatusVariantProps } from 'src/organisms/TopBar/Account/Account.types';
import { impersonateUserDtoToFullName } from 'src/organisms/TopBar/Account/ImpersonateMenu/Impersonate.utils';
import { StatusAvatar } from 'src/organisms/TopBar/Account/StatusAvatar';
import { useAuth } from 'src/providers/AuthProvider/AuthProvider';

import styled from 'styled-components';
Expand All @@ -14,27 +18,73 @@ const Wrapper = styled.div`
margin-bottom: ${spacings.small};
`;

const ImpersonateChip = styled(Chip)`
const StatusChip = styled(Chip)<StatusVariantProps>`
position: absolute;
bottom: calc(${spacings.x_small} * -1);
left: 50%;
transform: translateX(-50%);
background: ${colors.interactive.warning__resting.rgba};
outline-color: ${colors.interactive.warning__resting.rgba};
white-space: nowrap;
background: ${({ $variant }) => getVariantColors($variant).chipBackground};
outline-color: ${({ $variant }) => getVariantColors($variant).outline};
`;

export const AccountAvatar: FC = () => {
interface AccountAvatarProps {
environmentToggle?: SelectOptionRequired[];
}

export const AccountAvatar: FC<AccountAvatarProps> = ({
environmentToggle,
}) => {
const { account, photo } = useAuth();
const { data: activeImpersonationUser } = useActiveImpersonationUser();
const fullName = activeImpersonationUser
? impersonateUserDtoToFullName(activeImpersonationUser)
: account?.name;

const isActiveFeatureOnCurrentEnvironment =
environmentToggle != null && environmentToggle.length > 0;
const activeFeatureNames =
environmentToggle == null
? ''
: environmentToggle.map((x) => x.label).join(', ');

const getAvatar = () => {
if (isActiveFeatureOnCurrentEnvironment && activeImpersonationUser) {
return (
<>
<StatusAvatar
size={64}
variant="combined"
name="Impersonate & Environment"
/>
<StatusChip $variant="combined">Impersonate & Environment</StatusChip>
</>
);
}
if (isActiveFeatureOnCurrentEnvironment) {
return (
<>
<StatusAvatar
size={64}
variant="environment"
name={activeFeatureNames}
/>
<StatusChip $variant="environment">Environment</StatusChip>
</>
);
}

if (activeImpersonationUser) {
return (
<>
<StatusAvatar size={64} variant="impersonate" name={fullName} />
<StatusChip $variant="impersonate">Impersonating</StatusChip>
</>
);
}

if (activeImpersonationUser) {
return (
<Wrapper>
<ImpersonateAvatar size={64} />
<ImpersonateChip>Impersonating</ImpersonateChip>
</Wrapper>
);
}
return <ProfileAvatar size={64} name={account?.name} url={photo} />;
};

return <ProfileAvatar size={64} name={account?.name} url={photo} />;
return <Wrapper>{getAvatar()}</Wrapper>;
};
Loading
Loading