End to End React with Prisma 2

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.


    17. Add Layout and Navbar

    Objective: Create the basic layout for our site.

    The first thing that we will tackle from a ui perspective is creating a unified layout with a navbar that will show on every page. Start by creating a new folder called component and then a file called layout.tsx within that folder.

    components/layout.tsx

    tsx

    export const Layout = ({ children }) => {
    return <div className="p-4">{children}</div>
    }

    We use the children prop so that we can wrap the <Layout> around whatever page specific components we have. Like we saw before, adding classes is the foundation of how Tailwind works. Here we are just adding a padding of width 4 around the entire site.

    Now let's try using it on our index page component. Wrap it around a h3 component, and let's add a little styling to that header.

    pages/index.tsx

    tsx

    import { Layout } from '../components/layout'
    const Index = () => {
    return (
    <Layout>
    <h3 className="justify-start flex text-lg font-medium py-4">Home Page</h3>
    </Layout>
    )
    }
    export default Index

    Next, let's add our nav component. Before we get there though, we will want to create a react hook for determining whether we are logged in or not.

    npm install isomorphic-unfetch

    utils/user.ts

    ts

    import { useState, useEffect } from 'react'
    import fetch from 'isomorphic-unfetch'
    interface MyWindow extends Window {
    __user: any
    }
    declare var window: MyWindow
    export async function fetchUser(cookie = '') {
    if (typeof window !== 'undefined' && window.__user) {
    return window.__user
    }
    const res = await fetch(
    '/api/me',
    cookie
    ? {
    headers: {
    cookie,
    },
    }
    : {}
    )
    if (!res.ok) {
    delete window.__user
    return null
    }
    const json = await res.json()
    if (typeof window !== 'undefined') {
    window.__user = json
    }
    return json
    }
    // @ts-ignore
    export function useFetchUser({ required } = {}) {
    const [loading, setLoading] = useState(
    () => !(typeof window !== 'undefined' && window.__user)
    )
    const [user, setUser] = useState(() => {
    if (typeof window === 'undefined') {
    return null
    }
    return window.__user || null
    })
    useEffect(
    () => {
    if (!loading && user) {
    return
    }
    setLoading(true)
    let isMounted = true
    fetchUser().then(user => {
    // Only set the user if the component is still mounted
    if (isMounted) {
    // When the user is not logged in but login is required
    if (required && !user) {
    window.location.href = '/api/login'
    return
    }
    setUser(user)
    setLoading(false)
    }
    })
    return () => {
    isMounted = false
    }
    },
    // eslint-disable-next-line react-hooks/exhaustive-deps
    []
    )
    return { user, loading }
    }

    We will create it with all the page links in place now just so we won't have to worry about it later.

    components/nav.tsx

    tsx

    import Link from 'next/link'
    import { useFetchUser } from '../utils/user'
    export const Nav = () => {
    const { user, loading } = useFetchUser()
    return (
    <ul className="flex grid grid-cols-4">
    <div className="col-span-1 flex justify-start">
    <li className="mr-6">
    <Link href="/">
    <div className="inline-flex cursor-pointer">
    <img className="sm:h-10 h-8 pr-1" src="/logo.png" />
    <a className="p-2 text-center block hover:blue-700 sm:visible invisible">
    Newsprism
    </a>
    </div>
    </Link>
    </li>
    </div>
    <div className="col-span-3 flex justify-end"></div>
    </ul>
    )
    }

    We'll need to download the logo.png and add it to the public directory along with blank.png.

    Click to download the logo file

    Let's turn back to the layout.tsx file and add the <Nav /> component.

    components/layout.tsx

    tsx

    import { Nav } from './nav'
    export const Layout = ({ children }) => {
    return (
    <div className="p-4">
    <Nav />
    {children}
    </div>
    )
    }

    We should look at our running website and now see that there is a navbar added to the top of the page. It is also responsive, so if you reduce your browser width, the logo eventually will dissapear at the smallest size. Awesome! Now let's add all of the navbar links that we will need for our project. We'll add them now and then build the pages as we progress througout the course.

    A navbar link will have a <a> element nested inside a Next.js <Link> component which is finally listed inside of an <li>. The <Link> component is used for all links that point to other Next.js pages. This ensures that the page doesn't do a hard reload when we are clicking between links.

    We will also use the user hook called useFetchUser that we already created to fetch the user information from the backend using a me query.

    Here's what the final Nav component will look like:

    components/nav.tsx

    tsx

    import Link from 'next/link'
    import { useFetchUser } from '../utils/user'
    export const Nav = () => {
    const { user, loading } = useFetchUser()
    return (
    <ul className="flex grid grid-cols-4">
    <div className="col-span-1 flex justify-start">
    <li className="mr-6">
    <Link href="/">
    <div className="inline-flex cursor-pointer">
    <img className="sm:h-10 h-8 pr-1" src="/logo.png" />
    <a className="p-2 text-center block hover:blue-700 sm:visible invisible">
    Newsprism
    </a>
    </div>
    </Link>
    </li>
    </div>
    <div className="col-span-3 flex justify-end">
    {user ? (
    <li className="sm:mr-6">
    <Link href="/saved-articles">
    <a className="p-2 text-center block hover:blue-700 text-blue-500">
    Saved Articles
    </a>
    </Link>
    </li>
    ) : null}
    <li className="sm:mr-6">
    <Link href="/bundles">
    <a className="p-2 text-center block hover:blue-700 text-blue-500">
    Bundles
    </a>
    </Link>
    </li>
    <li className="sm:mr-6">
    <Link href="/feeds">
    <a className="p-2 text-center block hover:blue-700 text-blue-500">
    Feeds
    </a>
    </Link>
    </li>
    <li className="sm:mr-6">
    {user && !loading ? (
    <Link href="/api/logout">
    <a className="text-center block border border-blue-500 rounded py-2 px-4 hover:bg-blue-700 text-blue-500">
    Logout
    </a>
    </Link>
    ) : (
    <Link href="/api/login">
    <a className="text-center block border border-blue-500 rounded py-2 px-4 bg-blue-500 hover:bg-blue-700 text-white">
    Login
    </a>
    </Link>
    )}
    </li>
    </div>
    </ul>
    )
    }

    If the user object from the hook returns the user information we know that the user is logged in so we can show the logout button, otherwise we show the login button. The saved articles page will only be available for users that are signed in so that's why we have a ternary operator to check, but the feeds and bundles pages are visible to both kinds of users.

    Now click the login button and try to log in. You should see an error- we need to go into Auth0 and allow set our allowed callback urls and logout urls. On the Auth0 site, go to applications -> our application -> settings.

    In Allowed Callback URLs enter http://localhost:3000/api/callback In Allowed Logout URLs enter http://localhost:3000/

    Click save at the bottom of the Auth0 page, and try to log in again on our website. Click the green checkmark when it asks you to authorize.