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

View File

@ -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>
);
};

View File

@ -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',
};
});

View File

@ -1,5 +1,5 @@
export * from './general';
export * from './ExportPage';
export * from './MembersPage';
export * from './member';
export * from './SyncPage';
export * from './PublishPage';

View File

@ -4,9 +4,9 @@ import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal';
import { Button } from '@/ui/button';
import Input from '@/ui/input';
import { useState } from 'react';
// import { getDataCenter } from '@affine/datacenter';
import { Avatar } from '@mui/material';
import { setMember } from '@/hooks/mock-data/mock';
import useMembers from '@/hooks/use-members';
import { User } from '@affine/datacenter';
interface LoginModalProps {
open: boolean;
onClose: () => void;
@ -43,46 +43,27 @@ export const debounce = <T extends (...args: any) => any>(
};
const gmailReg = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@gmail\.com$/;
export const InviteMembers = ({
export const InviteMemberModal = ({
open,
onClose,
workspaceId,
onInviteSuccess,
}: LoginModalProps) => {
const [email, setEmail] = useState<string>('');
const [showMember, setShowMember] = useState<boolean>(false);
const [showTip, setShowTip] = useState<boolean>(false);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const [userData, setUserData] = useState<any>({});
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);
setUserData({
name: 'wxl',
avatar: 'https://avatars.githubusercontent.com/u/20501502?v=4',
email: value,
getUserByEmail(value).then(data => {
if (data?.name) {
setUserData(data);
setShowTip(false);
}
});
// debounce(
// () => {
// getDataCenter()
// .then(dc =>
// dc.apis.getUserByEmail({
// email: value,
// workspace_id: workspaceId,
// })
// )
// .then(data => {
// if (data?.name) {
// setUserData(data);
// setShowTip(false);
// }
// });
// },
// 300,
// true
// )();
} else {
setShowTip(true);
}
@ -118,8 +99,8 @@ export const InviteMembers = ({
<NoFind>Non-Gmail is not supported</NoFind>
) : (
<Member>
{userData?.avatar_url ? (
<Avatar src={userData?.avatar_url}></Avatar>
{userData?.avatar ? (
<Avatar src={userData?.avatar}></Avatar>
) : (
<MemberIcon>
<EmailIcon></EmailIcon>
@ -139,19 +120,9 @@ export const InviteMembers = ({
<Button
shape="circle"
type="primary"
onClick={() => {
setMember(workspaceId, userData);
onClick={async () => {
await inviteMember(email);
onInviteSuccess();
// getDataCenter()
// .then(dc => dc.apis.inviteMember({ id: workspaceId, email }))
// .then(() => {
// onClose();
// onInviteSuccess && onInviteSuccess();
// })
// .catch(err => {
// // toast('Invite failed');
// console.log(err);
// });
}}
>
Invite

View File

@ -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>
);
};

View File

@ -0,0 +1 @@
export * from './MembersPage';

View File

@ -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',
};
});

View File

@ -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',

View File

@ -0,0 +1,51 @@
import { useCallback, useEffect, useState } from 'react';
import { Member } from '@affine/datacenter';
import { useAppState } from '@/providers/app-state-provider';
export const useMembers = () => {
const { dataCenter, currentWorkspace } = useAppState();
const [members, setMembers] = useState<Member[]>([]);
const [loaded, setLoaded] = useState(false);
const refreshMembers = useCallback(async () => {
if (!currentWorkspace || !dataCenter) return;
const members = await dataCenter.getMembers(currentWorkspace.id);
setMembers(members);
}, [dataCenter, currentWorkspace]);
useEffect(() => {
const init = async () => {
await refreshMembers();
setLoaded(true);
};
init();
}, [refreshMembers]);
const inviteMember = async (email: string) => {
currentWorkspace &&
dataCenter &&
(await dataCenter.inviteMember(currentWorkspace?.id, email));
};
const removeMember = async (permissionId: number) => {
if (!currentWorkspace || !dataCenter) {
return;
}
setLoaded(false);
await dataCenter.removeMember(currentWorkspace.id, permissionId);
await refreshMembers();
setLoaded(true);
};
const getUserByEmail = async (email: string) => {
if (!currentWorkspace) return null;
return dataCenter?.getUserByEmail(currentWorkspace.id, email);
};
return {
members,
removeMember,
inviteMember,
getUserByEmail,
loaded,
};
};
export default useMembers;

View File

@ -56,16 +56,10 @@ export const useWorkspaceHelper = () => {
});
};
const inviteMember = async (email: string) => {
currentWorkspace &&
(await dataCenter.inviteMember(currentWorkspace?.id, email));
};
return {
createWorkspace,
publishWorkspace,
updateWorkspace,
enableWorkspace,
inviteMember,
};
};

View File

@ -20,11 +20,9 @@ export const AppStateProvider = ({
children,
}: PropsWithChildren<AppStateContextProps>) => {
const [appState, setAppState] = useState<AppStateValue>({} as AppStateValue);
useEffect(() => {
const initState = async () => {
const dataCenter = await getDataCenter();
// Ensure datacenter has at least one workspace
if (dataCenter.workspaces.length === 0) {
await createDefaultWorkspace(dataCenter);

View File

@ -26,7 +26,7 @@ const _initializeDataCenter = () => {
export const getDataCenter = _initializeDataCenter();
export type { DataCenter };
export type { AccessTokenMessage } from './provider/affine/apis';
export * from './provider/affine/apis';
export { WorkspaceUnit } from './workspace-unit';
export { getLogger } from './logger';
export * from './message';