39
loading...
This website collects cookies to deliver better user experience
npx create-next-app $relativePathToDir # npx create-next-app
package.json
file to check out the dependencies and scripts there.package.json
file has three scripts and three dependencies.dev
command is used to start the app in development mode, while the build
command is used to compile it. Meanwhile,the start
command runs the app in production mode. Note, however, we need to compile our application before running it in production mode. react
, react-dom
, and next
itself.npm run dev
in the application's root directory. We should see the following:Next.js
created for us: pages
folder, you can create a file, and that file name will be the route's endpoint. For example, say I want to have a /login
endpoint; all I need to do is create a pages/login.js
file. The page will then show a return value of the exported component.api
should contain a file called hello.js
with a simple express-like server in your pages
folder. To test the API, go to the api/hello
endpoint. You should see the following: {"name": "John Doe"}
. That is the JSON object, which is sent as a response. Just as we route in the client, we create a file with the name we want to give the endpoint.api/users/:userId
, where userId
is dynamic, create a route like api/users/contacts/follow
, or api/users/:userId/follow/:friendId
. How can we achieve this?. api/users/contacts/follow
or /users/contacts/follow
. We need to chain it down using directories and sub-directories in our pages
folder. /users/contacts/follow
route, we need to create a pages/users/contacts/follow.js
file in our application.api/users/userId
, we need to just create a file pages/api/users/[userId].js
npm i @auth0/nextjs-auth0
AUTH0_SECRET=#random character
AUTH0_BASE_URL=<http://localhost:3000> #base URL of the application
AUTH0_ISSUER_BASE_URL=#Your domain
AUTH0_CLIENT_ID=#Your client id
AUTH0_CLIENT_SECRET=#Your Client Secret
.env.local
file in the root directory of our application. We need to create this file and pass in these values. Next js will parse the environment variables for us automatically, which we can use in the node environment of our app. NEXT_PUBLIC_.
pages/api/auth/[...auth0].js
, which will expose us to four different endpoints due to the fact that we’re destructuring the file: api/auth/login
, api/auth/callback
, api/auth/me
and api/auth/logout
that we can use in our application.import { handleAuth } from '@auth0/nextjs-auth0';
export default handleAuth();
pages/_app.js
file with the following:import { UserProvider } from '@auth0/nextjs-auth0';
import '../styles/globals.css'
function MyApp({ Component, pageProps }) {
return (
<UserProvider>
<Component {...pageProps} />
</UserProvider>
);
}
export default MyApp
pages/index.js
file to the code snippet below:import Head from 'next/head'
import Image from 'next/image'
import styles from '../styles/Home.module.css'
export default function Home() {
return (
<div className={styles.container}>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
<h1 className={styles.title}>
Welcome to <a href="/">Next.js!</a>
</h1>
<p className={styles.description}>
<a className={styles.code} href="/api/auth/login">Get started</a> by Creating an account or logging in
</p>
<p className={styles.description}>
<a className={styles.code} href="/api/auth/logout">Logout</a>
</p>
<p className={styles.description}>
<a className={styles.code} href="/api/auth/me">Profile</a>
</p>
<p className={styles.description}>
<a className={styles.code} href="/api/auth/callback">callback</a>
</p>
</main>
<footer className={styles.footer}>
<a
href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Powered by{' '}
<span className={styles.logo}>
<Image src="/vercel.svg" alt="Vercel Logo" width={72} height={16} />
</span>
</a>
</footer>
</div>
)
}
api/auth/me
in the client-side of our app as auth0
provided us with a hook called useUser
which returns the same thing when the user is logged in, and it returns null
when the user is logged out.withApiAuthRequired
and withPageAuthRequired
withApiAuthRequired
in the API part of the app, and we use withPageAuthRequired
in the components. api/user
and the dashboard
page.pages/api/user.js
and pages/dashboard.js
We need to put the following in the pages/api/user.js
file:import { withApiAuthRequired ,getSession } from "@auth0/nextjs-auth0"
export default withApiAuthRequired(async (req, res) => {
const user = getSession(req, res).user // the getSession function is used to get the session object that's created in the app. Which is where auth data is kepy
res.json({user})
})
pages/dashboard.js
file, let’s type the following:import { withPageAuthRequired, useUser } from '@auth0/nextjs-auth0'
const Dashboard = () => {
const {user} = useUser()
return (
<div>
<main>
{user && (
<div>
<div>
{user.email} {!user.email_verified && <span>Your account is not verified</span>}
</div>
</div>
)}
</main>
</div>
)
}
export const getServerSideProps = withPageAuthRequired()
export default Dashboard
api/user
endpoint, it will return with an error message. We've successfully protected routes both on the client and server-side.New Database
button, enter the database name, and click enter.contacts
. The user collection is where we’ll be storing our contact data. New Collection
. Enter only the collection name (contacts
), then click save.indexes
section on the left of your dashboard.user_contacts
index, this is used to retrieve all passwords created by a particular user.New Key.
Enter your key name, and a new key will be generated for you. Paste the key in your .env.local
file in this format: REACT_APP_FAUNA_KEY={{ API key }}
/
: home route/dashboard
: The dashboard route. Only authenticated users can access this page.api/contacts
: This is an API. It will support the GET
HTTP method for getting all the contacts created by the user and the POST
HTTP method for creating a new contactapi/contacts/:contactId
: This is also an API which will support GET
, PUT
and theDELETE
HTTP method for getting a single contact, updating it, and deleting a contact respectively.components
folder in the root directory of our app and put each component there:Navbar
: This is the navbar of the app,. We Will create a file called components/Navbar.js
for this.Contact
: This contains details of a single contact detail. We won't have a separate file for this.Contacts
: This will use the Contact
component and display all the contacts created by the authenticated user. We will create a file called components/Contacts
and put both the Contacts
and Contact
components there.BaseModal
: is the component we will build all our modals upon. We will place it in a file called components/BaseModal.js
.CreateContact.modal
: is the component that creates a modal for creating a new contact. We will place it in a file called CreateContact.modal.js
.EditContact.modal
: This is the component that creates a modal for editing a contact. We will add it to a file called EditContact.modal.js
api
folder. This file models.js
will be in the root directory of our app.npm i faunadb axios @fortawesome/react-fontawesome @fortawesome/free-solid-svg-icons @fortawesome/fontawesome-svg-core @fortawesome/fontawesome-free react-bootstrap
models.js
, type the followingimport faunadb, {query as q} from 'faunadb'
const client = new faunadb.Client({secret: process.env.REACT_APP_FAUNA_KEY})
export const createContact = async (
firstName,
lastName,
email,
phone,
user,
jobTitle,
company,
address,
avatar
) => {
const date = new Date()
const months = [
"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
]
let newContact = await client.query(
q.Create(
q.Collection('contacts'),
{
data: {
firstName,
lastName,
email,
phone,
company,
jobTitle,
address,
avatar,
created__at: `${months[date.getMonth()]} ${date.getDate()}, ${date.getFullYear()}`,
user: {
id: user.sub
}
}
}
)
)
if (newContact.name === 'BadRequest') return
newContact.data.id = newContact.ref.value.id
return newContact.data
}
export const getContactsByUserID = async id => {
try {
let userContacts = await client.query(
q.Paginate(
q.Match(q.Index('user_contacts'), id)
)
)
if (userContacts.name === "NotFound") return
if (userContacts.name === "BadRequest") return "Something went wrong"
let contacts = []
for (let contactId of userContacts.data) {
let contact = await getContact(contactId.value.id)
contacts.push(contact)
}
return contacts
} catch (error) {
if (error.message === 'instance not found') return []
return
}
}
export const getContact = async id => {
let contact = await client.query(
q.Get(q.Ref(q.Collection('contacts'), id))
)
if (contact.name === "NotFound") return
if (contact.name === "BadRequest") return "Something went wrong"
contact.data.id = contact.ref.value.id
return contact.data
}
export const updateContact = async (payload, id) => {
let contact = await client.query(
q.Update(
q.Ref(q.Collection('contacts'), id),
{data: payload}
)
)
if (contact.name === "NotFound") return
if (contact.name === "BadRequest") return "Something went wrong"
contact.data.id = contact.ref.value.id
return contact.data
}
export const deleteContact = async id => {
let contact = await client.query(
q.Delete(
q.Ref(q.Collection('contacts'), id)
)
)
if (contact.name === "NotFound") return
if (contact.name === "BadRequest") return "Something went wrong"
contact.data.id = contact.ref.value.id
return contact.data
}
components/Navbar.js
file, type the following:import {
Navbar, Nav
} from 'react-bootstrap'
import { useUser } from '@auth0/nextjs-auth0';
import Image from 'next/image';
const NavbarComponent = () => {
const {user, isLoading, error} = useUser()
return (
<Navbar fixed="top" collapseOnSelect expand="lg" bg="dark" variant="dark">
<Navbar.Brand className="mx-2 mx-md-4" href="/">Contact Manager</Navbar.Brand>
<Navbar.Toggle aria-controls="responsive-navbar-nav" />
<Navbar.Collapse className="d-lg-flex justify-content-end" id="responsive-navbar-nav">
{(!user & !error) ?
<>
<Nav.Link className="text-light" href="api/auth/login">Sign In </Nav.Link> :
<Image alt="avatar" loader={myLoader} src={`https://ui-avatars.com/api/?background=random&name=John+Doe`} width="35" height="35" className="rounded-circle" />
</> :
<>
<Nav.Link className="text-light" href="/dashboard">Dashboard</Nav.Link>
<Nav.Link className="text-light" href="api/auth/logout">Sign Out</Nav.Link>
<Nav.Link href="/profile">
<Image alt="avatar" loader={myLoader} src={user.picture || `https://ui-avatars.com/api/?background=random&name=${firstName}+${lastName}`} width="35" height="35" className="rounded-circle" />
</Nav.Link>
</>
}
</Navbar.Collapse>
</Navbar>
)
}
const myLoader=({src})=>{
return src;
}
export default NavbarComponent
useUser
hook here to determine if the user is logged in or not since we want to return things from this component dynamically. We also have a myLoader
function at the bottom of the file, and this is because we are using the Image
tag with a link.components/BaseModal.js
file, type the following:import Modal from 'react-bootstrap/Modal'
import Container from "react-bootstrap/Container";
import Button from "react-bootstrap/Button";
import Form from "react-bootstrap/Form";
import Row from "react-bootstrap/Row";
import Col from "react-bootstrap/Col";
const BaseModal = (props) => {
const onHide = () => {
if (props.create) {
props.updateFirstName('')
props.updateLastName('')
props.updateEmail('')
props.updatePhone('' )
props.updateAddress('')
}
props.onHide()
}
return (
<Modal
{...props}
size="xlg"
aria-labelledby="contained-modal-title-vcenter"
centered
onHide={onHide}
>
<Modal.Header closeButton>
<Modal.Title id="contained-modal-title-vcenter">
{props.header && props.header}
{props.title && props.title}
</Modal.Title>
</Modal.Header>
<Modal.Body className="show-grid">
<Container>
<Form>
<Row>
<Form.Group as={Col} className='form-group'>
<Form.Control placeholder="First name" className='form-control' value={props.firstName} onChange={e => {props.updateFirstName(e.target.value)}}/>
</Form.Group>
<Form.Group as={Col} className='form-group'>
<Form.Control placeholder="Last name" className='form-control' value={props.lastName} onChange={e => {props.updateLastName(e.target.value)}}/>
</Form.Group>
</Row>
<Row>
<Form.Group as={Col}>
<Form.Control type="email" placeholder="Email" value={props.email} onChange={e => {props.updateEmail(e.target.value)}}/>
</Form.Group>
</Row>
<Row>
<Form.Group as={Col}>
<Form.Control type="phone" placeholder="Phone number(+2348180854296)" value={props.phone} onChange={e => {props.updatePhone(e.target.value)}}/>
</Form.Group>
</Row>
<Row>
<Form.Group as={Col}>
<Form.Control placeholder="Address" value={props.address} onChange={e => {props.updateAddress(e.target.value)}}/>
</Form.Group>
</Row>
</Form>
</Container>
</Modal.Body>
<Modal.Footer>
<Button variant="danger" onClick={onHide}>Close</Button>
<Button variant="success" onClick={props.create ? props.handleCreate: props.handleEdit} disabled={(!props.firstName || !props.lastName || !props.phone) ? true : false}>{props.btnText}</Button>
</Modal.Footer>
</Modal>
);
}
export default BaseModal
components/Contacts.js
file, type the following:import Image from 'next/image';
import Button from 'react-bootstrap/Button'
import Table from 'react-bootstrap/Table'
import { useState } from 'react'
import EditContactModal from './EditContact.modal'
const Contact = ({
id,
firstName,
lastName,
email,
phone,
address
avatar,
handleDelete,
handleEdit
}) => {
const [editModal, setEditModal] = useState(false)
const editContact = () => {
setEditModal(true)
}
const deleteContact = () => {
handleDelete(id)
alert('Contact deleted successfully')
}
return (
<tr>
<td>
<Image alt="avt" loader={myLoader} src={avatar} width="35" height="35" className="rounded-circle" />
</td>
<td>{firstName} {lastName}</td>
<td>
<a href={`mailto:${email}`}>{email}</a>
</td>
<td>
<a href={`tel:${phone}`}>{phone}</a>
</td>
<td>{address}</td>
<td><Button onClick={editContact}>Edit</Button></td>
<td><Button onClick={deleteContact}>Delete</Button></td>
<EditContactModal
show={editModal}
firstname={firstName}
lastname={lastName}
email={email}
phone={phone}
address={address}
title={"Edit Contact for "+firstName}
onHide={() => {
let n = window.confirm("Your changes won't be saved...")
if (n) setEditModal(false)
}}
onEdit ={(contact) => {
contact.id = id
handleEdit(contact)
alert(`Contact for ${firstName} updated successfully`)
setEditModal(false)
}}
/>
</tr>
)
}
const Contacts = ({contacts, handleEdit, handleDelete}) => {
return (
<>
{!contacts && 'Fetching contacts...'}
<Table striped bordered hover responsive>
<thead>
<tr>
<th>avatar</th>
<th>Name</th>
<th>Email</th>
<th>Phone</th>
<th>Address</th>
<th>Edit</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
{contacts.map(ele => <Contact {...ele}
key={ele.id}
handleEdit={handleEdit}
handleDelete={handleDelete} />)}
</tbody>
</Table>
</>
)
}
const myLoader=({src})=>{
return src;
}
export default Contacts
CreateContact.modal.js
file, type the following:import BaseModal from './BaseModal'
import { useState } from 'react'
const CreateContactModal = (props) => {
const [firstName, setFirstName] = useState('')
const [lastName, setLastName] = useState('')
const [email, setEmail] = useState('')
const [phone, setPhone] = useState('')
const [address, setAddress] = useState('')
const handleCreate = () => {
const payload = {
firstName,
lastName,
email,
phone,
address
}
props.onCreate(payload)
}
return <BaseModal
show={props.show}
onHide={props.onHide}
firstName={firstName}
lastName={lastName}
email={email}
phone={phone}
address={address}
updateFirstName={newInput => setFirstName(newInput)}
updateLastName={newInput => setLastName(newInput)}
updateEmail={newInput => setEmail(newInput)}
updatePhone={newInput => setPhone(newInput)}
updateAddress={newInput => setAddress(newInput)}
header="Create New Contact"
btnText="Create"
handleCreate={handleCreate}
create={true}
/>
}
export default CreateContactModal
BaseModal.js
file and passes props to the component.components/EditContact.modal.js
file, type the following:import BaseModal from './BaseModal'
import { useState } from 'react'
const EditContactModal = props => {
const [firstName, setFirstName] = useState(props.firstname)
const [lastName, setLastName] = useState(props.lastname)
const [email, setEmail] = useState(props.email)
const [phone, setPhone] = useState(props.phone)
const [address, setAddress] = useState(props.address)
const onEdit = () => {
const payload = {
firstName
lastName,
email,
phone,
address
}
props.onEdit(payload)
}
return <BaseModal
show={props.show}
onHide={props.onHide}
title={props.title}
firstName={firstName}
lastName={lastName}
email={email}
phone={phone}
address={address}
updateFirstName={newInput => setFirstName(newInput)}
updateLastName={newInput => setLastName(newInput)}
updateEmail={newInput => setEmail(newInput)}
updatePhone={newInput => setPhone(newInput)}
updateAddress={newInput => setAddress(newInput)}
btnText="Edit"
handleEdit={onEdit}
create={false}
/>
}
export default EditContactModal
pages,/index.js
file has a Meta
tag.All pages should have their meta tag for SEO optimization. components/MetaData.js
file:components/MetaData.js
file, type the following:import Head from 'next/head'
const MetaData = ({title}) => {
return (
<Head>
<title>{`Contact Manager App ${title && "| " +title}`}</title>
<meta name="description" content="A simple Contact Manager" />
<link rel="icon" href="/favicon.ico" />
</Head>
)
}
export default MetaData
api/contacts
- we need to create a pages/api/contacts.js
file
a. GET - get all contacts.
b. POST - create a new contact.
api/contacts/:id
- we need to create a pages/api/contacts/[id].js
file
a. GET - get a single contact
b. PUT - update a single contact
c. DELETE - delete a single contacts
pages/api/contacts.js
file, type the following:// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import { withApiAuthRequired, getSession } from "@auth0/nextjs-auth0"
import { createContact, deleteContact, getContactsByUserID } from "../../models"
export default withApiAuthRequired(async (req, res) => {
const user = getSession(req, res).user
if (req.method === 'POST') {
let {
firstName, lastName, email,
company, jobTitle, phone, address, avatar
} = req.body
let newContact = await createContact(
firstName, lastName,
email, phone,
user, jobTitle,
company, address, avatar
)
res.status(201).json({
message: "Successfully created contact",
data: newContact,
status: 'ok'
})
} else if (req.method === 'GET') {
let contacts = await getContactsByUserID(user.sub)
if (!contacts) return res.status(400).json({
message: 'Something went wrong',
data: null,
status: false
})
res.status(200).json({
message: "Successfully retrieved contacts",
data: contacts,
status: 'ok'
})
} else {
res.status(405).json({
message: 'Method not allowed',
data: null,
status: false
})
}
})
getSession
function to get the current user from the request and response object. We then used this to set the contact creator and get contacts created by the user.pages/api/contacts/[id].js
type the following:// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import { withApiAuthRequired ,getSession } from "@auth0/nextjs-auth0"
import { deleteContact, getContact, updateContact } from "../../../models"
export default withApiAuthRequired(async (req, res) => {
const user = getSession(req, res).user
if (req.method === 'PUT') {
let contact = await updateContact(
req.body, req.query.id
)
res.status(201).json({
message: "Successfully updated contact",
data: contact,
status: 'ok'
})
} else if (req.method === 'GET') {
let contact = await getContact(req.query.id)
res.status(200).json({
message: "Successfully retrieved contact",
data: contact,
status: 'ok'
})
} else if (req.method === 'DELETE') {
let contact = await getContact(req.query.id)
if (contact.user.id !== user.sub) {
return res.status(403).json({
message: "Forbidden",
status: false,
data: null
})
}
contact = await deleteContact(req.query.id)
res.status(200).json({
message: "Successfully deleted contact",
data: contact,
status: 'ok'
})
} else {
res.status(405).json({
message: 'Method not allowed',
data: null,
status: false
})
}
})
pages/index.js
file to the following:import Image from 'next/image';
import { useUser } from '@auth0/nextjs-auth0';
import MetaData from '../components/MetaData'
import styles from '../styles/Home.module.css'
export default function Home() {
const { error, isLoading } = useUser();
if (isLoading) return <div>Loading...</div>;
if (error) return <div>{error.message}</div>;
return (
<div>
<MetaData title="" />
<main className={styles.container}>
<Image className={styles.img} alt="home" src="/home.jpeg" width="400" height="200" />
</main>
</div>
)
}
pages/_app.js
file. Basically, this file is what is served, and it changes based on what is happening on the current page.pages/dasboard.js
file, type the following:import { useEffect, useState } from 'react'
import { withPageAuthRequired, useUser } from '@auth0/nextjs-auth0'
import Button from 'react-bootstrap/Button'
import axios from 'axios'
import CreateContactModal from '../components/CreateContact.modal'
import Contacts from '../components/Contacts'
import MetaData from '../components/MetaData'
import styles from '../styles/Home.module.css'
const Dashboard = () => {
const {user} = useUser()
const [contacts, setContacts] = useState([])
const [createModalShow, setCreateModalShow] = useState(false);
const handleHide = () => {
let n = window.confirm("Your changes won't be saved...")
if (n) setCreateModalShow(false)
}
useEffect(async () => {
let res = (await axios.get(`/api/contacts`)).data
res = await res.data
setContacts(res.reverse())
}, [])
const createContact = async payload => {
payload.avatar = `https://ui-avatars.com/api/?background=random&name=${payload.firstName}+${payload.lastName}`
let newContact = (await axios.post(`/api/contacts`, payload)).data
setContacts([newContact.data, ...contacts])
}
const editContact = async payload => {
let id = payload.id
delete payload.id
let replacedContact = (await axios.put(`/api/contacts/${id}`, payload)).data
setContacts(contacts.map(contact => contact.id === id? replacedContact.data : contact))
}
const deleteContact = async id => {
(await axios.delete(`/api/contacts/${id}`)).data
setContacts(contacts.filter(contact => contact.id !== id))
}
return (
<div>
<MetaData title="Dashboard" />
<main>
{user && (
<div className={styles.dashboardContainer}>
<div>
<img alt="avatar" src={user.picture} className="rounded-circle m-3" width="100" height="100"/>
<span>Welcome {user.nickname.toLowerCase().charAt(0).toUpperCase()+user.nickname.toLowerCase().slice(1)}</span>
{!user.email_verified && <div>Your account is not verified</div>}
</div>
<div>
<Button variant="primary" onClick={() => setCreateModalShow(true)}>
Create New Contact
</Button>
<CreateContactModal
show={createModalShow}
onHide={handleHide}
onCreate ={(payload) => {createContact(payload); setCreateModalShow(false)}}
/>
</div>
</div>
)}
</main>
<Contacts
contacts={contacts}
handleEdit={(id) => editContact(id)}
handleDelete={(id) => deleteContact(id)}
/>
</div>
)
}
export const getServerSideProps = withPageAuthRequired()
export default Dashboard
pages/_app.js
file.pages/_app.js
file with the following:import React, { useEffect, useState } from 'react'
import { UserProvider } from '@auth0/nextjs-auth0';
import axios from 'axios'
import MetaData from '../components/MetaData'
import NavbarComponent from '../components/Navbar'
import 'bootstrap/dist/css/bootstrap.min.css';
import '../styles/globals.css'
export default function App({ Component, pageProps }) {
return (
<UserProvider>
<NavbarComponent />
<Component {...pageProps} />
</UserProvider>
);
}