트위터(React, TypeScript, Firebase, Vite)
10. Authentication - Log In
본시모니
2025. 4. 15. 10:59
1. 최종 로그인 & 회원가입 구현
auth-components.ts
import { styled } from "styled-components";
export const Wrapper = styled.div`
height: 100vh; /* 화면 전체 높이를 기준으로 중앙 정렬 */
display: flex;
justify-content: center; /* 수평 중앙 */
align-items: center; /* 수직 중앙 */
flex-direction: column;
width: 420px;
margin: 0 auto; /* 화면 가운데로 */
padding: 50px 0px;
`;
export const Title = styled.h1`
font-size: 42px;
`;
export const Form = styled.form`
margin-top: 50px;
display: flex;
flex-direction: column;
gap: 10px;
width: 100%;
margin-bottom: 10px;
`;
export const Input = styled.input`
padding: 10px 20px;
border-radius: 50px;
border: none;
width: 100%;
font-size: 16px;
&[type="submit"] {
cursor: pointer;
&:hover {
opacity: 0.8;
}
}
`;
export const Error = styled.span`
font-weight: 600;
color: tomato;
`;
export const Switcher = styled.span`
margin-top: 20px;
a{
color : #1d9bf0;
}
`;
login.tsx
import { useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import { FirebaseError } from "firebase/app";
import { signInWithEmailAndPassword } from "firebase/auth";
import { auth } from "./firebase";
import { Error, Form, Input, Switcher, Title, Wrapper } from "../components/auth-components";
export default function Login() {
const navigate = useNavigate();
const [isLoading, setLoading] = useState(false);
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const {
target: { name, value },
} = e;
if (name === "email") {
setEmail(value);
} else if (name === "password") {
setPassword(value);
}
};
const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setError("");
if(isLoading === true || email === "" || password === ""){
return;
}
try {
setLoading(true);
await signInWithEmailAndPassword(auth, email, password);
navigate("/");
} catch (e) {
if(e instanceof FirebaseError){
setError(e.message);
}
} finally {
setLoading(false);
}
};
return (
<Wrapper>
<Title>Log into 𝕏</Title>
<Form onSubmit={onSubmit}>
<Input
onChange={onChange}
name="email"
value={email}
placeholder="Email"
type="email"
required
/>
<Input
onChange={onChange}
value={password}
name="password"
placeholder="Password"
type="password"
required
/>
<Input
type="submit"
value={isLoading ? "Loading..." : "Log in"}
/>
</Form>
{error !== "" ? <Error>{error}</Error> : null}
<Switcher>
Don't have an accrount? <Link to="/create-account">Create one →</Link>
</Switcher>
</Wrapper>
);
}
create-account.tsx
import { createUserWithEmailAndPassword, updateProfile } from "firebase/auth";
import { useState } from "react";
import { auth } from "./firebase";
import { Link, useNavigate } from "react-router-dom";
import { FirebaseError } from "firebase/app";
import { Form, Input, Switcher, Title, Wrapper } from "../components/auth-components";
export default function CreateAccount() {
const navigate = useNavigate();
const [isLoading, setLoading] = useState(false);
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const {
target: { name, value },
} = e;
if (name === "name") {
setName(value);
} else if (name === "email") {
setEmail(value);
} else if (name === "password") {
setPassword(value);
}
};
const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setError("");
if(isLoading === true ||name === "" || email === "" || password === ""){
return;
}
try {
setLoading(true);
const credentials = await createUserWithEmailAndPassword(auth, email, password)
// createUserWithEmailAndPassword는 Firebase Auth SDK에서 제공하는 함수로 인자에 auth 인스턴스가 필요하다.
// firebase.ts 파일 내 auth 인스턴스가 생성되어 있음!
await updateProfile(credentials.user, {displayName : name});
navigate("/");
} catch (e) {
if(e instanceof FirebaseError){
setError(e.message);
}
} finally {
setLoading(false);
}
};
return (
<Wrapper>
<Title>Join 𝕏</Title>
<Form onSubmit={onSubmit}>
<Input
onChange={onChange}
name="name"
value={name}
placeholder="Name"
type="text"
required
/>
<Input
onChange={onChange}
name="email"
value={email}
placeholder="Email"
type="email"
required
/>
<Input
onChange={onChange}
value={password}
name="password"
placeholder="Password"
type="password"
required
/>
<Input
type="submit"
value={isLoading ? "Loading..." : "Create Account"}
/>
</Form>
{error !== "" ? <Error>{error}</Error> : null}
<Switcher>
Already have an accrount?{""} <Link to="/login">Log in →</Link>
</Switcher>
</Wrapper>
);
}
nwitter-reloaded.z01
19.53MB
nwitter-reloaded.z02
19.53MB
nwitter-reloaded.z03
19.53MB
nwitter-reloaded.z04
19.53MB
nwitter-reloaded.z05
19.53MB
nwitter-reloaded.zip
14.59MB
2. 최초 로그인 구현
login.tsx
import { useState } from "react";
import { styled } from "styled-components";
import { useNavigate } from "react-router-dom";
import { FirebaseError } from "firebase/app";
import { signInWithEmailAndPassword } from "firebase/auth";
import { auth } from "./firebase";
const Wrapper = styled.div`
height: 100vh; /* 화면 전체 높이를 기준으로 중앙 정렬 */
display: flex;
justify-content: center; /* 수평 중앙 */
align-items: center; /* 수직 중앙 */
flex-direction: column;
width: 420px;
margin: 0 auto; /* 화면 가운데로 */
padding: 50px 0px;
`;
const Title = styled.h1`
font-size: 42px;
`;
const Form = styled.form`
margin-top: 50px;
display: flex;
flex-direction: column;
gap: 10px;
width: 100%;
margin-bottom: 10px;
`;
const Input = styled.input`
padding: 10px 20px;
border-radius: 50px;
border: none;
width: 100%;
font-size: 16px;
&[type="submit"] {
cursor: pointer;
&:hover {
opacity: 0.8;
}
}
`;
const Error = styled.span`
font-weight: 600;
color: tomato;
`;
export default function Login() {
const navigate = useNavigate();
const [isLoading, setLoading] = useState(false);
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const {
target: { name, value },
} = e;
if (name === "email") {
setEmail(value);
} else if (name === "password") {
setPassword(value);
}
};
const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setError("");
if(isLoading === true || email === "" || password === ""){
return;
}
try {
setLoading(true);
await signInWithEmailAndPassword(auth, email, password);
navigate("/");
} catch (e) {
if(e instanceof FirebaseError){
setError(e.message);
}
} finally {
setLoading(false);
}
};
return (
<Wrapper>
<Title>Log into 𝕏</Title>
<Form onSubmit={onSubmit}>
<Input
onChange={onChange}
name="email"
value={email}
placeholder="Email"
type="email"
required
/>
<Input
onChange={onChange}
value={password}
name="password"
placeholder="Password"
type="password"
required
/>
<Input
type="submit"
value={isLoading ? "Loading..." : "Log in"}
/>
</Form>
{error !== "" ? <Error>{error}</Error> : null}
</Wrapper>
);
}
3. 최초 회원가입 시 중복된 이메일 확인
create-account.tsx
import { createUserWithEmailAndPassword, updateProfile } from "firebase/auth";
import { useState } from "react";
import { styled } from "styled-components";
import { auth } from "./firebase";
import { useNavigate } from "react-router-dom";
import { FirebaseError } from "firebase/app";
const Wrapper = styled.div`
height: 100vh; /* 화면 전체 높이를 기준으로 중앙 정렬 */
display: flex;
justify-content: center; /* 수평 중앙 */
align-items: center; /* 수직 중앙 */
flex-direction: column;
width: 420px;
margin: 0 auto; /* 화면 가운데로 */
padding: 50px 0px;
`;
const Title = styled.h1`
font-size: 42px;
`;
const Form = styled.form`
margin-top: 50px;
display: flex;
flex-direction: column;
gap: 10px;
width: 100%;
`;
const Input = styled.input`
padding: 10px 20px;
border-radius: 50px;
border: none;
width: 100%;
font-size: 16px;
&[type="submit"] {
cursor: pointer;
&:hover {
opacity: 0.8;
}
}
`;
const Error = styled.span`
font-weight: 600;
color: tomato;
`;
export default function CreateAccount() {
const navigate = useNavigate();
const [isLoading, setLoading] = useState(false);
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const {
target: { name, value },
} = e;
if (name === "name") {
setName(value);
} else if (name === "email") {
setEmail(value);
} else if (name === "password") {
setPassword(value);
}
};
const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
if(isLoading === true ||name === "" || email === "" || password === ""){
return;
}
try {
setLoading(true);
const credentials = await createUserWithEmailAndPassword(auth, email, password)
// createUserWithEmailAndPassword는 Firebase Auth SDK에서 제공하는 함수로 인자에 auth 인스턴스가 필요하다.
// firebase.ts 파일 내 auth 인스턴스가 생성되어 있음!
await updateProfile(credentials.user, {displayName : name});
navigate("/");
} catch (e) {
if(e instanceof FirebaseError){
setError(e.message);
}
} finally {
setLoading(false);
}
};
return (
<Wrapper>
<Title>Join 𝕏</Title>
<Form onSubmit={onSubmit}>
<Input
onChange={onChange}
name="name"
value={name}
placeholder="Name"
type="text"
required
/>
<Input
onChange={onChange}
name="email"
value={email}
placeholder="Email"
type="email"
required
/>
<Input
onChange={onChange}
value={password}
name="password"
placeholder="Password"
type="password"
required
/>
<Input
type="submit"
value={isLoading ? "Loading..." : "Create Account"}
/>
</Form>
{error !== "" ? <Error>{error}</Error> : null}
</Wrapper>
);
}