├── .prettierrc
├── pages
├── index.js
├── 404.js
├── _app.js
├── _document.js
└── api
│ └── posts
│ ├── index.js
│ └── [postId].js
├── .gitattributes
├── .babelrc
├── utils.js
├── public
├── favicon.ico
└── zeit.svg
├── .eslintrc
├── README.md
├── src
├── components
│ ├── GlobalLoader.js
│ ├── Sidebar.js
│ ├── styled.js
│ └── PostForm.js
├── hooks
│ ├── useDeletePost.js
│ ├── useCreatePost.js
│ ├── useSavePost.js
│ ├── usePosts.js
│ └── usePost.js
├── screens
│ ├── blog
│ │ ├── Post.js
│ │ └── index.js
│ └── admin
│ │ ├── index.js
│ │ └── Post.js
└── index.js
├── db
└── index.js
├── .gitignore
├── store.json
├── package.json
└── next.config.js
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": false,
3 | "singleQuote": true
4 | }
5 |
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | import App from '../src/'
2 |
3 | export default App
4 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["next/babel"],
3 | "plugins": ["styled-components"]
4 | }
5 |
--------------------------------------------------------------------------------
/utils.js:
--------------------------------------------------------------------------------
1 | export function sleep(time) {
2 | return new Promise((resolve) => setTimeout(resolve, time))
3 | }
4 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tannerlinsley/react-query-cached-in-60-minutes/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": ["react-app", "prettier"],
4 | "env": {
5 | "es6": true
6 | },
7 | "parserOptions": {
8 | "sourceType": "module"
9 | },
10 | "rules": {
11 | "jsx-a11y/anchor-is-valid": 0,
12 | "eqeqeq": 0
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | - Run `yarn` and then `yarn dev` locally.
2 | - The Next.js API server is an in-memory local server, so the data is not actually persisted to disk. The posts that are stored or altered will be reset when the server restarts or when a file is saved and the server is recompiled.
3 | - Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
4 |
--------------------------------------------------------------------------------
/pages/404.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default () => (
4 |
9 |
15 | 404
16 |
17 |
22 | Page not found
23 |
24 |
25 | )
26 |
--------------------------------------------------------------------------------
/src/components/GlobalLoader.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Loader } from './styled'
3 |
4 | export default function GlobalLoader() {
5 | return (
6 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/db/index.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs-extra'
2 | import path from 'path'
3 |
4 | const storeLocation = path.resolve(process.cwd(), 'store.json')
5 |
6 | export default {
7 | set,
8 | get,
9 | }
10 |
11 | async function set(updater) {
12 | const file = await fs.readJSON(storeLocation)
13 | const newFile = updater(file)
14 | await fs.writeJSON(storeLocation, newFile)
15 | }
16 |
17 | function get() {
18 | return fs.readJSON(storeLocation)
19 | }
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 |
21 | # debug
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
26 | # local env files
27 | .env.local
28 | .env.development.local
29 | .env.test.local
30 | .env.production.local
31 |
--------------------------------------------------------------------------------
/src/components/Sidebar.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router-dom'
3 |
4 | import { SidebarStyles } from './styled'
5 |
6 | export default function Sidebar() {
7 | return (
8 |
9 |
10 | -
11 | Home
12 |
13 | -
14 | Blog
15 |
16 |
17 | -
18 | Admin
19 |
20 |
21 |
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/src/hooks/useDeletePost.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import axios from 'axios'
3 |
4 | export default function useDeletePost() {
5 | const [state, setState] = React.useReducer((_, action) => action, {
6 | isIdle: true,
7 | })
8 |
9 | const mutate = React.useCallback(async (postId) => {
10 | setState({ isLoading: true })
11 | try {
12 | await axios.delete(`/api/posts/${postId}`).then((res) => res.data)
13 | setState({ isSuccess: true })
14 | } catch (error) {
15 | setState({ isError: true, error })
16 | }
17 | }, [])
18 |
19 | return [mutate, state]
20 | }
21 |
--------------------------------------------------------------------------------
/src/hooks/useCreatePost.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import axios from 'axios'
3 |
4 | export default function useCreatePost() {
5 | const [state, setState] = React.useReducer((_, action) => action, {
6 | isIdle: true,
7 | })
8 |
9 | const mutate = React.useCallback(async (values) => {
10 | setState({ isLoading: true })
11 | try {
12 | const data = axios.post('/api/posts', values).then((res) => res.data)
13 | setState({ isSuccess: true, data })
14 | } catch (error) {
15 | setState({ isError: true, error })
16 | }
17 | }, [])
18 |
19 | return [mutate, state]
20 | }
21 |
--------------------------------------------------------------------------------
/src/screens/blog/Post.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useParams } from 'react-router-dom'
3 |
4 | //
5 |
6 | import usePost from '../../hooks/usePost'
7 |
8 | export default function Post() {
9 | const { postId } = useParams()
10 | const postQuery = usePost(postId)
11 |
12 | return (
13 | <>
14 | {postQuery.isLoading ? (
15 | Loading...
16 | ) : postQuery.isError ? (
17 | postQuery.error.message
18 | ) : (
19 |
20 |
{postQuery.data.title}
21 |
{postQuery.data.body}
22 |
23 | )}
24 | >
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/src/hooks/useSavePost.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import axios from 'axios'
3 |
4 | export default function useSavePost() {
5 | const [state, setState] = React.useReducer((_, action) => action, {
6 | isIdle: true,
7 | })
8 |
9 | const mutate = React.useCallback(async (values) => {
10 | setState({ isLoading: true })
11 | try {
12 | const data = await axios
13 | .patch(`/api/posts/${values.id}`, values)
14 | .then((res) => res.data)
15 | setState({ isSuccess: true, data })
16 | } catch (error) {
17 | setState({ isError: true, error })
18 | }
19 | }, [])
20 |
21 | return [mutate, state]
22 | }
23 |
--------------------------------------------------------------------------------
/src/hooks/usePosts.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import axios from 'axios'
3 |
4 | export default function usePosts() {
5 | const [state, setState] = React.useReducer((_, action) => action, {
6 | isLoading: true,
7 | })
8 |
9 | const fetch = async () => {
10 | setState({ isLoading: true })
11 | try {
12 | const data = await axios.get('/api/posts').then((res) => res.data)
13 | setState({ isSuccess: true, data })
14 | } catch (error) {
15 | setState({ isError: true, error })
16 | }
17 | }
18 |
19 | React.useEffect(() => {
20 | fetch()
21 | }, [])
22 |
23 | return {
24 | ...state,
25 | fetch,
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/hooks/usePost.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import axios from 'axios'
3 |
4 | export const fetchPost = (postId) =>
5 | axios.get(`/api/posts/${postId}`).then((res) => res.data)
6 |
7 | export default function usePost(postId) {
8 | const [state, setState] = React.useReducer((_, action) => action, {
9 | isLoading: true,
10 | })
11 |
12 | const fetch = React.useCallback(async () => {
13 | setState({ isLoading: true })
14 | try {
15 | const data = await fetchPost(postId)
16 | setState({ isSuccess: true, data })
17 | } catch (error) {
18 | setState({ isError: true, error })
19 | }
20 | }, [postId])
21 |
22 | React.useEffect(() => {
23 | fetch()
24 | }, [fetch])
25 |
26 | return {
27 | ...state,
28 | fetch,
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/public/zeit.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/pages/_app.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import App from 'next/app'
3 | import { createGlobalStyle } from 'styled-components'
4 | import normalize from 'styled-normalize'
5 |
6 | //
7 |
8 | const GlobalStyles = createGlobalStyle`
9 | ${normalize};
10 | html, body, body, [data-reactroot] {
11 | min-height: 100%;
12 | max-width: 100%;
13 | }
14 |
15 | html, body {
16 | width: 100%;
17 | font-size: 16px;
18 | font-family: "Helvetica", "Georgia", sans-serif;
19 | }
20 |
21 | * {
22 | box-sizing: border-box;
23 | }
24 |
25 | input {
26 | max-width: 100%;
27 | }
28 |
29 | a {
30 | text-decoration: none;
31 | cursor: pointer;
32 | }
33 | `
34 |
35 | export default class MyApp extends App {
36 | render() {
37 | const { Component, pageProps } = this.props
38 | return (
39 | <>
40 |
41 |
42 | >
43 | )
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/screens/blog/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router-dom'
3 | //
4 | import usePosts from '../../hooks/usePosts'
5 | import { PostStyles } from '../../components/styled'
6 |
7 | export default function Home() {
8 | const postsQuery = usePosts()
9 |
10 | return (
11 |
12 |
Blog
13 |
14 |
21 | {postsQuery.isLoading ? (
22 |
Loading...
23 | ) : postsQuery.isError ? (
24 | postsQuery.error.message
25 | ) : (
26 | postsQuery.data.map((post) => (
27 |
28 | {post.title}
29 | {post.body}
30 |
31 | ))
32 | )}
33 |
34 |
35 | )
36 | }
37 |
--------------------------------------------------------------------------------
/store.json:
--------------------------------------------------------------------------------
1 | {"posts":[{"userId":1,"id":1,"title":"sunt aut facere repellat provident occaecati excepturi optio reprehenderit","body":"quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"},{"userId":1,"id":2,"title":"qui est esse","body":"est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla"},{"userId":1,"id":3,"title":"ea molestias quasi exercitationem repellat qui ipsa sit aut","body":"et iusto sed quo iure\nvoluptatem occaecati omnis eligendi aut ad\nvoluptatem doloribus vel accusantium quis pariatur\nmolestiae porro eius odio et labore et velit aut"},{"userId":1,"id":4,"title":"eum et est occaecati","body":"ullam et saepe reiciendis voluptatem adipisci\nsit amet autem assumenda provident rerum culpa\nquis hic commodi nesciunt rem tenetur doloremque ipsam iure\nquis sunt voluptatem rerum illo velit"},{"userId":1,"id":5,"title":"nesciunt quas odio","body":"repudiandae veniam quaerat sunt sed\nalias aut fugiat sit autem sed est\nvoluptatem omnis possimus esse voluptatibus quis\nest aut tenetur dolor neque"},{"id":"a8nX64wMH","title":"test","body":"asdfasd"}]}
2 |
--------------------------------------------------------------------------------
/src/components/styled.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled, { css, keyframes } from 'styled-components'
3 | import { ImSpinner2 } from 'react-icons/im'
4 |
5 | export const Wrapper = styled.div`
6 | display: flex;
7 | height: 96vh;
8 | `
9 |
10 | export const SidebarStyles = styled.div`
11 | width: 175px;
12 | border-right: 1px solid black;
13 | padding: 1rem;
14 | `
15 |
16 | export const Main = styled.div`
17 | flex: 1;
18 | padding: 1rem;
19 | `
20 |
21 | export const PostStyles = styled.div`
22 | display: inline-block;
23 | border: solid 1px rgba(130, 130, 130, 0.3);
24 | padding: 1rem;
25 | color: inherit;
26 |
27 | :hover {
28 | text-decoration: none;
29 | h3 {
30 | text-decoration: underline;
31 | }
32 | }
33 |
34 | ${(props) =>
35 | props.disabled &&
36 | css`
37 | opacity: 0.5;
38 | pointer-events: none;
39 | `}
40 | `
41 |
42 | const rotate = keyframes`
43 | from {
44 | transform: rotate(0deg);
45 | }
46 |
47 | to {
48 | transform: rotate(360deg);
49 | }
50 | `
51 |
52 | export function Loader(props) {
53 | return (
54 |
61 | )
62 | }
63 |
--------------------------------------------------------------------------------
/pages/_document.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Document, { Html, Head, Main, NextScript } from 'next/document'
3 | import { ServerStyleSheet } from 'styled-components'
4 |
5 | export default class MyDocument extends Document {
6 | static async getInitialProps(ctx) {
7 | const sheet = new ServerStyleSheet()
8 | const originalRenderPage = ctx.renderPage
9 |
10 | try {
11 | ctx.renderPage = () =>
12 | originalRenderPage({
13 | enhanceApp: (App) => (props) =>
14 | sheet.collectStyles(),
15 | })
16 |
17 | const initialProps = await Document.getInitialProps(ctx)
18 |
19 | return {
20 | ...initialProps,
21 | styles: (
22 | <>
23 | {initialProps.styles}
24 | {sheet.getStyleElement()}
25 | >
26 | ),
27 | }
28 | } finally {
29 | sheet.seal()
30 | }
31 | }
32 |
33 | render() {
34 | return (
35 |
36 |
37 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | )
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "temp",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start"
9 | },
10 | "dependencies": {
11 | "axios": "^0.20.0",
12 | "fs-extra": "^9.0.1",
13 | "history": "^5.0.0",
14 | "next": "9.5.4",
15 | "react": "16.13.1",
16 | "react-dom": "16.13.1",
17 | "react-icons": "^3.11.0",
18 | "react-query": "^2.23.1",
19 | "react-query-devtools": "^2.5.1",
20 | "react-router-dom": "^6.0.0-beta.0",
21 | "shortid": "^2.2.15",
22 | "styled-components": "^5.2.0",
23 | "styled-normalize": "^8.0.7"
24 | },
25 | "devDependencies": {
26 | "babel-core": "^6.26.3",
27 | "babel-eslint": "10.1.0",
28 | "eslint": "6.6.0",
29 | "eslint-config-prettier": "^6.7.0",
30 | "eslint-config-react-app": "^5.0.2",
31 | "eslint-config-standard": "^14.1.0",
32 | "eslint-config-standard-react": "^9.2.0",
33 | "eslint-plugin-flowtype": "4.4.1",
34 | "eslint-plugin-import": "2.18.2",
35 | "eslint-plugin-jsx-a11y": "6.2.3",
36 | "eslint-plugin-node": "^10.0.0",
37 | "eslint-plugin-prettier": "^3.1.1",
38 | "eslint-plugin-promise": "^4.2.1",
39 | "eslint-plugin-react": "7.16.0",
40 | "eslint-plugin-react-hooks": "2.3.0",
41 | "eslint-plugin-standard": "^4.0.1"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { BrowserRouter, Routes, Route } from 'react-router-dom'
3 | //
4 |
5 | import { Wrapper, Main } from './components/styled'
6 | import Sidebar from './components/Sidebar'
7 |
8 | import Admin from './screens/admin'
9 | import AdminPost from './screens/admin/Post'
10 | import Blog from './screens/blog'
11 | import BlogPost from './screens/blog/Post'
12 |
13 | function SafeHydrate({ children }) {
14 | return (
15 |
16 | {typeof document === 'undefined' ? null : children}
17 |
18 | )
19 | }
20 |
21 | export default function App() {
22 | return (
23 |
24 |
25 |
26 |
27 |
28 |
29 |
33 | Welcome!
34 | >
35 | }
36 | />
37 | } />
38 | } />
39 | } />
40 | } />
41 |
42 |
43 |
44 |
45 |
46 | )
47 | }
48 |
--------------------------------------------------------------------------------
/src/components/PostForm.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const defaultFormValues = {
4 | title: '',
5 | body: '',
6 | }
7 |
8 | export default function PostForm({
9 | onSubmit,
10 | initialValues = defaultFormValues,
11 | submitText,
12 | clearOnSubmit,
13 | }) {
14 | const [values, setValues] = React.useState(initialValues)
15 |
16 | const setValue = (field, value) =>
17 | setValues((old) => ({ ...old, [field]: value }))
18 |
19 | const handleSubmit = (e) => {
20 | if (clearOnSubmit) {
21 | setValues(defaultFormValues)
22 | }
23 | e.preventDefault()
24 | onSubmit(values)
25 | }
26 |
27 | React.useEffect(() => {
28 | setValues(initialValues)
29 | }, [initialValues])
30 |
31 | return (
32 |
58 | )
59 | }
60 |
--------------------------------------------------------------------------------
/pages/api/posts/index.js:
--------------------------------------------------------------------------------
1 | import shortid from 'shortid'
2 | import db from '../../../db'
3 | import { sleep } from '../../../utils'
4 |
5 | //
6 | const failureRate = 0
7 |
8 | export default async (req, res) => {
9 | await sleep(1000)
10 |
11 | try {
12 | if (req.method === 'GET') {
13 | return await GET(req, res)
14 | } else if (req.method === 'POST') {
15 | return await POST(req, res)
16 | }
17 | } catch (err) {
18 | console.error(err)
19 | res.status(500)
20 | res.json({ message: 'An unknown error occurred!' })
21 | }
22 | }
23 |
24 | async function GET(req, res) {
25 | const {
26 | query: { pageOffset, pageSize },
27 | } = req
28 |
29 | const posts = (await db.get()).posts.map((d) => ({
30 | ...d,
31 | body: d.body.substring(0, 50) + (d.body.length > 100 ? '...' : ''), // Don't return full body in list calls
32 | }))
33 |
34 | if (Number(pageSize)) {
35 | const start = Number(pageSize) * Number(pageOffset)
36 | const end = start + Number(pageSize)
37 | const page = posts.slice(start, end)
38 |
39 | return res.json({
40 | items: page,
41 | nextPageOffset: posts.length > end ? Number(pageOffset) + 1 : undefined,
42 | })
43 | }
44 |
45 | res.json(posts)
46 | }
47 |
48 | async function POST(req, res) {
49 | if (Math.random() < failureRate) {
50 | res.status(500)
51 | res.json({ message: 'An unknown error occurred!' })
52 | return
53 | }
54 |
55 | const row = {
56 | id: shortid.generate(),
57 | ...req.body,
58 | }
59 |
60 | await db.set((old) => {
61 | return {
62 | ...old,
63 | posts: [...old.posts, row],
64 | }
65 | })
66 |
67 | res.json(row)
68 | }
69 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | const Module = require('module')
2 | const path = require('path')
3 | const resolveFrom = require('resolve-from')
4 |
5 | const node_modules = path.resolve(__dirname, 'node_modules')
6 |
7 | const originalRequire = Module.prototype.require
8 |
9 | // The following ensures that there is always only a single (and same)
10 | // copy of React in an app at any given moment.
11 | Module.prototype.require = function (modulePath) {
12 | // Only redirect resolutions to non-relative and non-absolute modules
13 | if (
14 | ['/react/', '/react-dom/', '/react-query/'].some((d) => {
15 | try {
16 | return require.resolve(modulePath).includes(d)
17 | } catch (err) {
18 | return false
19 | }
20 | })
21 | ) {
22 | try {
23 | modulePath = resolveFrom(node_modules, modulePath)
24 | } catch (err) {
25 | //
26 | }
27 | }
28 |
29 | return originalRequire.call(this, modulePath)
30 | }
31 |
32 | module.exports = {
33 | target: 'serverless',
34 | async rewrites() {
35 | return [
36 | {
37 | source: '/api/:any*',
38 | destination: '/api/:any*',
39 | },
40 | {
41 | source: '/:any*',
42 | destination: '/',
43 | },
44 | ]
45 | },
46 | webpack: (config) => {
47 | config.resolve = {
48 | ...config.resolve,
49 | alias: {
50 | ...config.resolve.alias,
51 | react$: resolveFrom(path.resolve('node_modules'), 'react'),
52 | 'react-query$': resolveFrom(
53 | path.resolve('node_modules'),
54 | 'react-query'
55 | ),
56 | 'react-dom$': resolveFrom(path.resolve('node_modules'), 'react-dom'),
57 | },
58 | }
59 | return config
60 | },
61 | }
62 |
--------------------------------------------------------------------------------
/src/screens/admin/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router-dom'
3 |
4 | import PostForm from '../../components/PostForm'
5 | import { Loader } from '../../components/styled'
6 |
7 | import usePosts from '../../hooks/usePosts'
8 | import useCreatePost from '../../hooks/useCreatePost'
9 |
10 | export default function Posts() {
11 | const postsQuery = usePosts()
12 | const [createPost, createPostInfo] = useCreatePost()
13 |
14 | const onSubmit = async (values) => {
15 | await createPost(values)
16 | postsQuery.fetch()
17 | }
18 |
19 | return (
20 |
21 |
22 |
23 | {postsQuery.isLoading ? (
24 |
25 | Loading
26 |
27 | ) : (
28 | <>
29 |
Posts
30 |
31 | {postsQuery.data.map((post) => (
32 | -
33 | {post.title}
34 |
35 | ))}
36 |
37 |
38 | >
39 | )}
40 |
41 |
42 |
43 |
44 |
Create New Post
45 |
60 |
61 |
62 | )
63 | }
64 |
--------------------------------------------------------------------------------
/src/screens/admin/Post.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link, useParams, useNavigate } from 'react-router-dom'
3 |
4 | //
5 |
6 | import usePost from '../../hooks/usePost'
7 | import useSavePost from '../../hooks/useSavePost'
8 | import useDeletePost from '../../hooks/useDeletePost'
9 |
10 | import PostForm from '../../components/PostForm'
11 | import { Loader } from '../../components/styled'
12 |
13 | export default function Post() {
14 | const { postId } = useParams()
15 | const navigate = useNavigate()
16 |
17 | const postQuery = usePost(postId)
18 | const [savePost, savePostInfo] = useSavePost()
19 | const [deletePost, deletePostInfo] = useDeletePost()
20 |
21 | const onSubmit = async (values) => {
22 | await savePost(values)
23 | postQuery.fetch()
24 | }
25 |
26 | const onDelete = async () => {
27 | await deletePost(postId)
28 | navigate('/admin')
29 | }
30 |
31 | return (
32 | <>
33 | {postQuery.isLoading ? (
34 |
35 | Loading...
36 |
37 | ) : (
38 |
39 |
{postQuery.data.title}
40 |
41 | View Post
42 |
43 |
56 |
57 |
58 |
67 |
68 |
69 | )}
70 | >
71 | )
72 | }
73 |
--------------------------------------------------------------------------------
/pages/api/posts/[postId].js:
--------------------------------------------------------------------------------
1 | import db from '../../../db'
2 | import { sleep } from '../../../utils'
3 |
4 | const deleteFailureRate = 0
5 |
6 | export default async (req, res) => {
7 | await sleep(1000)
8 |
9 | try {
10 | if (req.method === 'GET') {
11 | return await GET(req, res)
12 | } else if (req.method === 'PATCH') {
13 | return await PATCH(req, res)
14 | } else if (req.method === 'DELETE') {
15 | return await DELETE(req, res)
16 | }
17 | } catch (err) {
18 | console.error(err)
19 | res.status(500)
20 | res.json({ message: 'An unknown error occurred!' })
21 | }
22 | }
23 |
24 | async function GET(req, res) {
25 | const {
26 | query: { postId },
27 | } = req
28 |
29 | const row = (await db.get()).posts.find((d) => d.id == postId)
30 |
31 | if (!row) {
32 | res.status(404)
33 | return res.send('Not found')
34 | }
35 |
36 | res.json(row)
37 | }
38 |
39 | async function PATCH(req, res) {
40 | const {
41 | query: { postId },
42 | body,
43 | } = req
44 |
45 | if (body.body.includes('fail')) {
46 | res.status(500)
47 | res.json({ message: 'An unknown error occurred!' })
48 | return
49 | }
50 |
51 | const row = (await db.get()).posts.find((d) => d.id == postId)
52 |
53 | if (!row) {
54 | res.status(404)
55 | return res.send('Not found')
56 | }
57 |
58 | delete body.id
59 |
60 | const newRow = {
61 | ...row,
62 | ...body,
63 | }
64 |
65 | await db.set((old) => {
66 | return {
67 | ...old,
68 | posts: old.posts.map((d) => (d.id == postId ? newRow : d)),
69 | }
70 | })
71 |
72 | res.json(newRow)
73 | }
74 |
75 | async function DELETE(req, res) {
76 | const {
77 | query: { postId },
78 | } = req
79 |
80 | if (Math.random() < deleteFailureRate) {
81 | res.status(500)
82 | res.json({ message: 'An unknown error occurred!' })
83 | return
84 | }
85 |
86 | const row = (await db.get()).posts.find((d) => d.id == postId)
87 |
88 | if (!row) {
89 | res.status(404)
90 | return res.send('Not found')
91 | }
92 |
93 | await db.set((old) => {
94 | return {
95 | ...old,
96 | posts: old.posts.filter((d) => d.id != postId),
97 | }
98 | })
99 |
100 | res.status(200)
101 | res.send('Resource Deleted')
102 | }
103 |
--------------------------------------------------------------------------------