diff --git a/packages/app/src/components/create-workspace/index.tsx b/packages/app/src/components/create-workspace/index.tsx
new file mode 100644
index 0000000000..1a04d3ce9a
--- /dev/null
+++ b/packages/app/src/components/create-workspace/index.tsx
@@ -0,0 +1,83 @@
+import { styled } from '@/styles';
+import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal';
+import { Button } from '@/ui/button';
+import { useState } from 'react';
+import { createWorkspace } from '@/hooks/mock-data/mock';
+import Input from '@/ui/input';
+
+interface ModalProps {
+ open: boolean;
+ onClose: () => void;
+}
+
+export const CreateWorkspaceModal = ({ open, onClose }: ModalProps) => {
+ const [workspaceName, setWorkspaceName] = useState('');
+ const handleCreateWorkspace = () => {
+ createWorkspace(workspaceName);
+ onClose();
+ };
+ return (
+
+
+
+
+ New Workspace
+ {
+ onClose();
+ }}
+ />
+
+
+
+ Workspace is your virtual space to capture, create and plan as
+ just one person or together as a team.
+
+ {
+ setWorkspaceName(value);
+ }}
+ >
+
+
+
+
+
+ );
+};
+
+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('span')({
+ fontSize: '20px',
+ lineHeight: '28px',
+ fontWeight: 600,
+ textAlign: 'left',
+ paddingBottom: '16px',
+});
+
+const Footer = styled('div')({
+ height: '70px',
+ paddingLeft: '24px',
+ marginTop: '32px',
+ textAlign: 'center',
+});
diff --git a/packages/app/src/components/workspace-modal/index.tsx b/packages/app/src/components/workspace-modal/index.tsx
new file mode 100644
index 0000000000..84e56dd60d
--- /dev/null
+++ b/packages/app/src/components/workspace-modal/index.tsx
@@ -0,0 +1,117 @@
+import { styled } from '@/styles';
+import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal';
+import { Button } from '@/ui/button';
+import { useEffect, useState } from 'react';
+import { getWorkspaceList, Workspace } from '@/hooks/mock-data/mock';
+import { CreateWorkspaceModal } from '../create-workspace';
+
+interface LoginModalProps {
+ open: boolean;
+ onClose: () => void;
+}
+
+export const WorkspaceModal = ({ open, onClose }: LoginModalProps) => {
+ const [workspaceList, setWorkspaceList] = useState([]);
+ const [createWorkspaceOpen, setCreateWorkspaceOpen] = useState(false);
+
+ useEffect(() => {
+ getList();
+ }, []);
+
+ const getList = () => {
+ const data = getWorkspaceList();
+ setWorkspaceList(data);
+ };
+ return (
+
+
+
+
+ My workspace
+ {
+ onClose();
+ }}
+ />
+
+
+
+ {workspaceList.map(item => {
+ return (
+
+
+ {item.name}
+ {item.type === 'local' && local}
+ {item.type === 'share' && share}
+ {item.isPublish ? 'public' : 'private'}
+ {item.isLocal ? 'local' : 'local'}
+
+ );
+ })}
+
+
+
+
+
+
+ {
+ setCreateWorkspaceOpen(false);
+ getList();
+ }}
+ >
+
+
+
+ );
+};
+
+const Header = styled('div')({
+ position: 'relative',
+ height: '44px',
+});
+
+const Content = styled('div')({
+ padding: '0 20px',
+ flexDirection: 'column',
+ alignItems: 'center',
+ gap: '16px',
+});
+
+const ContentTitle = styled('span')({
+ fontSize: '20px',
+ lineHeight: '28px',
+ fontWeight: 600,
+ textAlign: 'left',
+ paddingBottom: '16px',
+});
+
+const Footer = styled('div')({
+ height: '70px',
+ paddingLeft: '24px',
+ marginTop: '32px',
+ textAlign: 'center',
+});
+
+const WorkspaceList = styled('div')({
+ display: 'grid',
+ gridRowGap: '10px',
+ gridColumnGap: '10px',
+ fontSize: '16px',
+ gridTemplateColumns: 'repeat(2, 1fr)',
+});
+
+const WorkspaceItem = styled('div')({
+ border: '1px solid #e5e5e5',
+});
diff --git a/packages/app/src/components/workspace-modal/styles.ts b/packages/app/src/components/workspace-modal/styles.ts
new file mode 100644
index 0000000000..40a8bcc375
--- /dev/null
+++ b/packages/app/src/components/workspace-modal/styles.ts
@@ -0,0 +1,38 @@
+import { displayFlex, styled } from '@/styles';
+
+export const StyledTitle = styled.div(() => {
+ return {
+ ...displayFlex('center', 'center'),
+ fontSize: '20px',
+ fontWeight: 500,
+ marginTop: '60px',
+ lineHeight: 1,
+ };
+});
+
+export const StyledContent = styled.div(() => {
+ return {
+ padding: '0 40px',
+ marginTop: '32px',
+ fontSize: '18px',
+ lineHeight: '25px',
+ 'p:not(last-of-type)': {
+ marginBottom: '10px',
+ },
+ };
+});
+
+export const StyledButton = styled.div(({ theme }) => {
+ return {
+ width: '146px',
+ height: '42px',
+ background: theme.colors.primaryColor,
+ color: '#FFFFFF',
+ fontSize: '18px',
+ fontWeight: 500,
+ borderRadius: '21px',
+ margin: '52px auto 0',
+ cursor: 'pointer',
+ ...displayFlex('center', 'center'),
+ };
+});
diff --git a/packages/app/src/hooks/mock-data/mock.ts b/packages/app/src/hooks/mock-data/mock.ts
new file mode 100644
index 0000000000..e484dfcc87
--- /dev/null
+++ b/packages/app/src/hooks/mock-data/mock.ts
@@ -0,0 +1,75 @@
+export interface Workspace {
+ name: string; // 名称
+ id: string; //唯一标识
+ isPublish?: boolean; // 是否公开
+ isLocal?: boolean; // 是否全部数据都在本地
+ avatar?: string; // 封面
+ type: 'local' | 'cloud' | 'share'; // cloud: 云端(本次暂不支持),local: 本地,share: 分享
+ workspaceOwner?: User; // 本地工作空间的拥有者
+}
+
+interface User {
+ name: string;
+ id: string;
+ email: string;
+ avatar: string;
+}
+
+export function getWorkspaceList(): Workspace[] {
+ const workspacesMeta = JSON.parse(
+ localStorage.getItem('affine-workspace') ?? '[]'
+ );
+ return workspacesMeta;
+}
+
+export function getPagesByWorkspaceId(workspaceId: string) {
+ if (!workspaceId) return [];
+ const workspacesMeta = [];
+ for (let i = 0; i < 10; i++) {
+ workspacesMeta.push({
+ id: 'page-' + i,
+ name: 'page ' + i,
+ });
+ }
+}
+
+export function addWorkSpace(workspaceData: Workspace) {
+ const workspacesMeta = getWorkspaceList();
+ workspacesMeta.push(workspaceData);
+ localStorage.setItem('affine-workspace', JSON.stringify(workspacesMeta));
+}
+
+export function deleteWorkspaceById(workspaceId: string) {
+ const workspacesMeta = getWorkspaceList();
+ const newWorkspacesMeta = workspacesMeta.filter(() => {
+ return workspaceId !== workspaceId;
+ });
+ localStorage.setItem('affine-workspace', JSON.stringify(newWorkspacesMeta));
+}
+
+export function updateWorkspaceById(
+ workspaceId: string,
+ workspaceData: Workspace
+) {
+ const workspacesMeta = getWorkspaceList();
+ const newWorkspacesMeta = workspacesMeta.map((workspace: Workspace) => {
+ if (workspace.id === workspaceId) {
+ return workspaceData;
+ }
+ return workspace;
+ });
+ localStorage.setItem('affine-workspace', JSON.stringify(newWorkspacesMeta));
+}
+export function createWorkspace(workspaceName: string) {
+ const workspaceData = {
+ name: workspaceName,
+ id: 'workspace-' + Date.now(),
+ isPublish: false,
+ isLocal: true,
+ avatar: '',
+ type: 'local',
+ } as Workspace;
+ const workspacesMeta = getWorkspaceList();
+ workspacesMeta.push(workspaceData);
+ localStorage.setItem('affine-workspace', JSON.stringify(workspacesMeta));
+}
diff --git a/packages/app/src/pages/new-workspace/index.tsx b/packages/app/src/pages/new-workspace/index.tsx
new file mode 100644
index 0000000000..b46e99d917
--- /dev/null
+++ b/packages/app/src/pages/new-workspace/index.tsx
@@ -0,0 +1,47 @@
+import { WorkspaceModal } from '@/components/workspace-modal';
+import { getWorkspaceList } from '@/hooks/mock-data/mock';
+import { useEffect, useState } from 'react';
+import { styled } from '@/styles';
+import Button from '@/ui/button/Button';
+
+const Page = () => {
+ const [open, setOpen] = useState(false);
+
+ useEffect(() => {
+ const data = getWorkspaceList();
+ if (!data.length) {
+ setOpen(true);
+ }
+ }, []);
+ return (
+
+ workspace
+
+
+
+ {
+ setOpen(false);
+ }}
+ >
+
+ );
+};
+export default Page;
+
+const Workspace = styled.div(({ theme }) => {
+ return {
+ height: '100vh',
+ background: theme.colors.pageBackground,
+ color: '#FFFFFF',
+ fontSize: '18px',
+ fontWeight: 500,
+ };
+});