├── README.md ├── .eslintrc.json ├── next.config.js ├── public ├── favicon.ico └── vercel.svg ├── postcss.config.js ├── reducers ├── index.js └── auth.js ├── tailwind.config.js ├── pages ├── _app.js ├── index.js ├── api │ └── account │ │ ├── logout.js │ │ ├── register.js │ │ ├── user.js │ │ ├── verify.js │ │ ├── refresh.js │ │ └── login.js ├── login.js └── register.js ├── .gitignore ├── styles └── globals.css ├── package.json ├── components ├── Layout.js └── Navigation.js ├── actions ├── types.js └── auth.js └── store.js /README.md: -------------------------------------------------------------------------------- 1 | # nextjs-account-auth-finish 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | reactStrictMode: true, 3 | } 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eien0618/nextjs-account-auth-finish/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import authReducer from './auth' 3 | 4 | export default combineReducers({ 5 | auth: authReducer, 6 | }) 7 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mode: 'jit', 3 | purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'], 4 | darkMode: false, // or 'media' or 'class' 5 | theme: { 6 | extend: {}, 7 | }, 8 | variants: { 9 | extend: {}, 10 | }, 11 | plugins: [], 12 | } 13 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import { Provider } from 'react-redux' 2 | import { useStore } from '../store' 3 | import Layout from '../components/Layout' 4 | import '../styles/globals.css' 5 | 6 | function MyApp({ Component, pageProps }) { 7 | const store = useStore(pageProps.initialReduxState) 8 | 9 | return ( 10 | 11 | 12 | 13 | 14 | 15 | ) 16 | } 17 | 18 | export default MyApp 19 | -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react' 2 | import { useSelector } from 'react-redux' 3 | import Head from 'next/head' 4 | import Link from 'next/link' 5 | 6 | const Index = () => { 7 | const isAuthenticated = useSelector((state) => state.auth.isAuthenticated) 8 | const user = useSelector((state) => state.auth.user) 9 | 10 | return ( 11 | <> 12 | 13 | Instagram Clone 14 | 15 | 16 | ) 17 | } 18 | 19 | export default Index 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 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | 36 | .env 37 | -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | .input-form { 6 | @apply w-full bg-gray-100 bg-opacity-50 rounded border border-gray-300 focus:border-yellow-500 focus:bg-transparent focus:ring-2 focus:ring-yellow-300 text-base outline-none text-gray-700 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out; 7 | } 8 | 9 | .button-yellow { 10 | @apply text-center inline-block text-white bg-yellow-500 py-2 px-4 focus:outline-none hover:bg-yellow-600 rounded; 11 | } 12 | 13 | .button-nav { 14 | @apply inline-block bg-white py-2 px-4 rounded font-medium text-gray-500 hover:bg-gray-50; 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs-instagram-clone", 3 | "private": true, 4 | "scripts": { 5 | "dev": "next dev", 6 | "build": "next build", 7 | "start": "next start", 8 | "lint": "next lint" 9 | }, 10 | "dependencies": { 11 | "cookie": "^0.4.1", 12 | "next": "^11.1.2", 13 | "react": "17.0.2", 14 | "react-dom": "17.0.2", 15 | "react-loader-spinner": "^4.0.0", 16 | "react-redux": "^7.2.6", 17 | "redux": "^4.1.2", 18 | "redux-devtools-extension": "^2.13.9", 19 | "redux-thunk": "^2.4.0" 20 | }, 21 | "devDependencies": { 22 | "autoprefixer": "^10.4.0", 23 | "eslint": "7.32.0", 24 | "eslint-config-next": "12.0.3", 25 | "postcss": "^8.3.11", 26 | "tailwindcss": "^2.2.19" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /components/Layout.js: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import { useDispatch } from 'react-redux' 3 | import { refresh } from '../actions/auth' 4 | import Head from 'next/head' 5 | import Navigation from './Navigation' 6 | 7 | const Layout = (props) => { 8 | const dispatch = useDispatch() 9 | 10 | useEffect(() => { 11 | const fn = async () => { 12 | if (dispatch && dispatch !== null && dispatch !== undefined) { 13 | await dispatch(refresh()) 14 | } 15 | } 16 | fn() 17 | }, [dispatch]) 18 | 19 | return ( 20 | <> 21 | 22 | 23 | 24 | 25 |
{props.children}
26 | 27 | ) 28 | } 29 | 30 | export default Layout 31 | -------------------------------------------------------------------------------- /pages/api/account/logout.js: -------------------------------------------------------------------------------- 1 | import cookie from 'cookie' 2 | 3 | export default async (req, res) => { 4 | if (req.method === 'POST') { 5 | res.setHeader('Set-Cookie', [ 6 | cookie.serialize('access', '', { 7 | httpOnly: false, 8 | secure: true, 9 | sameSite: 'Lax', 10 | path: '/', 11 | expires: new Date(0), 12 | }), 13 | cookie.serialize('refresh', '', { 14 | httpOnly: false, 15 | secure: true, 16 | sameSite: 'Lax', 17 | path: '/', 18 | expires: new Date(0), 19 | }), 20 | ]) 21 | 22 | return res.status(200).json({ 23 | success: 'ログアウトに成功しました', 24 | }) 25 | } else { 26 | res.setHeader('Allow', ['POST']) 27 | return res.status(405).json({ 28 | error: `Method ${req.method} now allowed`, 29 | }) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /actions/types.js: -------------------------------------------------------------------------------- 1 | // ユーザー登録 2 | export const REGISTER_SUCCESS = 'REGISTER_SUCCESS' 3 | export const REGISTER_FAIL = 'REGISTER_FAIL' 4 | 5 | // ログイン 6 | export const LOGIN_SUCCESS = 'LOGIN_SUCCESS' 7 | export const LOGIN_FAIL = 'LOGIN_FAIL' 8 | 9 | // ユーザー情報取得 10 | export const USER_SUCCESS = 'USER_SUCCESS' 11 | export const USER_FAIL = 'USER_FAIL' 12 | 13 | // リフレッシュトークン 14 | export const REFRESH_SUCCESS = 'REFRESH_SUCCESS' 15 | export const REFRESH_FAIL = 'REFRESH_FAIL' 16 | 17 | // 認証チェック 18 | export const AUTHENTICATED_SUCCESS = 'AUTHENTICATED_SUCCESS' 19 | export const AUTHENTICATED_FAIL = 'AUTHENTICATED_FAIL' 20 | 21 | // ログアウト 22 | export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS' 23 | export const LOGOUT_FAIL = 'LOGOUT_FAIL' 24 | 25 | // 読み込み中 26 | export const SET_AUTH_LOADING = 'SET_AUTH_LOADING' 27 | export const REMOVE_AUTH_LOADING = 'REMOVE_AUTH_LOADING' 28 | -------------------------------------------------------------------------------- /store.js: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react' 2 | import { createStore, applyMiddleware } from 'redux' 3 | import { composeWithDevTools } from 'redux-devtools-extension' 4 | import thunkMiddleware from 'redux-thunk' 5 | import reducers from './reducers' 6 | 7 | let store 8 | 9 | function initStore(initialState) { 10 | return createStore(reducers, initialState, composeWithDevTools(applyMiddleware(thunkMiddleware))) 11 | } 12 | 13 | export const initializeStore = (preloadedState) => { 14 | let _store = store ?? initStore(preloadedState) 15 | 16 | if (preloadedState && store) { 17 | _store = initStore({ 18 | ...store.getState(), 19 | ...preloadedState, 20 | }) 21 | store = undefined 22 | } 23 | 24 | if (typeof window === 'undefined') return _store 25 | if (!store) store = _store 26 | 27 | return _store 28 | } 29 | 30 | export function useStore(initialState) { 31 | const store = useMemo(() => initializeStore(initialState), [initialState]) 32 | return store 33 | } 34 | -------------------------------------------------------------------------------- /pages/api/account/register.js: -------------------------------------------------------------------------------- 1 | export default async (req, res) => { 2 | if (req.method === 'POST') { 3 | const { name, email, password } = req.body 4 | 5 | const body = JSON.stringify({ 6 | name, 7 | email, 8 | password, 9 | }) 10 | 11 | try { 12 | const apiRes = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/auth/register/`, { 13 | method: 'POST', 14 | headers: { 15 | 'Content-Type': 'application/json', 16 | }, 17 | body: body, 18 | }) 19 | 20 | if (apiRes.status === 201) { 21 | return res.status(200).json({ 22 | success: 'アカウント登録に成功しました', 23 | }) 24 | } else { 25 | return res.status(apiRes.status).json({ 26 | error: 'アカウント登録に失敗しました', 27 | }) 28 | } 29 | } catch (err) { 30 | return res.status(500).json({ 31 | error: 'アカウント登録に失敗しました', 32 | }) 33 | } 34 | } else { 35 | res.setHeader('Allow', ['POST']) 36 | return res.status(405).json({ error: `Method ${req.method} not allowed` }) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /pages/api/account/user.js: -------------------------------------------------------------------------------- 1 | import cookie from 'cookie' 2 | 3 | export default async (req, res) => { 4 | if (req.method === 'GET') { 5 | const cookies = cookie.parse(req.headers.cookie ?? '') 6 | const access = cookies.access ?? false 7 | 8 | if (access === false) { 9 | return res.status(401).json({ 10 | error: 'アクセストークンがありません', 11 | }) 12 | } 13 | 14 | try { 15 | const apiRes = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/auth/user/`, { 16 | method: 'GET', 17 | headers: { 18 | Authorization: `Bearer ${access}`, 19 | }, 20 | }) 21 | const data = await apiRes.json() 22 | 23 | if (apiRes.status === 200) { 24 | return res.status(200).json({ 25 | user: data.user, 26 | }) 27 | } else { 28 | return res.status(apiRes.status).json({ 29 | error: 'ユーザー情報取得に失敗しました', 30 | }) 31 | } 32 | } catch (err) { 33 | return res.status(500).json({ 34 | error: 'ユーザー情報取得に失敗しました', 35 | }) 36 | } 37 | } else { 38 | res.setHeader('Allow', ['GET']) 39 | return res.status(405).json({ 40 | error: `Method ${req.method} not allowed`, 41 | }) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /pages/api/account/verify.js: -------------------------------------------------------------------------------- 1 | import cookie from 'cookie' 2 | 3 | export default async (req, res) => { 4 | if (req.method === 'GET') { 5 | const cookies = cookie.parse(req.headers.cookie ?? '') 6 | const access = cookies.access ?? false 7 | 8 | if (access === false) { 9 | return res.status(403).json({ 10 | error: 'アクセストークンがありません', 11 | }) 12 | } 13 | 14 | const body = JSON.stringify({ 15 | token: access, 16 | }) 17 | 18 | try { 19 | const apiRes = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/verify/`, { 20 | method: 'POST', 21 | headers: { 22 | 'Content-Type': 'application/json', 23 | }, 24 | body: body, 25 | }) 26 | 27 | if (apiRes.status === 200) { 28 | return res.status(200).json({ 29 | success: '認証に成功しました', 30 | }) 31 | } else { 32 | return res.status(apiRes.status).json({ 33 | error: '認証に失敗しました', 34 | }) 35 | } 36 | } catch (err) { 37 | return res.status(500).json({ 38 | error: '認証に失敗しました', 39 | }) 40 | } 41 | } else { 42 | res.setHeader('Allow', ['GET']) 43 | return res.status(405).json({ error: `Method ${req.method} not allowed` }) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /pages/api/account/refresh.js: -------------------------------------------------------------------------------- 1 | import cookie from 'cookie' 2 | 3 | export default async (req, res) => { 4 | if (req.method === 'GET') { 5 | const cookies = cookie.parse(req.headers.cookie ?? '') 6 | const refresh = cookies.refresh ?? false 7 | 8 | if (refresh === false) { 9 | return res.status(401).json({ 10 | error: 'リフレッシュトークンがありません', 11 | }) 12 | } 13 | 14 | const body = JSON.stringify({ 15 | refresh, 16 | }) 17 | 18 | try { 19 | const apiRes = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/refresh/`, { 20 | method: 'POST', 21 | headers: { 22 | 'Content-Type': 'application/json', 23 | }, 24 | body: body, 25 | }) 26 | 27 | const data = await apiRes.json() 28 | 29 | if (apiRes.status === 200) { 30 | res.setHeader('Set-Cookie', [ 31 | cookie.serialize('access', data.access, { 32 | httpOnly: false, 33 | secure: true, 34 | sameSite: 'Lax', 35 | path: '/', 36 | maxAge: 60 * 60 * 24, // 1日 37 | }), 38 | ]) 39 | 40 | return res.status(200).json({ 41 | success: 'リフレッシュトークン取得に成功しました', 42 | }) 43 | } else { 44 | return res.status(apiRes.status).json({ 45 | error: 'リフレッシュトークン取得に失敗しました', 46 | }) 47 | } 48 | } catch (err) { 49 | return res.status(500).json({ 50 | error: 'リフレッシュトークン取得に失敗しました', 51 | }) 52 | } 53 | } else { 54 | res.setHeader('Allow', ['GET']) 55 | return res.status(405).json({ error: `Method ${req.method} not allowed` }) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /components/Navigation.js: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | import { useSelector, useDispatch } from 'react-redux' 3 | import { logout } from '../actions/auth' 4 | 5 | const Navigation = () => { 6 | const dispatch = useDispatch() 7 | const isAuthenticated = useSelector((state) => state.auth.isAuthenticated) 8 | 9 | const logoutHandler = async () => { 10 | if (dispatch && dispatch !== null && dispatch !== undefined) { 11 | await dispatch(logout()) 12 | } 13 | } 14 | 15 | return ( 16 | <> 17 |
18 |
19 |
20 |
21 | 22 | 23 | フルスタックチャンネル 24 | 25 | 26 |
27 |
28 | {isAuthenticated ? ( 29 |
30 | ログアウト 31 |
32 | ) : ( 33 |
34 | 35 | ログイン 36 | 37 | 38 | アカウント登録 39 | 40 |
41 | )} 42 |
43 |
44 |
45 |
46 | 47 | ) 48 | } 49 | 50 | export default Navigation 51 | -------------------------------------------------------------------------------- /pages/api/account/login.js: -------------------------------------------------------------------------------- 1 | import cookie from 'cookie' 2 | 3 | export default async (req, res) => { 4 | if (req.method === 'POST') { 5 | const { email, password } = req.body 6 | 7 | const body = JSON.stringify({ 8 | email, 9 | password, 10 | }) 11 | 12 | try { 13 | const apiRes = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/login/`, { 14 | method: 'POST', 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | }, 18 | body: body, 19 | }) 20 | 21 | const data = await apiRes.json() 22 | 23 | if (apiRes.status === 200) { 24 | res.setHeader('Set-Cookie', [ 25 | cookie.serialize('access', data.access, { 26 | httpOnly: false, 27 | secure: true, 28 | sameSite: 'Lax', 29 | path: '/', 30 | maxAge: 60 * 60, // 1時間 31 | }), 32 | cookie.serialize('refresh', data.refresh, { 33 | httpOnly: false, 34 | secure: true, 35 | sameSite: 'Lax', 36 | path: '/', 37 | maxAge: 60 * 60 * 24 * 3, // 3日 38 | }), 39 | ]) 40 | 41 | return res.status(200).json({ 42 | success: 'ログインに成功しました', 43 | }) 44 | } else { 45 | return res.status(apiRes.status).json({ 46 | error: 'ログインに失敗しました', 47 | }) 48 | } 49 | } catch (err) { 50 | return res.status(500).json({ 51 | error: 'ログインに失敗しました', 52 | }) 53 | } 54 | } else { 55 | res.setHeader('Allow', ['POST']) 56 | return res.status(405).json({ error: `Method ${req.method} now allowed` }) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /pages/login.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import { useSelector, useDispatch } from 'react-redux' 3 | import { useRouter } from 'next/router' 4 | import { login } from '../actions/auth' 5 | import Loader from 'react-loader-spinner' 6 | import Head from 'next/head' 7 | 8 | const Login = () => { 9 | const dispatch = useDispatch() 10 | const router = useRouter() 11 | const isAuthenticated = useSelector((state) => state.auth.isAuthenticated) 12 | const loading = useSelector((state) => state.auth.loading) 13 | 14 | const [formData, setFormData] = useState({ 15 | email: '', 16 | password: '', 17 | }) 18 | 19 | const { email, password } = formData 20 | 21 | const onChange = (e) => { 22 | setFormData({ ...formData, [e.target.name]: e.target.value }) 23 | } 24 | 25 | const onSubmit = async (e) => { 26 | e.preventDefault() 27 | 28 | if (dispatch && dispatch !== null && dispatch !== undefined) { 29 | await dispatch(login(email, password)) 30 | } 31 | } 32 | 33 | if (typeof window !== 'undefined' && isAuthenticated) { 34 | router.push('/') 35 | } 36 | 37 | return ( 38 | <> 39 | 40 | Instagram Clone | ログイン 41 | 42 | 43 |
ログイン
44 |
45 |
46 |
47 | メールアドレス 48 |
49 | 58 |
59 |
60 |
61 | パスワード 62 |
63 | 73 |
74 | 75 |
76 | {loading ? ( 77 | 78 | ) : ( 79 | 82 | )} 83 |
84 |
85 | 86 | ) 87 | } 88 | 89 | export default Login 90 | -------------------------------------------------------------------------------- /reducers/auth.js: -------------------------------------------------------------------------------- 1 | import { 2 | // ユーザー登録 3 | REGISTER_SUCCESS, 4 | REGISTER_FAIL, 5 | 6 | // ログイン 7 | LOGIN_SUCCESS, 8 | LOGIN_FAIL, 9 | 10 | // ユーザー情報取得 11 | USER_SUCCESS, 12 | USER_FAIL, 13 | 14 | // リフレッシュトークン 15 | REFRESH_SUCCESS, 16 | REFRESH_FAIL, 17 | 18 | // 認証チェック 19 | AUTHENTICATED_SUCCESS, 20 | AUTHENTICATED_FAIL, 21 | 22 | // ログアウト 23 | LOGOUT_SUCCESS, 24 | LOGOUT_FAIL, 25 | 26 | // 読み込み中 27 | SET_AUTH_LOADING, 28 | REMOVE_AUTH_LOADING, 29 | } from '../actions/types' 30 | 31 | const initialState = { 32 | user: null, 33 | isAuthenticated: null, 34 | loading: false, 35 | } 36 | 37 | const authReducer = (state = initialState, action) => { 38 | const { type, payload } = action 39 | 40 | switch (type) { 41 | // ユーザー登録 42 | case REGISTER_SUCCESS: 43 | return { 44 | ...state, 45 | } 46 | case REGISTER_FAIL: 47 | return { 48 | ...state, 49 | } 50 | 51 | // ログイン 52 | case LOGIN_SUCCESS: 53 | return { 54 | ...state, 55 | isAuthenticated: true, 56 | } 57 | case LOGIN_FAIL: 58 | return { 59 | ...state, 60 | isAuthenticated: false, 61 | } 62 | 63 | // ユーザー情報取得 64 | case USER_SUCCESS: 65 | return { 66 | ...state, 67 | user: payload.user, 68 | } 69 | case USER_FAIL: 70 | return { 71 | ...state, 72 | user: null, 73 | } 74 | 75 | // リフレッシュトークン 76 | case REFRESH_SUCCESS: 77 | return { 78 | ...state, 79 | } 80 | case REFRESH_FAIL: 81 | return { 82 | ...state, 83 | isAuthenticated: false, 84 | user: null, 85 | } 86 | 87 | // 認証チェック 88 | case AUTHENTICATED_SUCCESS: 89 | return { 90 | ...state, 91 | isAuthenticated: true, 92 | } 93 | case AUTHENTICATED_FAIL: 94 | return { 95 | ...state, 96 | isAuthenticated: false, 97 | user: null, 98 | } 99 | 100 | // ログアウト 101 | case LOGOUT_SUCCESS: 102 | return { 103 | ...state, 104 | isAuthenticated: false, 105 | user: null, 106 | } 107 | case LOGOUT_FAIL: 108 | return { 109 | ...state, 110 | } 111 | 112 | // 読み込み中 113 | case SET_AUTH_LOADING: 114 | return { 115 | ...state, 116 | loading: true, 117 | } 118 | case REMOVE_AUTH_LOADING: 119 | return { 120 | ...state, 121 | loading: false, 122 | } 123 | default: 124 | return state 125 | } 126 | } 127 | 128 | export default authReducer 129 | -------------------------------------------------------------------------------- /pages/register.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import { useSelector, useDispatch } from 'react-redux' 3 | import { useRouter } from 'next/router' 4 | import { register } from '../actions/auth' 5 | import Loader from 'react-loader-spinner' 6 | import Head from 'next/head' 7 | 8 | const Register = () => { 9 | const dispatch = useDispatch() 10 | const router = useRouter() 11 | const isAuthenticated = useSelector((state) => state.auth.isAuthenticated) 12 | const loading = useSelector((state) => state.auth.loading) 13 | 14 | const [formData, setFormData] = useState({ 15 | name: '', 16 | email: '', 17 | password: '', 18 | }) 19 | 20 | const { name, email, password } = formData 21 | 22 | const onChange = (e) => { 23 | setFormData({ ...formData, [e.target.name]: e.target.value }) 24 | } 25 | 26 | const onSubmit = async (e) => { 27 | e.preventDefault() 28 | 29 | if (dispatch && dispatch !== null && dispatch !== undefined) { 30 | await dispatch(register(name, email, password)) 31 | } 32 | } 33 | 34 | if (typeof window !== 'undefined' && isAuthenticated) { 35 | router.push('/') 36 | } 37 | 38 | return ( 39 | <> 40 | 41 | Instagram Clone | アカウント登録 42 | 43 | 44 |
アカウント登録
45 |
46 |
47 |
48 | 名前 49 |
50 | 59 |
60 |
61 |
62 | メールアドレス 63 |
64 | 73 |
74 |
75 |
76 | パスワード 77 |
78 | 88 |
89 | 90 |
91 | {loading ? ( 92 | 93 | ) : ( 94 | 97 | )} 98 |
99 |
100 | 101 | ) 102 | } 103 | 104 | export default Register 105 | -------------------------------------------------------------------------------- /actions/auth.js: -------------------------------------------------------------------------------- 1 | import { 2 | // ユーザー登録 3 | REGISTER_SUCCESS, 4 | REGISTER_FAIL, 5 | 6 | // ログイン 7 | LOGIN_SUCCESS, 8 | LOGIN_FAIL, 9 | 10 | // ユーザー情報取得 11 | USER_SUCCESS, 12 | USER_FAIL, 13 | 14 | // リフレッシュトークン 15 | REFRESH_SUCCESS, 16 | REFRESH_FAIL, 17 | 18 | // 認証チェック 19 | AUTHENTICATED_SUCCESS, 20 | AUTHENTICATED_FAIL, 21 | 22 | // ログアウト 23 | LOGOUT_SUCCESS, 24 | LOGOUT_FAIL, 25 | 26 | // 読み込み中 27 | SET_AUTH_LOADING, 28 | REMOVE_AUTH_LOADING, 29 | } from './types' 30 | 31 | // ユーザー登録 32 | export const register = (name, email, password) => async (dispatch) => { 33 | dispatch({ 34 | type: SET_AUTH_LOADING, 35 | }) 36 | 37 | const body = JSON.stringify({ 38 | name, 39 | email, 40 | password, 41 | }) 42 | 43 | try { 44 | const res = await fetch('/api/account/register', { 45 | method: 'POST', 46 | headers: { 47 | 'Content-Type': 'application/json', 48 | }, 49 | body: body, 50 | }) 51 | 52 | if (res.status === 200) { 53 | dispatch({ 54 | type: REGISTER_SUCCESS, 55 | }) 56 | } else { 57 | dispatch({ 58 | type: REGISTER_FAIL, 59 | }) 60 | } 61 | } catch (err) { 62 | dispatch({ 63 | type: REGISTER_FAIL, 64 | }) 65 | } 66 | 67 | dispatch({ 68 | type: REMOVE_AUTH_LOADING, 69 | }) 70 | } 71 | 72 | // ログイン 73 | export const login = (email, password) => async (dispatch) => { 74 | dispatch({ 75 | type: SET_AUTH_LOADING, 76 | }) 77 | 78 | const body = JSON.stringify({ 79 | email, 80 | password, 81 | }) 82 | 83 | try { 84 | const res = await fetch('/api/account/login', { 85 | method: 'POST', 86 | headers: { 87 | 'Content-Type': 'application/json', 88 | }, 89 | body: body, 90 | }) 91 | 92 | if (res.status === 200) { 93 | dispatch({ 94 | type: LOGIN_SUCCESS, 95 | }) 96 | dispatch(user()) 97 | } else { 98 | dispatch({ 99 | type: LOGIN_FAIL, 100 | }) 101 | } 102 | } catch (err) { 103 | dispatch({ 104 | type: LOGIN_FAIL, 105 | }) 106 | } 107 | 108 | dispatch({ 109 | type: REMOVE_AUTH_LOADING, 110 | }) 111 | } 112 | 113 | // ユーザー情報取得 114 | export const user = () => async (dispatch) => { 115 | dispatch({ 116 | type: SET_AUTH_LOADING, 117 | }) 118 | 119 | try { 120 | const res = await fetch('/api/account/user', { 121 | method: 'GET', 122 | }) 123 | 124 | const data = await res.json() 125 | 126 | if (res.status === 200) { 127 | dispatch({ 128 | type: USER_SUCCESS, 129 | payload: data, 130 | }) 131 | } else { 132 | dispatch({ 133 | type: USER_FAIL, 134 | }) 135 | } 136 | } catch (err) { 137 | dispatch({ 138 | type: USER_FAIL, 139 | }) 140 | } 141 | 142 | dispatch({ 143 | type: REMOVE_AUTH_LOADING, 144 | }) 145 | } 146 | 147 | // リフレッシュトークン 148 | export const refresh = () => async (dispatch) => { 149 | dispatch({ 150 | type: SET_AUTH_LOADING, 151 | }) 152 | 153 | try { 154 | const res = await fetch('/api/account/refresh', { 155 | method: 'GET', 156 | }) 157 | 158 | if (res.status === 200) { 159 | dispatch({ 160 | type: REFRESH_SUCCESS, 161 | }) 162 | dispatch(verify()) 163 | } else { 164 | dispatch({ 165 | type: REFRESH_FAIL, 166 | }) 167 | } 168 | } catch (err) { 169 | dispatch({ 170 | type: REFRESH_FAIL, 171 | }) 172 | } 173 | 174 | dispatch({ 175 | type: REMOVE_AUTH_LOADING, 176 | }) 177 | } 178 | 179 | // 認証チェック 180 | export const verify = () => async (dispatch) => { 181 | dispatch({ 182 | type: SET_AUTH_LOADING, 183 | }) 184 | 185 | try { 186 | const res = await fetch('/api/account/verify', { 187 | method: 'GET', 188 | }) 189 | 190 | if (res.status === 200) { 191 | dispatch({ 192 | type: AUTHENTICATED_SUCCESS, 193 | }) 194 | dispatch(user()) 195 | } else { 196 | dispatch({ 197 | type: AUTHENTICATED_FAIL, 198 | }) 199 | } 200 | } catch (err) { 201 | dispatch({ 202 | type: AUTHENTICATED_FAIL, 203 | }) 204 | } 205 | 206 | dispatch({ 207 | type: REMOVE_AUTH_LOADING, 208 | }) 209 | } 210 | 211 | // ログアウト 212 | export const logout = () => async (dispatch) => { 213 | dispatch({ 214 | type: SET_AUTH_LOADING, 215 | }) 216 | 217 | try { 218 | const res = await fetch('/api/account/logout', { 219 | method: 'POST', 220 | }) 221 | 222 | if (res.status === 200) { 223 | dispatch({ 224 | type: LOGOUT_SUCCESS, 225 | }) 226 | } else { 227 | dispatch({ 228 | type: LOGOUT_FAIL, 229 | }) 230 | } 231 | } catch (err) { 232 | dispatch({ 233 | type: LOGOUT_FAIL, 234 | }) 235 | } 236 | 237 | dispatch({ 238 | type: REMOVE_AUTH_LOADING, 239 | }) 240 | } 241 | --------------------------------------------------------------------------------