Skip to content

Commit

Permalink
Update CodePair Workspace Page Layout with Recent Figma Design (#207)
Browse files Browse the repository at this point in the history
* Remove login page

* Change WorkspaceLayout

* Remove unused import statement

* Implement WorkspaceHeader

* Implement WorkspaceDrawer

* Implement Workspace Page

* Implement member page

* Change drawer option to permanent store

* Fix typo

* Add home button to `WorkspaceHeader`

* Add theme change button in `ProfilePopover`
  • Loading branch information
devleejb authored Jun 21, 2024
1 parent 2bcb3bf commit 1c72c20
Show file tree
Hide file tree
Showing 13 changed files with 488 additions and 302 deletions.
162 changes: 48 additions & 114 deletions frontend/src/components/drawers/WorkspaceDrawer.tsx
Original file line number Diff line number Diff line change
@@ -1,57 +1,39 @@
import {
Avatar,
Button,
Divider,
Drawer,
IconButton,
ListItem,
ListItemAvatar,
ListItemButton,
ListItemIcon,
ListItemSecondaryAction,
ListItemText,
Stack,
} from "@mui/material";
import MoreVertIcon from "@mui/icons-material/MoreVert";
import { useSelector } from "react-redux";
import { selectUser } from "../../store/userSlice";
import { MouseEventHandler, useState } from "react";
import ProfilePopover from "../popovers/ProfilePopover";
import { useNavigate } from "react-router-dom";
import { MouseEventHandler, useMemo, useState } from "react";
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp";
import WorkspaceListPopover from "../popovers/WorkspaceListPopover";
import AddIcon from "@mui/icons-material/Add";
import CreateModal from "../modals/CreateModal";
import { useCreateDocumentMutation } from "../../hooks/api/workspaceDocument";
import ThemeButton from "../common/ThemeButton";
import PeopleIcon from "@mui/icons-material/People";
import MemberModal from "../modals/MemberModal";
import { selectWorkspace } from "../../store/workspaceSlice";
import { DRAWER_WIDTH, WorkspaceDrawerHeader } from "../layouts/WorkspaceLayout";
import { useLocation, useNavigate, useParams } from "react-router-dom";

const DRAWER_WIDTH = 240;
interface WorkspaceDrawerProps {
open: boolean;
}

function WorkspaceDrawer() {
function WorkspaceDrawer(props: WorkspaceDrawerProps) {
const { open } = props;
const location = useLocation();
const params = useParams();
const navigate = useNavigate();
const userStore = useSelector(selectUser);
const workspaceStore = useSelector(selectWorkspace);
const { mutateAsync: createDocument } = useCreateDocumentMutation(
workspaceStore.data?.id || ""
);
const [profileAnchorEl, setProfileAnchorEl] = useState<(EventTarget & Element) | null>(null);
const [workspaceListAnchorEl, setWorkspaceListAnchorEl] = useState<
(EventTarget & Element) | null
>(null);
const [createWorkspaceModalOpen, setCreateWorkspaceModalOpen] = useState(false);
const [memberModalOpen, setMemberModalOpen] = useState(false);

const handleOpenProfilePopover: MouseEventHandler = (event) => {
setProfileAnchorEl(event.currentTarget);
};

const handleCloseProfilePopover = () => {
setProfileAnchorEl(null);
};
const currentPage = useMemo(() => {
return location.pathname.split("/")[2] ?? "main";
}, [location.pathname]);

const handleOpenWorkspacePopover: MouseEventHandler = (event) => {
setWorkspaceListAnchorEl(event.currentTarget);
Expand All @@ -61,18 +43,8 @@ function WorkspaceDrawer() {
setWorkspaceListAnchorEl(null);
};

const handleCreateWorkspace = async (data: { title: string }) => {
const document = await createDocument(data);

navigate(document.id);
};

const handleCreateWorkspaceModalOpen = () => {
setCreateWorkspaceModalOpen((prev) => !prev);
};

const handleMemberModalOpen = () => {
setMemberModalOpen((prev) => !prev);
const handleNavigateToMember = () => {
navigate(`/${params.workspaceSlug}/member`);
};

return (
Expand All @@ -85,88 +57,50 @@ function WorkspaceDrawer() {
boxSizing: "border-box",
},
}}
variant="permanent"
variant="persistent"
anchor="left"
open
open={open}
>
<ListItem disablePadding>
<ListItemButton onClick={handleOpenWorkspacePopover}>
<ListItemText
primary={workspaceStore.data?.title}
primaryTypographyProps={{
variant: "subtitle1",
noWrap: true,
}}
<WorkspaceDrawerHeader>
<ListItem disablePadding>
<ListItemButton onClick={handleOpenWorkspacePopover}>
<ListItemText
primary={workspaceStore.data?.title}
primaryTypographyProps={{
variant: "subtitle1",
noWrap: true,
}}
/>
<ListItemSecondaryAction>
<IconButton>
{workspaceListAnchorEl ? (
<KeyboardArrowUpIcon />
) : (
<KeyboardArrowDownIcon />
)}
</IconButton>
</ListItemSecondaryAction>
</ListItemButton>
<WorkspaceListPopover
open={Boolean(workspaceListAnchorEl)}
anchorEl={workspaceListAnchorEl}
onClose={handleCloseWorkspacePopover}
width={DRAWER_WIDTH - 32}
/>
<ListItemSecondaryAction>
<IconButton>
{workspaceListAnchorEl ? (
<KeyboardArrowUpIcon />
) : (
<KeyboardArrowDownIcon />
)}
</IconButton>
</ListItemSecondaryAction>
</ListItemButton>
<WorkspaceListPopover
open={Boolean(workspaceListAnchorEl)}
anchorEl={workspaceListAnchorEl}
onClose={handleCloseWorkspacePopover}
width={DRAWER_WIDTH - 32}
/>
</ListItem>
<Divider />
<ListItem>
<Button
variant="contained"
startIcon={<AddIcon />}
sx={{
width: 1,
}}
onClick={handleCreateWorkspaceModalOpen}
>
New Note
</Button>
</ListItem>
</ListItem>
</WorkspaceDrawerHeader>
<Divider />
<ListItem disablePadding>
<ListItemButton onClick={handleMemberModalOpen}>
<ListItemButton
onClick={handleNavigateToMember}
selected={currentPage === "member"}
>
<ListItemIcon>
<PeopleIcon />
</ListItemIcon>
<ListItemText primary="Members" />
</ListItemButton>
</ListItem>
<Divider />
<ListItem sx={{ mt: "auto" }}>
<Stack width={1} alignItems="center" justifyContent="flex-end" direction="row">
<ThemeButton />
</Stack>
</ListItem>
<Divider />
<ListItem disablePadding>
<ListItemButton onClick={handleOpenProfilePopover}>
<ListItemAvatar>
<Avatar>{userStore.data?.nickname?.charAt(0)}</Avatar>
</ListItemAvatar>
<ListItemText primary={userStore.data?.nickname} />
<ListItemSecondaryAction>
<MoreVertIcon />
</ListItemSecondaryAction>
</ListItemButton>
<ProfilePopover
open={Boolean(profileAnchorEl)}
anchorEl={profileAnchorEl}
onClose={handleCloseProfilePopover}
/>
</ListItem>
<CreateModal
open={createWorkspaceModalOpen}
title="Note"
onSuccess={handleCreateWorkspace}
onClose={handleCreateWorkspaceModalOpen}
/>
<MemberModal open={memberModalOpen} onClose={handleMemberModalOpen} />
</Drawer>
);
}
Expand Down
35 changes: 0 additions & 35 deletions frontend/src/components/headers/MainHeader.tsx

This file was deleted.

106 changes: 106 additions & 0 deletions frontend/src/components/headers/WorkspaceHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { MouseEventHandler, useState } from "react";
import { Avatar, IconButton, Stack, Toolbar, styled, useTheme } from "@mui/material";
import AppBar, { AppBarProps } from "@mui/material/AppBar";
import { DRAWER_WIDTH } from "../layouts/WorkspaceLayout";
import MenuIcon from "@mui/icons-material/Menu";
import { useSelector } from "react-redux";
import { selectUser } from "../../store/userSlice";
import ProfilePopover from "../popovers/ProfilePopover";
import KeyboardDoubleArrowLeftIcon from "@mui/icons-material/KeyboardDoubleArrowLeft";
import KeyboardDoubleArrowRightIcon from "@mui/icons-material/KeyboardDoubleArrowRight";
import CodePairIcon from "../icons/CodePairIcon";
import { useNavigate } from "react-router-dom";
import { selectWorkspace } from "../../store/workspaceSlice";

interface WorkspaceAppBarProps extends AppBarProps {
open?: boolean;
}

const WorkspaceAppBar = styled(AppBar, {
shouldForwardProp: (prop) => prop !== "open",
})<WorkspaceAppBarProps>(({ theme, open }) => ({
transition: theme.transitions.create(["margin", "width"], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
...(open && {
width: `calc(100% - ${DRAWER_WIDTH}px)`,
marginLeft: `${DRAWER_WIDTH}px`,
transition: theme.transitions.create(["margin", "width"], {
easing: theme.transitions.easing.easeOut,
duration: theme.transitions.duration.enteringScreen,
}),
}),
}));

interface WorkspaceHeaderProps {
open: boolean;
onDrawerOpen: () => void;
}

function WorkspaceHeader(props: WorkspaceHeaderProps) {
const { open, onDrawerOpen } = props;
const theme = useTheme();
const navigate = useNavigate();
const userStore = useSelector(selectUser);
const workspaceStore = useSelector(selectWorkspace);
const [profileAnchorEl, setProfileAnchorEl] = useState<(EventTarget & Element) | null>(null);

const handleOpenProfilePopover: MouseEventHandler = (event) => {
setProfileAnchorEl(event.currentTarget);
};

const handleCloseProfilePopover = () => {
setProfileAnchorEl(null);
};

const handleToWorkspace = () => {
navigate(`/${workspaceStore.data?.slug}`);
};

return (
<WorkspaceAppBar position="fixed" open={open}>
<Toolbar>
<Stack
width="100%"
direction="row-reverse"
justifyContent="space-between"
alignItems="center"
>
<IconButton onClick={handleOpenProfilePopover}>
<Avatar>{userStore.data?.nickname?.charAt(0)}</Avatar>
</IconButton>
<Stack direction="row">
<IconButton
color="inherit"
aria-label="open drawer"
onClick={onDrawerOpen}
edge="start"
sx={{ mr: 2 }}
>
{open ? (
theme.direction === "ltr" ? (
<KeyboardDoubleArrowLeftIcon />
) : (
<KeyboardDoubleArrowRightIcon />
)
) : (
<MenuIcon />
)}
</IconButton>
<IconButton onClick={handleToWorkspace}>
<CodePairIcon />
</IconButton>
</Stack>
</Stack>
</Toolbar>
<ProfilePopover
open={Boolean(profileAnchorEl)}
anchorEl={profileAnchorEl}
onClose={handleCloseProfilePopover}
/>
</WorkspaceAppBar>
);
}

export default WorkspaceHeader;
2 changes: 0 additions & 2 deletions frontend/src/components/layouts/MainLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { Stack } from "@mui/material";
import { Outlet } from "react-router-dom";
import MainHeader from "../headers/MainHeader";

function MainLayout() {
return (
<Stack sx={{ flexGrow: 1, height: "100vh" }}>
<MainHeader />
<Outlet />
</Stack>
);
Expand Down
Loading

0 comments on commit 1c72c20

Please sign in to comment.