Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Beta Tester / VC campaign better treatment #7250

Merged
merged 8 commits into from
Nov 25, 2024
13 changes: 6 additions & 7 deletions src/core/apollo/generated/apollo-hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18056,6 +18056,8 @@ export function refetchSubspacePageQuery(variables: SchemaTypes.SubspacePageQuer
export const PlatformLevelAuthorizationDocument = gql`
query PlatformLevelAuthorization {
platform {
id
myRoles
authorization {
...MyPrivileges
}
Expand Down Expand Up @@ -22270,16 +22272,13 @@ export function refetchInnovationLibraryQuery(variables?: SchemaTypes.Innovation

export const CampaignBlockCredentialsDocument = gql`
query CampaignBlockCredentials {
platform {
id
myRoles
}
me {
user {
id
agent {
id
credentials {
resourceID
type
}
}
account {
id
license {
Expand Down
8 changes: 3 additions & 5 deletions src/core/apollo/generated/graphql-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23536,6 +23536,8 @@ export type PlatformLevelAuthorizationQuery = {
__typename?: 'Query';
platform: {
__typename?: 'Platform';
id: string;
myRoles: Array<PlatformRole>;
authorization?:
| { __typename?: 'Authorization'; myPrivileges?: Array<AuthorizationPrivilege> | undefined }
| undefined;
Expand Down Expand Up @@ -28784,17 +28786,13 @@ export type CampaignBlockCredentialsQueryVariables = Exact<{ [key: string]: neve

export type CampaignBlockCredentialsQuery = {
__typename?: 'Query';
platform: { __typename?: 'Platform'; id: string; myRoles: Array<PlatformRole> };
me: {
__typename?: 'MeQueryResults';
user?:
| {
__typename?: 'User';
id: string;
agent: {
__typename?: 'Agent';
id: string;
credentials?: Array<{ __typename?: 'Credential'; resourceID: string; type: CredentialType }> | undefined;
};
account?:
| {
__typename?: 'Account';
Expand Down
9 changes: 8 additions & 1 deletion src/core/i18n/en/translation.en.json
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,13 @@
"year": "{{count}} year",
"year_plural": "{{count}} years"
}
},
"roles": {
"GLOBAL_ADMIN": "Global Admin",
"SUPPORT": "Support Admin",
"LICENSE_MANAGER": "License Manager",
"BETA_TESTER": "Beta Tester",
"VC_CAMPAIGN": "VC Early Access"
}
},
"community": {
Expand All @@ -585,7 +592,7 @@
"memberOrganizations": "Member Organizations ({{count}})",
"pendingApplications": "Pending applications",
"pendingMemberships": "Pending applications & invitations",
"invitationSent": "Invitation sent succesfully",
"invitationSent": "Invitation sent successfully",
"leads": "Leads",
"members": "Members",
"host": "Host",
Expand Down
44 changes: 44 additions & 0 deletions src/core/ui/icon/BadgeLabel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { TypographyProps, useTheme } from '@mui/material';
import { gutters } from '../grid/utils';
import { Caption } from '../typography';

interface BadgeLabelProps extends TypographyProps {
count?: string;
size?: 'small' | 'medium';
}

// copy of BadgeCounter but supporting auto width text
const BadgeSizes: Record<NonNullable<BadgeLabelProps['size']>, TypographyProps> = {
small: {
fontSize: '0.7rem',
lineHeight: gutters(0.8),
width: 'auto',
height: gutters(0.8),
},
medium: {
width: 'auto',
height: gutters(1),
},
};

const BadgeLabel = ({ count, size = 'medium', ...props }: BadgeLabelProps) => {
const theme = useTheme();

return (
<Caption
bgcolor={theme.palette.text.primary}
color={theme.palette.background.paper}
fontWeight="bold"
display="inline-block"
borderRadius={`${theme.shape.borderRadius / 2}px`}
paddingX={gutters(0.2)}
textAlign="center"
{...BadgeSizes[size]}
{...props}
>
{count}
</Caption>
);
};

export default BadgeLabel;
1 change: 1 addition & 0 deletions src/core/ui/navigation/NavigationBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const NavigationBarContent = ({ transparent, children }: PropsWithChildren<Navig
backgroundColor: theme => alpha(theme.palette.primary.main, transparent ? 0 : 0.25),
backdropFilter: transparent ? 'none' : 'blur(8px)',
position: 'relative',
overflow: 'visible',
height: gutters(NAVIGATION_CONTAINER_HEIGHT_GUTTERS - 1),
maxWidth: gutters(MAX_CONTENT_WIDTH_GUTTERS - 2),
}}
Expand Down
20 changes: 18 additions & 2 deletions src/domain/community/user/providers/UserProvider/UserProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import {
useUserProviderQuery,
} from '@/core/apollo/generated/apollo-hooks';
import { ErrorPage } from '@/core/pages/Errors/ErrorPage';
import { AuthorizationPrivilege, LicenseEntitlementType, User } from '@/core/apollo/generated/graphql-schema';
import {
AuthorizationPrivilege,
LicenseEntitlementType,
PlatformRole,
User,
} from '@/core/apollo/generated/graphql-schema';
import { useAuthenticationContext } from '@/core/auth/authentication/hooks/useAuthenticationContext';
import { toUserMetadata, UserMetadata } from '../../hooks/useUserMetadataWrapper';

Expand All @@ -17,6 +22,7 @@ export interface UserContextValue {
loadingMe: boolean; // Loading Authentication and Profile data. Once it's false that's enough for showing the page header and avatar.
verified: boolean;
isAuthenticated: boolean;
platformRoles: PlatformRole[];
accountPrivileges: AuthorizationPrivilege[];
accountEntitlements: LicenseEntitlementType[];
}
Expand All @@ -28,6 +34,7 @@ const UserContext = createContext<UserContextValue>({
loadingMe: true,
verified: false,
isAuthenticated: false,
platformRoles: [],
accountPrivileges: [],
accountEntitlements: [],
});
Expand Down Expand Up @@ -75,10 +82,19 @@ const UserProvider: FC = ({ children }) => {
loadingMe: loadingMeAndParentQueries,
verified,
isAuthenticated,
platformRoles: platformLevelAuthorizationData?.platform.myRoles ?? [],
accountPrivileges: meData?.me.user?.account?.authorization?.myPrivileges ?? [],
accountEntitlements: meData?.me.user?.account?.license?.myLicensePrivileges ?? [],
}),
[userMetadata, loading, loadingMeAndParentQueries, verified, isAuthenticated]
[
userMetadata,
loading,
loadingMeAndParentQueries,
verified,
isAuthenticated,
platformLevelAuthorizationData,
meData,
]
);

if (error) return <ErrorPage error={error} />;
Expand Down
2 changes: 2 additions & 0 deletions src/domain/platform/PlatformLevelAuthorization.graphql
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
query PlatformLevelAuthorization {
platform {
id
myRoles
authorization {
...MyPrivileges
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
query CampaignBlockCredentials {
platform {
id
myRoles
}
me {
user {
id
agent {
id
credentials {
resourceID
type
}
}
account {
id
license {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useCampaignBlockCredentialsQuery } from '@/core/apollo/generated/apollo-hooks';
import { CredentialType, LicenseEntitlementType } from '@/core/apollo/generated/graphql-schema';
import { LicenseEntitlementType, PlatformRole } from '@/core/apollo/generated/graphql-schema';
import PageContentBlock from '@/core/ui/content/PageContentBlock';
import useNewVirtualContributorWizard from '../newVirtualContributorWizard/useNewVirtualContributorWizard';
import CampaignBlockCreateVC from './CampaignBlockCreateVC';
Expand All @@ -10,11 +10,12 @@ const CampaignBlock = () => {
// Do not remove: Inside the blocks startWizard() is being called with a ClickEvent and that messes up with the param that startWizard expects
const handleStartWizard = () => startWizard();

const userRoles: CredentialType[] | undefined = data?.me.user?.agent.credentials?.map(credential => credential.type);
const userRoles: PlatformRole[] = data?.platform.myRoles ?? [];
const userEntitlements: LicenseEntitlementType[] | undefined = data?.me.user?.account?.license?.myLicensePrivileges;
const rolesAvailableTo = [CredentialType.VcCampaign, CredentialType.BetaTester];
const rolesAvailableTo = [PlatformRole.VcCampaign];
const entitlementsAvailableTo = [LicenseEntitlementType.AccountVirtualContributor];

// the campaign block should be visible only for VcCampaign users ATM
if (
!userRoles?.some(role => rolesAvailableTo.includes(role)) ||
!userEntitlements?.some(entitlement => entitlementsAvailableTo.includes(entitlement))
Expand Down
21 changes: 19 additions & 2 deletions src/main/ui/platformNavigation/PlatformNavigationUserAvatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import MenuTriggerButton from '@/core/ui/tooltip/MenuTriggerButton';
import { PLATFORM_NAVIGATION_MENU_Z_INDEX } from './constants';
import NavigationItemContainer from '@/core/ui/navigation/NavigationItemContainer';
import { useTranslation } from 'react-i18next';
import BadgeLabel from '@/core/ui/icon/BadgeLabel';
import { PlatformRole } from '@/core/apollo/generated/graphql-schema';

interface PlatformNavigationUserAvatarProps {
children: ReactElement<{ onClose?: () => void }>;
Expand All @@ -17,24 +19,27 @@ interface PlatformNavigationUserAvatarProps {

const PlatformNavigationUserAvatar = ({ drawer, children }: PlatformNavigationUserAvatarProps) => {
const { t } = useTranslation();
const { user, isAuthenticated, loadingMe } = useUserContext();
const { user, isAuthenticated, loadingMe, platformRoles } = useUserContext();

const theme = useTheme();

const showBetaBadge = user && isAuthenticated && platformRoles.includes(PlatformRole.BetaTester);

return (
<MenuTriggerButton
keepMounted
drawer={drawer}
placement="bottom-end"
renderTrigger={({ ref, onClick, ...props }) => (
<SwapColors>
<NavigationItemContainer ref={ref as Ref<HTMLDivElement>} position="relative">
<NavigationItemContainer ref={ref as Ref<HTMLDivElement>} position="relative" overflow="visible">
<Paper
component={Avatar}
src={user?.user.profile.avatar?.uri}
sx={{
padding: 0,
cursor: 'pointer',
position: 'relative',
}}
aria-label={t('buttons.userMenu')}
onClick={onClick}
Expand All @@ -47,6 +52,18 @@ const PlatformNavigationUserAvatar = ({ drawer, children }: PlatformNavigationUs
)}
{!loadingMe && !isAuthenticated && <Person color="primary" />}
</Paper>
{showBetaBadge && (
<BadgeLabel
count="Beta"
size="small"
sx={{
position: 'absolute',
bottom: '-8px',
right: '-12px',
zIndex: PLATFORM_NAVIGATION_MENU_Z_INDEX + 1,
}}
/>
)}
<Box
position="absolute"
top={0}
Expand Down
35 changes: 27 additions & 8 deletions src/main/ui/platformNavigation/PlatformNavigationUserMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { forwardRef, PropsWithChildren, ReactNode, useMemo, useState } from 'react';
import { Box, Divider, MenuList, Typography } from '@mui/material';
import AlkemioAvatar from '@/core/ui/image/AlkemioAvatar';
import { BlockTitle, Caption } from '@/core/ui/typography';
import { gutters } from '@/core/ui/grid/utils';
import { buildLoginUrl, buildUserProfileUrl } from '@/main/routing/urlBuilders';
Expand All @@ -16,7 +15,7 @@ import {
import SettingsIcon from '@mui/icons-material/SettingsOutlined';
import { AUTH_LOGOUT_PATH } from '@/core/auth/authentication/constants/authentication.constants';
import { useTranslation } from 'react-i18next';
import { AuthorizationPrivilege } from '@/core/apollo/generated/graphql-schema';
import { AuthorizationPrivilege, PlatformRole } from '@/core/apollo/generated/graphql-schema';
import { useUserContext } from '@/domain/community/user';
import Gutters from '@/core/ui/grid/Gutters';
import { ROUTE_HOME } from '@/domain/platform/routes/constants';
Expand All @@ -29,6 +28,7 @@ import NavigatableMenuItem from '@/core/ui/menu/NavigatableMenuItem';
import GlobalMenuSurface from '@/core/ui/menu/GlobalMenuSurface';
import { FocusTrap } from '@mui/base/FocusTrap';
import usePlatformOrigin from '@/domain/platform/routes/usePlatformOrigin';
import Avatar from '@/core/ui/avatar/Avatar';

interface PlatformNavigationUserMenuProps {
surface: boolean;
Expand All @@ -47,18 +47,32 @@ const PlatformNavigationUserMenu = forwardRef<HTMLDivElement, PropsWithChildren<
const platformOrigin = usePlatformOrigin();
const homeUrl = platformOrigin && `${platformOrigin}${ROUTE_HOME}`;

const { user: { user, hasPlatformPrivilege } = {}, isAuthenticated } = useUserContext();
const { user: { user, hasPlatformPrivilege } = {}, isAuthenticated, platformRoles } = useUserContext();

// todo: change with PlatformRole.GlobalAdmin?
const isAdmin = hasPlatformPrivilege?.(AuthorizationPrivilege.PlatformAdmin);

const [isHelpDialogOpen, setIsHelpDialogOpen] = useState(false);

// the roles should follow the order
const role = useMemo(() => {
if (isAdmin) {
// TODO change role name path
return t('common.enums.authorization-credentials.GLOBAL_ADMIN.name');
for (const platformRole of platformRoles) {
switch (platformRole) {
case PlatformRole.GlobalAdmin:
return t('common.roles.GLOBAL_ADMIN');
case PlatformRole.Support:
return t('common.roles.SUPPORT');
case PlatformRole.LicenseManager:
return t('common.roles.LICENSE_MANAGER');
case PlatformRole.BetaTester:
return t('common.roles.BETA_TESTER');
case PlatformRole.VcCampaign:
return t('common.roles.VC_CAMPAIGN');
default:
return null;
}
}
}, [isAdmin, t]);
}, [platformRoles, t]);

const Wrapper = surface ? GlobalMenuSurface : Box;

Expand All @@ -67,7 +81,12 @@ const PlatformNavigationUserMenu = forwardRef<HTMLDivElement, PropsWithChildren<
<Wrapper ref={ref}>
{user && (
<Gutters disableGap alignItems="center" sx={{ paddingBottom: 1 }}>
<AlkemioAvatar size="lg" src={user.profile.avatar?.uri} />
<Avatar
size="large"
src={user.profile.avatar?.uri}
aria-label="User avatar"
alt={t('common.avatar-of', { user: user.profile?.displayName })}
/>
<BlockTitle lineHeight={gutters(2)}>{user.profile.displayName}</BlockTitle>
{role && (
<Caption color="neutralMedium.main" textTransform="uppercase">
Expand Down