feat: refactoring casl permission checks for recursive nested operations (#778)
* feat: nested casl abilities * fix: remove unused packages * Fixes * Fix createMany broken * Fix lint * Fix lint * Fix lint * Fix lint * Fixes * Fix CommentThread * Fix bugs * Fix lint * Fix bugs * Fixed auto routing * Fixed app path --------- Co-authored-by: Charles Bochet <charles@twenty.com> Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
@ -1,6 +1,6 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
|
||||
import { Button } from '@/ui/button/components/Button';
|
||||
import { Button, ButtonVariant } from '@/ui/button/components/Button';
|
||||
import { ButtonGroup } from '@/ui/button/components/ButtonGroup';
|
||||
import { IconCheckbox, IconNotes, IconTimelineEvent } from '@/ui/icon/index';
|
||||
|
||||
@ -17,7 +17,7 @@ export function CommentThreadCreateButton({
|
||||
}: CommentThreadCreateButtonProps) {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<ButtonGroup variant="secondary">
|
||||
<ButtonGroup variant={ButtonVariant.Secondary}>
|
||||
<Button
|
||||
icon={<IconNotes size={theme.icon.size.sm} />}
|
||||
title="Note"
|
||||
|
||||
190
front/src/modules/activities/components/CommentThreadEditor.tsx
Normal file
190
front/src/modules/activities/components/CommentThreadEditor.tsx
Normal file
@ -0,0 +1,190 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { CommentThreadBodyEditor } from '@/activities/components/CommentThreadBodyEditor';
|
||||
import { CommentThreadComments } from '@/activities/components/CommentThreadComments';
|
||||
import { CommentThreadRelationPicker } from '@/activities/components/CommentThreadRelationPicker';
|
||||
import { CommentThreadTypeDropdown } from '@/activities/components/CommentThreadTypeDropdown';
|
||||
import { GET_COMMENT_THREADS_BY_TARGETS } from '@/activities/queries';
|
||||
import { PropertyBox } from '@/ui/editable-field/property-box/components/PropertyBox';
|
||||
import { PropertyBoxItem } from '@/ui/editable-field/property-box/components/PropertyBoxItem';
|
||||
import { useIsMobile } from '@/ui/hooks/useIsMobile';
|
||||
import { IconArrowUpRight } from '@/ui/icon/index';
|
||||
import {
|
||||
CommentThread,
|
||||
CommentThreadTarget,
|
||||
useUpdateCommentThreadMutation,
|
||||
} from '~/generated/graphql';
|
||||
import { debounce } from '~/utils/debounce';
|
||||
|
||||
import { CommentThreadActionBar } from '../right-drawer/components/CommentThreadActionBar';
|
||||
import { CommentForDrawer } from '../types/CommentForDrawer';
|
||||
|
||||
import '@blocknote/core/style.css';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
justify-content: space-between;
|
||||
overflow-y: auto;
|
||||
`;
|
||||
|
||||
const StyledUpperPartContainer = styled.div`
|
||||
align-items: flex-start;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
gap: ${({ theme }) => theme.spacing(4)};
|
||||
justify-content: flex-start;
|
||||
`;
|
||||
|
||||
const StyledTopContainer = styled.div`
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
background: ${({ theme }) => theme.background.secondary};
|
||||
border-bottom: ${({ theme }) =>
|
||||
useIsMobile() ? 'none' : `1px solid ${theme.border.color.medium}`};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
padding: 24px 24px 24px 48px;
|
||||
`;
|
||||
|
||||
const StyledEditableTitleInput = styled.input`
|
||||
background: transparent;
|
||||
|
||||
border: none;
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
display: flex;
|
||||
flex: 1 0 0;
|
||||
|
||||
flex-direction: column;
|
||||
font-family: Inter;
|
||||
font-size: ${({ theme }) => theme.font.size.xl};
|
||||
font-style: normal;
|
||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||
justify-content: center;
|
||||
|
||||
line-height: ${({ theme }) => theme.text.lineHeight.md};
|
||||
outline: none;
|
||||
width: calc(100% - ${({ theme }) => theme.spacing(2)});
|
||||
&::placeholder {
|
||||
color: ${({ theme }) => theme.font.color.light};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledTopActionsContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
type OwnProps = {
|
||||
commentThread: Pick<CommentThread, 'id' | 'title' | 'body' | 'type'> & {
|
||||
comments?: Array<CommentForDrawer> | null;
|
||||
} & {
|
||||
commentThreadTargets?: Array<
|
||||
Pick<CommentThreadTarget, 'id' | 'commentableId' | 'commentableType'>
|
||||
> | null;
|
||||
};
|
||||
showComment?: boolean;
|
||||
autoFillTitle?: boolean;
|
||||
};
|
||||
|
||||
export function CommentThreadEditor({
|
||||
commentThread,
|
||||
showComment = true,
|
||||
autoFillTitle = false,
|
||||
}: OwnProps) {
|
||||
const [hasUserManuallySetTitle, setHasUserManuallySetTitle] =
|
||||
useState<boolean>(false);
|
||||
|
||||
const [title, setTitle] = useState<string | null>(commentThread.title ?? '');
|
||||
|
||||
const [updateCommentThreadMutation] = useUpdateCommentThreadMutation();
|
||||
|
||||
const updateTitle = useCallback(
|
||||
(newTitle: string) => {
|
||||
updateCommentThreadMutation({
|
||||
variables: {
|
||||
id: commentThread.id,
|
||||
title: newTitle ?? '',
|
||||
},
|
||||
refetchQueries: [
|
||||
getOperationName(GET_COMMENT_THREADS_BY_TARGETS) ?? '',
|
||||
],
|
||||
});
|
||||
},
|
||||
[commentThread, updateCommentThreadMutation],
|
||||
);
|
||||
const debouncedUpdateTitle = debounce(updateTitle, 200);
|
||||
|
||||
function updateTitleFromBody(body: string) {
|
||||
const parsedTitle = JSON.parse(body)[0]?.content[0]?.text;
|
||||
if (!hasUserManuallySetTitle && autoFillTitle) {
|
||||
setTitle(parsedTitle);
|
||||
debouncedUpdateTitle(parsedTitle);
|
||||
}
|
||||
}
|
||||
|
||||
if (!commentThread) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledUpperPartContainer>
|
||||
<StyledTopContainer>
|
||||
<StyledTopActionsContainer>
|
||||
<CommentThreadTypeDropdown commentThread={commentThread} />
|
||||
<CommentThreadActionBar commentThreadId={commentThread?.id ?? ''} />
|
||||
</StyledTopActionsContainer>
|
||||
<StyledEditableTitleInput
|
||||
autoComplete="off"
|
||||
autoFocus
|
||||
placeholder={`${commentThread.type} title (optional)`}
|
||||
onChange={(event) => {
|
||||
setHasUserManuallySetTitle(true);
|
||||
setTitle(event.target.value);
|
||||
debouncedUpdateTitle(event.target.value);
|
||||
}}
|
||||
value={title ?? ''}
|
||||
/>
|
||||
<PropertyBox>
|
||||
<PropertyBoxItem
|
||||
icon={<IconArrowUpRight />}
|
||||
value={
|
||||
<CommentThreadRelationPicker
|
||||
commentThread={{
|
||||
id: commentThread.id,
|
||||
commentThreadTargets:
|
||||
commentThread.commentThreadTargets ?? [],
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="Relations"
|
||||
/>
|
||||
</PropertyBox>
|
||||
</StyledTopContainer>
|
||||
<CommentThreadBodyEditor
|
||||
commentThread={commentThread}
|
||||
onChange={updateTitleFromBody}
|
||||
/>
|
||||
</StyledUpperPartContainer>
|
||||
{showComment && (
|
||||
<CommentThreadComments
|
||||
commentThread={{
|
||||
id: commentThread.id,
|
||||
comments: commentThread.comments ?? [],
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</StyledContainer>
|
||||
);
|
||||
}
|
||||
@ -1,88 +1,8 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { CommentThreadBodyEditor } from '@/activities/components/CommentThreadBodyEditor';
|
||||
import { CommentThreadComments } from '@/activities/components/CommentThreadComments';
|
||||
import { CommentThreadRelationPicker } from '@/activities/components/CommentThreadRelationPicker';
|
||||
import { CommentThreadTypeDropdown } from '@/activities/components/CommentThreadTypeDropdown';
|
||||
import { GET_COMMENT_THREAD } from '@/activities/queries';
|
||||
import { PropertyBox } from '@/ui/editable-field/property-box/components/PropertyBox';
|
||||
import { PropertyBoxItem } from '@/ui/editable-field/property-box/components/PropertyBoxItem';
|
||||
import { useIsMobile } from '@/ui/hooks/useIsMobile';
|
||||
import { IconArrowUpRight } from '@/ui/icon/index';
|
||||
import {
|
||||
useGetCommentThreadQuery,
|
||||
useUpdateCommentThreadMutation,
|
||||
} from '~/generated/graphql';
|
||||
import { debounce } from '~/utils/debounce';
|
||||
|
||||
import { CommentThreadActionBar } from './CommentThreadActionBar';
|
||||
import { CommentThreadEditor } from '@/activities/components/CommentThreadEditor';
|
||||
import { useGetCommentThreadQuery } from '~/generated/graphql';
|
||||
|
||||
import '@blocknote/core/style.css';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
justify-content: space-between;
|
||||
overflow-y: auto;
|
||||
`;
|
||||
|
||||
const StyledUpperPartContainer = styled.div`
|
||||
align-items: flex-start;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
gap: ${({ theme }) => theme.spacing(4)};
|
||||
justify-content: flex-start;
|
||||
`;
|
||||
|
||||
const StyledTopContainer = styled.div`
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
background: ${({ theme }) => theme.background.secondary};
|
||||
border-bottom: ${({ theme }) =>
|
||||
useIsMobile() ? 'none' : `1px solid ${theme.border.color.medium}`};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
padding: 24px 24px 24px 48px;
|
||||
`;
|
||||
|
||||
const StyledEditableTitleInput = styled.input`
|
||||
background: transparent;
|
||||
|
||||
border: none;
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
display: flex;
|
||||
flex: 1 0 0;
|
||||
|
||||
flex-direction: column;
|
||||
font-family: Inter;
|
||||
font-size: ${({ theme }) => theme.font.size.xl};
|
||||
font-style: normal;
|
||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||
justify-content: center;
|
||||
|
||||
line-height: ${({ theme }) => theme.text.lineHeight.md};
|
||||
outline: none;
|
||||
width: calc(100% - ${({ theme }) => theme.spacing(2)});
|
||||
&::placeholder {
|
||||
color: ${({ theme }) => theme.font.color.light};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledTopActionsContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
type OwnProps = {
|
||||
commentThreadId: string;
|
||||
showComment?: boolean;
|
||||
@ -101,103 +21,14 @@ export function CommentThread({
|
||||
skip: !commentThreadId,
|
||||
});
|
||||
const commentThread = data?.findManyCommentThreads[0];
|
||||
const [hasUserManuallySetTitle, setHasUserManuallySetTitle] =
|
||||
useState<boolean>(false);
|
||||
|
||||
const [title, setTitle] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasUserManuallySetTitle) {
|
||||
setTitle(commentThread?.title ?? '');
|
||||
}
|
||||
}, [setTitle, commentThread?.title, hasUserManuallySetTitle]);
|
||||
|
||||
const [updateCommentThreadMutation] = useUpdateCommentThreadMutation();
|
||||
|
||||
const debounceUpdateTitle = useMemo(() => {
|
||||
function updateTitle(title: string) {
|
||||
if (commentThread) {
|
||||
updateCommentThreadMutation({
|
||||
variables: {
|
||||
id: commentThreadId,
|
||||
title: title ?? '',
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_COMMENT_THREAD) ?? ''],
|
||||
optimisticResponse: {
|
||||
__typename: 'Mutation',
|
||||
updateOneCommentThread: {
|
||||
__typename: 'CommentThread',
|
||||
id: commentThreadId,
|
||||
title: title,
|
||||
type: commentThread.type,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
return debounce(updateTitle, 200);
|
||||
}, [commentThreadId, updateCommentThreadMutation, commentThread]);
|
||||
|
||||
function updateTitleFromBody(body: string) {
|
||||
const parsedTitle = JSON.parse(body)[0]?.content[0]?.text;
|
||||
if (!hasUserManuallySetTitle && autoFillTitle) {
|
||||
setTitle(parsedTitle);
|
||||
debounceUpdateTitle(parsedTitle);
|
||||
}
|
||||
}
|
||||
|
||||
if (!commentThread) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledUpperPartContainer>
|
||||
<StyledTopContainer>
|
||||
<StyledTopActionsContainer>
|
||||
<CommentThreadTypeDropdown commentThread={commentThread} />
|
||||
<CommentThreadActionBar commentThreadId={commentThread?.id ?? ''} />
|
||||
</StyledTopActionsContainer>
|
||||
<StyledEditableTitleInput
|
||||
autoComplete="off"
|
||||
autoFocus
|
||||
placeholder={`${commentThread.type} title (optional)`}
|
||||
onChange={(event) => {
|
||||
setHasUserManuallySetTitle(true);
|
||||
setTitle(event.target.value);
|
||||
debounceUpdateTitle(event.target.value);
|
||||
}}
|
||||
value={title ?? ''}
|
||||
/>
|
||||
<PropertyBox>
|
||||
<PropertyBoxItem
|
||||
icon={<IconArrowUpRight />}
|
||||
value={
|
||||
<CommentThreadRelationPicker
|
||||
commentThread={{
|
||||
id: commentThread.id,
|
||||
commentThreadTargets:
|
||||
commentThread.commentThreadTargets ?? [],
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="Relations"
|
||||
/>
|
||||
</PropertyBox>
|
||||
</StyledTopContainer>
|
||||
<CommentThreadBodyEditor
|
||||
commentThread={commentThread}
|
||||
onChange={updateTitleFromBody}
|
||||
/>
|
||||
</StyledUpperPartContainer>
|
||||
{showComment && (
|
||||
<CommentThreadComments
|
||||
commentThread={{
|
||||
id: commentThread.id,
|
||||
comments: commentThread.comments ?? [],
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</StyledContainer>
|
||||
return commentThread ? (
|
||||
<CommentThreadEditor
|
||||
commentThread={commentThread}
|
||||
showComment={showComment}
|
||||
autoFillTitle={autoFillTitle}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ import { useRecoilState } from 'recoil';
|
||||
import { GET_COMMENT_THREADS_BY_TARGETS } from '@/activities/queries';
|
||||
import { GET_COMPANIES } from '@/companies/queries';
|
||||
import { GET_PEOPLE } from '@/people/queries';
|
||||
import { Button } from '@/ui/button/components/Button';
|
||||
import { Button, ButtonVariant } from '@/ui/button/components/Button';
|
||||
import { IconTrash } from '@/ui/icon';
|
||||
import { isRightDrawerOpenState } from '@/ui/right-drawer/states/isRightDrawerOpenState';
|
||||
import { useDeleteCommentThreadMutation } from '~/generated/graphql';
|
||||
@ -44,7 +44,7 @@ export function CommentThreadActionBar({ commentThreadId }: OwnProps) {
|
||||
<IconTrash size={theme.icon.size.sm} stroke={theme.icon.stroke.md} />
|
||||
}
|
||||
onClick={deleteCommentThread}
|
||||
variant="tertiary"
|
||||
variant={ButtonVariant.Tertiary}
|
||||
/>
|
||||
</StyledContainer>
|
||||
);
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { companyAddressFamilyState } from '@/companies/states/companyAddressFamilyState';
|
||||
@ -15,28 +14,21 @@ export function EditableCompanyAddressCell() {
|
||||
companyAddressFamilyState(currentRowEntityId ?? ''),
|
||||
);
|
||||
|
||||
const [internalValue, setInternalValue] = useState(address ?? '');
|
||||
useEffect(() => {
|
||||
setInternalValue(address ?? '');
|
||||
}, [address]);
|
||||
|
||||
return (
|
||||
<EditableCellText
|
||||
value={internalValue}
|
||||
onChange={setInternalValue}
|
||||
onSubmit={() =>
|
||||
value={address || ''}
|
||||
onSubmit={(newAddress) =>
|
||||
updateCompany({
|
||||
variables: {
|
||||
where: {
|
||||
id: currentRowEntityId,
|
||||
},
|
||||
data: {
|
||||
address: internalValue,
|
||||
address: newAddress,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
onCancel={() => setInternalValue(address ?? '')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { companyEmployeesFamilyState } from '@/companies/states/companyEmployeesFamilyState';
|
||||
@ -15,30 +14,22 @@ export function EditableCompanyEmployeesCell() {
|
||||
companyEmployeesFamilyState(currentRowEntityId ?? ''),
|
||||
);
|
||||
|
||||
const [internalValue, setInternalValue] = useState(employees ?? '');
|
||||
|
||||
useEffect(() => {
|
||||
setInternalValue(employees ?? '');
|
||||
}, [employees]);
|
||||
|
||||
return (
|
||||
// TODO: Create an EditableCellNumber component
|
||||
<EditableCellText
|
||||
value={internalValue}
|
||||
onChange={setInternalValue}
|
||||
onSubmit={() =>
|
||||
value={employees || ''}
|
||||
onSubmit={(newValue) =>
|
||||
updateCompany({
|
||||
variables: {
|
||||
where: {
|
||||
id: currentRowEntityId,
|
||||
},
|
||||
data: {
|
||||
employees: parseInt(internalValue),
|
||||
employees: parseInt(newValue),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
onCancel={() => setInternalValue(employees ?? '')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
|
||||
@ -15,28 +14,22 @@ export function EditableCompanyLinkedinUrlCell() {
|
||||
const linkedinUrl = useRecoilValue(
|
||||
companyLinkedinUrlFamilyState(currentRowEntityId ?? ''),
|
||||
);
|
||||
const [internalValue, setInternalValue] = useState(linkedinUrl ?? '');
|
||||
useEffect(() => {
|
||||
setInternalValue(linkedinUrl ?? '');
|
||||
}, [linkedinUrl]);
|
||||
|
||||
return (
|
||||
<EditableCellURL
|
||||
url={internalValue}
|
||||
onChange={setInternalValue}
|
||||
onSubmit={() =>
|
||||
url={linkedinUrl || ''}
|
||||
onSubmit={(newUrl) =>
|
||||
updateCompany({
|
||||
variables: {
|
||||
where: {
|
||||
id: currentRowEntityId,
|
||||
},
|
||||
data: {
|
||||
linkedinUrl: internalValue,
|
||||
linkedinUrl: newUrl,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
onCancel={() => setInternalValue(linkedinUrl ?? '')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { peopleEmailFamilyState } from '@/people/states/peopleEmailFamilyState';
|
||||
@ -15,29 +14,21 @@ export function EditablePeopleEmailCell() {
|
||||
peopleEmailFamilyState(currentRowEntityId ?? ''),
|
||||
);
|
||||
|
||||
const [internalValue, setInternalValue] = useState(email ?? '');
|
||||
|
||||
useEffect(() => {
|
||||
setInternalValue(email ?? '');
|
||||
}, [email]);
|
||||
|
||||
return (
|
||||
<EditableCellText
|
||||
value={internalValue}
|
||||
onChange={setInternalValue}
|
||||
onSubmit={() =>
|
||||
value={email || ''}
|
||||
onSubmit={(newEmail: string) =>
|
||||
updatePerson({
|
||||
variables: {
|
||||
where: {
|
||||
id: currentRowEntityId,
|
||||
},
|
||||
data: {
|
||||
email: internalValue,
|
||||
email: newEmail,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
onCancel={() => setInternalValue(email ?? '')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
export enum AppPath {
|
||||
// Not logged-in
|
||||
Verify = 'verify',
|
||||
SignIn = 'sign-in',
|
||||
SignUp = 'sign-up',
|
||||
Invite = 'invite/:workspaceInviteHash',
|
||||
Verify = '/verify',
|
||||
SignIn = '/sign-in',
|
||||
SignUp = '/sign-up',
|
||||
Invite = '/invite/:workspaceInviteHash',
|
||||
|
||||
// Onboarding
|
||||
CreateWorkspace = 'create/workspace',
|
||||
CreateProfile = 'create/profile',
|
||||
CreateWorkspace = '/create/workspace',
|
||||
CreateProfile = '/create/profile',
|
||||
|
||||
// Onboarded
|
||||
Index = '',
|
||||
Index = '/',
|
||||
PeoplePage = '/people',
|
||||
CompaniesPage = '/companies',
|
||||
CompanyShowPage = '/companies/:companyId',
|
||||
|
||||
@ -19,11 +19,11 @@ export function ButtonGroup({ children, variant, size }: ButtonGroupProps) {
|
||||
let position: ButtonPosition;
|
||||
|
||||
if (index === 0) {
|
||||
position = 'left';
|
||||
position = ButtonPosition.Left;
|
||||
} else if (index === children.length - 1) {
|
||||
position = 'right';
|
||||
position = ButtonPosition.Right;
|
||||
} else {
|
||||
position = 'middle';
|
||||
position = ButtonPosition.Middle;
|
||||
}
|
||||
|
||||
const additionalProps: any = { position };
|
||||
|
||||
@ -48,6 +48,7 @@ export function InplaceInputTextEditMode({
|
||||
|
||||
return (
|
||||
<StyledInput
|
||||
autoComplete="off"
|
||||
ref={wrapperRef}
|
||||
placeholder={placeholder}
|
||||
onChange={handleChange}
|
||||
|
||||
@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { Button } from '@/ui/button/components/Button';
|
||||
import { Button, ButtonVariant } from '@/ui/button/components/Button';
|
||||
import { IconFileUpload, IconTrash, IconUpload } from '@/ui/icon';
|
||||
|
||||
const Container = styled.div`
|
||||
@ -123,7 +123,7 @@ export function ImageInput({
|
||||
<Button
|
||||
icon={<IconUpload size={theme.icon.size.sm} />}
|
||||
onClick={onUploadButtonClick}
|
||||
variant="secondary"
|
||||
variant={ButtonVariant.Secondary}
|
||||
title="Upload"
|
||||
disabled={disabled}
|
||||
fullWidth
|
||||
@ -131,7 +131,7 @@ export function ImageInput({
|
||||
<Button
|
||||
icon={<IconTrash size={theme.icon.size.sm} />}
|
||||
onClick={onRemove}
|
||||
variant="secondary"
|
||||
variant={ButtonVariant.Secondary}
|
||||
title="Remove"
|
||||
disabled={!picture || disabled}
|
||||
fullWidth
|
||||
|
||||
@ -48,9 +48,9 @@ export function EditableCellDoubleTextEditMode({
|
||||
setSecondInternalValue(secondValue);
|
||||
}, [firstValue, secondValue]);
|
||||
|
||||
function handleOnChange(firstValue: string, secondValue: string): void {
|
||||
setFirstInternalValue(firstValue);
|
||||
setSecondInternalValue(secondValue);
|
||||
function handleOnChange(newFirstValue: string, newSecondValue: string): void {
|
||||
setFirstInternalValue(newFirstValue);
|
||||
setSecondInternalValue(newSecondValue);
|
||||
}
|
||||
|
||||
const [focusPosition, setFocusPosition] = useState<'left' | 'right'>('left');
|
||||
@ -142,18 +142,18 @@ export function EditableCellDoubleTextEditMode({
|
||||
autoFocus
|
||||
placeholder={firstValuePlaceholder}
|
||||
ref={firstValueInputRef}
|
||||
value={firstValue}
|
||||
value={firstInternalValue}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
handleOnChange(event.target.value, secondValue);
|
||||
handleOnChange(event.target.value, secondInternalValue);
|
||||
}}
|
||||
/>
|
||||
<StyledInput
|
||||
autoComplete="off"
|
||||
placeholder={secondValuePlaceholder}
|
||||
ref={secondValueInputRef}
|
||||
value={secondValue}
|
||||
value={secondInternalValue}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
handleOnChange(firstValue, event.target.value);
|
||||
handleOnChange(firstInternalValue, event.target.value);
|
||||
}}
|
||||
/>
|
||||
</StyledContainer>
|
||||
|
||||
@ -14,7 +14,6 @@ export function EditableCellPhone({ value, placeholder, onSubmit }: OwnProps) {
|
||||
<EditableCell
|
||||
editModeContent={
|
||||
<InplaceInputTextEditMode
|
||||
autoComplete="off"
|
||||
autoFocus
|
||||
placeholder={placeholder || ''}
|
||||
value={value}
|
||||
|
||||
@ -32,7 +32,6 @@ export function EditableCellText({
|
||||
autoFocus
|
||||
value={value}
|
||||
onSubmit={(newText) => onSubmit?.(newText)}
|
||||
autoComplete="off"
|
||||
/>
|
||||
}
|
||||
nonEditModeContent={
|
||||
|
||||
@ -27,7 +27,6 @@ export function EditableCellURL({
|
||||
editModeContent={
|
||||
<InplaceInputTextEditMode
|
||||
placeholder={placeholder}
|
||||
autoComplete="off"
|
||||
autoFocus
|
||||
value={url}
|
||||
onSubmit={(newURL) => onSubmit?.(newURL)}
|
||||
|
||||
@ -56,7 +56,6 @@ export function EditableCellChip({
|
||||
placeholder={placeholder || ''}
|
||||
autoFocus
|
||||
value={inputValue}
|
||||
autoComplete="off"
|
||||
onSubmit={(newValue) => onSubmit?.(newValue)}
|
||||
/>
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { Button } from '@/ui/button/components/Button';
|
||||
import { Button, ButtonVariant } from '@/ui/button/components/Button';
|
||||
import { IconCopy, IconLink } from '@/ui/icon';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { useSnackBar } from '@/ui/snack-bar/hooks/useSnackBar';
|
||||
@ -33,7 +33,7 @@ export function WorkspaceInviteLink({ inviteLink }: OwnProps) {
|
||||
</StyledLinkContainer>
|
||||
<Button
|
||||
icon={<IconLink size={theme.icon.size.md} />}
|
||||
variant="primary"
|
||||
variant={ButtonVariant.Primary}
|
||||
title="Copy link"
|
||||
onClick={() => {
|
||||
enqueueSnackBar('Link copied to clipboard', {
|
||||
|
||||
Reference in New Issue
Block a user