Skip to content

Commit

Permalink
[Feat] 파이어베이스과 Oauth 공급자 연결 (#74)
Browse files Browse the repository at this point in the history
  • Loading branch information
yangjiwoong1 authored Sep 25, 2023
2 parents a8f0eaf + fa9d334 commit b70e7c7
Show file tree
Hide file tree
Showing 15 changed files with 1,039 additions and 119 deletions.
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"doctor": "yarn workspace @housepital/doctor",
"hospital-desk": "yarn workspace @housepital/hospital-desk",
"center119": "yarn workspace @housepital/center119",
"pharmacy": "yarn workspace @housepital/pharmacy"
"pharmacy": "yarn workspace @housepital/pharmacy",
"server": "yarn workspace @housepital/server"
},
"private": true,
"workspaces": {
Expand All @@ -29,6 +30,7 @@
"@reduxjs/toolkit": "^1.9.5",
"@tanstack/react-query": "^4.32.5",
"@tanstack/react-query-devtools": "^4.32.5",
"axios": "^1.5.0",
"firebase": "^10.1.0",
"framer-motion": "^10.15.0",
"react-hook-form": "^7.45.4",
Expand All @@ -37,7 +39,7 @@
"react-router-dom": "^6.14.2"
},
"devDependencies": {
"@babel/eslint-parser": "^10.1.0",
"@babel/eslint-parser": "^7.22.15",
"@jest/globals": "^29.6.2",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^14.0.0",
Expand All @@ -49,6 +51,7 @@
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-react": "^7.33.1",
"eslint-plugin-react-hooks": "^4.3.0",
"firebase-admin": "^11.10.1",
"prettier": "^3.0.1",
"react-daum-postcode": "^3.1.3"
}
Expand Down
57 changes: 48 additions & 9 deletions packages/apps/user/firebase.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { initializeApp } from 'firebase/app';
import {
getAuth,
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
GoogleAuthProvider,
signInWithPopup,
createUserWithEmailAndPassword,
} from 'firebase/auth';
import { getStorage } from 'firebase/storage';
import { getDownloadURL, getStorage, ref, uploadBytes } from 'firebase/storage';
import { getMe, createUser } from './src/api';

const firebaseConfig = {
apiKey: process.env.REACT_APP_FB_API_KEY,
Expand All @@ -22,16 +23,54 @@ const auth = getAuth(app);
const storage = getStorage(app);
const provider = new GoogleAuthProvider();

const signIn = (email, password) => {
return createUserWithEmailAndPassword(auth, email, password);
export const uploadBlob = async (blob, email) => {
const storageRef = ref(storage, `${email}/profileImg.png`);
await uploadBytes(storageRef, blob);
};

const logIn = (email, password) => {
return signInWithEmailAndPassword(auth, email, password);
export const getUserImage = email => {
getDownloadURL(ref(storage, `${email}/profileImg.png`))
.then(url => console.log(url))
.catch(() => null);
};

const googleLogin = () => {
return signInWithPopup(auth, provider);
export const fbSignUp = async data => {
const { email, password, ...rest } = data;
const isUserExist = false; // getMe();

if (isUserExist) {
//TODO: 사용자가 존재한다는 알림 전송
//TODO: 로그인 페이지로 리디렉트
} else {
createUserWithEmailAndPassword(auth, email, password).then(() => {
createUser({ email, ...rest });
uploadBlob(rest.profileImgBlob, email);
});
return createUser({ ...rest, email });
}
};

export const fbLogIn = async data => {
const { email, password } = data;
const isUserExist = true;

if (!isUserExist) {
//TODO: 존재하지 않는 회원입니다 알림 발송
} else {
const userCredential = await signInWithEmailAndPassword(
auth,
email,
password,
);
console.log(userCredential);
console.log(userCredential._tokenResponse);
//TODO: email을 가지는 user 정보를 DB에서 가져온다.

//TODO: CASE1. 이미 존재하는 경우 -> 경고알림 or 바로 로그인
//TODO: CASE2. 존재하지 않는 경우 -> 회원가입 진행
}
};

export { auth, signIn, logIn, googleLogin, provider, storage };
export const googleLogin = () => {
return signInWithPopup(auth, provider);
};
19 changes: 19 additions & 0 deletions packages/apps/user/src/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import axios from 'axios';
import { getUserImage } from '../firebase';

const instance = axios.create({
baseURL: 'http://localhost:3000/api',
});

export const getMe = async () => {
const { user } = await instance.get(`/users/me`);
if (!user) {
//TODO: 해당 유저가 존재하지 않는 경우에 대한 처리
} else {
// return user
return {
name: '하철환',
profileImg: getUserImage(),
};
}
};
19 changes: 19 additions & 0 deletions packages/apps/user/src/components/Root/AuthRoot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Outlet, useNavigate } from 'react-router-dom';

import useUser from '../../hooks/useUser';
import LoadingPage from '../../../../../common/LoadingPage';

const AuthRoot = function () {
const navigate = useNavigate();
const { userLoading, user, isLoggedIn } = useUser();
console.log(userLoading, user, isLoggedIn);

return (
// <>{userLoading ? <LoadingPage /> : user ? navigate('/') : <Outlet />}</>
<>
<Outlet />
</>
);
};

export default AuthRoot;
40 changes: 17 additions & 23 deletions packages/apps/user/src/components/Root/Root.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,31 @@
import { useEffect, useState } from 'react';

import { Outlet, useNavigate } from 'react-router-dom';
import { useDispatch } from 'react-redux';
import { onAuthStateChanged, getAuth } from 'firebase/auth';
import { Box } from '@chakra-ui/react';

import StatusBar from '../StatusBar/StatusBar';
import SideBar from '../SideBar/SideBar';
import { setUser } from '../../store';
import useUser from '../../hooks/useUser';
import LoadingPage from '../../../../../common/LoadingPage';

const Root = function () {
const navigate = useNavigate();
const dispatch = useDispatch();

useEffect(() => {
const auth = getAuth();

onAuthStateChanged(auth, user => {
if (user) {
dispatch(setUser(user));
} else {
navigate('/auth/log-in');
}
});
}, [navigate]);
const { userLoading, user, isLoggedIn } = useUser();
console.log(userLoading, user, isLoggedIn);

return (
<>
<SideBar />
<StatusBar />
<Box ml="40" p="6" pt="14" height="100vh">
<Outlet />
</Box>
{userLoading ? (
<LoadingPage />
) : user ? (
<>
<SideBar />
<StatusBar />
<Box ml="40" p="6" pt="14" height="100vh">
<Outlet />
</Box>
</>
) : (
navigate('/auth/log-in')
)}
</>
);
};
Expand Down
34 changes: 6 additions & 28 deletions packages/apps/user/src/components/SignUpForm/SignUpForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@ import { Outlet, useLocation, useNavigate } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import { useForm } from 'react-hook-form';
import PropTypes from 'prop-types';
import { ref, uploadBytes } from 'firebase/storage';
import { motion } from 'framer-motion';
import { Box, Button, ButtonGroup, VStack } from '@chakra-ui/react';

import { storage } from '../../../firebase';
import { setErrors } from '../../store';
import { fbSignUp } from '../../../firebase';

const SignUpForm = function ({
activeStep,
Expand Down Expand Up @@ -48,33 +47,12 @@ const SignUpForm = function ({
goToPrevious();
}, [goToPrevious, activeStep, navigate]);

const uploadBlob = blob => {
const storageRef = ref(storage, 'images/' + new Date().getTime() + '.png');

uploadBytes(storageRef, blob).catch(() => {
navigate('/error');
});
};

const onSubmit = function (data) {
uploadBlob(profileImgBlob);
navigate('/');

//Todo: firebase로부터 받아온 토큰을 DB에 저장하기
// setPersistence(auth, browserLocalPersistence)
// .then(() => {
// signIn(email, password)
// .then(userCredential => {
// updateProfile(userCredential.user, {
// displayName: username,
// }).then(() => navigate('/'));
// })
// .catch(error => {
// console.log(error);
// navigate('/error');
// });
// })
// .catch(() => navigate('/error'));
const user = fbSignUp({ ...data, profileImgBlob });

if (user) {
//TODO: redux-persist를 이용하여 사용자 정보를 저장
}
};

const location = useLocation();
Expand Down
15 changes: 15 additions & 0 deletions packages/apps/user/src/hooks/useUser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { useQuery } from '@tanstack/react-query';
import { getMe } from '../api';

const useUser = () => {
const { isLoading, data, isError } = useQuery(['me'], getMe, {
retry: false,
});
return {
userLoading: isLoading,
user: data,
isLoggedIn: !isError,
};
};

export default useUser;
20 changes: 12 additions & 8 deletions packages/apps/user/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,30 @@ import { RouterProvider } from 'react-router-dom';
import { Provider } from 'react-redux';
import ReactDOM from 'react-dom/client';
import { ChakraProvider } from '@chakra-ui/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

import theme from '../theme';

import { store } from './store';
import router from './routes/index';
import { AnimatePresence } from 'framer-motion';

const client = new QueryClient();

const root = ReactDOM.createRoot(document.getElementById('root'));

// In a browser environment, render instead of exporting
if (typeof window !== 'undefined') {
root.render(
<React.StrictMode>
<ChakraProvider theme={theme}>
<Provider store={store}>
<AnimatePresence>
<RouterProvider router={router} />
</AnimatePresence>
</Provider>
</ChakraProvider>
<QueryClientProvider client={client}>
<ChakraProvider theme={theme}>
<Provider store={store}>
<AnimatePresence>
<RouterProvider router={router} />
</AnimatePresence>
</Provider>
</ChakraProvider>
</QueryClientProvider>
</React.StrictMode>,
);
}
5 changes: 2 additions & 3 deletions packages/apps/user/src/routes/authRoutes.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { Outlet } from 'react-router-dom';

import NotFound from '../views/NotFound/NotFound';
import SignUp from '../views/Auth/SignUp/SignUp';
import LogIn from '../views/Auth/LogIn/LogIn';
Expand All @@ -9,11 +7,12 @@ import AfterCapture from '../components/SignUpForm/AfterCapture';
import Step1 from '../views/Auth/SignUp/Step1';
import Step2 from '../views/Auth/SignUp/Step2';
import Step3 from '../views/Auth/SignUp/Step3';
import AuthRoot from '../components/Root/AuthRoot';
import AuthCallback from '../views/Auth/AuthCallback/AuthCallback';

const authRoutes = {
path: 'auth',
element: <Outlet />,
element: <AuthRoot />,
errorElement: <NotFound />,
children: [
{
Expand Down
15 changes: 4 additions & 11 deletions packages/apps/user/src/views/Auth/LogIn/LogIn.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useCallback, useState } from 'react';

import { useNavigate , Link as ReactRouterLink } from 'react-router-dom';
import { useNavigate, Link as ReactRouterLink } from 'react-router-dom';
import { FaUserAlt, FaLock } from 'react-icons/fa';
import {
AiFillGithub,
Expand All @@ -25,13 +25,14 @@ import {
Icon,
} from '@chakra-ui/react';

import { logIn, googleLogin, auth, provider } from '../../../../firebase';
import { googleLogin, auth, provider, fbLogIn } from '../../../../firebase';

function LogIn() {
const [showPassword, setShowPassword] = useState(false);
const handleShowClick = useCallback(() => {
setShowPassword(!showPassword);
}, [showPassword]);

const navigate = useNavigate();
const {
register,
Expand All @@ -40,15 +41,7 @@ function LogIn() {
} = useForm();

const onSubmit = function (data) {
const { email, password } = data;
logIn(email, password)
.then(() => {
navigate('/');
})
.catch(error => {
console.log(error);
navigate('/error');
});
fbLogIn(data);
};

const googleClick = useCallback(() => {
Expand Down
Loading

0 comments on commit b70e7c7

Please sign in to comment.