App.js
import './App.css';
import { Outlet } from 'react-router-dom';
import Navbar from './components/Navbar';
function App() {
return (
<>
<Navbar />
<Outlet />
</>
);
}
export default App;
index.css
@tailwind base;
@tailwind components;
@tailwind utilities;
@import url("https://fonts.googleapis.com/css2?family=Noto+Sans+KR&family=Playfair+Display&display=swap");
body {
margin: 0;
font-family: Noto Sans KR;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
@apply text-slate-900;
}
#root {
@apply w-full text-inherit;
}
index.js
import React from "react";
import ReactDOM from "react-dom/client";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import "./index.css";
import App from "./App";
import NotFound from "./pages/NotFound";
import Home from "./pages/Home";
import AllProducts from "./pages/AllProducts";
import NewProduct from "./pages/NewProduct";
import ProductDetail from "./pages/ProductDetail";
import MyCart from "./pages/MyCart";
const router = createBrowserRouter([
{
path: "/",
element: <App />,
errorElement: <NotFound />,
children: [
{ index: true, path: "/", element: <Home /> },
{ path: "/products", element: <AllProducts /> },
{ path: "/products/new", element: <NewProduct /> },
{ path: "/products/:id", element: <ProductDetail /> },
{ path: "/cart", element: <MyCart /> },
],
},
]);
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
);
Navbar.jsx
import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import { HiPencilAlt } from "react-icons/hi";
import { login, logout, onUserStateChange } from "../api/firebase";
import User from "./User";
import Button from "./ui/Button";
// * 1-1. useState ์ ์ธ
export default function Navbar() {
const [user, setUser] = useState();
// * 2. ํ๋ฉด์ด ๋ง์ดํธ๋ ๋(reload๋ ๋) ๋ก๊ทธ์ธ์ด ๋์ด์๋์ง ์๋์ง ์ํ๋ฅผ ์์๋ณด๋ ํจ์ ํธ์ถ
useEffect(() => {
onUserStateChange((user) => {
setUser(user);
console.log("user? : ", user); // admin์ ๋ง๋ค๊ณ ์ถ์ ์ ์ ์ uid๋ฅผ ํ์ธํ๊ธฐ ์ํด ์์ฑ
});
}, []);
// * 1-2. onClick์ login ํจ์๋ฅผ ๋ฃ์ง ์๊ณ ์ด๋ ๊ฒ ์์ฑํ๋ ์ด์ ๋ firebase.js์ ์๋ user๋ฅผ ๋ฐ์์์ useState์ ์ง์ด๋ฃ๊ธฐ ์ํจ
/**
* ๋ก๊ทธ์ธํ ๋ ์ฌ์ฉ๋๋ ํจ์
*/
// ๋ฆฌํฉํ ๋ง
// const handleLogin = () => {
// login().then(setUser);
// };
// const handleLogout = () => {
// logout().then(setUser); // useState์ user๋ฅผ ๋น์ด๋ค. (null ์ํ๋ก ๋ง๋ฆ)
// };
return (
<div className="border-b border-t-slate-300">
<div className="w-full max-w-screen-2xl m-auto">
<header className="flex justify-between items-center p-5">
<h1 className="text-3xl font-logoFont tracking-widest">
RALPH<span className="pl-3 md:pl-6">LAUREN</span>
</h1>
<nav className="flex items-center gap-4">
<Link to="/products">Product</Link>
<Link to="/cart">Cart</Link>
{/* // * 5. isAdmin์ด true์ผ ๋๋ง ๋ณด์ด๋๋ก */}
{user && user.isAdmin && (<Link to="/products/new"><HiPencilAlt /></Link>)}
{/* // *3. User.jsx - user๊ฐ ์์ ๊ฒฝ์ฐ ์คํ */}
{user && <User user={user} />}
{/*// * 1-2. */}
{/* // * 2-1. ๋ฐ๋ก ์ ์ธํ์ง ์๊ณ firebase ์์ ์๋ ํจ์๋ฅผ ๋ฐ๋ก ํธ์ถ */}
{/* // * 6. Button ์ปดํฌ๋ํธ๋ก ์ ํ */}
{!user && <Button onClick={login} text={"login"} />}
{user && <Button onClick={logout} text={"logout"} />}
</nav>
</header>
</div>
</div>
);
}
User.jsx
import React from "react";
// * 3. ์ ์ ๋ฅผ ๋ํ๋ด๋ User.jsx ๋ง๋ฆ
export default function User({ user: { displayName, photoURL } }) {
// console.log("user: ", user);
return (
// shrink : ๋ถ๋ชจ ์์ญ์ด ์ค๋ฉด ์์ item๋ค๋ ๊ฐ์ด ์ค์ด๋ฆ - shrink-0์ ๊ทธ๊ฒ์ ๋ฐฉ์งํด์ค
<div className="flex items-center shrink-0">
<img
className="w-10 h-10 rounded-full mr-2"
src={photoURL}
alt={displayName}
/>
<span className="hidden md:block">{displayName}</span>
</div>
);
}
Button.jsx
import React from "react";
export default function Button({ onClick, text }) {
return (
<button
className="bg-brand text-white py-2 px-4 rounded-full hover:brightness-200 text-sm"
onClick={onClick}
>
{text}
</button>
);
}
firebase.js
import { initializeApp } from "firebase/app";
import {
getAuth,
signInWithPopup,
GoogleAuthProvider,
signOut,
onAuthStateChanged,
} from "firebase/auth";
import { getDatabase, get, ref } from "firebase/database";
const firebaseConfig = {
apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN,
projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
databaseURL: process.env.REACT_APP_FIREBASE_DB_URL,
};
const app = initializeApp(firebaseConfig);
const provider = new GoogleAuthProvider();
const auth = getAuth();
// * 1-1. ํจ์ ์ ์ธ์ firebase ์์์ ํจ
export function login() {
return signInWithPopup(auth, provider) // ์๋ ๋ฆฌํด๋ user ๊ฐ์ ๋ฐ์์์ ๊ฒฐ๊ณผ๊ฐ์ผ๋ก ๋ด๋ณด๋ด๊ธฐ ์ํด return ์์ฑ
.then((result) => {
// ๋ก๊ทธ์ธ ๋์๋์ง ๊ฒฐ๊ณผ๋ฅผ ์ป์ด์ด
const user = result.user;
// console.log("user? : ", user);
return user;
})
.catch(console.error);
}
export async function logout() {
return signOut(auth) // null๊ฐ์ด ๋ฆฌํด๋จ
.then(() => null); // null
// .catch((error) => {});
}
// * 2. ํ๋ฉด์ด ๋ง์ดํธ๋ ๋(reload๋ ๋) ๋ก๊ทธ์ธ์ด ๋์ด์๋์ง ์๋์ง ์ํ๋ฅผ ์์๋ณด๋ ํจ์ ์ ์ธ (callback)
export function onUserStateChange(callback) {
// ๋ง์ฝ user๊ฐ ์์ ๊ฒฝ์ฐ
onAuthStateChanged(auth, async (user) => {
// ? 1. ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์ธ ํ ๊ฒฝ์ฐ
// user && adminUser(user);
const updatedUser = user ? await adminUser(user) : null ;
callback(updatedUser);
});
}
// * 4. ์ค์๊ฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฌ์ฉ ์ ์ธ
const database = getDatabase(app);
// ? 2. ์ฌ์ฉ์๊ฐ ์ด๋๋ฏผ ๊ถํ์ด ์๋์ง ํ์ธ -> isAdmin์ user ์์ ๋ฃ์
// ๋ฐ์ดํฐ๋ฒ ์ด์ค ์์ ์๋ admins๋ฅผ ์ฐธ์กฐํ๋ ํจ์
function adminUser(user) {
// database ์์ admins key๊ฐ ์์
return get(ref(database, "admins")) // ref(database์ด๋ฆ, key๊ฐ)
// ๋ง์ฝ snapshot(๊ฒฐ๊ณผ๊ฐ)์ด ์กด์ฌํ๋ฉด
.then((snapshot) => {
if (snapshot.exists()) {
const admins = snapshot.val(); // snapshot์ value
// admins๊ฐ user.uid๋ฅผ ํฌํจํจ
const isAdmin = admins.includes(user.uid);
return { ...user, isAdmin }; // ๋ง์ user๋ค์ ํญ๋ชฉ ์ค์์ isAdmin๋ง ๋ผ์๋ฃ์
}
});
}
/*
1. ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์ธ ํ ๊ฒฝ์ฐ
2. ์ฌ์ฉ์๊ฐ ์ด๋๋ฏผ ๊ถํ์ด ์๋์ง ํ์ธ
3. ์ฌ์ฉ์์๊ฒ ์๋ ค์ค
*/
js
์ด๋ฏธ์ง๋ก ๋์ฒด
์ด๋ฏธ ๋ก๊ทธ์ธ ๋์ด ์๊ธฐ ๋๋ฌธ์ ์๋์ ๊ฐ์ด ๋ก๊ทธ์ธ ์ฐฝ์ด ๋จ์ง ์๊ณ ๋ฐ๋ก ๋ก๊ทธ์์์ผ๋ก ๋์ด๊ฐ
์๋ ํ์ธ์. ๋๋ฆผ์ฝ๋ฉ ์ด์์ ์ ๋๋ค.
์๊ฐ์๋ถ์ด ๋ฐ๊ฒฌํ์ฌ ์ ๊ณ ๊ฐ ๋ค์ด์ ๋ธ๋ก๊ทธ์ ๋ํด ์๊ฒ ๋์์ต๋๋ค.
ํด๋น ๊ฒ์๊ธ๊ณผ ๋ธ๋ก๊ทธ์ ์ฌ๋ฆฌ์ ๋ค์์ ๊ธ๋ค์ด ๋๋ฆผ์ฝ๋ฉ ์์นด๋ฐ๋ฏธ ์ ๋ฃ ๊ฐ์์ ๋ด์ฉ์ ์ ๋ฆฌ ํ์ ๊ฑธ๋ก ํ์ธ๋ฉ๋๋ค.
์ด๋ ์์ฐํ ์ ์๊ถ๋ฒ ์๋ฐ์ ๋๋ค.
ํด๋น ํฌ์คํธ๋ค์ ๋น๊ณต๊ฐ ๋๋ ์ญ์ ์ฒ๋ฆฌํ info@dream-coding.com ๋ก ๋ฉ์ผ ๋ถํ๋๋ฆฝ๋๋ค.
์์ผ๋ด์ ์ฒ๋ฆฌํ์ง ์์ผ์๋ฉด ๊ฐ์ ์ทจ์ ๋ฐ ๋ฒ์ ๋์ ํ๊ฒ ์ต๋๋ค.
๊ฐ์ ์์์ ์ ์๊ถ๋ฒ์ ๊ด๋ จํด์ ๋ธ๋ก๊ทธ์ ์ ๋ฆฌํ์ง ๋ง์ ๋ฌ๋ผ๊ณ ์๋ดํด ๋๋ ธ์ต๋๋ค.
https://academy.dream-coding.com/courses/player/react/lessons/1462