From e9d187799652efec6f58c6d70579b6937fc64f2b Mon Sep 17 00:00:00 2001 From: Colby Fayock Date: Wed, 10 Mar 2021 15:40:39 -0500 Subject: [PATCH] updating to use serverless functions and identity --- package.json | 1 + src/components/PostForm/PostForm.js | 22 +++++++- src/hooks/useAuth.js | 49 ++++++++++++++++ src/lib/auth.js | 27 +++++++++ src/lib/posts.js | 19 +++++++ src/pages/_app.js | 8 ++- src/pages/api/hello.js | 5 -- src/pages/api/posts.js | 76 +++++++++++++++++++++++++ src/pages/index.js | 88 ++++++++++++++++++++--------- yarn.lock | 5 ++ 10 files changed, 265 insertions(+), 35 deletions(-) create mode 100644 src/hooks/useAuth.js create mode 100644 src/lib/auth.js create mode 100644 src/lib/posts.js delete mode 100644 src/pages/api/hello.js create mode 100644 src/pages/api/posts.js diff --git a/package.json b/package.json index 2a12b10..3fd04e1 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "build-storybook": "build-storybook" }, "dependencies": { + "netlify-identity-widget": "^1.9.1", "next": "10.0.7", "react": "17.0.1", "react-dom": "17.0.1", diff --git a/src/components/PostForm/PostForm.js b/src/components/PostForm/PostForm.js index 52533b3..f684fbd 100644 --- a/src/components/PostForm/PostForm.js +++ b/src/components/PostForm/PostForm.js @@ -1,9 +1,25 @@ import styles from './PostForm.module.scss'; -const PostForm = () => { +const PostForm = ({ onSubmit }) => { + function handleOnSubmit(e) { + const { currentTarget } = e; + + const fields = Array.from(currentTarget.elements); + const data = {}; + + fields.forEach(field => { + if ( !field.name ) return; + data[field.name] = field.value; + }) + + if ( typeof onSubmit === 'function' ) { + onSubmit(data, e) + } + } + return ( -
- + +
) diff --git a/src/hooks/useAuth.js b/src/hooks/useAuth.js new file mode 100644 index 0000000..f2215a6 --- /dev/null +++ b/src/hooks/useAuth.js @@ -0,0 +1,49 @@ +import { createContext, useContext, useEffect, useState } from 'react'; + +import { auth, init, logIn as authLogIn, logOut as authLogOut } from '../lib/auth.js'; + +export const AuthContext = createContext(); + +export const AuthProvider = ({ children }) => { + const [user, setUser] = useState(); + + useEffect(() => { + init((user) => { + setUser(user) + }); + + auth.on('login', setUser); + + return () => { + auth.off('login', setUser); + } + }, []); + + function logIn() { + authLogIn((user) => { + setUser(user) + }) + } + + function logOut() { + authLogOut(() => { + setUser(undefined) + }) + } + + const contextValue = { + user, + logIn, + logOut + } + + return ( + + { children } + + ) +} + +export function useAuth() { + return useContext(AuthContext); +} \ No newline at end of file diff --git a/src/lib/auth.js b/src/lib/auth.js new file mode 100644 index 0000000..0877c37 --- /dev/null +++ b/src/lib/auth.js @@ -0,0 +1,27 @@ +import netlifyIdentity from 'netlify-identity-widget'; + +export const auth = netlifyIdentity; + +export function init(callback) { + netlifyIdentity.on('init', user => { + callback(user); + }); + netlifyIdentity.init({ + APIUrl: process.env.NEXT_PUBLIC_AUTH_ENDPOINT + }); +} + +export function logIn(callback) { + netlifyIdentity.open(); + netlifyIdentity.on('login', user => { + callback(user); + netlifyIdentity.close(); + }); +} + +export function logOut(callback) { + netlifyIdentity.logout(); + netlifyIdentity.on('logout', () => { + callback(); + }); +} \ No newline at end of file diff --git a/src/lib/posts.js b/src/lib/posts.js new file mode 100644 index 0000000..7fd1ce8 --- /dev/null +++ b/src/lib/posts.js @@ -0,0 +1,19 @@ +import { auth } from './auth'; + +export async function getAllPosts() { + const response = await fetch(`${process.env.NEXT_PUBLIC_API_ENDPOINT}/api/posts`); + const { posts } = await response.json(); + return posts; +} + +export async function createPost(data) { + const user = auth.currentUser(); + + await fetch(`${process.env.NEXT_PUBLIC_API_ENDPOINT}/api/posts`, { + method: 'POST', + body: JSON.stringify(data), + headers: { + Authorization: `Bearer ${user.token.access_token}` + } + }); +} \ No newline at end of file diff --git a/src/pages/_app.js b/src/pages/_app.js index eabbdd8..2e6ca33 100644 --- a/src/pages/_app.js +++ b/src/pages/_app.js @@ -1,7 +1,13 @@ +import { AuthProvider } from '../hooks/useAuth'; + import '../styles/globals.scss' function MyApp({ Component, pageProps }) { - return + return ( + + + + ); } export default MyApp diff --git a/src/pages/api/hello.js b/src/pages/api/hello.js deleted file mode 100644 index 9987aff..0000000 --- a/src/pages/api/hello.js +++ /dev/null @@ -1,5 +0,0 @@ -// Next.js API route support: https://nextjs.org/docs/api-routes/introduction - -export default (req, res) => { - res.status(200).json({ name: 'John Doe' }) -} diff --git a/src/pages/api/posts.js b/src/pages/api/posts.js new file mode 100644 index 0000000..453572e --- /dev/null +++ b/src/pages/api/posts.js @@ -0,0 +1,76 @@ +// Next.js API route support: https://nextjs.org/docs/api-routes/introduction + +export default async (req, res) => { + + console.log('req', req.method); + + if ( req.method === 'GET' ) { + const response = await fetch(`https://api.airtable.com/v0/${process.env.AIRTABLE_BASE_ID}/Posts`, { + headers: { + Authorization: `Bearer ${process.env.AIRTABLE_API_KEY}` + } + }); + + const { records } = await response.json(); + + const posts = records.map(record => { + return { + id: record.id, + ...record.fields + } + }) + + res.status(200).json({ posts }) + + return; + } + + if ( req.method === 'POST' ) { + + const { authorization } = req.headers; + + const auth = await fetch(`${process.env.NEXT_PUBLIC_AUTH_ENDPOINT}/user`, { + headers: { + Authorization: authorization + } + }); + + const authJson = await auth.json(); + + if ( !authJson.id ) { + res.status(401).json({ + error: 'Invalid token' + }); + return; + } + + const { content } = JSON.parse(req.body); + + const data = { + records: [ + { + fields: { + content, + date: new Date().toISOString() + } + } + ] + } + + const response = await fetch(`https://api.airtable.com/v0/${process.env.AIRTABLE_BASE_ID}/Posts`, { + method: 'POST', + headers: { + Authorization: `Bearer ${process.env.AIRTABLE_API_KEY}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify(data) + }); + + console.log('response', response); + + res.status(201).json({ response }) + + return; + } + +} diff --git a/src/pages/index.js b/src/pages/index.js index 609947f..9736dc3 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -1,12 +1,34 @@ +import { useState, useEffect } from 'react'; import Head from 'next/head' +import { useAuth } from '../hooks/useAuth'; +import { getAllPosts, createPost } from '../lib/posts'; + import Bio from '../components/Bio'; import Post from '../components/Post'; import PostForm from '../components/PostForm'; import styles from '../styles/Home.module.scss' -export default function Home() { +export default function Home({ posts: defaultPosts }) { + + const [posts, updatePosts] = useState(defaultPosts); + + const postsSorted = posts.sort(function(a,b){ + return new Date(b.date) - new Date(a.date); + }); + + const { user, logIn, logOut } = useAuth(); + + async function handleOnSubmit(data, e) { + e.preventDefault(); + + await createPost(data); + + const posts = await getAllPosts(); + updatePosts(posts); + } + return (
@@ -14,6 +36,18 @@ export default function Home() { + { !user && ( +

+ +

+ ) } + + { user && ( +

+ +

+ ) } +
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • + {postsSorted.map(post => { + const { content, id, date } = post; + return ( +
  • + +
  • + ) + })}
- + { user && ( + + ) }
) } + +export async function getStaticProps() { + const posts = await getAllPosts(); + + return { + props: { + posts + } + } +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 9c29029..6e212f5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7681,6 +7681,11 @@ nested-error-stacks@^2.0.0, nested-error-stacks@^2.1.0: resolved "https://registry.yarnpkg.com/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz#0fbdcf3e13fe4994781280524f8b96b0cdff9c61" integrity sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug== +netlify-identity-widget@^1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/netlify-identity-widget/-/netlify-identity-widget-1.9.1.tgz#9e716c4b92b9f0cc041074eb86fc962f35295b46" + integrity sha512-9oIWjwUSdRk3SkREcZNjZaVuDDx9T/wSIXZNQsQeY4qoXic/FiXVEGgu2RU3IuA4OI3L2652xY1o+PpS03Ugaw== + next@10.0.7: version "10.0.7" resolved "https://registry.yarnpkg.com/next/-/next-10.0.7.tgz#442f8e1da7454de33b0bbcc1ce5684b923597ee6"