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
190 changes: 95 additions & 95 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

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

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

// ✅ 반환 타입 명시
const useGithubLogin = (): { githubLogin: () => Promise<void> } => {
const navigate = useNavigate();

// ✅ 함수 타입 명시
const githubLogin = async (): Promise<void> => {
try {
const result = await signInWithPopup(auth, githubProvider);
const user = result.user;

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

if (error.code === "auth/account-exists-with-different-credential") {
const email = error.customData?.email;

if (email) {
const methods = await fetchSignInMethodsForEmail(auth, email);
console.warn("이미 가입된 로그인 방법:", methods);

if (methods.length > 0) {
if (methods.includes("google.com")) {
alert(
"이미 Google 계정으로 가입된 이메일입니다. Google 로그인을 이용해주세요."
);
} else {
alert(`이미 가입된 로그인 방법: ${methods.join(", ")}`);
}
} else {
alert(
"이미 다른 로그인 방법으로 가입된 이메일입니다. 다른 로그인 방법을 사용해주세요."
);
}
} else {
alert(
"이미 다른 로그인 방법으로 가입된 계정입니다. 이메일 정보를 가져올 수 없습니다."
);
}
}
}
};
Comment on lines +21 to +48
Copy link
Contributor

@tkyoun0421 tkyoun0421 Jun 24, 2025

Choose a reason for hiding this comment

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

early return 방식을 해보시는 것도 추천 드립니다.
if depth가 줄어서 코드 가독성이 좋아질 수 있을거에요!!


return { githubLogin };
};

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

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

const useGoogleLogin = (): { googleLogin: () => Promise<void> } => {
const navigate = useNavigate();

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

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

return { googleLogin };
Comment on lines +6 to +21
Copy link
Contributor

@tkyoun0421 tkyoun0421 Jun 24, 2025

Choose a reason for hiding this comment

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

위에 useGithubLogin과 동일한 로직인거로 보입니다!
hook 인자로 provider를 제공받아 사용하여 재사용성 좋게 관리하면
혹시 나중에 다른 OAuth 서비스를 제공하더라도 같은 로직을 여러번 작성하지 않을 수 있을 것 같습니다!

};

export { useGoogleLogin };
36 changes: 36 additions & 0 deletions src/features/auth/ui/LoginForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
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 { LoginTitle } from "@features/auth/ui/LoginTitle";
import { SignupGuide } from "@features/auth/ui/SignupGuide";
import { SocialLoginButton } from "@features/auth/ui/SocialLoginButton";

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

return (
<>
<LoginTitle />

<SocialLoginButton
label="Google로 로그인"
logo="https://developers.google.com/identity/images/g-logo.png"
onClick={googleLogin}
/>
<SocialLoginButton
label="GitHub로 로그인"
logo="https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png"
onClick={githubLogin}
/>

<Divider sx={{ mb: 3 }}>또는</Divider>

<SignupGuide />
</>
);
};

export { LoginForm };
17 changes: 17 additions & 0 deletions src/features/auth/ui/LoginTitle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Typography } from "@mui/material";
import type { JSX } from "react";

const LoginTitle = (): JSX.Element => {
return (
<>
<Typography variant="h5" fontWeight={700} mb={3}>
로그인
</Typography>
<Typography variant="body2" mb={3} color="textSecondary">
소셜 계정으로 간편하게 시작하세요 ✨
</Typography>
</>
);
};

export { LoginTitle };
19 changes: 19 additions & 0 deletions src/features/auth/ui/SignupGuide.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Typography } from "@mui/material";
import type { JSX } from "react";

const SignupGuide = (): JSX.Element => {
return (
<Typography variant="body2" color="textSecondary">
계정이 없으신가요?{" "}
<Typography
component="span"
color="primary"
sx={{ cursor: "pointer", fontWeight: 600 }}
>
회원가입하기 →
</Typography>
</Typography>
);
};

export { SignupGuide };
36 changes: 36 additions & 0 deletions src/features/auth/ui/SocialLoginButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Button, styled } from "@mui/material";
import type { JSX } from "react";

interface SocialLoginButtonProps {
label: string;
logo: string;
onClick: () => void;
}

const SocialButton = styled(Button)({
width: "100%",
marginBottom: "16px",
textTransform: "none",
display: "flex",
alignItems: "center",
justifyContent: "center",
});
Comment on lines +10 to +17
Copy link
Contributor

Choose a reason for hiding this comment

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

styled component는 render component export 아래에 선언해주시면 감사하겠습니다 🙇


const SocialLoginButton = ({
label,
logo,
onClick,
}: SocialLoginButtonProps): JSX.Element => {
return (
<SocialButton variant="outlined" onClick={onClick}>
<img
src={logo}
alt="Social Logo"
style={{ width: "18px", marginRight: "8px" }}
/>
{label}
</SocialButton>
);
};

export { SocialLoginButton };
2 changes: 2 additions & 0 deletions src/pages/home/ui/HomePage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { JSX } from "react";

const HomePage = (): JSX.Element => {
console.log("API_KEY: ", import.meta.env.VITE_API_KEY);

return (
<div>
<h1>홈 페이지</h1>
Expand Down
38 changes: 37 additions & 1 deletion src/pages/login/ui/LoginPage.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,43 @@
import { Box, Typography, styled } from "@mui/material";
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 => {
return <div>LoginPagesad</div>;
console.log("API_KEY: ", import.meta.env.VITE_API_KEY);
return (
<PageWrapper>
<Typography variant="h3" fontWeight={700} mb={1} color="#4F46E5B">
⚡ 프로젝트 잼
</Typography>
<Typography variant="body1" mb={6} color="textSecondary">
함께 만들어가는 사이드 프로젝트
</Typography>

<LoginBox>
<LoginForm />
</LoginBox>
</PageWrapper>
);
};

export default LoginPage;
14 changes: 10 additions & 4 deletions src/shared/firebase/firebase.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { initializeApp, type FirebaseApp } from "firebase/app";
import { Firestore, getFirestore } from "firebase/firestore/lite";
import { initializeApp } from "firebase/app";
import { getAuth, GoogleAuthProvider, GithubAuthProvider } from "firebase/auth";
import { getFirestore } from "firebase/firestore/lite";

const firebaseConfig = {
apiKey: import.meta.env.VITE_API_KEY,
Expand All @@ -11,5 +12,10 @@ const firebaseConfig = {
measurementId: import.meta.env.VITE_MEASUREMENT_ID,
};

const app: FirebaseApp = initializeApp(firebaseConfig);
export const db: Firestore = getFirestore(app);
const app = initializeApp(firebaseConfig);

export const auth = getAuth(app);
export const googleProvider = new GoogleAuthProvider();
export const githubProvider = new GithubAuthProvider();

export const db = getFirestore(app);