diff --git a/packages/app/src/components/workspace-modal/Footer.tsx b/packages/app/src/components/workspace-modal/Footer.tsx
new file mode 100644
index 000000000..ea38de2a9
--- /dev/null
+++ b/packages/app/src/components/workspace-modal/Footer.tsx
@@ -0,0 +1,56 @@
+import { CloudInsyncIcon, LogOutIcon } from '@blocksuite/icons';
+import { Wrapper } from '@/ui/layout';
+import { WorkspaceAvatar } from '@/components/workspace-avatar';
+import { IconButton } from '@/ui/button';
+import { useAppState } from '@/providers/app-state-provider';
+import { StyledFooter, StyleUserInfo, StyleSignIn } from './styles';
+
+export const Footer = ({
+ onLogin,
+ onLogout,
+}: {
+ onLogin: () => void;
+ onLogout: () => void;
+}) => {
+ const { user } = useAppState();
+
+ return (
+
+ {user && (
+ <>
+
+
+
+ {user.name}
+ {user.email}
+
+
+ {
+ onLogout();
+ }}
+ >
+
+
+ >
+ )}
+
+ {!user && (
+ {
+ onLogin();
+ }}
+ >
+
+
+
+ Sign in to sync with AFFINE Cloud
+
+ )}
+
+ );
+};
diff --git a/packages/app/src/components/workspace-modal/LanguageMenu.tsx b/packages/app/src/components/workspace-modal/LanguageMenu.tsx
new file mode 100644
index 000000000..ad579e145
--- /dev/null
+++ b/packages/app/src/components/workspace-modal/LanguageMenu.tsx
@@ -0,0 +1,61 @@
+import { LOCALES } from '@affine/i18n';
+import { styled } from '@/styles';
+import { useTranslation } from '@affine/i18n';
+import { ArrowDownIcon } from '@blocksuite/icons';
+import { Button } from '@/ui/button';
+import { Menu, MenuItem } from '@/ui/menu';
+
+const LanguageMenuContent = () => {
+ const { i18n } = useTranslation();
+ const changeLanguage = (event: string) => {
+ i18n.changeLanguage(event);
+ };
+ return (
+ <>
+ {LOCALES.map(option => {
+ return (
+ {
+ changeLanguage(option.tag);
+ }}
+ >
+ {option.originalName}
+
+ );
+ })}
+ >
+ );
+};
+export const LanguageMenu = () => {
+ const { i18n } = useTranslation();
+
+ const currentLanguage = LOCALES.find(item => item.tag === i18n.language);
+
+ return (
+
}
+ placement="bottom"
+ trigger="click"
+ disablePortal={true}
+ >
+ }
+ iconPosition="end"
+ noBorder={true}
+ style={{ textTransform: 'capitalize' }}
+ >
+ {currentLanguage?.originalName}
+
+
+ );
+};
+
+const ListItem = styled(MenuItem)(({ theme }) => ({
+ height: '38px',
+ color: theme.colors.popoverColor,
+ fontSize: theme.font.sm,
+ textTransform: 'capitalize',
+ padding: '0 24px',
+}));
diff --git a/packages/app/src/components/workspace-modal/WorkspaceCard.tsx b/packages/app/src/components/workspace-modal/WorkspaceCard.tsx
new file mode 100644
index 000000000..1a6032d65
--- /dev/null
+++ b/packages/app/src/components/workspace-modal/WorkspaceCard.tsx
@@ -0,0 +1,69 @@
+import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
+import {
+ CloudIcon,
+ LocalIcon,
+ OfflineIcon,
+} from '@/components/workspace-modal/icons';
+import { PublishIcon, UsersIcon } from '@blocksuite/icons';
+import { WorkspaceUnit } from '@affine/datacenter';
+import { useAppState } from '@/providers/app-state-provider';
+import { StyleWorkspaceInfo, StyleWorkspaceTitle, StyledCard } from './styles';
+import { Wrapper } from '@/ui/layout';
+
+export const WorkspaceCard = ({
+ workspaceData,
+ onClick,
+}: {
+ workspaceData: WorkspaceUnit;
+ onClick: (data: WorkspaceUnit) => void;
+}) => {
+ const { currentWorkspace, isOwner } = useAppState();
+
+ return (
+ {
+ onClick(workspaceData);
+ }}
+ active={workspaceData.id === currentWorkspace?.id}
+ >
+
+
+
+
+
+
+ {workspaceData.name || 'AFFiNE'}
+
+ {isOwner ? (
+ workspaceData.provider === 'local' ? (
+
+
+ Local Workspace
+
+ ) : (
+
+
+ Cloud Workspace
+
+ )
+ ) : (
+
+
+ Joined Workspace
+
+ )}
+ {workspaceData.provider === 'local' && (
+
+
+ All data can be accessed offline
+
+ )}
+ {workspaceData.published && (
+
+ Published to Web
+
+ )}
+
+
+ );
+};
diff --git a/packages/app/src/components/workspace-modal/index.tsx b/packages/app/src/components/workspace-modal/index.tsx
index c0dd2a8bd..93b1bb727 100644
--- a/packages/app/src/components/workspace-modal/index.tsx
+++ b/packages/app/src/components/workspace-modal/index.tsx
@@ -1,28 +1,35 @@
-import { styled } from '@/styles';
-import { Modal, ModalWrapper } from '@/ui/modal';
-import { Button, IconButton } from '@/ui/button';
+import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal';
+import { Wrapper } from '@/ui/layout';
import { useState } from 'react';
import { CreateWorkspaceModal } from '../create-workspace';
-import {
- UsersIcon,
- AddIcon,
- LogOutIcon,
- CloudInsyncIcon,
- PublishIcon,
- CloseIcon,
-} from '@blocksuite/icons';
-import {
- WorkspaceAvatar,
- WorkspaceUnitAvatar,
-} from '@/components/workspace-avatar';
+import { Tooltip } from '@/ui/tooltip';
+import { toast } from '@/ui/toast';
+
+import { AddIcon, HelpCenterIcon } from '@blocksuite/icons';
+
import { useAppState } from '@/providers/app-state-provider';
import { useRouter } from 'next/router';
import { useTranslation } from '@affine/i18n';
-import { LanguageMenu } from './languageMenu';
+import { LanguageMenu } from './LanguageMenu';
-import { CloudIcon, LineIcon, LocalIcon, OfflineIcon } from './icons';
import { LoginModal } from '../login-modal';
import { LogoutModal } from '../logout-modal';
+import {
+ StyledCard,
+ StyledSplitLine,
+ StyleWorkspaceInfo,
+ StyleWorkspaceTitle,
+ StyledModalHeaderLeft,
+ StyledModalTitle,
+ StyledHelperContainer,
+ StyledModalContent,
+ StyledOperationWrapper,
+ StyleWorkspaceAdd,
+ StyledModalHeader,
+} from './styles';
+import { WorkspaceCard } from './WorkspaceCard';
+import { Footer } from './Footer';
+import { useConfirm } from '@/providers/ConfirmProvider';
interface WorkspaceModalProps {
open: boolean;
onClose: () => void;
@@ -30,337 +37,128 @@ interface WorkspaceModalProps {
export const WorkspaceModal = ({ open, onClose }: WorkspaceModalProps) => {
const [createWorkspaceOpen, setCreateWorkspaceOpen] = useState(false);
- const { workspaceList, currentWorkspace, user, logout, isOwner } =
- useAppState();
+ const { workspaceList, logout } = useAppState();
const router = useRouter();
const { t } = useTranslation();
const [loginOpen, setLoginOpen] = useState(false);
const [logoutOpen, setLogoutOpen] = useState(false);
+ const { confirm } = useConfirm();
+
return (
-
+ <>
-
-
-
- {workspaceList.map((item, index) => {
- return (
- {
- router.replace(`/workspace/${item.id}`);
- onClose();
- }}
- active={item.id === currentWorkspace?.id}
- key={index}
- >
-
-
-
+ absolute={false}
+ />
+
+
-
-
- {item.name || 'AFFiNE'}
-
- {isOwner ? (
- item.provider === 'local' ? (
-
-
- Local Workspace
-
- ) : (
-
-
- Cloud Workspace
-
- )
- ) : (
-
-
- Joined Workspace
-
- )}
- {item.provider === 'local' && (
-
-
- All data can be accessed offline
-
- )}
- {item.published && (
-
- Published to Web
-
- )}
-
-
- );
- })}
- {
- setCreateWorkspaceOpen(true);
- }}
- >
-
+
+ {workspaceList.map((item, index) => {
+ return (
+ {
+ router.replace(`/workspace/${workspaceData.id}`);
+ onClose();
+ }}
+ key={index}
+ >
+ );
+ })}
+ {
+ setCreateWorkspaceOpen(true);
+ }}
+ >
+
+
+
+
+
-
- New workspace
- Crete or import
-
-
-
- {/*
- {t('Tips')}
- {t('Workspace description')}
-
*/}
-
- {
- setLoginOpen(false);
+
+ New workspace
+ Crete or import
+
+
+
+
+
-
- {
- setCreateWorkspaceOpen(false);
+ onLogout={() => {
+ setLoginOpen(true);
+ confirm({
+ title: 'Sign out?',
+ content: `All data has been stored in the cloud. `,
+ confirmText: 'Sign out',
+ cancelText: 'Cancel',
+ }).then(async confirm => {
+ if (confirm) {
+ await logout();
+ await router.replace(`/workspace`);
+ toast('Enabled success');
+ }
+ });
}}
- >
- {
- if (!wait) {
- await logout();
- router.replace(`/workspace`);
- }
- setLogoutOpen(false);
- }}
- >
+ />
-
+
+ {
+ setLoginOpen(false);
+ }}
+ />
+ {
+ if (!wait) {
+ await logout();
+ router.replace(`/workspace`);
+ }
+ setLogoutOpen(false);
+ }}
+ />
+ {
+ setCreateWorkspaceOpen(false);
+ }}
+ />
+ >
);
};
-
-const Header = styled('div')({
- display: 'flex',
-});
-
-const Content = styled('div')({
- flexDirection: 'column',
- alignItems: 'center',
- gap: '16px',
- flex: 1,
-});
-const HeaderOption = styled.div(() => {
- return {
- marginLeft: '16px',
- };
-});
-const ContentTitle = styled('div')({
- fontSize: '20px',
- lineHeight: '24px',
- fontWeight: 600,
- textAlign: 'left',
- flex: 1,
-});
-
-const WorkspaceList = styled('div')({
- maxHeight: '500px',
- overflow: 'auto',
- display: 'grid',
- gridRowGap: '24px',
- gridColumnGap: '24px',
- fontSize: '16px',
- marginTop: '36px',
- gridTemplateColumns: 'repeat(2, 1fr)',
-});
-
-export const WorkspaceItem = styled.div<{
- active?: boolean;
-}>(({ theme, active }) => {
- const borderColor = active ? theme.colors.primaryColor : 'transparent';
- return {
- cursor: 'pointer',
- padding: '16px',
- height: '124px',
- boxShadow: theme.shadow.modal,
- display: 'flex',
- borderRadius: '12px',
- border: `1px solid ${borderColor}`,
- ':hover': {
- background: theme.colors.hoverBackground,
- '.add-icon': {
- border: `1.5px dashed ${theme.colors.primaryColor}`,
- svg: {
- fill: theme.colors.primaryColor,
- },
- },
- },
- };
-});
-
-const StyleWorkspaceInfo = styled.div(({ theme }) => {
- return {
- marginLeft: '16px',
- p: {
- fontSize: theme.font.xs,
- lineHeight: '16px',
- },
- svg: {
- verticalAlign: 'text-bottom',
- marginRight: '8px',
- },
- };
-});
-
-const StyleWorkspaceTitle = styled.div(({ theme }) => {
- return {
- fontSize: theme.font.base,
- fontWeight: 600,
- lineHeight: '24px',
- marginBottom: '8px',
- };
-});
-
-const StyleWorkspaceAdd = styled.div(() => {
- return {
- width: '58px',
- height: '58px',
- borderRadius: '100%',
- textAlign: 'center',
- background: '#f4f5fa',
- border: '1.5px dashed #f4f5fa',
- lineHeight: '58px',
- marginTop: '2px',
- };
-});
-
-const Footer = styled('div')({
- paddingTop: '16px',
-});
-
-const StyleUserInfo = styled.div(({ theme }) => {
- return {
- textAlign: 'left',
- marginLeft: '16px',
- marginTop: '16px',
- flex: 1,
- p: {
- lineHeight: '24px',
- color: theme.colors.iconColor,
- },
- 'p:nth-child(1)': {
- color: theme.colors.textColor,
- fontWeight: 600,
- },
- };
-});
-
-const StyleSignIn = styled.div(({ theme }) => {
- return {
- cursor: 'pointer',
- fontSize: '16px',
- fontWeight: 700,
- color: theme.colors.iconColor,
- span: {
- display: 'inline-block',
- width: '40px',
- height: '40px',
- borderRadius: '40px',
- backgroundColor: theme.colors.innerHoverBackground,
- textAlign: 'center',
- lineHeight: '44px',
- marginRight: '16px',
- svg: {
- fill: theme.colors.primaryColor,
- },
- },
- };
-});
diff --git a/packages/app/src/components/workspace-modal/languageMenu.tsx b/packages/app/src/components/workspace-modal/languageMenu.tsx
index d8f907663..ad579e145 100644
--- a/packages/app/src/components/workspace-modal/languageMenu.tsx
+++ b/packages/app/src/components/workspace-modal/languageMenu.tsx
@@ -1,93 +1,61 @@
import { LOCALES } from '@affine/i18n';
-import { TooltipProps } from '@mui/material';
import { styled } from '@/styles';
-import { Tooltip } from '@mui/material';
-import { useState } from 'react';
import { useTranslation } from '@affine/i18n';
import { ArrowDownIcon } from '@blocksuite/icons';
import { Button } from '@/ui/button';
+import { Menu, MenuItem } from '@/ui/menu';
-export const LanguageMenu = () => {
+const LanguageMenuContent = () => {
const { i18n } = useTranslation();
const changeLanguage = (event: string) => {
i18n.changeLanguage(event);
};
- const [show, setShow] = useState(false);
- const currentLanguage = LOCALES.find(item => item.tag === i18n.language);
- const [languageName, setLanguageName] = useState(
- currentLanguage?.originalName
- );
return (
-
- {LOCALES.map(option => {
- return (
- {
- changeLanguage(option.tag);
- setShow(false);
- setLanguageName(option.originalName);
- }}
- >
- {option.originalName}
-
- );
- })}
- >
- }
- open={show}
+ <>
+ {LOCALES.map(option => {
+ return (
+ {
+ changeLanguage(option.tag);
+ }}
+ >
+ {option.originalName}
+
+ );
+ })}
+ >
+ );
+};
+export const LanguageMenu = () => {
+ const { i18n } = useTranslation();
+
+ const currentLanguage = LOCALES.find(item => item.tag === i18n.language);
+
+ return (
+ }
+ placement="bottom"
+ trigger="click"
+ disablePortal={true}
>
- {
- setShow(!show);
- }}
+ }
+ iconPosition="end"
+ noBorder={true}
+ style={{ textTransform: 'capitalize' }}
>
-
- {languageName}
-
-
-
-
+ {currentLanguage?.originalName}
+
+
);
};
-const StyledContainer = styled('div')(() => ({
- display: 'flex',
- alignItems: 'center',
-}));
-
-const StyledText = styled('span')(({ theme }) => ({
- marginRight: '4px',
- marginLeft: '16px',
- fontSize: theme.font.sm,
- fontWeight: '500',
- textTransform: 'capitalize',
-}));
-const StyledTooltip = styled(({ className, ...props }: TooltipProps) => (
-
-))(({ theme }) => ({
- zIndex: theme.zIndex.modal,
- '& .MuiTooltip-tooltip': {
- backgroundColor: theme.colors.popoverBackground,
- boxShadow: theme.shadow.modal,
- color: theme.colors.popoverColor,
- },
-}));
-
-const ListItem = styled(Button)(({ theme }) => ({
- display: 'block',
- width: '100%',
+const ListItem = styled(MenuItem)(({ theme }) => ({
+ height: '38px',
color: theme.colors.popoverColor,
fontSize: theme.font.sm,
textTransform: 'capitalize',
-}));
-
-const StyledTitleButton = styled(Button)(({ theme }) => ({
- color: theme.colors.popoverColor,
- fontSize: theme.font.sm,
+ padding: '0 24px',
}));
diff --git a/packages/app/src/components/workspace-modal/styles.ts b/packages/app/src/components/workspace-modal/styles.ts
index 40a8bcc37..57146afbd 100644
--- a/packages/app/src/components/workspace-modal/styles.ts
+++ b/packages/app/src/components/workspace-modal/styles.ts
@@ -1,38 +1,161 @@
import { displayFlex, styled } from '@/styles';
-export const StyledTitle = styled.div(() => {
+export const StyledSplitLine = styled.div(({ theme }) => {
return {
- ...displayFlex('center', 'center'),
- fontSize: '20px',
- fontWeight: 500,
- marginTop: '60px',
- lineHeight: 1,
+ width: '1px',
+ height: '20px',
+ background: theme.colors.iconColor,
+ marginRight: '24px',
};
});
-export const StyledContent = styled.div(() => {
+export const StyleWorkspaceInfo = styled.div(({ theme }) => {
return {
- padding: '0 40px',
- marginTop: '32px',
- fontSize: '18px',
- lineHeight: '25px',
- 'p:not(last-of-type)': {
- marginBottom: '10px',
+ marginLeft: '15px',
+ p: {
+ height: '20px',
+ fontSize: theme.font.xs,
+ ...displayFlex('flex-start', 'center'),
+ },
+ svg: {
+ marginRight: '10px',
},
};
});
-export const StyledButton = styled.div(({ theme }) => {
+export const StyleWorkspaceTitle = styled.div(({ theme }) => {
return {
- width: '146px',
- height: '42px',
- background: theme.colors.primaryColor,
- color: '#FFFFFF',
- fontSize: '18px',
- fontWeight: 500,
- borderRadius: '21px',
- margin: '52px auto 0',
+ fontSize: theme.font.base,
+ fontWeight: 600,
+ lineHeight: '24px',
+ marginBottom: '10px',
+ };
+});
+
+export const StyledCard = styled.div<{
+ active?: boolean;
+}>(({ theme, active }) => {
+ const borderColor = active ? theme.colors.primaryColor : 'transparent';
+ return {
+ width: '310px',
+ height: '124px',
cursor: 'pointer',
+ padding: '16px',
+ boxShadow: '0px 0px 8px rgba(0, 0, 0, 0.1)',
+ borderRadius: '12px',
+ border: `1px solid ${borderColor}`,
+ ...displayFlex('flex-start', 'flex-start'),
+ ':hover': {
+ background: theme.colors.hoverBackground,
+ '.add-icon': {
+ border: `1.5px dashed ${theme.colors.primaryColor}`,
+ svg: {
+ fill: theme.colors.primaryColor,
+ },
+ },
+ },
+ };
+});
+
+export const StyledFooter = styled('div')({
+ height: '84px',
+ padding: '0 40px',
+ ...displayFlex('space-between', 'center'),
+});
+
+export const StyleUserInfo = styled.div(({ theme }) => {
+ return {
+ textAlign: 'left',
+ marginLeft: '16px',
+ flex: 1,
+ p: {
+ lineHeight: '24px',
+ color: theme.colors.iconColor,
+ },
+ 'p:nth-child(1)': {
+ color: theme.colors.textColor,
+ fontWeight: 600,
+ },
+ };
+});
+
+export const StyleSignIn = styled.div(({ theme }) => {
+ return {
+ cursor: 'pointer',
+ fontSize: '16px',
+ fontWeight: 700,
+ color: theme.colors.iconColor,
+ span: {
+ display: 'inline-block',
+ width: '40px',
+ height: '40px',
+ borderRadius: '40px',
+ backgroundColor: theme.colors.innerHoverBackground,
+ textAlign: 'center',
+ lineHeight: '44px',
+ marginRight: '16px',
+ svg: {
+ fill: theme.colors.primaryColor,
+ },
+ },
+ };
+});
+
+export const StyledModalHeaderLeft = styled.div(() => {
+ return { ...displayFlex('flex-start', 'center') };
+});
+export const StyledModalTitle = styled.div(({ theme }) => {
+ return {
+ fontWeight: 600,
+ fontSize: theme.font.h6,
+ };
+});
+
+export const StyledHelperContainer = styled.div(({ theme }) => {
+ return {
+ color: theme.colors.iconColor,
+ marginLeft: '15px',
+ fontWeight: 400,
...displayFlex('center', 'center'),
};
});
+
+export const StyledModalContent = styled('div')({
+ height: '534px',
+ padding: '8px 40px',
+ marginTop: '72px',
+ overflow: 'auto',
+ ...displayFlex('space-between', 'flex-start', 'column'),
+ flexWrap: 'wrap',
+});
+export const StyledOperationWrapper = styled.div(() => {
+ return {
+ ...displayFlex('flex-end', 'center'),
+ };
+});
+
+export const StyleWorkspaceAdd = styled.div(() => {
+ return {
+ width: '58px',
+ height: '58px',
+ borderRadius: '100%',
+ textAlign: 'center',
+ background: '#f4f5fa',
+ border: '1.5px dashed #f4f5fa',
+ lineHeight: '58px',
+ marginTop: '2px',
+ };
+});
+export const StyledModalHeader = styled('div')(({ theme }) => {
+ return {
+ width: '100%',
+ height: '72px',
+ position: 'absolute',
+ left: 0,
+ top: 0,
+ background: theme.colors.pageBackground,
+ borderRadius: '24px 24px 0 0',
+ padding: '0 40px',
+ ...displayFlex('space-between', 'center'),
+ };
+});