Skip to content

Commit

Permalink
Merge pull request #5 from wafflestudio/feat/login
Browse files Browse the repository at this point in the history
FEAT: 구글 로그인
  • Loading branch information
jwchoi-kr authored Jan 31, 2025
2 parents 281542e + 0219ba7 commit d1f6200
Show file tree
Hide file tree
Showing 18 changed files with 613 additions and 8 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@ dist-ssr
*.njsproj
*.sln
*.sw?

#environment variables
.env
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
},
"devDependencies": {
"@eslint/js": "^9.17.0",
"@tanstack/eslint-plugin-query": "^5.64.2",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@typescript-eslint/eslint-plugin": "^8.18.2",
Expand Down
33 changes: 30 additions & 3 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,37 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';

import { ProtectedRoute } from '@/components/Auth';
import { Home } from '@/pages/home';
import { Landing } from '@/pages/landing';
import { Signup } from '@/pages/signup';

import '@/styles/styles.css';
const routes = [
{
path: '/',
element: <ProtectedRoute redirectTo="/auth/login" />,
children: [{ path: '', element: <Signup /> }],
},
{ path: '/auth/login', element: <Landing /> },
{
path: '/home',
element: <Home />,
},
{
path: '/signup',
element: <ProtectedRoute redirectTo="/auth/login" />,
children: [{ path: '', element: <Signup /> }],
},
];

const router = createBrowserRouter(routes);
function App() {
const queryClient = new QueryClient();
return (
<>
<h1 className="text-3xl font-bold underline">WEMADE 프로젝트</h1>
</>
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
</QueryClientProvider>
);
}

Expand Down
5 changes: 5 additions & 0 deletions src/assets/images/backward-button.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions src/assets/images/signup.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions src/assets/images/wemade-logo-gray-small.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions src/components/Auth.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react';
import { Navigate, Outlet } from 'react-router-dom';

import { useToken } from '@/utils/api';
export const ProtectedRoute: React.FC<{ redirectTo: string }> = ({
redirectTo,
}) => {
const { accessToken } = useToken();
if (!accessToken) {
console.log('redirecting to', redirectTo);
return (
<Navigate
to={redirectTo}
replace
/>
);
}

return <Outlet />;
};
25 changes: 25 additions & 0 deletions src/components/BigButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React, { useState } from 'react';

const BigButton = ({ text }: { text: string }) => {
const [isHovered, setIsHovered] = useState(false);

return (
<div className="flex h-12 w-full flex-shrink-0 items-center justify-center">
<button
className={`h-10 w-full cursor-pointer rounded-md border border-none transition-colors duration-300 ${
isHovered
? 'bg-[#35C273] text-[#000000]'
: 'bg-[#F6F4F1] text-[#9F9A94]'
}`}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<div className="text-center font-['Pretendard'] text-[15px] font-semibold">
{text}
</div>
</button>
</div>
);
};

export default BigButton;
9 changes: 9 additions & 0 deletions src/components/Layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { type ReactNode } from 'react';

export const Layout = ({ children }: { children: ReactNode }) => {
return (
<div className="relative mx-auto flex h-screen w-full max-w-[560px] flex-col overflow-hidden">
{children}
</div>
);
};
24 changes: 24 additions & 0 deletions src/components/LoginButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const LoginButton = ({
text,
onClick,
isClicked,
}: {
text: string;
onClick: () => void;
isClicked: boolean;
}) => {
return (
<div className="flex h-12 w-full flex-shrink-0 items-center justify-center">
<button
className={`cursor-pointer rounded-md border border-borderGrey px-[73.5px] py-[9px] ${
isClicked ? 'bg-backgroundGrey' : 'bg-backgroundWhite'
}`}
onClick={onClick}
>
<div className="text-[15px] text-titleBlack">{text}</div>
</button>
</div>
);
};

export default LoginButton;
6 changes: 6 additions & 0 deletions src/constants/images.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import BACK_BUTTON from '@/assets/images/backward-button.svg';
import SIGNUP_LOGO from '@/assets/images/signup.svg';
import BLACK_LOGO from '@/assets/images/wemade-logo-black.png';
import GRAY_SMALL_LOGO from '@/assets/images/wemade-logo-gray-small.svg';

export { BLACK_LOGO, GRAY_SMALL_LOGO, BACK_BUTTON, SIGNUP_LOGO };
34 changes: 34 additions & 0 deletions src/pages/home.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { useSearchParams, useNavigate } from 'react-router-dom';

import { useGetRequestWithoutToken, useToken } from '@/utils/api';
type AuthResponse = {
access_token: string;
refresh_token: string;
user: {
id: string;
email: string;
};
};

export const Home = () => {
const { setAccessToken } = useToken();
const [searchParams] = useSearchParams();
const navigate = useNavigate();
const code = searchParams.get('code');
const { data, isLoading } = useGetRequestWithoutToken<AuthResponse>(
['google-code'],
`/api/v1/auth/google/callback?code=${code}`,
{
refetchOnWindowFocus: true,
}
);
if (isLoading) return <div>Loading...</div>;
if (data) {
setAccessToken(data.access_token);
localStorage.setItem('accessToken', data.access_token);
localStorage.setItem('refreshToken', data.refresh_token);
navigate('/signup');
}

return <div>Home</div>;
};
50 changes: 50 additions & 0 deletions src/pages/landing.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React, { useState } from 'react';
import { Link } from 'react-router-dom';

import { Layout } from '@/components/Layout';
import LoginButton from '@/components/LoginButton';
import { BLACK_LOGO } from '@/constants/images';
export const Landing = () => {
const [isClicked, setIsClicked] = useState(false);

const handleLogin = async () => {
setIsClicked(!isClicked);
window.location.href = `https://accounts.google.com/o/oauth2/v2/auth?redirect_uri=${import.meta.env.VITE_DOMAIN}/home&prompt=consent&response_type=code&client_id=${import.meta.env.VITE_GOOGLE_OAUTH_CLIENT_ID}&scope=openid%20email%20profile&access_type=offline`;
};

return (
<Layout>
<div className="flex h-full flex-col items-center justify-center">
<div className="mb-[82px] flex flex-col items-center justify-center">
<img
src={BLACK_LOGO}
alt="logo"
className="w-[274.8px]"
/>
<div className="font-isans text-xl text-titleBlack">
안녕하세요 위메이드입니다.
</div>
</div>
<LoginButton
text="Google 계정으로 로그인하기"
onClick={handleLogin}
isClicked={isClicked}
/>
<div className="my-[38px] flex w-[320px] items-center">
<hr className="flex-grow border-t border-textGrey1" />
<span className="mx-3.5 text-sm text-textGrey1">OR</span>
<hr className="flex-grow border-t border-textGrey1" />
</div>
<div className="text-[15px] text-textGrey2">
처음이시라면?
<Link
to="/signup"
className="ml-2.5 font-psemibold text-textGreen underline"
>
회원가입하기
</Link>
</div>
</div>
</Layout>
);
};
81 changes: 81 additions & 0 deletions src/pages/signup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import React from 'react';
import { Link } from 'react-router-dom';

import BigButton from '@/components/BigButton';
import { Layout } from '@/components/Layout';
import { GRAY_SMALL_LOGO, BACK_BUTTON, SIGNUP_LOGO } from '@/constants/images';

export const Signup = () => {
return (
<Layout>
<div className="mx-auto flex h-full w-full max-w-[330px] flex-col justify-center gap-8">
<div className="mb-8 flex w-full flex-col items-start">
<Link
to="/"
className="mb-4 h-[30px] w-[30px]"
>
<img
src={BACK_BUTTON}
alt="back"
className="cursor-pointer"
/>
</Link>
<img
src={GRAY_SMALL_LOGO}
alt="logo"
className="mb-4 w-[120px]"
/>
<img
src={SIGNUP_LOGO}
alt="signup"
className="w-[120px]"
/>
</div>

<div className="flex flex-col gap-6">
<div className="flex flex-col gap-1.5">
<label className="text-sm font-medium text-textGrey2">이메일</label>
<div className="flex items-center gap-3">
<div className="flex w-full items-center rounded-md border border-borderGrey bg-backgroundWhite px-3 py-2">
<input
type="email"
placeholder="이메일을 입력하세요."
className="flex-grow text-[15px] font-medium text-textGrey1 focus:outline-none"
/>
</div>
<button className="whitespace-nowrap rounded-md border border-borderGrey bg-backgroundGrey px-4 py-2 text-[15px] font-medium">
인증하기
</button>
</div>
</div>

<div className="flex flex-col gap-1.5">
<label className="text-sm font-medium text-textGrey2">
전화번호
</label>
<div className="flex w-full items-center rounded-md border border-borderGrey bg-backgroundWhite px-3 py-2">
<input
type="tel"
placeholder="전화번호를 입력하세요."
className="flex-grow text-[15px] font-medium text-textGrey1 focus:outline-none"
/>
</div>
</div>

<div className="flex flex-col gap-1.5 pb-2">
<label className="text-sm font-medium text-textGrey2">직책</label>
<div className="flex w-full items-center rounded-md border border-borderGrey bg-backgroundWhite px-3 py-2">
<input
type="text"
placeholder="직책을 입력하세요."
className="flex-grow text-[15px] font-medium text-textGrey1 focus:outline-none"
/>
</div>
</div>
</div>

<BigButton text="다음" />
</div>
</Layout>
);
};
37 changes: 37 additions & 0 deletions src/styles/styles.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,40 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

body {
font-family: 'Pretendard Medium`', sans-serif;
}
@font-face {
font-family: 'Pretendard Medium';
font-weight: 500;
font-style: normal;
font-display: swap;
src:
url('https://cdn.jsdelivr.net/gh/fonts-archive/Pretendard/Pretendard-Medium.woff2')
format('woff2'),
url('https://cdn.jsdelivr.net/gh/fonts-archive/Pretendard/Pretendard-Medium.woff')
format('woff'),
url('https://cdn.jsdelivr.net/gh/fonts-archive/Pretendard/Pretendard-Medium.otf')
format('opentype');
}
@font-face {
font-family: 'Pretendard SemiBold';
font-weight: 600;
font-style: normal;
font-display: swap;
src:
url('https://cdn.jsdelivr.net/gh/fonts-archive/Pretendard/Pretendard-SemiBold.woff2')
format('woff2'),
url('https://cdn.jsdelivr.net/gh/fonts-archive/Pretendard/Pretendard-SemiBold.woff')
format('woff'),
url('https://cdn.jsdelivr.net/gh/fonts-archive/Pretendard/Pretendard-SemiBold.otf')
format('opentype');
}
@font-face {
font-family: 'InfinitySans-RegularA1';
src: url('https://fastly.jsdelivr.net/gh/projectnoonnu/[email protected]/InfinitySans-RegularA1.woff')
format('woff');
font-weight: 600;
font-style: normal;
}
Loading

0 comments on commit d1f6200

Please sign in to comment.