Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
LeeJunhyeok369 committed Feb 15, 2024
2 parents f5aa2ae + 77d0117 commit 21e703e
Show file tree
Hide file tree
Showing 11 changed files with 309 additions and 101 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
"preview": "vite preview"
},
"dependencies": {
"@tanstack/react-query": "^5.20.5",
"axios": "^1.6.7",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^5.0.1",
Expand Down
40 changes: 12 additions & 28 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,19 @@
import React from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import "./tailwind.css";
import Splash from './pages/Splash';
import Login from './pages/Login';
import JoinWay from './pages/JoinWay';
import Join from './pages/Join';
import Home from './pages/Home';
import Alarm from './pages/Alarm';
import SearchPerson from './pages/SearchPerson';
import SearchPlace from './pages/SearchPlace';
import Store from './pages/Store';
import Videos from './pages/Videos';
import './tailwind.css';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import Router from './routes/Router';
import { AuthProvider } from './api/auth';

const App: React.FC = () => {
const queryClient = new QueryClient();

const App: React.FC = () => {
return (
<BrowserRouter>
<Routes>
<Route path='/' element={<Splash />}/>
<Route path='/Login' element={<Login/>}/>
<Route path='/JoinWay' element={<JoinWay/>}/>
<Route path='/Join' element={<Join/>}/>
<Route path='/Home' element={<Home/>}/>
<Route path='/Alarm' element={<Alarm/>}/>
<Route path='/SearchPerson' element={<SearchPerson/>}/>
<Route path='/SearchPlace' element={<SearchPlace/>}/>
<Route path='/Store' element={<Store/>}/>
<Route path='/Videos' element={<Videos/>}/>
</Routes>
</BrowserRouter>
<QueryClientProvider client={queryClient}>
<AuthProvider>
<Router />
</AuthProvider>
</QueryClientProvider>
);
};

export default App;
export default App;
40 changes: 40 additions & 0 deletions src/api/auth.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { useMutation } from '@tanstack/react-query';
import api from '.';
import { createContext, useContext } from 'react';

const loginByEmail = ({
email,
password,
}: {
email: string;
password: string;
}) =>
api.post('/auth/email/login', { email, password }).then(({ data }) => ({
accessToken: data.accessToken as string,
}));

const useAuthProvider = () => {
const { mutate: mutateByEmail, error: emailLoginError } = useMutation({
mutationFn: loginByEmail,
});
return { loginByEmail: mutateByEmail, error: emailLoginError };
};

const authContext = createContext<
ReturnType<typeof useAuthProvider> | undefined
>(undefined);

export const AuthProvider = ({ children }: React.PropsWithChildren) => {
const value = useAuthProvider();
return <authContext.Provider value={value}>{children}</authContext.Provider>;
};

// eslint-disable-next-line react-refresh/only-export-components
export const useAuth = () => {
const value = useContext(authContext);
if (!value)
throw new Error(
'Cannot find AuthProvider. Please wrap your component with AuthProvider',
);
return value;
};
5 changes: 5 additions & 0 deletions src/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import axios from 'axios';

const api = axios.create({ baseURL: 'https://wayu.hackathon.sparcs.net/' });

export default api;
14 changes: 6 additions & 8 deletions src/pages/Join.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React from 'react';
import Btn from './component/Btn';
import Input from './component/Input';
import Header from './component/Header';
import Input from './component/Input';

export default function Join() {
return (
Expand All @@ -12,7 +11,7 @@ export default function Join() {
type="text"
text="abcde@gmail.com"
label="이메일"
errorMsg="규칙에 맞는 이메일을 입력해주세요!"
errorMessage="규칙에 맞는 이메일을 입력해주세요!"
className="mt-[10%] h-[115px]"
/>
<Input
Expand All @@ -31,13 +30,12 @@ export default function Join() {
type="text"
text="닉네임 입"
label="닉네임"
errorMsg="중복된 닉네임 입니다!"
errorMessage="중복된 닉네임 입니다!"
className="h-[115px]"
/>
<Btn
text="가입하기"
className="bg-primary text-[#ffffff] absolute bottom-[5%] left-0"
/>
<Btn className="bg-primary text-[#ffffff] absolute bottom-[5%] left-0">
가입하기
</Btn>
</div>
</div>
);
Expand Down
16 changes: 7 additions & 9 deletions src/pages/JoinWay.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
import React from 'react';
import Btn from './component/Btn';
import LinkText from './component/LinkText';

export default function JoinWay() {
return (
<div className="mx-[10%] pt-[30%]">
<h3 className="text-3xl leading-[250px] text-center font-bold">
<h3 className="text-2xl leading-[250px] text-center font-bold">
회원가입 방법을 선택하세요!
</h3>
<Btn text="이메일로 가입하기" className="bg-primary text-[#ffffff]" />
<Btn
text="카카오톡으로 가입하기"
className="bg-[#F2DA00] text-[#282732] border border-[#E8E9EA]"
/>
<Btn className="bg-primary text-white">이메일로 가입하기</Btn>
<Btn className="bg-[#F2DA00] text-[#282732] border border-[#E8E9EA]">
카카오톡으로 가입하기
</Btn>
<LinkText
className="leading-[80px] mb-[40px] justify-center"
className="leading-10 mb-10 justify-center"
text="이미 회원이신가요?"
LinkMsg="로그인"
to="/Login"
to="/login"
/>
</div>
);
Expand Down
66 changes: 56 additions & 10 deletions src/pages/Login.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,75 @@
import { useEffect, useState } from 'react';
import Btn from './component/Btn';
import Input from './component/Input';
import LinkText from './component/LinkText';
import { useAuth } from '../api/auth';

export default function Login() {
const [emailError, setEmailError] = useState<string>();
const [passwordError, setPasswordError] = useState<string>();
const { loginByEmail, error } = useAuth();

useEffect(() => {
const message = error
? '이메일 또는 비밀번호가 일치하지 않습니다'
: undefined;
setEmailError(message);
setPasswordError(message);
}, [error]);

const login = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const payload = {
email: e.currentTarget.email.value,
password: e.currentTarget.password.value,
};

if (!payload.email) {
setEmailError('이메일을 입력해주세요');
return;
}
setEmailError(undefined);
if (!payload.password) {
setPasswordError('비밀번호를 입력해주세요');
return;
}
setPasswordError(undefined);

loginByEmail(payload);
};

return (
<div className="mx-[10%] pt-[30%]">
<form onSubmit={login} className="mx-[10%] pt-[30%]">
<h3 className="text-3xl leading-[100px] text-center font-bold">로그인</h3>
<Input type="text" text="아이디 또는 이메일주소" />
<Input type="password" text="비밀번호" />
<Input
type="text"
text="이메일주소"
name="email"
autoCapitalize="off"
autoComplete="off"
errorMessage={emailError}
/>
<Input
type="password"
text="비밀번호"
name="password"
errorMessage={passwordError}
/>
<LinkText
className="leading-[30px] mb-[40px] justify-end"
LinkMsg="비밀번호 재설정"
to="/Login"
/>
<Btn text="로그인" className="bg-primary text-white" />
<Btn
text="카카오톡으로 로그인하기"
className="bg-[#F2DA00] text-[#282732] border border-[#E8E9EA]"
to="/login"
/>
<Btn className="bg-primary text-white">로그인</Btn>
<Btn className="bg-[#F2DA00] text-[#282732] border border-[#E8E9EA]">
카카오톡으로 로그인하기
</Btn>
<LinkText
className="leading-[80px] mb-[40px] justify-center"
text="아직계정이 없으신가요?"
LinkMsg="회원가입"
to="/JoinWay"
/>
</div>
</form>
);
}
30 changes: 18 additions & 12 deletions src/pages/component/Btn.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import React from 'react';

type BtnProps = {
text: string;
className: string;
action?: () => void;
}
className: string;
};

const Btn: React.FC<BtnProps> = ({text, className, action}) => {
return (
<div className={`w-[100%] h-[48px] mb-[10px] ${className} text-lg text-center leading-[48px] rounded-lg`}>
{text}
</div>
);
}
const Btn = ({
children,
className,
...props
}: React.PropsWithChildren<BtnProps> &
React.ButtonHTMLAttributes<HTMLButtonElement>) => {
return (
<button
{...props}
className={`w-[100%] h-[48px] mb-[10px] ${className} text-lg text-center leading-[48px] rounded-lg`}
>
{children}
</button>
);
};

export default Btn;
export default Btn;
95 changes: 61 additions & 34 deletions src/pages/component/Input.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,70 @@
import React, { useState } from 'react';

type InputProps = {
text: string;
type: string;
label?: string;
className?: string;
errorMsg?: string;
condition?: string;
action?: () => void;
}
text: string;
type: string;
label?: string;
className?: string;
errorMessage?: string;
};

const Input: React.FC<InputProps> = ({ type, text, label, className, action, errorMsg, condition}) => {
const [value, setValue] = useState<string>('');
const [typeInput, setTypeInput] = useState<string>(type);
const Input = ({
type,
text,
label,
className,
errorMessage,
...inputProps
}: InputProps & React.InputHTMLAttributes<HTMLInputElement>) => {
const [value, setValue] = useState<string>('');
const [typeInput, setTypeInput] = useState<string>(type);

const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const newValue = event.target.value;
setValue(newValue);
}
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const newValue = event.target.value;
setValue(newValue);
};

const togglePasswordVisibility = () => {
setTypeInput(typeInput === "text" ? "password" : "text");
}
const togglePasswordVisibility = () => {
setTypeInput(typeInput === 'text' ? 'password' : 'text');
};

return (
<div className={`${className}`}>
{label && <h3 className='leading-[30px]'>{label}</h3>}
<div className={` w-[100%] h-[48px] flex justify-between mb-[8px] pl-3 leading-[48px] rounded-lg border border-[#E5E5E5] bg-[#F7F8F9]`}>
<input
type={typeInput}
value={value}
onChange={handleChange}
className={`${type=="text"? "w-[98%]": "w-[85%]"} h-[48px] text-base mb-[4px] outline-none placeholder-[#CECED1] bg-[#F7F8F9]`}
placeholder={text}
/>
{type=="password"&& <button onClick={togglePasswordVisibility} className='bg-transparent outline-none h-[24px] w-[24px] m-auto'><img src={typeInput=="text" ? "/assets/show.png" : "/assets/hide.png"} alt="eye" className="object-cover w-full h-full" /></button>}
</div>
</div>
);
}
return (
<div className={`${className} mb-[8px]`}>
{label && <h3 className="leading-[30px]">{label}</h3>}
<div
className={`w-[100%] h-[48px] flex justify-between pl-3 leading-[48px] rounded-lg border border-[#E5E5E5] bg-[#F7F8F9]`}
>
<input
{...inputProps}
type={typeInput}
value={value}
onChange={handleChange}
className={`${
type == 'text' ? 'w-[98%]' : 'w-[85%]'
} h-[48px] text-base mb-[4px] outline-none placeholder-[#CECED1] bg-[#F7F8F9]`}
placeholder={text}
/>
{type == 'password' && (
<button
type="button"
onClick={togglePasswordVisibility}
className="bg-transparent outline-none h-[24px] w-[24px] m-auto"
>
<img
src={
typeInput == 'text' ? '/assets/show.png' : '/assets/hide.png'
}
alt="eye"
className="object-cover w-full h-full"
/>
</button>
)}
</div>
{errorMessage && (
<p className="text-[#FF0000] text-sm leading-[24px]">{errorMessage}</p>
)}
</div>
);
};

export default Input;
Loading

0 comments on commit 21e703e

Please sign in to comment.