Frontend Serverless with React and GraphQL

Start at the beginning and work your way through this project. The code for each step as well as the finished project can be found in the Github repository.


24. Add a MyRecipes Page

Objective: Add a MyRecipes page so we can see the list of recipes that we created.

Now that we have a useFetchUser hook, we can really start diving in and creating components that will only be available to logged in users. While we will get to the create, update, and delete recipe components soon, let's start off with a <MyRecipes> component to use the <RecipesList> component that we just created.

Create a new my-recipes folder within pages and create an index.tsx file in it. This will create a route at /my-recipes. The component will look quite similar to the index.tsx file that is in the pages folder except that we use our new useFetchUser hook. If the hook is loading, we will return the <Loading> but otherwise, it will pull the sub property off the user object that it receives from fetching the user. In the event that the user isn't logged in, we won't get anything back from trying to pull this sub property off user and we will set owner to be equal to an empty string.

Once we have the owner string, we are ready to create the options object that <RecipesList> will use to pull the recipes query with a search parameter of owner. This means that the backend will find all of the recipes where the owner field equals the string that we are passing in.

pages/my-recipes/index.tsx

import styled from 'styled-components';
import { MainLayout } from '../../components/layout/MainLayout';
import { RecipesList, queryEnum } from '../../components/RecipesList';
import { Col, Row } from 'antd';
import { useFetchUser } from '../../utils/user';
import * as _ from 'lodash';
import Router from 'next/router';
import { Loading } from '../../components/notify/Loading';
const StyledRow = styled(Row)`
${({ theme }) => `
padding: ${theme['padding-small']} ${theme['padding-small']};
display: flex;
h1 {
padding-left: ${theme['padding-small']};
text-align: left;
}
`}
`;
const Index = () => {
const { user, loading: isFetchUser } = useFetchUser();
const owner = _.get(user, 'sub');
const options = owner ? { variables: { where: { owner } } } : {};
if (isFetchUser) return <Loading />;
if (!user) {
Router.replace('/');
}
return (
<MainLayout title="My Recipes">
<StyledRow>
<Col>
<h1>My Recipes</h1>
</Col>
</StyledRow>
<RecipesList
options={options}
parentRoute="my-recipes"
queryType={queryEnum.recipes}
/>
</MainLayout>
);
};
export default Index;

Once we finish this component, we can add a new button in the navbar that will be called My Recipes. Update the <MainNavbar /> to have this new button and then test the button to verify that you are redirected to the /my-recipes route. For now, you should see an empty list since we are not logged in yet.

components/layout/MainNavbar.tsx

import styled from 'styled-components';
import { Layout, Menu } from 'antd';
import Link from 'next/link';
import { useFetchUser } from '../../utils/user';
const { Header } = Layout;
const TitleContainer = styled.div`
${({ theme }) => `
background-color: ${theme['header-color']};
width: 50%;
display: flex;
align-items: center;
@media (max-width: 890px){
visibility: hidden;
width: 0;
}
`}
`;
const Title = styled.div`
${({ theme }) => `
text-align: left;
display: flex;
line-height: 50px;
div {
width: 100%;
padding-left: ${theme['padding-small']};
}
h2 {
display: inline;
color: inherit;
}
img {
width: 64px;
}
p {
line-height: 0;
}
`}
`;
const StyledHeader = styled(Header)`
${({ theme }) => `
background-color: ${theme['header-color']};
border-bottom-color: ${theme['header-border-color']};
border-bottom-right: 1px;
border-bottom-style: solid;
text-align: right;
display: flex;
li {
font-size: ${theme['font-size-md']};
}
`}
`;
const StyledMenu = styled(Menu)`
border-bottom-width: 0px;
width: 50%;
@media (max-width: 890px) {
width: 100%;
}
`;
export const MainNavbar = () => {
const { user, loading } = useFetchUser();
return (
<StyledHeader>
<TitleContainer>
<Title>
<img src="/logo.svg" alt="Next Chop Logo" />
<div>
<h2>The Next Chop</h2>
<p>A recipe discovery app powered by Next.js</p>
</div>
</Title>
</TitleContainer>
<StyledMenu
theme="light"
mode="horizontal"
style={{ lineHeight: '64px' }}
>
<Menu.Item key="/">
<Link href="/">
<a>Home</a>
</Link>
</Menu.Item>
{user && !loading
? [
<Menu.Item key="/my-recipes">
<Link href="/my-recipes">
<a>My Recipes</a>
</Link>
</Menu.Item>,
<Menu.Item key="/api/logout">
<Link href="/api/logout">
<a>Logout</a>
</Link>
</Menu.Item>,
]
: [
<Menu.Item key="/api/login">
<Link href="/api/login">
<a>Login</a>
</Link>
</Menu.Item>,
]}
</StyledMenu>
</StyledHeader>
);
};