Skip to content

Commit

Permalink
Merge pull request #7 from P-r4Tes/feat/firebase-authentication
Browse files Browse the repository at this point in the history
Feat/firebase authentication
  • Loading branch information
SayisMe authored Mar 9, 2024
2 parents 93143bf + 19c0d16 commit 6723742
Show file tree
Hide file tree
Showing 15 changed files with 2,412 additions and 2,031 deletions.
2 changes: 2 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ module.exports = {
"plugin:@typescript-eslint/recommended",
"eslint-config-prettier",
"plugin:storybook/recommended",
"next",
"next/core-web-vitals",
],

plugins: ["@typescript-eslint", "import", "prettier", "react", "react-hooks"],
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
/node_modules
/.pnp
.pnp.js
.pnp.cjs
.yarn/install-state.gz

# testing
Expand Down
4,111 changes: 2,120 additions & 1,991 deletions .pnp.cjs

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"next": "14.1.0",
"prettier-plugin-tailwindcss": "^0.5.11",
"react": "^18",
"react-dom": "^18"
"react-dom": "^18",
"react-query": "^3.39.3"
},
"type": "commonjs",
"devDependencies": {
Expand All @@ -47,7 +48,7 @@
"autoprefixer": "^10.0.1",
"chromatic": "^11.0.0",
"eslint": "^8.57.0",
"eslint-config-next": "14.1.0",
"eslint-config-next": "^14.1.3",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-prettier": "^5.1.3",
Expand Down
16 changes: 9 additions & 7 deletions src/__test__/api/user.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@ describe("User functions", () => {
jest.clearAllMocks();
});

test("name이 비어있으면 postUser는 에러를 반환해야 한다", () => {
const body = { name: "", groups: ["g1"], personalSchedules: ["s1"] };
expect(() => postUser(body)).toThrow("Invalid name");
test("email 비어있으면 postUser는 에러를 반환해야 한다", () => {
const id = "TEST_USER_ID"; // id 추가
const body = { email: "", name: "name", groups: ["g1"], personalSchedules: ["s1"] };
expect(() => postUser(id, body)).toThrow("Invalid email");
});

test("id가 비어있지 않으면 getUser는 getFirebase를 호출해야 한다", () => {
Expand All @@ -42,10 +43,11 @@ describe("User functions", () => {
expect(firebaseModule.deleteFirebase).toHaveBeenCalledWith("users", id);
});

test("name이 비어있지 않으면 postUser는 postFirebase를 호출해야 한다", () => {
const body = { name: "CSKIM", groups: ["g1"], personalSchedules: ["s1"] };
postUser(body);
expect(firebaseModule.postFirebase).toHaveBeenCalledWith("users", body);
test("email이 비어있지 않으면 postUser는 postFirebase를 호출해야 한다", () => {
const id = "TEST_USER_ID"; // id 추가
const body = { email: "[email protected]", name: "CSKIM", groups: ["g1"], personalSchedules: ["s1"] };
postUser(id, body);
expect(firebaseModule.postFirebase).toHaveBeenCalledWith("users", id, body);
});

test("전달인자가 유효하지 않다면 pushUserPersonalSchedule는 에러를 반환해야 한다", () => {
Expand Down
7 changes: 4 additions & 3 deletions src/api/firebase.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { db } from "@/lib/firebaseConfig";
import { addDoc, arrayRemove, arrayUnion, collection, deleteDoc, doc, getDoc, updateDoc } from "firebase/firestore";
import { arrayRemove, arrayUnion, collection, deleteDoc, doc, getDoc, updateDoc, addDoc } from "firebase/firestore";

export const postFirebase = async <T extends TableTypes>(table: T, body: TalbeField<T>) => {
// 조건부 파라미터를 추가하고 addDoc을 바꾸고 둘다 해야한다.
export const postFirebase = async <T extends TableTypes>(table: T, id: string, body: TalbeField<T>) => {
try {
await addDoc(collection(db, table), body);
await addDoc(collection(db, table), { ...body, id });
} catch (e) {
console.error("Invalid Data");
}
Expand Down
6 changes: 3 additions & 3 deletions src/api/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import {
updateFirebaseField,
} from "./firebase";

export const postUser = (body: user) => {
if (isEmpty(body.name)) throw new Error("Invalid name");
postFirebase<"users">("users", body);
export const postUser = (id: string, body: user) => {
if (isEmpty(body.email)) throw new Error("Invalid email");
postFirebase<"users">("users", id, body);
};

export const getUser = (id: string) => {
Expand Down
5 changes: 4 additions & 1 deletion src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client";

import LoginForm from "@/components/LoginForm";
import SignupForm from "@/components/SignupForm";
import { getGroup } from "@/api/groups";
import { getSchedule } from "@/api/schedules";
import { getTag } from "@/api/tags";
Expand All @@ -26,6 +27,8 @@ export default function Home() {
return (
<main className="flex flex-col min-h-screen" data-testid="root-layout">
<Calendar />
<LoginForm />
<SignupForm />
</main>
);
}
81 changes: 81 additions & 0 deletions src/components/LoginForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { useState } from "react";
import { signInWithEmailAndPassword, GoogleAuthProvider, signInWithPopup } from "firebase/auth";
import { auth } from "@/lib/firebaseAuth";
import { doc, setDoc } from "firebase/firestore";
import { db } from "@/lib/firebaseConfig";

export default function LoginForm() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");

const handleEmailLogin = async e => {
e.preventDefault();
try {
await signInWithEmailAndPassword(auth, email, password);
alert("이메일 로그인에 성공했습니다!");
} catch (error) {
alert("이메일 로그인에 실패했습니다.");
}
};

const handleGoogleLogin = async () => {
const provider = new GoogleAuthProvider();
try {
const result = await signInWithPopup(auth, provider);
const user = result.user;
await setDoc(
doc(db, "users", user.uid),
{
email: user.email,
groups: [],
name: user.displayName,
personalSchedules: [],
},
{ merge: true }
);
alert("구글 로그인에 성공했습니다!");
} catch (error) {
alert("구글 로그인에 실패했습니다.");
console.log(error.message);
}
};

return (
<div className="max-w-md mx-auto p-5 shadow-md">
<h2 className="text-lg font-bold mb-4">로그인</h2>
<form onSubmit={handleEmailLogin} className="flex flex-col">
<div className="mb-5">
<input
type="email"
placeholder="이메일"
value={email}
onChange={e => setEmail(e.target.value)}
className="p-2.5 text-base rounded-md border border-gray-300"
/>
</div>
<div className="mb-5">
<input
type="password"
placeholder="비밀번호"
value={password}
onChange={e => setPassword(e.target.value)}
className="p-2.5 text-base rounded-md border border-gray-300"
/>
</div>
<button
type="submit"
className="p-2.5 text-base bg-blue-500 text-white rounded-md border-none cursor-pointer hover:bg-blue-700"
>
이메일로 로그인
</button>
<button
type="button"
onClick={handleGoogleLogin}
className="p-2.5 text-base bg-blue-500 text-white rounded-md border-none cursor-pointer hover:bg-blue-700 mt-3"
>
구글로 로그인
</button>
</form>
</div>
);
}
57 changes: 57 additions & 0 deletions src/components/SignupForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { useState } from "react";
import { createUserWithEmailAndPassword } from "firebase/auth";
import { auth } from "@/lib/firebaseAuth";
import { doc, setDoc } from "firebase/firestore";
import { db } from "@/lib/firebaseConfig";

export default function SignupForm() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");

const handleSignUp = async e => {
e.preventDefault();
try {
const userCredential = createUserWithEmailAndPassword(auth, email, password);
const user = (await userCredential).user;
await setDoc(doc(db, "users", user.uid), {
email: email,
groups: [],
name: "",
personalSchedules: [],
});
alert("회원가입에 성공했습니다!");
} catch (error) {
alert("회원가입에 실패했습니다.");
console.log(error.message);
}
};

return (
<div className="max-w-md mx-auto p-5 shadow-md">
<h2 className="text-lg font-bold mb-4">회원가입</h2>
<form onSubmit={handleSignUp} className="flex flex-col space-y-4">
<div>
<input
type="email"
placeholder="이메일"
value={email}
onChange={e => setEmail(e.target.value)}
className="w-full p-2 text-base border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<div>
<input
type="password"
placeholder="비밀번호"
value={password}
onChange={e => setPassword(e.target.value)}
className="w-full p-2 text-base border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<button type="submit" className="w-full p-2 bg-blue-500 text-white rounded-md hover:bg-blue-700">
회원가입
</button>
</form>
</div>
);
}
4 changes: 4 additions & 0 deletions src/lib/firebaseAuth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { app } from "./firebaseConfig";
import { getAuth } from "firebase/auth";

export const auth = getAuth(app);
2 changes: 1 addition & 1 deletion src/lib/firebaseConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ const firebaseConfig = {
measurementId,
};

const app = initializeApp(firebaseConfig);
export const app = initializeApp(firebaseConfig);

export const db = getFirestore(app);
2 changes: 1 addition & 1 deletion src/lib/functions/stringValidation.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export function isEmpty(str: string): boolean {
const result = str.trim();
const result = str?.trim();
if (result === "") return true;
return false;
}
Expand Down
1 change: 1 addition & 0 deletions src/types/firestore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ type TableTypes = "users" | "tags" | "schedules" | "groups";
type id = { id: string };

type user = {
email: string;
name: string;
groups: string[];
personalSchedules: string[];
Expand Down
Loading

0 comments on commit 6723742

Please sign in to comment.