Various frontend fixes for roles pages (#10654)

This commit is contained in:
Weiko
2025-03-04 18:09:23 +01:00
committed by GitHub
parent 4bf8af472a
commit 5d2be60758
14 changed files with 147 additions and 141 deletions

View File

@ -118,13 +118,6 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
Icon: IconUsers,
isHidden: !permissionMap[SettingsPermissions.WORKSPACE_MEMBERS],
},
{
label: t`Billing`,
path: SettingsPath.Billing,
Icon: IconCurrencyDollar,
isHidden:
!isBillingEnabled || !permissionMap[SettingsPermissions.WORKSPACE],
},
{
label: t`Roles`,
path: SettingsPath.Roles,
@ -133,6 +126,13 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
!featureFlags[FeatureFlagKey.IsPermissionsEnabled] ||
!permissionMap[SettingsPermissions.ROLES],
},
{
label: t`Billing`,
path: SettingsPath.Billing,
Icon: IconCurrencyDollar,
isHidden:
!isBillingEnabled || !permissionMap[SettingsPermissions.WORKSPACE],
},
{
label: t`Data model`,
path: SettingsPath.Objects,

View File

@ -11,7 +11,7 @@ const StyledTableHeaderRow = styled(Table)`
export const RolesTableHeader = () => {
return (
<StyledTableHeaderRow>
<TableRow>
<TableRow gridAutoColumns="3fr 2fr 1fr">
<TableHeader>
<Trans>Name</Trans>
</TableHeader>

View File

@ -24,20 +24,21 @@ const StyledAvatarContainer = styled.div`
`;
const StyledAssignedText = styled.div`
color: ${({ theme }) => theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.md};
color: ${({ theme }) => theme.font.color.secondary};
font-size: ${({ theme }) => theme.font.size.sm};
`;
const StyledNameCell = styled.div`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
gap: ${({ theme }) => theme.spacing(1)};
color: ${({ theme }) => theme.font.color.primary};
`;
const StyledAssignedCell = styled.div`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
gap: ${({ theme }) => theme.spacing(4)};
`;
const StyledAvatarGroup = styled.div`
@ -71,7 +72,11 @@ export const RolesTableRow = ({ role }: { role: Role }) => {
};
return (
<StyledTableRow key={role.id} onClick={() => handleRoleClick(role.id)}>
<StyledTableRow
key={role.id}
gridAutoColumns="3fr 2fr 1fr"
onClick={() => handleRoleClick(role.id)}
>
<TableCell>
<StyledNameCell>
<IconUser size={theme.icon.size.md} />

View File

@ -7,7 +7,6 @@ import { SettingsPath } from '@/types/SettingsPath';
import { TextInput } from '@/ui/input/components/TextInput';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { Table } from '@/ui/layout/table/components/Table';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { useState } from 'react';
@ -31,25 +30,22 @@ import { useNavigateSettings } from '~/hooks/useNavigateSettings';
import { RoleAssignmentConfirmationModal } from './RoleAssignmentConfirmationModal';
import { RoleAssignmentTableRow } from './RoleAssignmentTableRow';
const StyledBottomSection = styled(Section)<{ hasRows: boolean }>`
${({ hasRows, theme }) =>
hasRows
? `
border-top: 1px solid ${theme.border.color.light};
margin-top: ${theme.spacing(2)};
padding-top: ${theme.spacing(4)};
`
: `
margin-top: ${theme.spacing(8)};
`}
const StyledAssignToMemberContainer = styled.div`
display: flex;
justify-content: flex-end;
padding-top: ${({ theme }) => theme.spacing(2)};
padding-bottom: ${({ theme }) => theme.spacing(2)};
`;
const StyledSearchContainer = styled.div`
margin: ${({ theme }) => theme.spacing(2)} 0;
`;
const StyledTable = styled.div<{ hasRows: boolean }>`
border-bottom: ${({ hasRows, theme }) =>
hasRows ? `1px solid ${theme.border.color.light}` : 'none'};
`;
const StyledSearchInput = styled(TextInput)`
input {
background: ${({ theme }) => theme.background.transparent.lighter};
@ -134,6 +130,7 @@ export const RoleAssignment = ({ role }: RoleAssignmentProps) => {
id: workspaceMember.id,
name: `${workspaceMember.name.firstName} ${workspaceMember.name.lastName}`,
role: existingRole,
avatarUrl: workspaceMember.avatarUrl,
});
setConfirmationModalIsOpen(true);
closeDropdown();
@ -178,7 +175,7 @@ export const RoleAssignment = ({ role }: RoleAssignmentProps) => {
sizeVariant="lg"
/>
</StyledSearchContainer>
<Table>
<StyledTable hasRows={filteredWorkspaceMembers.length > 0}>
<RoleAssignmentTableHeader />
{filteredWorkspaceMembers.map((workspaceMember) => (
<RoleAssignmentTableRow
@ -186,42 +183,43 @@ export const RoleAssignment = ({ role }: RoleAssignmentProps) => {
workspaceMember={workspaceMember}
/>
))}
</Table>
</Section>
<StyledBottomSection hasRows={filteredWorkspaceMembers.length > 0}>
<Dropdown
dropdownId="role-member-select"
dropdownHotkeyScope={{ scope: 'roleAssignment' }}
clickableComponent={
<>
<div id="assign-member">
<Button
Icon={IconPlus}
title={t`Assign to member`}
variant="secondary"
size="small"
disabled={allWorkspaceMembersHaveThisRole}
</StyledTable>
<StyledAssignToMemberContainer>
<Dropdown
dropdownId="role-member-select"
dropdownHotkeyScope={{ scope: 'roleAssignment' }}
clickableComponent={
<>
<div id="assign-member">
<Button
Icon={IconPlus}
title={t`Assign to member`}
variant="secondary"
size="small"
disabled={allWorkspaceMembersHaveThisRole}
/>
</div>
<AppTooltip
anchorSelect="#assign-member"
content={t`No more members to assign`}
delay={TooltipDelay.noDelay}
hidden={!allWorkspaceMembersHaveThisRole}
/>
</div>
<AppTooltip
anchorSelect="#assign-member"
content={t`No more members to assign`}
delay={TooltipDelay.noDelay}
hidden={!allWorkspaceMembersHaveThisRole}
</>
}
dropdownComponents={
<RoleAssignmentWorkspaceMemberPickerDropdown
excludedWorkspaceMemberIds={[
...assignedWorkspaceMemberIds,
currentWorkspaceMember?.id,
]}
onSelect={handleSelectWorkspaceMember}
/>
</>
}
dropdownComponents={
<RoleAssignmentWorkspaceMemberPickerDropdown
excludedWorkspaceMemberIds={[
...assignedWorkspaceMemberIds,
currentWorkspaceMember?.id,
]}
onSelect={handleSelectWorkspaceMember}
/>
}
/>
</StyledBottomSection>
}
/>
</StyledAssignToMemberContainer>
</Section>
{confirmationModalIsOpen && selectedWorkspaceMember && (
<RoleAssignmentConfirmationModal

View File

@ -35,7 +35,7 @@ export const RoleAssignmentConfirmationModal = ({
}
onConfirmClick={onConfirm}
confirmButtonText={t`Confirm`}
confirmButtonAccent="blue"
confirmButtonAccent="danger"
/>
);
};

View File

@ -2,10 +2,10 @@ import { SettingsCard } from '@/settings/components/SettingsCard';
import { RoleAssignmentConfirmationModalSelectedWorkspaceMember } from '@/settings/roles/role-assignment/types/RoleAssignmentConfirmationModalSelectedWorkspaceMember';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { IconUser } from 'twenty-ui';
import { Avatar } from 'twenty-ui';
const StyledSettingsCardContainer = styled.div`
margin-top: ${({ theme }) => theme.spacing(2)};
margin-top: ${({ theme }) => theme.spacing(6)};
`;
type RoleAssignmentConfirmationModalSubtitleProps = {
@ -25,7 +25,15 @@ export const RoleAssignmentConfirmationModalSubtitle = ({
<StyledSettingsCardContainer>
<SettingsCard
title={selectedWorkspaceMember.role?.label || ''}
Icon={<IconUser />}
Icon={
<Avatar
avatarUrl={selectedWorkspaceMember.avatarUrl}
placeholderColorSeed={selectedWorkspaceMember.id}
placeholder={selectedWorkspaceMember.name}
size="md"
type="rounded"
/>
}
onClick={() =>
selectedWorkspaceMember.role &&
onRoleClick(selectedWorkspaceMember.role.id)

View File

@ -10,10 +10,9 @@ const StyledTableHeaderRow = styled(Table)`
export const RoleAssignmentTableHeader = () => (
<StyledTableHeaderRow>
<TableRow gridAutoColumns="150px 1fr 1fr">
<TableRow gridAutoColumns="2fr 4fr">
<TableHeader>{t`Name`}</TableHeader>
<TableHeader>{t`Email`}</TableHeader>
<TableHeader align={'right'} aria-label={t`Actions`}></TableHeader>
</TableRow>
</StyledTableHeaderRow>
);

View File

@ -1,18 +1,31 @@
import { Table } from '@/ui/layout/table/components/Table';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import styled from '@emotion/styled';
import { Avatar, OverflowingTextWithTooltip } from 'twenty-ui';
import { WorkspaceMember } from '~/generated-metadata/graphql';
const StyledTable = styled(Table)`
margin-top: ${({ theme }) => theme.spacing(0.5)};
const StyledIconWrapper = styled.div`
align-items: center;
display: flex;
flex-shrink: 0;
margin-right: ${({ theme }) => theme.spacing(2)};
`;
const StyledIconWrapper = styled.div`
display: flex;
const StyledNameCell = styled.div`
color: ${({ theme }) => theme.font.color.primary};
flex: 1;
min-width: 0;
`;
const StyledNameContainer = styled.div`
align-items: center;
margin-right: ${({ theme }) => theme.spacing(2)};
display: flex;
overflow: hidden;
width: 100%;
`;
const StyledTableCell = styled(TableCell)`
overflow: hidden;
`;
type RoleAssignmentTableRowProps = {
@ -23,9 +36,9 @@ export const RoleAssignmentTableRow = ({
workspaceMember,
}: RoleAssignmentTableRowProps) => {
return (
<StyledTable>
<TableRow gridAutoColumns="150px 1fr 1fr">
<TableCell>
<TableRow gridAutoColumns="2fr 4fr">
<StyledTableCell>
<StyledNameContainer>
<StyledIconWrapper>
<Avatar
avatarUrl={workspaceMember.avatarUrl}
@ -35,14 +48,16 @@ export const RoleAssignmentTableRow = ({
size="md"
/>
</StyledIconWrapper>
<OverflowingTextWithTooltip
text={`${workspaceMember.name.firstName} ${workspaceMember.name.lastName}`}
/>
</TableCell>
<TableCell>
<OverflowingTextWithTooltip text={workspaceMember.userEmail} />
</TableCell>
</TableRow>
</StyledTable>
<StyledNameCell>
<OverflowingTextWithTooltip
text={`${workspaceMember.name.firstName} ${workspaceMember.name.lastName}`}
/>
</StyledNameCell>
</StyledNameContainer>
</StyledTableCell>
<StyledTableCell>
<OverflowingTextWithTooltip text={workspaceMember.userEmail} />
</StyledTableCell>
</TableRow>
);
};

View File

@ -2,4 +2,5 @@ export type RoleAssignmentConfirmationModalSelectedWorkspaceMember = {
id: string;
name: string;
role?: { id: string; label: string };
avatarUrl?: string | null;
};

View File

@ -22,6 +22,11 @@ const StyledRolePermissionsContainer = styled.div`
gap: ${({ theme }) => theme.spacing(8)};
`;
const StyledTable = styled.div`
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
padding-bottom: ${({ theme }) => theme.spacing(2)};
`;
type RolePermissionsProps = {
role: Pick<
Role,
@ -114,25 +119,29 @@ export const RolePermissions = ({ role }: RolePermissionsProps) => {
title={t`Objects`}
description={t`Ability to interact with each object`}
/>
<RolePermissionsObjectsTableHeader allPermissions={true} />
{objectPermissionsConfig.map((permission) => (
<RolePermissionsObjectsTableRow
key={permission.key}
permission={permission}
/>
))}
<StyledTable>
<RolePermissionsObjectsTableHeader allPermissions={true} />
{objectPermissionsConfig.map((permission) => (
<RolePermissionsObjectsTableRow
key={permission.key}
permission={permission}
/>
))}
</StyledTable>
</Section>
<Section>
<H2Title title={t`Settings`} description={t`Settings permissions`} />
<RolePermissionsSettingsTableHeader
allPermissions={role.canUpdateAllSettings}
/>
{settingsPermissionsConfig.map((permission) => (
<RolePermissionsSettingsTableRow
key={permission.key}
permission={permission}
<StyledTable>
<RolePermissionsSettingsTableHeader
allPermissions={role.canUpdateAllSettings}
/>
))}
{settingsPermissionsConfig.map((permission) => (
<RolePermissionsSettingsTableRow
key={permission.key}
permission={permission}
/>
))}
</StyledTable>
</Section>
</StyledRolePermissionsContainer>
);

View File

@ -18,6 +18,8 @@ const StyledPermissionCell = styled(TableCell)`
flex: 1;
gap: ${({ theme }) => theme.spacing(2)};
padding-left: ${({ theme }) => theme.spacing(2)};
color: ${({ theme }) => theme.font.color.secondary};
font-size: ${({ theme }) => theme.font.size.sm};
`;
const StyledCheckboxCell = styled(TableCell)`

View File

@ -4,13 +4,14 @@ import { t } from '@lingui/core/macro';
import { IconPicker } from '@/ui/input/components/IconPicker';
import { TextArea } from '@/ui/input/components/TextArea';
import { TextInput } from '@/ui/input/components/TextInput';
import { Section } from 'twenty-ui';
import { Role } from '~/generated-metadata/graphql';
const StyledInputsContainer = styled.div`
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
margin-bottom: ${({ theme }) => theme.spacing(2)};
width: 100%;
margin-bottom: ${({ theme }) => theme.spacing(2)};
`;
const StyledInputContainer = styled.div`
@ -24,7 +25,7 @@ type RoleSettingsProps = {
export const RoleSettings = ({ role }: RoleSettingsProps) => {
return (
<>
<Section>
<StyledInputsContainer>
<StyledInputContainer>
<IconPicker
@ -41,6 +42,6 @@ export const RoleSettings = ({ role }: RoleSettingsProps) => {
value={role.description || ''}
disabled
/>
</>
</Section>
);
};

View File

@ -1,14 +1,7 @@
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { useEffect } from 'react';
import { useParams } from 'react-router-dom';
import {
H3Title,
IconLockOpen,
IconSettings,
IconUser,
IconUserPlus,
} from 'twenty-ui';
import { H3Title, IconLockOpen, IconSettings, IconUserPlus } from 'twenty-ui';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { RoleAssignment } from '@/settings/roles/role-assignment/components/RoleAssignment';
@ -18,27 +11,10 @@ import { SettingsPath } from '@/types/SettingsPath';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { TabList } from '@/ui/layout/tab/components/TabList';
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
import { useTheme } from '@emotion/react';
import { useGetRolesQuery } from '~/generated/graphql';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
const StyledContentContainer = styled.div`
flex: 1;
width: 100%;
padding-left: 0;
`;
const StyledTitleContainer = styled.div`
display: flex;
align-items: center;
gap: ${({ theme }) => theme.spacing(2)};
`;
const StyledIconUser = styled(IconUser)`
color: ${({ theme }) => theme.font.color.primary};
`;
export const SETTINGS_ROLE_DETAIL_TABS = {
COMPONENT_INSTANCE_ID: 'settings-role-detail-tabs',
TABS_IDS: {
@ -50,7 +26,6 @@ export const SETTINGS_ROLE_DETAIL_TABS = {
export const SettingsRoleEdit = () => {
const { roleId = '' } = useParams();
const theme = useTheme();
const navigateSettings = useNavigateSettings();
const { data: rolesData, loading: rolesLoading } = useGetRolesQuery({
fetchPolicy: 'network-only',
@ -106,12 +81,7 @@ export const SettingsRoleEdit = () => {
return (
<SubMenuTopBarContainer
title={
<StyledTitleContainer>
<StyledIconUser size={theme.icon.size.md} />
<H3Title title={role.label} />
</StyledTitleContainer>
}
title={<H3Title title={role.label} />}
links={[
{
children: 'Workspace',
@ -132,9 +102,7 @@ export const SettingsRoleEdit = () => {
tabs={tabs}
className="tab-list"
/>
<StyledContentContainer>
{renderActiveTabContent()}
</StyledContentContainer>
{renderActiveTabContent()}
</SettingsPageContainer>
</SubMenuTopBarContainer>
);

View File

@ -102,7 +102,7 @@ const StyledInput = styled.input<InputProps>`
checkboxSize === CheckboxSize.Large ? '18px' : '12px'};
background: ${({ theme, indeterminate, isChecked, disabled }) =>
disabled && isChecked
? theme.color.blue
? theme.adaptiveColors.blue3
: indeterminate || isChecked
? theme.color.blue
: 'transparent'};
@ -115,9 +115,9 @@ const StyledInput = styled.input<InputProps>`
}) => {
switch (true) {
case isChecked:
return theme.color.blue;
return disabled ? theme.adaptiveColors.blue3 : theme.color.blue;
case disabled:
return theme.background.transparent.medium;
return theme.border.color.strong;
case indeterminate || isChecked:
return theme.color.blue;
case variant === CheckboxVariant.Primary:
@ -166,7 +166,7 @@ export const Checkbox = ({
variant = CheckboxVariant.Primary,
size = CheckboxSize.Small,
shape = CheckboxShape.Squared,
hoverable = false,
hoverable = true,
className,
disabled = false,
}: CheckboxProps) => {