diff --git a/src/components/AuthContext.tsx b/src/components/AuthContext.tsx deleted file mode 100644 index c42d2f25..00000000 --- a/src/components/AuthContext.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { createContext } from "react"; - -const AuthContext = createContext({}); - -export default AuthContext; diff --git a/src/components/AuthProvider.tsx b/src/components/AuthProvider.tsx new file mode 100644 index 00000000..d4b794ba --- /dev/null +++ b/src/components/AuthProvider.tsx @@ -0,0 +1,40 @@ +import { User } from "firebase/auth"; +import { createContext, useEffect, useState } from "react"; + +import { auth } from "@/utils/firebase"; + +type AuthContextProps = { + currentUser: User | null | undefined; + signInCheck: boolean; +}; + +const AuthContext = createContext({ + currentUser: undefined, + signInCheck: false, +}); + +const AuthProvider = ({ children }: { children: JSX.Element }) => { + const [currentUser, setCurrentUser] = useState( + undefined + ); + const [signInCheck, setSignInCheck] = useState(false); + + useEffect(() => { + auth.onAuthStateChanged(async (user) => { + if (user) { + setCurrentUser(user); + setSignInCheck(true); + } else { + setSignInCheck(true); + } + }); + }, []); + + return ( + + {children} + + ); +}; + +export { AuthContext, AuthProvider }; diff --git a/src/components/login/LoginButton.tsx b/src/components/login/LoginButton.tsx index 6174cb6a..adb13bb9 100644 --- a/src/components/login/LoginButton.tsx +++ b/src/components/login/LoginButton.tsx @@ -1,10 +1,12 @@ import { Button } from "@chakra-ui/react"; -import React from "react"; +import React, { useContext } from "react"; import { DiGithubBadge } from "react-icons/di"; +import { AuthContext } from "@/components/AuthProvider"; import { useAuth } from "@/hooks/useAuth"; const LoginButton = () => { + const { signInCheck } = useContext(AuthContext); const { login } = useAuth(); return ( @@ -12,6 +14,7 @@ const LoginButton = () => { leftIcon={} bgColor="white" color="black" + isLoading={!signInCheck} onClick={login} > ログイン diff --git a/src/hooks/useApi.ts b/src/hooks/useApi.ts index 656528ca..0aee6b63 100644 --- a/src/hooks/useApi.ts +++ b/src/hooks/useApi.ts @@ -1,12 +1,12 @@ import { Octokit } from "@octokit/rest"; -import { useCurrentUser } from "@/hooks/useCurrentUser"; +import { currentUserSelectors } from "@/store/currentUserState"; export const useApi = () => { - const { accessToken } = useCurrentUser(); + const currentUser = currentUserSelectors.useCurrentUser(); const octokit = new Octokit({ - auth: accessToken, + auth: currentUser.accessToken, }); return { octokit }; diff --git a/src/hooks/useAuth.ts b/src/hooks/useAuth.ts index dbca3a99..f01103d2 100644 --- a/src/hooks/useAuth.ts +++ b/src/hooks/useAuth.ts @@ -1,14 +1,12 @@ import { GithubAuthProvider, signInWithRedirect, signOut } from "firebase/auth"; import { useRouter } from "next/router"; -import { useSetRecoilState } from "recoil"; -import { currentUserState } from "@/store/currentUserState"; -import { CurrentUserType } from "@/types/CurrentUserType"; +import { currentUserActions } from "@/store/currentUserState"; import { auth } from "@/utils/firebase"; export const useAuth = () => { const router = useRouter(); - const setCurrentUser = useSetRecoilState(currentUserState); + const updateCurrentUser = currentUserActions.useUpdateCurrentUser(); const provider = new GithubAuthProvider(); provider.addScope("repo"); @@ -18,7 +16,7 @@ export const useAuth = () => { const logout = () => { signOut(auth); - setCurrentUser({ + updateCurrentUser({ isSignedIn: false, username: "", accessToken: "", diff --git a/src/hooks/useCurrentUser.ts b/src/hooks/useCurrentUser.ts deleted file mode 100644 index fcfe3cc7..00000000 --- a/src/hooks/useCurrentUser.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { useRecoilValue } from "recoil"; - -import { currentUserState } from "@/store/currentUserState"; -import { CurrentUserType } from "@/types/CurrentUserType"; - -export const useCurrentUser = () => { - const currentUser = useRecoilValue(currentUserState); - - const isSignedIn = currentUser.isSignedIn; - const username = currentUser.username; - const accessToken = currentUser.accessToken; - const reviewId = currentUser.reviewId; - - return { isSignedIn, username, accessToken, reviewId }; -}; diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index a4c33055..4fae16eb 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -2,18 +2,14 @@ import { ChakraProvider } from "@chakra-ui/react"; import { NextPage } from "next"; import type { AppProps } from "next/app"; import Head from "next/head"; -import { useReducer } from "react"; import { RecoilRoot } from "recoil"; -import AuthContext from "@/components/AuthContext"; -import authReducer from "@/utils/authReducer"; +import { AuthProvider } from "@/components/AuthProvider"; import "@/style/difffile.css"; const MyApp: NextPage = ({ Component, pageProps }) => { - const [state] = useReducer(authReducer.reducer, authReducer.initialState); - return ( - + @@ -22,7 +18,7 @@ const MyApp: NextPage = ({ Component, pageProps }) => { - + ); }; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index ac0d7848..741ca956 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -3,24 +3,24 @@ import { Octokit } from "@octokit/rest"; import { getRedirectResult, GithubAuthProvider } from "firebase/auth"; import React, { useEffect, useState } from "react"; import { BsCheckCircleFill } from "react-icons/bs"; -import { useSetRecoilState } from "recoil"; import Layout from "@/components/Layout"; import NoPullsMessage from "@/components/top/NoPullsMessage"; import PullRequestList from "@/components/top/PullRequestList"; import withAuth from "@/hoc/withAuth"; import { useApi } from "@/hooks/useApi"; -import { useCurrentUser } from "@/hooks/useCurrentUser"; -import { currentUserState } from "@/store/currentUserState"; -import { CurrentUserType } from "@/types/CurrentUserType"; +import { + currentUserActions, + currentUserSelectors, +} from "@/store/currentUserState"; import { TopPullRequestType } from "@/types/PullRequestType"; import { auth } from "@/utils/firebase"; const TopPage = () => { const [pulls, setPulls] = useState([]); const { octokit } = useApi(); - const { username } = useCurrentUser(); - const setCurrentUser = useSetRecoilState(currentUserState); + const currentUser = currentUserSelectors.useCurrentUser(); + const updateCurrentUser = currentUserActions.useUpdateCurrentUser(); useEffect(() => { getRedirectResult(auth).then((result) => { @@ -34,27 +34,22 @@ const TopPage = () => { }); octokit.request("GET /user").then((res) => { - setCurrentUser((prevState) => ({ - ...prevState, + updateCurrentUser({ username: res.data.login, - })); + isSignedIn: true, + accessToken: String(token), + }); }); - - setCurrentUser((prevState) => ({ - ...prevState, - isSignedIn: true, - accessToken: String(token), - })); } } }); }, []); useEffect(() => { - if (username) { + if (currentUser.username) { octokit .request("GET /search/issues", { - q: `is:pr+user-review-requested:${username}+state:open`, + q: `is:pr+user-review-requested:${currentUser.username}+state:open`, }) .then((response) => { const items = response.data.items; @@ -77,7 +72,7 @@ const TopPage = () => { setPulls(newPulls); }); } - }, [username]); + }, [currentUser.username]); return ( ({ - key: "currentUserState", +const currentUserState = atom({ + key: RecoilAtomKeys.CURRENT_USER_STATE, default: initialState, effects_UNSTABLE: [persistAtom], }); + +type CurrentUserActions = { + useUpdateCurrentUser: () => ( + partialCurrentUser: Partial + ) => void; +}; + +export const currentUserActions: CurrentUserActions = { + useUpdateCurrentUser: () => { + const setCurrentUser = + useSetRecoilState(currentUserState); + + return useCallback((partialCurrentUser) => { + setCurrentUser((prevState) => ({ + ...prevState, + ...partialCurrentUser, + })); + }, []); + }, +}; + +type CurrentUserSelectors = { + useCurrentUser: () => CurrentUserState; +}; + +const currentUserSelector = selector({ + key: RecoilSelectorKeys.CURRENT_USER, + get: ({ get }) => get(currentUserState), +}); + +export const currentUserSelectors: CurrentUserSelectors = { + useCurrentUser: () => useRecoilValue(currentUserSelector), +}; diff --git a/src/types/CurrentUserType.ts b/src/types/CurrentUserType.ts deleted file mode 100644 index a4c573b1..00000000 --- a/src/types/CurrentUserType.ts +++ /dev/null @@ -1,6 +0,0 @@ -export type CurrentUserType = { - isSignedIn: boolean; - username: string; - accessToken: string; - reviewId: number; -}; diff --git a/src/utils/authReducer.ts b/src/utils/authReducer.ts deleted file mode 100644 index 95c5e07f..00000000 --- a/src/utils/authReducer.ts +++ /dev/null @@ -1,26 +0,0 @@ -import firebase from "firebase/compat"; - -const initialState = {}; - -type Action = { - type: "login" | "logout"; - payload?: { - user: firebase.User; - }; -}; - -const reducer = (state: any, action: Action) => { - switch (action.type) { - case "login": - return action.payload?.user; - case "logout": - return initialState; - default: - return state; - } -}; - -export default { - initialState, - reducer, -}; diff --git a/src/utils/firebase.ts b/src/utils/firebase.ts index d117ab9b..210aea04 100644 --- a/src/utils/firebase.ts +++ b/src/utils/firebase.ts @@ -1,4 +1,4 @@ -import { initializeApp } from "firebase/app"; +import { getApps, initializeApp } from "firebase/app"; import { getAuth } from "firebase/auth"; const firebaseConfig = { @@ -11,5 +11,8 @@ const firebaseConfig = { measurementId: process.env.NEXT_PUBLIC_MEASUREMENT_ID, }; -initializeApp(firebaseConfig); +if (!getApps().length) { + initializeApp(firebaseConfig); +} + export const auth = getAuth();