add stories to roles components (#10503)
## Context Adding stories for roles components. Also moving modules components to the proper "modules" folder, "pages" folder being only for entry points. ## Test Run storybook <img width="1145" alt="Screenshot 2025-02-26 at 13 40 40" src="https://github.com/user-attachments/assets/bc184ab0-c590-4362-8c5a-1bf5ef176e6c" /> <img width="1149" alt="Screenshot 2025-02-26 at 13 40 32" src="https://github.com/user-attachments/assets/699cd205-31db-45e9-b9c1-caff1832bd47" /> <img width="1153" alt="Screenshot 2025-02-26 at 13 40 11" src="https://github.com/user-attachments/assets/72e45a67-ea89-4999-8b16-6f7d027d07f6" /> <img width="471" alt="Screenshot 2025-02-26 at 13 38 16" src="https://github.com/user-attachments/assets/62676943-9935-42b5-b769-5544f7eed85f" /> <img width="472" alt="Screenshot 2025-02-26 at 13 38 12" src="https://github.com/user-attachments/assets/946baab9-1be4-439e-bf99-0ebeab0995f7" />
This commit is contained in:
@ -47,6 +47,7 @@ describe('getDisplayNameFromParticipant', () => {
|
||||
updatedAt: '',
|
||||
userEmail: '',
|
||||
userId: '',
|
||||
colorScheme: 'Light',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -28,6 +28,7 @@ const mockWorkspaceMember = {
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
},
|
||||
colorScheme: 'Light' as const,
|
||||
};
|
||||
|
||||
const createMockOptions = (): Options<any> => ({
|
||||
@ -168,6 +169,7 @@ describe('ApolloFactory', () => {
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
},
|
||||
colorScheme: 'Light' as const,
|
||||
};
|
||||
|
||||
apolloFactory.updateWorkspaceMember(newWorkspaceMember);
|
||||
|
||||
@ -42,6 +42,7 @@ describe('useFindManyRecords', () => {
|
||||
id: '32219445-f587-4c40-b2b1-6d3205ed96da',
|
||||
name: { firstName: 'John', lastName: 'Connor' },
|
||||
locale: 'en',
|
||||
colorScheme: 'Light',
|
||||
});
|
||||
|
||||
const setMetadataItems = useSetRecoilState(objectMetadataItemsState);
|
||||
|
||||
@ -73,6 +73,7 @@ describe('useFilteredSearchRecordQuery', () => {
|
||||
id: '32219445-f587-4c40-b2b1-6d3205ed96da',
|
||||
name: { firstName: 'John', lastName: 'Connor' },
|
||||
locale: 'en',
|
||||
colorScheme: 'Light',
|
||||
});
|
||||
|
||||
const setMetadataItems = useSetRecoilState(objectMetadataItemsState);
|
||||
|
||||
@ -0,0 +1,46 @@
|
||||
import { Table } from '@/ui/layout/table/components/Table';
|
||||
import styled from '@emotion/styled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
|
||||
import { RolesTableHeader } from '@/settings/roles/components/RolesTableHeader';
|
||||
import { RolesTableRow } from '@/settings/roles/components/RolesTableRow';
|
||||
import { Button, H2Title, IconPlus, Section } from 'twenty-ui';
|
||||
import { Role } from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledTable = styled(Table)`
|
||||
margin-top: ${({ theme }) => theme.spacing(0.5)};
|
||||
`;
|
||||
|
||||
const StyledBottomSection = styled(Section)`
|
||||
border-top: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
margin-top: ${({ theme }) => theme.spacing(2)};
|
||||
padding-top: ${({ theme }) => theme.spacing(4)};
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
`;
|
||||
|
||||
export const Roles = ({ roles }: { roles: Role[] }) => {
|
||||
return (
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`All roles`}
|
||||
description={t`Assign roles to specify each member's access permissions`}
|
||||
/>
|
||||
<StyledTable>
|
||||
<RolesTableHeader />
|
||||
{roles.map((role) => (
|
||||
<RolesTableRow key={role.id} role={role} />
|
||||
))}
|
||||
</StyledTable>
|
||||
<StyledBottomSection>
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title={t`Create Role`}
|
||||
variant="secondary"
|
||||
size="small"
|
||||
soon
|
||||
/>
|
||||
</StyledBottomSection>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,80 @@
|
||||
import {
|
||||
CurrentWorkspace,
|
||||
currentWorkspaceState,
|
||||
} from '@/auth/states/currentWorkspaceState';
|
||||
import { SettingsOptionCardContentSelect } from '@/settings/components/SettingsOptions/SettingsOptionCardContentSelect';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { Card, H2Title, IconUserPin, Section } from 'twenty-ui';
|
||||
import {
|
||||
Role,
|
||||
UpdateWorkspaceMutation,
|
||||
useUpdateWorkspaceMutation,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
export const RolesDefaultRole = ({ roles }: { roles: Role[] }) => {
|
||||
const [updateWorkspace] = useUpdateWorkspaceMutation();
|
||||
|
||||
const [currentWorkspace, setCurrentWorkspace] = useRecoilState(
|
||||
currentWorkspaceState,
|
||||
);
|
||||
|
||||
const defaultRole = currentWorkspace?.defaultRole;
|
||||
|
||||
const updateDefaultRole = (
|
||||
defaultRoleId: string | null,
|
||||
currentWorkspace: CurrentWorkspace,
|
||||
) => {
|
||||
updateWorkspace({
|
||||
variables: {
|
||||
input: {
|
||||
defaultRoleId: isDefined(defaultRoleId) ? defaultRoleId : null,
|
||||
},
|
||||
},
|
||||
onCompleted: (data: UpdateWorkspaceMutation) => {
|
||||
const defaultRole = data.updateWorkspace.defaultRole;
|
||||
|
||||
setCurrentWorkspace({
|
||||
...currentWorkspace,
|
||||
defaultRole: isDefined(defaultRole) ? defaultRole : null,
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
if (!currentWorkspace || !defaultRole) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Options`}
|
||||
description={t`Adjust the role-related settings`}
|
||||
/>
|
||||
<Card rounded>
|
||||
<SettingsOptionCardContentSelect
|
||||
Icon={IconUserPin}
|
||||
title="Default Role"
|
||||
description={t`Set a default role for this workspace`}
|
||||
>
|
||||
<Select
|
||||
selectSizeVariant="small"
|
||||
withSearchInput
|
||||
dropdownId="default-role-select"
|
||||
options={roles.map((role) => ({
|
||||
label: role.label,
|
||||
value: role.id,
|
||||
}))}
|
||||
value={defaultRole?.id ?? ''}
|
||||
onChange={(value) =>
|
||||
updateDefaultRole(value as string, currentWorkspace)
|
||||
}
|
||||
/>
|
||||
</SettingsOptionCardContentSelect>
|
||||
</Card>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,25 @@
|
||||
import { Table } from '@/ui/layout/table/components/Table';
|
||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import styled from '@emotion/styled';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
|
||||
const StyledTableHeaderRow = styled(Table)`
|
||||
margin-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
export const RolesTableHeader = () => {
|
||||
return (
|
||||
<StyledTableHeaderRow>
|
||||
<TableRow>
|
||||
<TableHeader>
|
||||
<Trans>Name</Trans>
|
||||
</TableHeader>
|
||||
<TableHeader align={'right'}>
|
||||
<Trans>Assigned to</Trans>
|
||||
</TableHeader>
|
||||
<TableHeader align={'right'}></TableHeader>
|
||||
</TableRow>
|
||||
</StyledTableHeaderRow>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,117 @@
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import React from 'react';
|
||||
import {
|
||||
AppTooltip,
|
||||
Avatar,
|
||||
IconChevronRight,
|
||||
IconLock,
|
||||
IconUser,
|
||||
TooltipDelay,
|
||||
} from 'twenty-ui';
|
||||
import { Role } from '~/generated-metadata/graphql';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
|
||||
const StyledIconChevronRight = styled(IconChevronRight)`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
`;
|
||||
|
||||
const StyledAvatarContainer = styled.div`
|
||||
border: 0px;
|
||||
`;
|
||||
|
||||
const StyledAssignedText = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
font-size: ${({ theme }) => theme.font.size.md};
|
||||
`;
|
||||
|
||||
const StyledNameCell = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledAssignedCell = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const StyledAvatarGroup = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
margin-right: ${({ theme }) => theme.spacing(1)};
|
||||
|
||||
> * {
|
||||
margin-left: -5px;
|
||||
|
||||
&:first-of-type {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledTableRow = styled(TableRow)`
|
||||
&:hover {
|
||||
background: ${({ theme }) => theme.background.transparent.light};
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
export const RolesTableRow = ({ role }: { role: Role }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const navigateSettings = useNavigateSettings();
|
||||
|
||||
const handleRoleClick = (roleId: string) => {
|
||||
navigateSettings(SettingsPath.RoleDetail, { roleId });
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledTableRow key={role.id} onClick={() => handleRoleClick(role.id)}>
|
||||
<TableCell>
|
||||
<StyledNameCell>
|
||||
<IconUser size={theme.icon.size.md} />
|
||||
{role.label}
|
||||
{!role.isEditable && <IconLock size={theme.icon.size.sm} />}
|
||||
</StyledNameCell>
|
||||
</TableCell>
|
||||
<TableCell align={'right'}>
|
||||
<StyledAssignedCell>
|
||||
<StyledAvatarGroup>
|
||||
{role.workspaceMembers.slice(0, 5).map((workspaceMember) => (
|
||||
<React.Fragment key={workspaceMember.id}>
|
||||
<StyledAvatarContainer id={`avatar-${workspaceMember.id}`}>
|
||||
<Avatar
|
||||
avatarUrl={workspaceMember.avatarUrl}
|
||||
placeholderColorSeed={workspaceMember.id}
|
||||
placeholder={workspaceMember.name.firstName ?? ''}
|
||||
type="rounded"
|
||||
size="md"
|
||||
/>
|
||||
</StyledAvatarContainer>
|
||||
<AppTooltip
|
||||
anchorSelect={`#avatar-${workspaceMember.id}`}
|
||||
content={`${workspaceMember.name.firstName} ${workspaceMember.name.lastName}`}
|
||||
noArrow
|
||||
place="top"
|
||||
positionStrategy="fixed"
|
||||
delay={TooltipDelay.shortDelay}
|
||||
/>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</StyledAvatarGroup>
|
||||
<StyledAssignedText>
|
||||
{role.workspaceMembers.length}
|
||||
</StyledAssignedText>
|
||||
</StyledAssignedCell>
|
||||
</TableCell>
|
||||
<TableCell align={'right'}>
|
||||
<StyledIconChevronRight size={theme.icon.size.md} />
|
||||
</TableCell>
|
||||
</StyledTableRow>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,24 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator, RouterDecorator } from 'twenty-ui';
|
||||
|
||||
import { Roles } from '@/settings/roles/components/Roles';
|
||||
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||
import { getRolesMock } from '~/testing/mock-data/roles';
|
||||
|
||||
const meta: Meta<typeof Roles> = {
|
||||
title: 'Modules/Settings/Roles/Roles',
|
||||
component: Roles,
|
||||
decorators: [ComponentDecorator, I18nFrontDecorator, RouterDecorator],
|
||||
parameters: {
|
||||
maxWidth: 800,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof Roles>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
roles: getRolesMock(),
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,43 @@
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { RolesDefaultRole } from '@/settings/roles/components/RolesDefaultRole';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||
import { getRolesMock } from '~/testing/mock-data/roles';
|
||||
import { mockCurrentWorkspace } from '~/testing/mock-data/users';
|
||||
|
||||
const rolesMock = getRolesMock();
|
||||
|
||||
const RolesDefaultRoleWrapper = () => {
|
||||
return (
|
||||
<RecoilRoot
|
||||
initializeState={(snapshot) => {
|
||||
snapshot.set(currentWorkspaceState, {
|
||||
...mockCurrentWorkspace,
|
||||
defaultRole: rolesMock[1],
|
||||
});
|
||||
}}
|
||||
>
|
||||
<RolesDefaultRole roles={rolesMock} />
|
||||
</RecoilRoot>
|
||||
);
|
||||
};
|
||||
|
||||
const meta: Meta<typeof RolesDefaultRoleWrapper> = {
|
||||
title: 'Modules/Settings/Roles/RolesDefaultRole',
|
||||
component: RolesDefaultRoleWrapper,
|
||||
decorators: [ComponentDecorator, I18nFrontDecorator],
|
||||
parameters: {
|
||||
maxWidth: 800,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof RolesDefaultRoleWrapper>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
roles: rolesMock,
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,237 @@
|
||||
import { currentWorkspaceMembersState } from '@/auth/states/currentWorkspaceMembersStates';
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { RoleAssignmentTableHeader } from '@/settings/roles/role-assignment/components/RoleAssignmentTableHeader';
|
||||
import { RoleAssignmentWorkspaceMemberPickerDropdown } from '@/settings/roles/role-assignment/components/RoleAssignmentWorkspaceMemberPickerDropdown';
|
||||
import { RoleAssignmentConfirmationModalSelectedWorkspaceMember } from '@/settings/roles/role-assignment/types/RoleAssignmentConfirmationModalSelectedWorkspaceMember';
|
||||
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';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import {
|
||||
AppTooltip,
|
||||
Button,
|
||||
H2Title,
|
||||
IconPlus,
|
||||
IconSearch,
|
||||
Section,
|
||||
TooltipDelay,
|
||||
} from 'twenty-ui';
|
||||
import { Role, WorkspaceMember } from '~/generated-metadata/graphql';
|
||||
import {
|
||||
GetRolesDocument,
|
||||
useGetRolesQuery,
|
||||
useUpdateWorkspaceMemberRoleMutation,
|
||||
} from '~/generated/graphql';
|
||||
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)};
|
||||
`}
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
`;
|
||||
|
||||
const StyledSearchContainer = styled.div`
|
||||
margin: ${({ theme }) => theme.spacing(2)} 0;
|
||||
`;
|
||||
|
||||
const StyledSearchInput = styled(TextInput)`
|
||||
input {
|
||||
background: ${({ theme }) => theme.background.transparent.lighter};
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
|
||||
&:hover {
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
type RoleAssignmentProps = {
|
||||
role: Pick<Role, 'id' | 'label' | 'canUpdateAllSettings'> & {
|
||||
workspaceMembers: Array<WorkspaceMember>;
|
||||
};
|
||||
};
|
||||
|
||||
export const RoleAssignment = ({ role }: RoleAssignmentProps) => {
|
||||
const navigateSettings = useNavigateSettings();
|
||||
const [updateWorkspaceMemberRole] = useUpdateWorkspaceMemberRoleMutation({
|
||||
refetchQueries: [GetRolesDocument],
|
||||
});
|
||||
|
||||
const [confirmationModalIsOpen, setConfirmationModalIsOpen] =
|
||||
useState<boolean>(false);
|
||||
const [selectedWorkspaceMember, setSelectedWorkspaceMember] =
|
||||
useState<RoleAssignmentConfirmationModalSelectedWorkspaceMember | null>(
|
||||
null,
|
||||
);
|
||||
const { data: rolesData } = useGetRolesQuery();
|
||||
const { closeDropdown } = useDropdown('role-member-select');
|
||||
const [searchFilter, setSearchFilter] = useState('');
|
||||
const currentWorkspaceMembers = useRecoilValue(currentWorkspaceMembersState);
|
||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||
|
||||
const workspaceMemberRoleMap = new Map<
|
||||
string,
|
||||
{ id: string; label: string }
|
||||
>();
|
||||
rolesData?.getRoles?.forEach((role) => {
|
||||
role.workspaceMembers.forEach((member) => {
|
||||
workspaceMemberRoleMap.set(member.id, { id: role.id, label: role.label });
|
||||
});
|
||||
});
|
||||
|
||||
const filteredWorkspaceMembers = !searchFilter
|
||||
? role.workspaceMembers
|
||||
: role.workspaceMembers.filter((member) => {
|
||||
const searchTerm = searchFilter.toLowerCase();
|
||||
const firstName = member.name.firstName?.toLowerCase() || '';
|
||||
const lastName = member.name.lastName?.toLowerCase() || '';
|
||||
const email = member.userEmail?.toLowerCase() || '';
|
||||
|
||||
return (
|
||||
firstName.includes(searchTerm) ||
|
||||
lastName.includes(searchTerm) ||
|
||||
email.includes(searchTerm)
|
||||
);
|
||||
});
|
||||
|
||||
const assignedWorkspaceMemberIds = role.workspaceMembers.map(
|
||||
(workspaceMember) => workspaceMember.id,
|
||||
);
|
||||
|
||||
const assignableWorkspaceMembers = currentWorkspaceMembers.filter(
|
||||
(member) => member.id !== currentWorkspaceMember?.id,
|
||||
);
|
||||
|
||||
const allWorkspaceMembersHaveThisRole = assignableWorkspaceMembers.every(
|
||||
(member) => assignedWorkspaceMemberIds.includes(member.id),
|
||||
);
|
||||
|
||||
const handleModalClose = () => {
|
||||
setConfirmationModalIsOpen(false);
|
||||
setSelectedWorkspaceMember(null);
|
||||
};
|
||||
|
||||
const handleSelectWorkspaceMember = (workspaceMember: WorkspaceMember) => {
|
||||
const existingRole = workspaceMemberRoleMap.get(workspaceMember.id);
|
||||
|
||||
setSelectedWorkspaceMember({
|
||||
id: workspaceMember.id,
|
||||
name: `${workspaceMember.name.firstName} ${workspaceMember.name.lastName}`,
|
||||
role: existingRole,
|
||||
});
|
||||
setConfirmationModalIsOpen(true);
|
||||
closeDropdown();
|
||||
};
|
||||
|
||||
const handleConfirm = async () => {
|
||||
if (!selectedWorkspaceMember || !confirmationModalIsOpen) return;
|
||||
|
||||
await updateWorkspaceMemberRole({
|
||||
variables: {
|
||||
workspaceMemberId: selectedWorkspaceMember.id,
|
||||
roleId: role.id,
|
||||
},
|
||||
});
|
||||
|
||||
handleModalClose();
|
||||
};
|
||||
|
||||
const handleRoleClick = (roleId: string) => {
|
||||
navigateSettings(SettingsPath.RoleDetail, { roleId });
|
||||
handleModalClose();
|
||||
};
|
||||
|
||||
const handleSearchChange = (text: string) => {
|
||||
setSearchFilter(text);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Assigned members`}
|
||||
description={t`This role is assigned to these workspace members.`}
|
||||
/>
|
||||
<StyledSearchContainer>
|
||||
<StyledSearchInput
|
||||
value={searchFilter}
|
||||
onChange={handleSearchChange}
|
||||
placeholder={t`Search a member`}
|
||||
fullWidth
|
||||
LeftIcon={IconSearch}
|
||||
sizeVariant="lg"
|
||||
/>
|
||||
</StyledSearchContainer>
|
||||
<Table>
|
||||
<RoleAssignmentTableHeader />
|
||||
{filteredWorkspaceMembers.map((workspaceMember) => (
|
||||
<RoleAssignmentTableRow
|
||||
key={workspaceMember.id}
|
||||
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}
|
||||
/>
|
||||
</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}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</StyledBottomSection>
|
||||
|
||||
{confirmationModalIsOpen && selectedWorkspaceMember && (
|
||||
<RoleAssignmentConfirmationModal
|
||||
selectedWorkspaceMember={selectedWorkspaceMember}
|
||||
isOpen={true}
|
||||
onClose={handleModalClose}
|
||||
onConfirm={handleConfirm}
|
||||
onRoleClick={handleRoleClick}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,41 @@
|
||||
import { RoleAssignmentConfirmationModalSubtitle } from '@/settings/roles/role-assignment/components/RoleAssignmentConfirmationModalSubtitle';
|
||||
import { RoleAssignmentConfirmationModalSelectedWorkspaceMember } from '@/settings/roles/role-assignment/types/RoleAssignmentConfirmationModalSelectedWorkspaceMember';
|
||||
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
||||
import { t } from '@lingui/core/macro';
|
||||
|
||||
type RoleAssignmentConfirmationModalProps = {
|
||||
selectedWorkspaceMember: RoleAssignmentConfirmationModalSelectedWorkspaceMember;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onConfirm: () => void;
|
||||
onRoleClick: (roleId: string) => void;
|
||||
};
|
||||
|
||||
export const RoleAssignmentConfirmationModal = ({
|
||||
selectedWorkspaceMember,
|
||||
isOpen,
|
||||
onClose,
|
||||
onConfirm,
|
||||
onRoleClick,
|
||||
}: RoleAssignmentConfirmationModalProps) => {
|
||||
const workspaceMemberName = selectedWorkspaceMember.name;
|
||||
|
||||
const title = t`Assign ${workspaceMemberName}?`;
|
||||
|
||||
return (
|
||||
<ConfirmationModal
|
||||
isOpen={isOpen}
|
||||
setIsOpen={onClose}
|
||||
title={title}
|
||||
subtitle={
|
||||
<RoleAssignmentConfirmationModalSubtitle
|
||||
selectedWorkspaceMember={selectedWorkspaceMember}
|
||||
onRoleClick={onRoleClick}
|
||||
/>
|
||||
}
|
||||
onConfirmClick={onConfirm}
|
||||
deleteButtonText={t`Confirm`}
|
||||
confirmButtonAccent="blue"
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,37 @@
|
||||
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';
|
||||
|
||||
const StyledSettingsCardContainer = styled.div`
|
||||
margin-top: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
type RoleAssignmentConfirmationModalSubtitleProps = {
|
||||
selectedWorkspaceMember: RoleAssignmentConfirmationModalSelectedWorkspaceMember;
|
||||
onRoleClick: (roleId: string) => void;
|
||||
};
|
||||
|
||||
export const RoleAssignmentConfirmationModalSubtitle = ({
|
||||
selectedWorkspaceMember,
|
||||
onRoleClick,
|
||||
}: RoleAssignmentConfirmationModalSubtitleProps) => {
|
||||
const workspaceMemberName = selectedWorkspaceMember.name;
|
||||
|
||||
return (
|
||||
<>
|
||||
{t`${workspaceMemberName} will be unassigned from the following role:`}
|
||||
<StyledSettingsCardContainer>
|
||||
<SettingsCard
|
||||
title={selectedWorkspaceMember.role?.label || ''}
|
||||
Icon={<IconUser />}
|
||||
onClick={() =>
|
||||
selectedWorkspaceMember.role &&
|
||||
onRoleClick(selectedWorkspaceMember.role.id)
|
||||
}
|
||||
/>
|
||||
</StyledSettingsCardContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,19 @@
|
||||
import { Table } from '@/ui/layout/table/components/Table';
|
||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import styled from '@emotion/styled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
|
||||
const StyledTableHeaderRow = styled(Table)`
|
||||
margin-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
export const RoleAssignmentTableHeader = () => (
|
||||
<StyledTableHeaderRow>
|
||||
<TableRow gridAutoColumns="150px 1fr 1fr">
|
||||
<TableHeader>{t`Name`}</TableHeader>
|
||||
<TableHeader>{t`Email`}</TableHeader>
|
||||
<TableHeader align={'right'} aria-label={t`Actions`}></TableHeader>
|
||||
</TableRow>
|
||||
</StyledTableHeaderRow>
|
||||
);
|
||||
@ -0,0 +1,48 @@
|
||||
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`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
type RoleAssignmentTableRowProps = {
|
||||
workspaceMember: WorkspaceMember;
|
||||
};
|
||||
|
||||
export const RoleAssignmentTableRow = ({
|
||||
workspaceMember,
|
||||
}: RoleAssignmentTableRowProps) => {
|
||||
return (
|
||||
<StyledTable>
|
||||
<TableRow gridAutoColumns="150px 1fr 1fr">
|
||||
<TableCell>
|
||||
<StyledIconWrapper>
|
||||
<Avatar
|
||||
avatarUrl={workspaceMember.avatarUrl}
|
||||
placeholderColorSeed={workspaceMember.id}
|
||||
placeholder={workspaceMember.name.firstName ?? ''}
|
||||
type="rounded"
|
||||
size="md"
|
||||
/>
|
||||
</StyledIconWrapper>
|
||||
<OverflowingTextWithTooltip
|
||||
text={`${workspaceMember.name.firstName} ${workspaceMember.name.lastName}`}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<OverflowingTextWithTooltip text={workspaceMember.userEmail} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</StyledTable>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,54 @@
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useSearchRecords } from '@/object-record/hooks/useSearchRecords';
|
||||
import { RoleAssignmentWorkspaceMemberPickerDropdownContent } from '@/settings/roles/role-assignment/components/RoleAssignmentWorkspaceMemberPickerDropdownContent';
|
||||
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { ChangeEvent, useState } from 'react';
|
||||
import { WorkspaceMember } from '~/generated-metadata/graphql';
|
||||
|
||||
type RoleAssignmentWorkspaceMemberPickerDropdownProps = {
|
||||
excludedWorkspaceMemberIds: string[];
|
||||
onSelect: (workspaceMember: WorkspaceMember) => void;
|
||||
};
|
||||
|
||||
export const RoleAssignmentWorkspaceMemberPickerDropdown = ({
|
||||
excludedWorkspaceMemberIds,
|
||||
onSelect,
|
||||
}: RoleAssignmentWorkspaceMemberPickerDropdownProps) => {
|
||||
const [searchFilter, setSearchFilter] = useState('');
|
||||
|
||||
const { loading, records: workspaceMembers } = useSearchRecords({
|
||||
objectNameSingular: CoreObjectNameSingular.WorkspaceMember,
|
||||
searchInput: searchFilter,
|
||||
});
|
||||
|
||||
const filteredWorkspaceMembers = (workspaceMembers?.filter(
|
||||
(workspaceMember) =>
|
||||
!excludedWorkspaceMemberIds.includes(workspaceMember.id),
|
||||
) ?? []) as WorkspaceMember[];
|
||||
|
||||
const handleSearchFilterChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
setSearchFilter(event.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuSearchInput
|
||||
value={searchFilter}
|
||||
onChange={handleSearchFilterChange}
|
||||
placeholder="Search"
|
||||
/>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer>
|
||||
<RoleAssignmentWorkspaceMemberPickerDropdownContent
|
||||
loading={loading}
|
||||
searchFilter={searchFilter}
|
||||
filteredWorkspaceMembers={filteredWorkspaceMembers}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownMenu>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,43 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { MenuItem, MenuItemAvatar } from 'twenty-ui';
|
||||
import { WorkspaceMember } from '~/generated-metadata/graphql';
|
||||
|
||||
type RoleAssignmentWorkspaceMemberPickerDropdownContentProps = {
|
||||
loading: boolean;
|
||||
searchFilter: string;
|
||||
filteredWorkspaceMembers: WorkspaceMember[];
|
||||
onSelect: (workspaceMember: WorkspaceMember) => void;
|
||||
};
|
||||
|
||||
export const RoleAssignmentWorkspaceMemberPickerDropdownContent = ({
|
||||
loading,
|
||||
searchFilter,
|
||||
filteredWorkspaceMembers,
|
||||
onSelect,
|
||||
}: RoleAssignmentWorkspaceMemberPickerDropdownContentProps) => {
|
||||
if (loading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!filteredWorkspaceMembers.length && searchFilter.length > 0) {
|
||||
return <MenuItem disabled text={t`No Results`} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{filteredWorkspaceMembers.map((workspaceMember) => (
|
||||
<MenuItemAvatar
|
||||
key={workspaceMember.id}
|
||||
onClick={() => onSelect(workspaceMember)}
|
||||
avatar={{
|
||||
type: 'rounded',
|
||||
size: 'md',
|
||||
placeholder: workspaceMember.name.firstName ?? '',
|
||||
placeholderColorSeed: workspaceMember.id,
|
||||
}}
|
||||
text={`${workspaceMember.name.firstName} ${workspaceMember.name.lastName}`}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,5 @@
|
||||
export type RoleAssignmentConfirmationModalSelectedWorkspaceMember = {
|
||||
id: string;
|
||||
name: string;
|
||||
role?: { id: string; label: string };
|
||||
};
|
||||
@ -0,0 +1,139 @@
|
||||
import { RolePermissionsObjectsTableHeader } from '@/settings/roles/role-permissions/components/RolePermissionsObjectsTableHeader';
|
||||
import { RolePermissionsSettingsTableHeader } from '@/settings/roles/role-permissions/components/RolePermissionsSettingsTableHeader';
|
||||
import { RolePermissionsSettingsTableRow } from '@/settings/roles/role-permissions/components/RolePermissionsSettingsTableRow';
|
||||
import { RolePermissionsObjectPermission } from '@/settings/roles/types/RolePermissionsObjectPermission';
|
||||
import styled from '@emotion/styled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import {
|
||||
H2Title,
|
||||
IconEye,
|
||||
IconPencil,
|
||||
IconTrash,
|
||||
IconTrashX,
|
||||
Section,
|
||||
} from 'twenty-ui';
|
||||
import { Role } from '~/generated-metadata/graphql';
|
||||
import { SettingsPermissions } from '~/generated/graphql';
|
||||
import { RolePermissionsObjectsTableRow } from './RolePermissionsObjectsTableRow';
|
||||
|
||||
const StyledRolePermissionsContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${({ theme }) => theme.spacing(8)};
|
||||
`;
|
||||
|
||||
type RolePermissionsProps = {
|
||||
role: Pick<
|
||||
Role,
|
||||
| 'id'
|
||||
| 'canUpdateAllSettings'
|
||||
| 'canReadAllObjectRecords'
|
||||
| 'canUpdateAllObjectRecords'
|
||||
| 'canSoftDeleteAllObjectRecords'
|
||||
| 'canDestroyAllObjectRecords'
|
||||
>;
|
||||
};
|
||||
|
||||
export const RolePermissions = ({ role }: RolePermissionsProps) => {
|
||||
const objectPermissionsConfig: RolePermissionsObjectPermission[] = [
|
||||
{
|
||||
key: 'seeRecords',
|
||||
label: 'See Records on All Objects',
|
||||
icon: <IconEye size={14} />,
|
||||
value: role.canReadAllObjectRecords,
|
||||
},
|
||||
{
|
||||
key: 'editRecords',
|
||||
label: 'Edit Records on All Objects',
|
||||
icon: <IconPencil size={14} />,
|
||||
value: role.canUpdateAllObjectRecords,
|
||||
},
|
||||
{
|
||||
key: 'deleteRecords',
|
||||
label: 'Delete Records on All Objects',
|
||||
icon: <IconTrash size={14} />,
|
||||
value: role.canSoftDeleteAllObjectRecords,
|
||||
},
|
||||
{
|
||||
key: 'destroyRecords',
|
||||
label: 'Destroy Records on All Objects',
|
||||
icon: <IconTrashX size={14} />,
|
||||
value: role.canDestroyAllObjectRecords,
|
||||
},
|
||||
];
|
||||
|
||||
const settingsPermissionsConfig = [
|
||||
{
|
||||
key: SettingsPermissions.API_KEYS_AND_WEBHOOKS,
|
||||
label: 'API Keys and Webhooks',
|
||||
type: 'Developer',
|
||||
value: role.canUpdateAllSettings,
|
||||
},
|
||||
{
|
||||
key: SettingsPermissions.ROLES,
|
||||
label: 'Roles',
|
||||
type: 'Members',
|
||||
value: role.canUpdateAllSettings,
|
||||
},
|
||||
{
|
||||
key: SettingsPermissions.WORKSPACE,
|
||||
label: 'Workspace Settings',
|
||||
type: 'General',
|
||||
value: role.canUpdateAllSettings,
|
||||
},
|
||||
{
|
||||
key: SettingsPermissions.WORKSPACE_MEMBERS,
|
||||
label: 'Workspace Users',
|
||||
type: 'Members',
|
||||
value: role.canUpdateAllSettings,
|
||||
},
|
||||
{
|
||||
key: SettingsPermissions.DATA_MODEL,
|
||||
label: 'Data Model',
|
||||
type: 'Data Model',
|
||||
value: role.canUpdateAllSettings,
|
||||
},
|
||||
{
|
||||
key: SettingsPermissions.ADMIN_PANEL,
|
||||
label: 'Admin Panel',
|
||||
type: 'Admin Panel',
|
||||
value: role.canUpdateAllSettings,
|
||||
},
|
||||
{
|
||||
key: SettingsPermissions.SECURITY,
|
||||
label: 'Security Settings',
|
||||
type: 'Security',
|
||||
value: role.canUpdateAllSettings,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<StyledRolePermissionsContainer>
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Objects`}
|
||||
description={t`Ability to interact with each object`}
|
||||
/>
|
||||
<RolePermissionsObjectsTableHeader allPermissions={true} />
|
||||
{objectPermissionsConfig.map((permission) => (
|
||||
<RolePermissionsObjectsTableRow
|
||||
key={permission.key}
|
||||
permission={permission}
|
||||
/>
|
||||
))}
|
||||
</Section>
|
||||
<Section>
|
||||
<H2Title title={t`Settings`} description={t`Settings permissions`} />
|
||||
<RolePermissionsSettingsTableHeader
|
||||
allPermissions={role.canUpdateAllSettings}
|
||||
/>
|
||||
{settingsPermissionsConfig.map((permission) => (
|
||||
<RolePermissionsSettingsTableRow
|
||||
key={permission.key}
|
||||
permission={permission}
|
||||
/>
|
||||
))}
|
||||
</Section>
|
||||
</StyledRolePermissionsContainer>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,44 @@
|
||||
import { Table } from '@/ui/layout/table/components/Table';
|
||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import styled from '@emotion/styled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { Checkbox } from 'twenty-ui';
|
||||
|
||||
const StyledTableHeaderRow = styled(TableRow)`
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const StyledNameHeader = styled(TableHeader)`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
const StyledActionsHeader = styled(TableHeader)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-right: ${({ theme }) => theme.spacing(4)};
|
||||
`;
|
||||
|
||||
const StyledTable = styled(Table)`
|
||||
margin-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
type RolePermissionsObjectsTableHeaderProps = {
|
||||
className?: string;
|
||||
allPermissions: boolean;
|
||||
};
|
||||
|
||||
export const RolePermissionsObjectsTableHeader = ({
|
||||
className,
|
||||
allPermissions,
|
||||
}: RolePermissionsObjectsTableHeaderProps) => (
|
||||
<StyledTable className={className}>
|
||||
<StyledTableHeaderRow>
|
||||
<StyledNameHeader>{t`Name`}</StyledNameHeader>
|
||||
<StyledActionsHeader aria-label={t`Actions`}>
|
||||
<Checkbox checked={allPermissions} disabled />
|
||||
</StyledActionsHeader>
|
||||
</StyledTableHeaderRow>
|
||||
</StyledTable>
|
||||
);
|
||||
@ -0,0 +1,69 @@
|
||||
import { RolePermissionsObjectPermission } from '@/settings/roles/types/RolePermissionsObjectPermission';
|
||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import styled from '@emotion/styled';
|
||||
import { Checkbox } from 'twenty-ui';
|
||||
|
||||
const StyledIconWrapper = styled.div`
|
||||
align-items: center;
|
||||
background: ${({ theme }) => theme.adaptiveColors.blue1};
|
||||
border: 1px solid ${({ theme }) => theme.adaptiveColors.blue3};
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
display: flex;
|
||||
height: ${({ theme }) => theme.spacing(4)};
|
||||
justify-content: center;
|
||||
width: ${({ theme }) => theme.spacing(4)};
|
||||
`;
|
||||
|
||||
const StyledIcon = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
color: ${({ theme }) => theme.color.blue};
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const StyledLabel = styled.span`
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
`;
|
||||
|
||||
const StyledPermissionCell = styled(TableCell)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledCheckboxCell = styled(TableCell)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-right: ${({ theme }) => theme.spacing(4)};
|
||||
`;
|
||||
|
||||
const StyledTableRow = styled(TableRow)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
type RolePermissionsObjectsTableRowProps = {
|
||||
permission: RolePermissionsObjectPermission;
|
||||
};
|
||||
|
||||
export const RolePermissionsObjectsTableRow = ({
|
||||
permission,
|
||||
}: RolePermissionsObjectsTableRowProps) => {
|
||||
return (
|
||||
<StyledTableRow key={permission.key}>
|
||||
<StyledPermissionCell>
|
||||
<StyledIconWrapper>
|
||||
<StyledIcon>{permission.icon}</StyledIcon>
|
||||
</StyledIconWrapper>
|
||||
<StyledLabel>{permission.label}</StyledLabel>
|
||||
</StyledPermissionCell>
|
||||
<StyledCheckboxCell>
|
||||
<Checkbox checked={permission.value} disabled />
|
||||
</StyledCheckboxCell>
|
||||
</StyledTableRow>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,52 @@
|
||||
import { Table } from '@/ui/layout/table/components/Table';
|
||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import styled from '@emotion/styled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { Checkbox } from 'twenty-ui';
|
||||
|
||||
const StyledTableHeaderRow = styled(TableRow)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: ${({ theme }) => theme.spacing(8)};
|
||||
`;
|
||||
|
||||
const StyledNameHeader = styled(TableHeader)`
|
||||
flex: 1;
|
||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledActionsHeader = styled(TableHeader)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-right: ${({ theme }) => theme.spacing(4)};
|
||||
`;
|
||||
|
||||
const StyledTable = styled(Table)`
|
||||
margin-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledTypeHeader = styled(TableHeader)`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
type RolePermissionsSettingsTableHeaderProps = {
|
||||
className?: string;
|
||||
allPermissions: boolean;
|
||||
};
|
||||
|
||||
export const RolePermissionsSettingsTableHeader = ({
|
||||
className,
|
||||
allPermissions,
|
||||
}: RolePermissionsSettingsTableHeaderProps) => (
|
||||
<StyledTable className={className}>
|
||||
<StyledTableHeaderRow>
|
||||
<StyledNameHeader>{t`Name`}</StyledNameHeader>
|
||||
<StyledTypeHeader>{t`Type`}</StyledTypeHeader>
|
||||
<StyledActionsHeader aria-label={t`Actions`}>
|
||||
<Checkbox checked={allPermissions} disabled />
|
||||
</StyledActionsHeader>
|
||||
</StyledTableHeaderRow>
|
||||
</StyledTable>
|
||||
);
|
||||
@ -0,0 +1,55 @@
|
||||
import { RolePermissionsSettingPermission } from '@/settings/roles/types/RolePermissionsSettingPermission';
|
||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import styled from '@emotion/styled';
|
||||
import { Checkbox } from 'twenty-ui';
|
||||
|
||||
const StyledLabel = styled.span`
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
`;
|
||||
|
||||
const StyledType = styled(StyledLabel)`
|
||||
color: ${({ theme }) => theme.font.color.secondary};
|
||||
`;
|
||||
|
||||
const StyledPermissionCell = styled(TableCell)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledCheckboxCell = styled(TableCell)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-right: ${({ theme }) => theme.spacing(4)};
|
||||
`;
|
||||
|
||||
const StyledTableRow = styled(TableRow)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
type RolePermissionsSettingsTableRowProps = {
|
||||
permission: RolePermissionsSettingPermission;
|
||||
};
|
||||
|
||||
export const RolePermissionsSettingsTableRow = ({
|
||||
permission,
|
||||
}: RolePermissionsSettingsTableRowProps) => {
|
||||
return (
|
||||
<StyledTableRow key={permission.key}>
|
||||
<StyledPermissionCell>
|
||||
<StyledLabel>{permission.label}</StyledLabel>
|
||||
</StyledPermissionCell>
|
||||
<StyledPermissionCell>
|
||||
<StyledType>{permission.type}</StyledType>
|
||||
</StyledPermissionCell>
|
||||
<StyledCheckboxCell>
|
||||
<Checkbox checked={permission.value} disabled />
|
||||
</StyledCheckboxCell>
|
||||
</StyledTableRow>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,46 @@
|
||||
import styled from '@emotion/styled';
|
||||
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 { Role } from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledInputsContainer = styled.div`
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
margin-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledInputContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
type RoleSettingsProps = {
|
||||
role: Pick<Role, 'id' | 'label' | 'description'>;
|
||||
};
|
||||
|
||||
export const RoleSettings = ({ role }: RoleSettingsProps) => {
|
||||
return (
|
||||
<>
|
||||
<StyledInputsContainer>
|
||||
<StyledInputContainer>
|
||||
<IconPicker
|
||||
disabled={true}
|
||||
selectedIconKey={'IconUser'}
|
||||
onChange={() => {}}
|
||||
/>
|
||||
</StyledInputContainer>
|
||||
<TextInput value={role.label} disabled fullWidth />
|
||||
</StyledInputsContainer>
|
||||
<TextArea
|
||||
minRows={4}
|
||||
placeholder={t`Write a description`}
|
||||
value={role.description || ''}
|
||||
disabled
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,6 @@
|
||||
export type RolePermissionsObjectPermission = {
|
||||
key: string;
|
||||
label: string;
|
||||
icon: React.ReactNode;
|
||||
value: boolean;
|
||||
};
|
||||
@ -0,0 +1,6 @@
|
||||
export type RolePermissionsSettingPermission = {
|
||||
key: string;
|
||||
label: string;
|
||||
type: string;
|
||||
value: boolean;
|
||||
};
|
||||
@ -24,6 +24,7 @@ const workspaceMember: Omit<
|
||||
lastName: 'lastName',
|
||||
},
|
||||
locale: 'en',
|
||||
colorScheme: 'System',
|
||||
};
|
||||
|
||||
describe('useColorScheme', () => {
|
||||
|
||||
@ -15,7 +15,7 @@ export type WorkspaceMember = {
|
||||
};
|
||||
avatarUrl?: string | null;
|
||||
locale: string | null;
|
||||
colorScheme?: ColorScheme;
|
||||
colorScheme: ColorScheme;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
userEmail: string;
|
||||
|
||||
Reference in New Issue
Block a user