Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 22 additions & 8 deletions src/app/routes/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import type { JSX } from "react";
import { lazy } from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";

import AuthLayout from "@app/routes/AuthLayout";
import MainLayout from "@app/routes/MainLayout";

import { useAuthObserver } from "@shared/hooks/useAuthObserver";

const HomePage = lazy(() => import("@pages/home/ui/HomePage"));
const NotFoundPage = lazy(() => import("@pages/not-found/ui/NotFoundPage"));
const UserProfilePage = lazy(
Expand All @@ -20,17 +25,26 @@ const LoginPage = lazy(() => import("@pages/login/ui/LoginPage"));
const SignUpPage = lazy(() => import("@pages/signup/ui/SignUpPage"));

function App(): JSX.Element {
useAuthObserver();

return (
<BrowserRouter>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/profile" element={<UserProfilePage />} />
<Route path="/project" element={<ProjectListPage />} />
<Route path="/project/insert" element={<ProjectInsertPage />} />
<Route path="/project/:id" element={<ProjectDetailPage />} />
<Route path="/login" element={<LoginPage />} />
<Route path="/signup" element={<SignUpPage />} />
<Route path="*" element={<NotFoundPage />} />
{/* 헤더 없는 레이아웃 (로그인/회원가입 전용) */}
<Route element={<AuthLayout />}>
<Route path="/login" element={<LoginPage />} />
<Route path="/signup" element={<SignUpPage />} />
</Route>

{/* 헤더 포함 레이아웃 (메인 페이지) */}
<Route element={<MainLayout />}>
<Route path="/" element={<HomePage />} />
<Route path="/profile" element={<UserProfilePage />} />
<Route path="/project" element={<ProjectListPage />} />
<Route path="/project/insert" element={<ProjectInsertPage />} />
<Route path="/project/:id" element={<ProjectDetailPage />} />
<Route path="*" element={<NotFoundPage />} />
</Route>
</Routes>
</BrowserRouter>
);
Expand Down
12 changes: 12 additions & 0 deletions src/app/routes/AuthLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { JSX } from "react";
import { Outlet } from "react-router-dom";

const AuthLayout = (): JSX.Element => {
return (
<main>
<Outlet />
</main>
);
};

export default AuthLayout;
17 changes: 17 additions & 0 deletions src/app/routes/MainLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { JSX } from "react";
import { Outlet } from "react-router-dom";

import Header from "@widgets/Header/Header";

const MainLayout = (): JSX.Element => {
return (
<>
<Header />
<main>
<Outlet />
</main>
</>
);
};

export default MainLayout;
53 changes: 0 additions & 53 deletions src/features/auth/hooks/useGithubLogin.ts

This file was deleted.

24 changes: 0 additions & 24 deletions src/features/auth/hooks/useGoogleLogin.ts

This file was deleted.

48 changes: 48 additions & 0 deletions src/features/auth/hooks/useSocialLogin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { signInWithPopup, fetchSignInMethodsForEmail } from "firebase/auth";
import type { AuthProvider } from "firebase/auth";
import { useNavigate } from "react-router-dom";

import { auth } from "@shared/firebase/firebase";

export const useSocialLogin = (): {
socialLogin: (provider: AuthProvider) => Promise<void>;
} => {
const navigate = useNavigate();

const socialLogin = async (provider: AuthProvider): Promise<void> => {
try {
const result = await signInWithPopup(auth, provider);
const user = result.user;

console.log("소셜 로그인 성공: ", user);
navigate("/");
} catch (error: any) {
console.error("소셜 로그인 실패: ", error);

if (error.code !== "auth/account-exists-with-different-credential")
return;

const email = error.customData?.email;
if (!email) {
alert("이메일 정보를 가져올 수 없습니다.");
return;
}

const methods = await fetchSignInMethodsForEmail(auth, email);
if (methods.length === 0) {
alert("이미 다른 로그인 방법으로 가입된 이메일입니다.");
return;
}

if (methods.includes("google.com")) {
alert(
"이미 Google 계정으로 가입된 이메일입니다. Google 로그인을 이용해주세요."
);
} else {
alert(`이미 가입된 로그인 방법: ${methods.join(", ")}`);
}
}
};

Comment on lines +12 to +46
Copy link
Contributor

Choose a reason for hiding this comment

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

바꿔주셨네요!! 👍 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

옙! 수정했습니다 ㅋㅋㅋㅋ

return { socialLogin };
};
15 changes: 15 additions & 0 deletions src/features/auth/ui/LoginButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Button } from "@mui/material";
import type { JSX } from "react";
import { useNavigate } from "react-router-dom";

const LoginButton = (): JSX.Element => {
const navigate = useNavigate();

return (
<Button variant="outlined" onClick={() => navigate("/login")}>
로그인
</Button>
);
};

export default LoginButton;
12 changes: 6 additions & 6 deletions src/features/auth/ui/LoginForm.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { Divider } from "@mui/material";
import type { JSX } from "react";

import { useGithubLogin } from "@features/auth/hooks/useGithubLogin";
import { useGoogleLogin } from "@features/auth/hooks/useGoogleLogin";
import { useSocialLogin } from "@features/auth/hooks/useSocialLogin";
import { LoginTitle } from "@features/auth/ui/LoginTitle";
import { SignupGuide } from "@features/auth/ui/SignupGuide";
import { SocialLoginButton } from "@features/auth/ui/SocialLoginButton";

import { githubProvider, googleProvider } from "@shared/firebase/firebase";

const LoginForm = (): JSX.Element => {
const { googleLogin } = useGoogleLogin();
const { githubLogin } = useGithubLogin();
const { socialLogin } = useSocialLogin();

return (
<>
Expand All @@ -18,12 +18,12 @@ const LoginForm = (): JSX.Element => {
<SocialLoginButton
label="Google로 로그인"
logo="https://developers.google.com/identity/images/g-logo.png"
onClick={googleLogin}
onClick={() => socialLogin(googleProvider)}
/>
<SocialLoginButton
label="GitHub로 로그인"
logo="https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png"
onClick={githubLogin}
onClick={() => socialLogin(githubProvider)}
/>

<Divider sx={{ mb: 3 }}>또는</Divider>
Expand Down
23 changes: 23 additions & 0 deletions src/features/auth/ui/LogoutButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Button } from "@mui/material";
import { signOut } from "firebase/auth";
import type { JSX } from "react";

import { auth } from "@shared/firebase/firebase";
import { useAuthStore } from "@shared/stores/authStore";

const LogoutButton = (): JSX.Element => {
const logout = useAuthStore((state) => state.logout);

const handleLogout = async (): Promise<void> => {
await signOut(auth);
logout();
};

return (
<Button variant="outlined" onClick={handleLogout}>
로그아웃
</Button>
);
};

export default LogoutButton;
18 changes: 9 additions & 9 deletions src/features/auth/ui/SocialLoginButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,6 @@ interface SocialLoginButtonProps {
onClick: () => void;
}

const SocialButton = styled(Button)({
width: "100%",
marginBottom: "16px",
textTransform: "none",
display: "flex",
alignItems: "center",
justifyContent: "center",
});

const SocialLoginButton = ({
label,
logo,
Expand All @@ -34,3 +25,12 @@ const SocialLoginButton = ({
};

export { SocialLoginButton };

const SocialButton = styled(Button)({
width: "100%",
marginBottom: "16px",
textTransform: "none",
display: "flex",
alignItems: "center",
justifyContent: "center",
});
38 changes: 19 additions & 19 deletions src/pages/login/ui/LoginPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,6 @@ import type { JSX } from "react";

import { LoginForm } from "@features/auth/ui/LoginForm";

const PageWrapper = styled(Box)({
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
minHeight: "100vh",
backgroundColor: "#f5f7fb",
});

const LoginBox = styled(Box)({
width: "100%",
maxWidth: "400px",
padding: "32px",
borderRadius: "12px",
backgroundColor: "#fff",
boxShadow: "0 4px 12px rgba(0, 0, 0, 0.1)",
textAlign: "center",
});

const LoginPage = (): JSX.Element => {
console.log("API_KEY: ", import.meta.env.VITE_API_KEY);
return (
Expand All @@ -41,3 +22,22 @@ const LoginPage = (): JSX.Element => {
};

export default LoginPage;

const PageWrapper = styled(Box)({
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
minHeight: "100vh",
backgroundColor: "#f5f7fb",
});

const LoginBox = styled(Box)({
width: "100%",
maxWidth: "400px",
padding: "32px",
borderRadius: "12px",
backgroundColor: "#fff",
boxShadow: "0 4px 12px rgba(0, 0, 0, 0.1)",
textAlign: "center",
});
19 changes: 19 additions & 0 deletions src/shared/hooks/useAuthObserver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { onAuthStateChanged } from "firebase/auth";
import { useEffect } from "react";

import { auth } from "@shared/firebase/firebase";
import { useAuthStore } from "@shared/stores/authStore";

export const useAuthObserver = (): void => {
const setUser = useAuthStore((state) => state.setUser);
const setLoading = useAuthStore((state) => state.setLoading);

useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
setUser(user);
setLoading(false);
});

return () => unsubscribe();
}, [setUser, setLoading]);
};
20 changes: 20 additions & 0 deletions src/shared/stores/authStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { User } from "firebase/auth";
import { create } from "zustand";

interface AuthState {
user: User | null;
isLoading: boolean;
setUser: (user: User | null) => void;
setLoading: (loading: boolean) => void;
logout: () => void;
}

export const useAuthStore = create<AuthState>((set) => ({
user: null,
isLoading: true,

setUser: (user) => set({ user }),
setLoading: (loading) => set({ isLoading: loading }),

logout: () => set({ user: null }),
}));
Loading