Skip to content

Commit

Permalink
Feature/api routes auth (#25)
Browse files Browse the repository at this point in the history
* home page styling and add view dashboard button

* only return skeleton if data is not received yet

* firebase jwt token in the user object

* get sites added by the authenticated user

* send token in swr mutate function

* children type

* install js-cookie

* automatically redirect to dashboard if authed

* set auth cookie

* fix getStaticPaths

* use incremental static regeneration for feedback

* yarn upgrade

* error handling in getStaticPaths
  • Loading branch information
lawandothman committed May 21, 2021
1 parent 494cb1e commit 6ca977a
Show file tree
Hide file tree
Showing 11 changed files with 795 additions and 780 deletions.
4 changes: 2 additions & 2 deletions components/AddSiteModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { ISite } from 'types'
import { createSite } from '@/lib/firestore'
import { useAuth } from '@/lib/auth'

const AddSiteModal: React.FC = ({ children }) => {
const AddSiteModal: React.FC<React.ReactNode> = ({ children }) => {
const toast = useToast()
const auth = useAuth()
const { isOpen, onOpen, onClose } = useDisclosure()
Expand All @@ -43,7 +43,7 @@ const AddSiteModal: React.FC = ({ children }) => {
isClosable: true,
})
mutate(
'/api/sites',
['/api/sites', auth?.user?.token],
async (data: { sites: ISite[] }) => ({ sites: [...data.sites, newSite] }),
false,
)
Expand Down
12 changes: 8 additions & 4 deletions lib/auth.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, {
createContext, useContext, useEffect, useState,
} from 'react'
import Cookies from 'js-cookie'
import { IUser } from 'types'
import firebase from './firebase'
import { createUser } from './firestore'
Expand All @@ -13,7 +14,7 @@ interface IAuthContext {

const AuthContext = createContext<IAuthContext | null>(null)

const formatUser = (user: firebase.User) => ({
const formatUser = (user: firebase.User): IUser => ({
uid: user.uid,
email: user.email,
name: user.displayName,
Expand All @@ -26,14 +27,17 @@ const useProvideAuth = () => {

console.log(user)

const handleUser = (rawUser: firebase.User | null) => {
const handleUser = async (rawUser: firebase.User | null): Promise<IUser | null> => {
if (rawUser) {
const token = await rawUser.getIdToken()
const formattedUser = formatUser(rawUser)
createUser(formattedUser)
setUser(formattedUser)
return formattedUser
setUser({ ...formattedUser, token })
Cookies.set('fast-feedback-auth', formattedUser, { expires: 1 })
return { ...formattedUser, token }
}
setUser(null)
Cookies.remove('fast-feedback-auth')
return null
}

Expand Down
27 changes: 18 additions & 9 deletions lib/firestore-admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,25 @@ export const getAllFeedback = async (siteId: string) => {
}

export const getAllSites = async () => {
try {
const snapshot = await firestore.collection('sites').get()
const snapshot = await firestore.collection('sites').get()
const sites: ISite[] = []

const sites: ISite[] = []
// @ts-ignore
snapshot.forEach((doc) => sites.push({ id: doc.id, ...doc.data() }))

// @ts-ignore
snapshot.forEach((doc) => sites.push({ id: doc.id, ...doc.data() }))
return { sites }
}

return { sites }
} catch (error) {
return { error }
}
export const getUserSites = async (userId: string) => {
const snapshot = await firestore
.collection('sites')
.where('authorId', '==', userId)
.get()

const sites: ISite[] = []

// @ts-ignore
snapshot.forEach((doc) => sites.push({ id: doc.id, ...doc.data() }))

return { sites }
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@
"firebase": "^8.6.1",
"firebase-admin": "^9.8.0",
"framer-motion": "^4",
"js-cookie": "^2.2.1",
"next": "10.x",
"react": "17.x",
"react-dom": "17.x",
"react-hook-form": "^7.5.3",
"swr": "^0.5.6"
},
"devDependencies": {
"@types/js-cookie": "^2.2.6",
"@types/react": "^17.0.5",
"@typescript-eslint/eslint-plugin": "^4.23.0",
"@typescript-eslint/parser": "^4.23.0",
Expand Down
19 changes: 12 additions & 7 deletions pages/api/sites.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { NextApiRequest, NextApiResponse } from 'next'
import { getAllSites } from '@/lib/firestore-admin'
import { getUserSites } from '@/lib/firestore-admin'
import admin from '@/lib/firebase-admin'

export default async (_req: NextApiRequest, res: NextApiResponse) => {
const { sites, error } = await getAllSites()

if (error) {
res.status(500).json({ error })
export default async (req: NextApiRequest, res: NextApiResponse) => {
if (!req.headers.token) {
return res.status(401).json({ error: 'Unauthorized' })
}

res.status(200).json({ sites })
try {
const { uid } = await admin.auth().verifyIdToken(req.headers.token as string)
const sites = await getUserSites(uid)
return res.status(200).json(sites)
} catch (error) {
return res.status(500).json({ error })
}
}
18 changes: 13 additions & 5 deletions pages/dashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
import useSwr from 'swr'
// import { useAuth } from '@/lib/auth'
import EmptyState from '@/components/EmptyState'
import SiteTableSkeleton from '@/components/SiteTableSkeleton'
import DashboardShell from '@/components/DashboardShell'
import fetcher from '@/util/fetcher'
import SiteTable from '@/components/SiteTable'
import { useAuth } from '@/lib/auth'

const Dashboard = () => {
// const auth = useAuth()
const { data } = useSwr('/api/sites', fetcher)
const auth = useAuth()
const { data } = useSwr(
auth?.user ? ['/api/sites', auth?.user.token] : null,
fetcher,
)

console.log(data)
if (!data) {
return (
<DashboardShell>
<SiteTableSkeleton />
</DashboardShell>
)
}

return (
<DashboardShell>
{!data && <SiteTableSkeleton />}
{data?.sites ? <SiteTable sites={data.sites} /> : <EmptyState />}
</DashboardShell>
)
Expand Down
29 changes: 24 additions & 5 deletions pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Head from 'next/head'
import { Button, Flex } from '@chakra-ui/react'
import { Button, Flex, Text } from '@chakra-ui/react'
import { useAuth } from '@/lib/auth'
import Logo from '@/components/Logo'

Expand All @@ -12,21 +12,40 @@ const Home = () => {
align='center'
justify='center'
h='100vh'
maxW='400px'
margin='0 auto'
>
<Head>
<script
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{
__html: `
if(document.cookie && document.cookie.includes('fast-feedback-auth')) {
window.location.href= '/dashboard'
}
`,
}}
/>
<title>Fast Feedback</title>
</Head>
<Logo color='black' boxSize='64px' />
<Logo color='black' boxSize='42px' mb={2} />
<Text mb={4}>
<Text as='span' fontWeight='bold' display='inline'>
Fast Feedback
</Text>
<br />
The easiest way to add comments or reviews to your static site.
</Text>
{auth?.user ? (
<Button onClick={() => auth?.signout()} type='button'>
Sign Out
<Button as='a' size='sm' fontWeight='medium' href='/dashboard'>
View Dashboard
</Button>
) : (
<Button
mt={4}
size='sm'
fontWeight='medium'
onClick={() => auth?.signinWithGithub()}
type='button'
>
Sign In
</Button>
Expand Down
27 changes: 14 additions & 13 deletions pages/p/[siteId].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,30 @@ export const getStaticProps: GetStaticProps = async (context) => {
props: {
initialFeedback: feedback,
},
revalidate: 1,
}
}

export const getStaticPaths: GetStaticPaths = async () => {
const { sites, error } = await getAllSites()
try {
const { sites } = await getAllSites()

const paths = sites.map((site) => ({
params: {
siteId: site.id,
},
}))

if (error) {
return {
paths,
fallback: false,
}
} catch (error) {
return {
paths: [],
fallback: false,
}
}

const paths = sites!.map((site) => ({
params: {
siteId: site.id,
},
}))

return {
paths,
fallback: false,
}
}

interface SiteFeedbackProps {
Expand Down
3 changes: 2 additions & 1 deletion types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ export interface IUser {
uid: string
email: string | null
name: string | null
provider: string | undefined
provider?: string
photoUrl: string | null
token?: string
}

export interface ISite {
Expand Down
9 changes: 7 additions & 2 deletions util/fetcher.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
const fetcher = async (input: RequestInfo, init: RequestInit) => {
const res = await fetch(input, init)
const fetcher = async (url: string, token: string) => {
const res = await fetch(url, {
method: 'GET',
headers: new Headers({ 'Content-Type': 'application/json', token }),
credentials: 'same-origin',
})

return res.json()
}

Expand Down

1 comment on commit 6ca977a

@vercel
Copy link

@vercel vercel bot commented on 6ca977a May 21, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.