feat: connect with datacenter invite function

This commit is contained in:
QiShaoXuan
2023-01-11 21:23:41 +08:00
parent e2a9c6c552
commit 0152172dd1
12 changed files with 364 additions and 368 deletions
@@ -1,214 +0,0 @@
import {
StyledMemberAvatar,
StyledMemberButtonContainer,
StyledMemberEmail,
StyledMemberInfo,
StyledMemberListContainer,
StyledMemberListItem,
StyledMemberName,
StyledMemberNameContainer,
StyledMemberRoleContainer,
StyledMemberTitleContainer,
StyledMoreVerticalButton,
StyledPublishExplanation,
} from './style';
import { MoreVerticalIcon, EmailIcon, TrashIcon } from '@blocksuite/icons';
import { useEffect, useState } from 'react';
import { Button, IconButton } from '@/ui/button';
import { InviteMembers } from '../invite-members/index';
import { Menu, MenuItem } from '@/ui/menu';
import { Empty } from '@/ui/empty';
// import {
// deleteMember,
// getMembers,
// User,
// Workspace,
// } from '@/hooks/mock-data/mock';
import { WorkspaceUnit } from '@affine/datacenter';
import { useTemporaryHelper } from '@/providers/temporary-helper-provider';
import { StyledMemberWarp } from './general/style';
import { useConfirm } from '@/providers/ConfirmProvider';
// import { useAppState } from '@/providers/app-state-provider';
// import { useAppState } from '@/providers/app-state-provider';
export const MembersPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
const [isInviteModalShow, setIsInviteModalShow] = useState(false);
const [members, setMembers] = useState<[{ name: string; email: string }?]>(
[]
);
// const getMembers = async () =>{
// const members = await dataCenter.
// }
console.log('setMembers: ', setMembers);
const { user, login, updateWorkspaceMeta } = useTemporaryHelper();
const { confirm } = useConfirm();
// const refreshMembers = useCallback(() => {
// getDataCenter()
// .then(dc =>
// dc.apis.getWorkspaceMembers({
// id: workspace.id,
// })
// )
// .then(data => {
// setMembers(data);
// })
// .catch(err => {
// console.log(err);
// });
// }, [workspace.id]);
const setMembersList = () => {
// setMembers([]);
// const members = getMembers(workspace.id);
// members && setMembers(members);
};
useEffect(() => {
setMembersList();
// refreshMembers();
});
return (
<div>
{workspace.provider === 'affine' ? (
<>
<StyledMemberTitleContainer>
<StyledMemberNameContainer>
Users({members.length})
</StyledMemberNameContainer>
<StyledMemberRoleContainer>Access level</StyledMemberRoleContainer>
</StyledMemberTitleContainer>
<StyledMemberListContainer>
{members.length ? (
members.map((member, index) => {
return (
<StyledMemberListItem key={index}>
<StyledMemberNameContainer>
<StyledMemberAvatar alt="member avatar">
<EmailIcon></EmailIcon>
</StyledMemberAvatar>
<StyledMemberInfo>
<StyledMemberName>{member?.name}</StyledMemberName>
<StyledMemberEmail>{member?.email}</StyledMemberEmail>
</StyledMemberInfo>
</StyledMemberNameContainer>
<StyledMemberRoleContainer>
{/* {member.accepted
? member.type !== 99
? 'Member'
: 'Workspace Owner'
: 'Pending'} */}
Pending
</StyledMemberRoleContainer>
<StyledMoreVerticalButton>
<Menu
content={
<>
<MenuItem
onClick={() => {
// deleteMember(workspace.id, 0);
setMembersList();
// confirm({
// title: 'Delete Member?',
// content: `will delete member`,
// confirmText: 'Delete',
// confirmType: 'danger',
// }).then(confirm => {
// getDataCenter()
// .then(dc =>
// dc.apis.removeMember({
// permissionId: member.id,
// })
// )
// .then(() => {
// // console.log('data: ', data);
// toast('Moved to Trash');
// // refreshMembers();
// });
// });
}}
icon={<TrashIcon />}
>
Delete
</MenuItem>
</>
}
placement="bottom-end"
disablePortal={true}
>
<IconButton>
<MoreVerticalIcon />
</IconButton>
</Menu>
</StyledMoreVerticalButton>
</StyledMemberListItem>
);
})
) : (
<Empty
width={648}
sx={{ marginTop: '60px' }}
height={300}
></Empty>
)}
</StyledMemberListContainer>
<StyledMemberButtonContainer>
<Button
onClick={() => {
setIsInviteModalShow(true);
}}
type="primary"
shape="circle"
>
Invite Members
</Button>
<InviteMembers
onClose={() => {
setIsInviteModalShow(false);
}}
onInviteSuccess={() => {
setMembersList();
setIsInviteModalShow(false);
// refreshMembers();
}}
workspaceId={workspace.id}
open={isInviteModalShow}
></InviteMembers>
</StyledMemberButtonContainer>
</>
) : (
<StyledMemberWarp>
<>Collaborating with other members requires AFFiNE Cloud service.</>
<StyledPublishExplanation>
<Button
type="primary"
shape="circle"
onClick={() => {
confirm({
title: 'Enable AFFiNE Cloud?',
content: `If enabled, the data in this workspace will be backed up and synchronized via AFFiNE Cloud.`,
confirmText: user ? 'Enable' : 'Sign in and Enable',
cancelText: 'Skip',
}).then(confirm => {
if (confirm) {
if (user) {
updateWorkspaceMeta(workspace.id, { isPublish: true });
} else {
login();
updateWorkspaceMeta(workspace.id, { isPublish: true });
}
}
});
}}
>
Enable AFFiNE Cloud
</Button>
</StyledPublishExplanation>
</StyledMemberWarp>
)}
</div>
);
};
@@ -26,11 +26,3 @@ export const StyledSettingAvatarContent = styled('div')(() => {
export const StyledSettingAvatar = styled(MuiAvatar)(() => {
return { height: '72px', width: '72px', marginRight: '24px' };
});
export const StyledMemberWarp = styled('div')(() => {
return {
display: 'flex',
flexDirection: 'column',
padding: '40px 0',
};
});
@@ -1,5 +1,5 @@
export * from './general';
export * from './ExportPage';
export * from './MembersPage';
export * from './member';
export * from './SyncPage';
export * from './PublishPage';
@@ -0,0 +1,235 @@
import { EmailIcon } from '@blocksuite/icons';
import { styled } from '@/styles';
import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal';
import { Button } from '@/ui/button';
import Input from '@/ui/input';
import { useState } from 'react';
import { Avatar } from '@mui/material';
import useMembers from '@/hooks/use-members';
import { User } from '@affine/datacenter';
interface LoginModalProps {
open: boolean;
onClose: () => void;
workspaceId: string;
onInviteSuccess: () => void;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const debounce = <T extends (...args: any) => any>(
fn: T,
time?: number,
immediate?: boolean
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): ((...args: any) => any) => {
let timeoutId: null | number;
let defaultImmediate = immediate || false;
const delay = time || 300;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (...args: any) => {
if (defaultImmediate) {
fn.apply(this, args);
defaultImmediate = false;
return;
}
if (timeoutId) {
clearTimeout(timeoutId);
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
timeoutId = setTimeout(() => {
fn.apply(this, args);
timeoutId = null;
}, delay);
};
};
const gmailReg = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@gmail\.com$/;
export const InviteMemberModal = ({
open,
onClose,
onInviteSuccess,
}: LoginModalProps) => {
const [email, setEmail] = useState<string>('');
const [showMember, setShowMember] = useState<boolean>(false);
const [showTip, setShowTip] = useState<boolean>(false);
const [userData, setUserData] = useState<User | null>(null);
const { inviteMember, getUserByEmail } = useMembers();
const inputChange = (value: string) => {
setShowMember(true);
if (gmailReg.test(value)) {
setEmail(value);
setShowTip(false);
getUserByEmail(value).then(data => {
if (data?.name) {
setUserData(data);
setShowTip(false);
}
});
} else {
setShowTip(true);
}
};
return (
<div>
<Modal open={open} onClose={onClose}>
<ModalWrapper width={460} height={236}>
<Header>
<ModalCloseButton
top={6}
right={6}
onClick={() => {
onClose();
}}
/>
</Header>
<Content>
<ContentTitle>Invite members</ContentTitle>
<InviteBox>
<Input
width={360}
value={email}
onChange={inputChange}
onBlur={() => {
setShowMember(false);
}}
placeholder="Search mail (Gmail support only)"
></Input>
{showMember ? (
<Members>
{showTip ? (
<NoFind>Non-Gmail is not supported</NoFind>
) : (
<Member>
{userData?.avatar ? (
<Avatar src={userData?.avatar}></Avatar>
) : (
<MemberIcon>
<EmailIcon></EmailIcon>
</MemberIcon>
)}
<Email>{email}</Email>
{/* <div>invited</div> */}
</Member>
)}
</Members>
) : (
<></>
)}
</InviteBox>
</Content>
<Footer>
<Button
shape="circle"
type="primary"
onClick={async () => {
await inviteMember(email);
onInviteSuccess();
}}
>
Invite
</Button>
</Footer>
</ModalWrapper>
</Modal>
</div>
);
};
const Header = styled('div')({
position: 'relative',
height: '44px',
});
const Content = styled('div')({
display: 'flex',
padding: '0 48px',
flexDirection: 'column',
alignItems: 'center',
gap: '16px',
});
const ContentTitle = styled('h1')({
fontSize: '20px',
lineHeight: '28px',
fontWeight: 600,
textAlign: 'center',
paddingBottom: '16px',
});
const Footer = styled('div')({
height: '70px',
paddingLeft: '24px',
marginTop: '32px',
textAlign: 'center',
});
const InviteBox = styled('div')({
position: 'relative',
});
const Members = styled('div')(({ theme }) => {
return {
position: 'absolute',
width: '100%',
background: theme.colors.pageBackground,
textAlign: 'left',
zIndex: 1,
borderRadius: '0px 10px 10px 10px',
height: '56px',
padding: '8px 12px',
input: {
'&::placeholder': {
color: theme.colors.placeHolderColor,
},
},
};
});
const NoFind = styled('div')(({ theme }) => {
return {
color: theme.colors.iconColor,
fontSize: theme.font.sm,
lineHeight: '40px',
userSelect: 'none',
width: '100%',
};
});
const Member = styled('div')(({ theme }) => {
return {
color: theme.colors.iconColor,
fontSize: theme.font.sm,
lineHeight: '40px',
userSelect: 'none',
display: 'flex',
};
});
const MemberIcon = styled('div')(({ theme }) => {
return {
width: '40px',
height: '40px',
borderRadius: '50%',
color: theme.colors.primaryColor,
background: '#F5F5F5',
marginRight: '8px',
textAlign: 'center',
lineHeight: '45px',
// icon size
fontSize: '20px',
overflow: 'hidden',
img: {
width: '100%',
height: '100%',
},
};
});
const Email = styled('div')(({ theme }) => {
return {
flex: '1',
color: theme.colors.popoverColor,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
};
});
@@ -0,0 +1,189 @@
import {
StyledMemberAvatar,
StyledMemberButtonContainer,
StyledMemberEmail,
StyledMemberInfo,
StyledMemberListContainer,
StyledMemberListItem,
StyledMemberName,
StyledMemberNameContainer,
StyledMemberRoleContainer,
StyledMemberTitleContainer,
StyledMoreVerticalButton,
StyledPublishExplanation,
StyledMemberWarp,
} from './style';
import { MoreVerticalIcon, EmailIcon, TrashIcon } from '@blocksuite/icons';
import { useState } from 'react';
import { Button, IconButton } from '@/ui/button';
import { InviteMemberModal } from './InviteMemberModal';
import { Menu, MenuItem } from '@/ui/menu';
import { Empty } from '@/ui/empty';
import { WorkspaceUnit } from '@affine/datacenter';
import { useTemporaryHelper } from '@/providers/temporary-helper-provider';
import { useConfirm } from '@/providers/ConfirmProvider';
import { toast } from '@/ui/toast';
import useMembers from '@/hooks/use-members';
import Loading from '@/components/loading';
import { Wrapper } from '@/ui/layout';
export const MembersPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
const [isInviteModalShow, setIsInviteModalShow] = useState(false);
const { members, removeMember, loaded } = useMembers();
// FIXME: DELETE THIS
const { user, login, updateWorkspaceMeta } = useTemporaryHelper();
const { confirm } = useConfirm();
if (workspace.provider === 'affine') {
return (
<>
<StyledMemberListContainer>
{!loaded && (
<Wrapper justifyContent="center">
<Loading size={25} />
</Wrapper>
)}
{loaded && members.length === 0 && (
<Empty width={648} sx={{ marginTop: '60px' }} height={300} />
)}
{loaded && members.length && (
<>
<StyledMemberTitleContainer>
<StyledMemberNameContainer>
Users({members.length})
</StyledMemberNameContainer>
<StyledMemberRoleContainer>
Access level
</StyledMemberRoleContainer>
</StyledMemberTitleContainer>
{members.map((member, index) => {
const user = Object.assign(
{
avatar_url: '',
email: '',
id: '',
name: '',
},
member.user
);
return (
<StyledMemberListItem key={index}>
<StyledMemberNameContainer>
<StyledMemberAvatar
alt="member avatar"
src={user.avatar_url}
>
<EmailIcon />
</StyledMemberAvatar>
<StyledMemberInfo>
<StyledMemberName>{user.name}</StyledMemberName>
<StyledMemberEmail>
{member.user.email}
</StyledMemberEmail>
</StyledMemberInfo>
</StyledMemberNameContainer>
<StyledMemberRoleContainer>
{member.accepted
? member.type !== 99
? 'Member'
: 'Workspace Owner'
: 'Pending'}
</StyledMemberRoleContainer>
<StyledMoreVerticalButton>
<Menu
content={
<>
<MenuItem
onClick={async () => {
const confirmRemove = await confirm({
title: 'Delete Member?',
content: `will delete member`,
confirmText: 'Delete',
confirmType: 'danger',
});
if (!confirmRemove) {
return;
}
await removeMember(member.id);
toast(`${user.name} has been removed`);
}}
icon={<TrashIcon />}
>
Delete
</MenuItem>
</>
}
placement="bottom-end"
disablePortal={true}
>
<IconButton>
<MoreVerticalIcon />
</IconButton>
</Menu>
</StyledMoreVerticalButton>
</StyledMemberListItem>
);
})}
</>
)}
</StyledMemberListContainer>
<StyledMemberButtonContainer>
<Button
onClick={() => {
setIsInviteModalShow(true);
}}
type="primary"
shape="circle"
>
Invite Members
</Button>
<InviteMemberModal
onClose={() => {
setIsInviteModalShow(false);
}}
onInviteSuccess={() => {
setIsInviteModalShow(false);
// refreshMembers();
}}
workspaceId={workspace.id}
open={isInviteModalShow}
></InviteMemberModal>
</StyledMemberButtonContainer>
</>
);
}
return (
<StyledMemberWarp>
<>Collaborating with other members requires AFFiNE Cloud service.</>
<StyledPublishExplanation>
<Button
type="primary"
shape="circle"
onClick={() => {
confirm({
title: 'Enable AFFiNE Cloud?',
content: `If enabled, the data in this workspace will be backed up and synchronized via AFFiNE Cloud.`,
confirmText: user ? 'Enable' : 'Sign in and Enable',
cancelText: 'Skip',
}).then(confirm => {
if (confirm) {
if (user) {
updateWorkspaceMeta(workspace.id, { isPublish: true });
} else {
login();
updateWorkspaceMeta(workspace.id, { isPublish: true });
}
}
});
}}
>
Enable AFFiNE Cloud
</Button>
</StyledPublishExplanation>
</StyledMemberWarp>
);
};
@@ -0,0 +1 @@
export * from './MembersPage';
@@ -0,0 +1,107 @@
import { styled } from '@/styles';
import MuiAvatar from '@mui/material/Avatar';
import { Button } from '@/ui/button';
export const StyledMemberTitleContainer = styled('div')(() => {
return {
display: 'flex',
marginTop: '60px',
fontWeight: '500',
flex: 1,
};
});
export const StyledMemberAvatar = styled(MuiAvatar)(() => {
return { height: '40px', width: '40px' };
});
export const StyledMemberNameContainer = styled('div')(() => {
return {
display: 'flex',
alignItems: 'center',
width: '402px',
};
});
export const StyledMemberRoleContainer = styled('div')(() => {
return {
display: 'flex',
alignItems: 'center',
width: '222px',
};
});
export const StyledMemberListContainer = styled('ul')(() => {
return {
marginTop: '15px',
overflowY: 'scroll',
};
});
export const StyledMemberListItem = styled('li')(() => {
return {
display: 'flex',
alignItems: 'center',
height: '72px',
};
});
export const StyledMemberInfo = styled('div')(() => {
return {
paddingLeft: '12px',
};
});
export const StyledMemberName = styled('div')(({ theme }) => {
return {
fontWeight: '400',
fontSize: '18px',
lineHeight: '16px',
color: theme.colors.textColor,
};
});
export const StyledMemberEmail = styled('div')(({ theme }) => {
return {
fontWeight: '400',
fontSize: '16px',
lineHeight: '22px',
color: theme.colors.iconColor,
};
});
export const StyledMemberButtonContainer = styled('div')(() => {
return {
marginTop: '14px',
};
});
export const StyledMoreVerticalButton = styled('button')(() => {
return {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
width: '24px',
height: '24px',
cursor: 'pointer',
};
});
export const StyledPublishExplanation = styled('div')(() => {
return {
paddingRight: '48px',
fontWeight: '500',
fontSize: '18px',
lineHeight: '26px',
flex: 1,
marginTop: '60px',
};
});
export const StyledMemberWarp = styled('div')(() => {
return {
display: 'flex',
flexDirection: 'column',
padding: '40px 0',
};
});
@@ -96,99 +96,6 @@ export const StyledSettingH2 = styled('h2')<{ marginTop?: number }>(
}
);
export const StyledAvatarUploadBtn = styled(Button)(({ theme }) => {
return {
backgroundColor: theme.colors.hoverBackground,
color: theme.colors.primaryColor,
margin: '0 12px 0 24px',
};
});
export const StyledMemberTitleContainer = styled('div')(() => {
return {
display: 'flex',
marginTop: '60px',
fontWeight: '500',
flex: 1,
};
});
export const StyledMemberAvatar = styled(MuiAvatar)(() => {
return { height: '40px', width: '40px' };
});
export const StyledMemberNameContainer = styled('div')(() => {
return {
display: 'flex',
alignItems: 'center',
width: '402px',
};
});
export const StyledMemberRoleContainer = styled('div')(() => {
return {
display: 'flex',
alignItems: 'center',
width: '222px',
};
});
export const StyledMemberListContainer = styled('ul')(() => {
return {
marginTop: '15px',
overflowY: 'scroll',
};
});
export const StyledMemberListItem = styled('li')(() => {
return {
display: 'flex',
alignItems: 'center',
height: '72px',
};
});
export const StyledMemberInfo = styled('div')(() => {
return {
paddingLeft: '12px',
};
});
export const StyledMemberName = styled('div')(({ theme }) => {
return {
fontWeight: '400',
fontSize: '18px',
lineHeight: '16px',
color: theme.colors.textColor,
};
});
export const StyledMemberEmail = styled('div')(({ theme }) => {
return {
fontWeight: '400',
fontSize: '16px',
lineHeight: '22px',
color: theme.colors.iconColor,
};
});
export const StyledMemberButtonContainer = styled('div')(() => {
return {
marginTop: '14px',
};
});
export const StyledMoreVerticalButton = styled('button')(() => {
return {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
width: '24px',
height: '24px',
cursor: 'pointer',
};
});
export const StyledPublishExplanation = styled('div')(() => {
return {
paddingRight: '48px',