Skip to content

Commit

Permalink
feat: add navigation and move
Browse files Browse the repository at this point in the history
  • Loading branch information
pyphilia committed Dec 4, 2020
1 parent 23211e3 commit 9895cca
Show file tree
Hide file tree
Showing 11 changed files with 392 additions and 23 deletions.
26 changes: 25 additions & 1 deletion src/api/item.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { API_HOST } from '../config/constants';
import { DEFAULT_DELETE, DEFAULT_GET, DEFAULT_POST } from './utils';

// payload = {id}
export const getItem = async (id) => {
const req = await fetch(`${API_HOST}/items/${id}`, {
...DEFAULT_GET,
headers: { 'Content-Type': 'application/json' },
});
return req.json();
};

// payload = {email}
export const getOwnItems = async () => {
const req = await fetch(`${API_HOST}/items/own`, {
Expand Down Expand Up @@ -38,4 +47,19 @@ export const deleteItem = async (id) => {
};

// we need this function for navigation purposes: when you click on an item, you want to see its 'immediate' children
export const fetchItemImmediateChildren = () => {};
export const getChildren = async (id) => {
const req = await fetch(`${API_HOST}/items/${id}/children`, DEFAULT_GET);
return req.json();
};

export const getItemTree = async (ownedItems) => {
// todo: use parallel promises
const items = JSON.parse(JSON.stringify(ownedItems));
// eslint-disable-next-line no-restricted-syntax
for (const item of items) {
// eslint-disable-next-line no-await-in-loop
const children = await getChildren(item.id);
item.children = children;
}
return items;
};
15 changes: 10 additions & 5 deletions src/components/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ import Header from './layout/Header';
import Items from './main/Items';
import items from '../data/sample';
import SignUp from './SignUp';
import { SIGN_UP_PATH, SIGN_IN_PATH } from '../config/paths';
import {
SIGN_UP_PATH,
SIGN_IN_PATH,
HOME_PATH,
ITEMS_PATH,
} from '../config/paths';
import SignIn from './SignIn';
import { ItemProvider } from './context/item';

Expand All @@ -29,7 +34,7 @@ function App() {
<Header />
<main className={classes.root}>
<Switch>
<Route path="/items" exact>
<Route path={HOME_PATH} exact>
<Items />
</Route>
<Route path="/items/:itemId">
Expand All @@ -41,10 +46,10 @@ function App() {
<Route path={SIGN_UP_PATH} exact>
<SignUp />
</Route>
<Route path="/" exact>
<Redirect to="/items" />
<Route path={ITEMS_PATH} exact>
<Items />
</Route>
<Redirect to="/items" />
<Redirect to={HOME_PATH} />
</Switch>
</main>
</div>
Expand Down
26 changes: 25 additions & 1 deletion src/components/context/item.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { getOwnItems, deleteItem } from '../../api/item';
import { getOwnItems, deleteItem, getChildren, getItem } from '../../api/item';
import sampleItems from '../../data/sample';

const ItemContext = React.createContext();
Expand Down Expand Up @@ -42,12 +42,36 @@ class ItemProvider extends Component {
});
};

getChildren = async (id) => {
if (!id) {
return getOwnItems();
}
return getChildren(id);
};

getNavigation = async (itemId) => {
if (!itemId) {
return [];
}
const navigation = [];
let currentParentId = itemId;
while (currentParentId) {
// eslint-disable-next-line no-await-in-loop
const parent = await getItem(currentParentId);
navigation.push(parent);
currentParentId = parent.parentId;
}
return navigation;
};

buildValue = () => {
const { items } = this.state;
return {
items,
addItem: this.addItem,
deleteItem: this.deleteItem,
getChildren: this.getChildren,
getNavigation: this.getNavigation,
};
};

Expand Down
92 changes: 92 additions & 0 deletions src/components/layout/Navigation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withRouter } from 'react-router';
import Breadcrumbs from '@material-ui/core/Breadcrumbs';
import Link from '@material-ui/core/Link';
import { HOME_PATH } from '../../config/paths';
import { ItemContext } from '../context/item';

function handleClick(event) {
event.preventDefault();
}

class Navigation extends Component {
static contextType = ItemContext;

static propTypes = {
history: PropTypes.shape({
push: PropTypes.func.isRequired,
}).isRequired,
match: PropTypes.shape({
params: PropTypes.shape({
itemId: PropTypes.string.isRequired,
}).isRequired,
}).isRequired,
};

state = {
navigation: [],
};

async componentDidMount() {
this.updateNavigation();
}

async componentDidUpdate({
match: {
params: { itemId: prevItemId },
},
}) {
const {
match: {
params: { itemId },
},
} = this.props;

if (prevItemId !== itemId) {
this.updateNavigation();
}
}

updateNavigation = async () => {
const { getNavigation } = this.context;
const {
match: {
params: { itemId },
},
} = this.props;

this.setState({ navigation: await getNavigation(itemId) });
};

goHome = (event) => {
const {
history: { push },
} = this.props;
event.preventDefault();
push(HOME_PATH);
};

render() {
const { navigation } = this.state;
return (
<Breadcrumbs aria-label="breadcrumb">
<Link color="inherit" href="/" onClick={this.goHome}>
Owned Items
</Link>
{navigation.map(({ name, id }) => (
<Link
key={id}
color="inherit"
href="/getting-started/installation/"
onClick={handleClick}
>
{name}
</Link>
))}
</Breadcrumbs>
);
}
}

export default withRouter(Navigation);
19 changes: 17 additions & 2 deletions src/components/main/CreateNewItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useState, useContext } from 'react';
import PropTypes from 'prop-types';
import { makeStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';
import { withRouter } from 'react-router';
import TextField from '@material-ui/core/TextField';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
Expand All @@ -26,7 +27,13 @@ const useStyles = makeStyles((theme) => ({
},
}));

const CreateNewItem = ({ open, handleClose }) => {
const CreateNewItem = ({
open,
handleClose,
match: {
params: { itemId },
},
}) => {
const classes = useStyles();
const itemContext = useContext(ItemContext);
const [itemName, setItemName] = useState('');
Expand All @@ -52,7 +59,9 @@ const CreateNewItem = ({ open, handleClose }) => {

const submitNewItem = async () => {
const { addItem } = itemContext;

const newItem = await createItem({
parentId: itemId,
name: itemName,
type: itemType,
description: itemDescription,
Expand Down Expand Up @@ -144,10 +153,16 @@ const CreateNewItem = ({ open, handleClose }) => {
CreateNewItem.propTypes = {
open: PropTypes.bool,
handleClose: PropTypes.func.isRequired,
match: PropTypes.shape({
params: PropTypes.shape({
itemId: PropTypes.string,
}).isRequired,
}),
};

CreateNewItem.defaultProps = {
open: false,
match: { params: { itemId: '' } },
};

export default CreateNewItem;
export default withRouter(CreateNewItem);
7 changes: 2 additions & 5 deletions src/components/main/CustomCardHeader.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import { Link } from 'react-router-dom';
import { makeStyles } from '@material-ui/core/styles';
import Avatar from '@material-ui/core/Avatar';
import Typography from '@material-ui/core/Typography';
import IconButton from '@material-ui/core/IconButton';
import MoreVertIcon from '@material-ui/icons/MoreVert';
import ItemMenu from './ItemMenu';

const useStyles = makeStyles((theme) => ({
root: {
Expand Down Expand Up @@ -50,9 +49,7 @@ const CustomCardHeader = ({ id, creator, title, type }) => {
</Typography>
</div>
</div>
<IconButton>
<MoreVertIcon />
</IconButton>
<ItemMenu />
</div>
);
};
Expand Down
51 changes: 51 additions & 0 deletions src/components/main/ItemMenu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React from 'react';
import Menu from '@material-ui/core/Menu';
import MenuItem from '@material-ui/core/MenuItem';
import IconButton from '@material-ui/core/IconButton';
import MoreVertIcon from '@material-ui/icons/MoreVert';
import MoveItemModal from './MoveItemModal';

const ItemMenu = () => {
const [anchorEl, setAnchorEl] = React.useState(null);
const [isMoveModalOpen, setIsMoveModalOpen] = React.useState(false);

const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};

const handleClose = () => {
setAnchorEl(null);
};

const handleMove = () => {
setIsMoveModalOpen(true);
handleClose();
};

const onModalClose = () => {
setIsMoveModalOpen(false);
};

// todo: only display one modal for the whole page

return (
<div>
<IconButton onClick={handleClick}>
<MoreVertIcon />
</IconButton>
<Menu
id="simple-menu"
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
>
<MenuItem onClick={handleMove}>Move</MenuItem>
<MenuItem onClick={handleClose}>Some action...</MenuItem>
</Menu>
<MoveItemModal onClose={onModalClose} open={isMoveModalOpen} />
</div>
);
};

export default ItemMenu;
Loading

0 comments on commit 9895cca

Please sign in to comment.