-
Notifications
You must be signed in to change notification settings - Fork 22
Mission 5 박한별 #54
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 박한별
Are you sure you want to change the base?
Mission 5 박한별 #54
Changes from all commits
d4f9f38
c2bc5b1
25d9864
957448a
6a25f3d
190af1c
3cb8835
ec7805c
b185bf8
7108d65
1ff4a90
3dc7874
4107fac
174b202
fd71794
0f4f9a2
c6cbd0f
ffc0596
dc1d720
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| { | ||
| "compilerOptions": { | ||
| "baseUrl": ".", | ||
| "paths": { | ||
| "@/*": ["src/*"], | ||
| "@pages/*": ["src/pages/*"], | ||
| "@components/*": ["src/components/*"], | ||
| "@common/*": ["src/components/common/*"], | ||
| "@hooks/*": ["src/hooks/*"], | ||
| "@api/*": ["src/api/*"], | ||
| "@sb/*": ["src/supabase/*"], | ||
| "@sbCtx/*": ["src/supabase/context/*"], | ||
| "@utils/*": ["src/utilities/*"] | ||
| } | ||
| }, | ||
| "include": ["src"] | ||
| } |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,14 +6,13 @@ import { | |
| SearchResults, | ||
| LoginPage, | ||
| SignupPage, | ||
| } from "./pages"; | ||
| import Layout from "./components/Layout"; | ||
|
|
||
| import { useSupabaseAuth } from "./supabase"; | ||
| import { UserContext } from "./supabase/context/UserContext"; | ||
|
|
||
| import OAuthCallback from "./components/OAuthCallback"; | ||
| MyPage, | ||
| } from "@pages"; | ||
| import Layout from "@components/Layout"; | ||
| import OAuthCallback from "@components/OAuthCallback"; | ||
|
|
||
| import { useSupabaseAuth } from "@sb"; | ||
| import { UserContext } from "@sbCtx/UserContext"; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 부분은 context라는 폴더로 만들어서 관리하면 좋습니다! sbCtx라는 명은 너무 불명확 합니다. |
||
| export default function App() { | ||
| const { getUserInfo } = useSupabaseAuth(); | ||
| const { setUser } = useContext(UserContext); | ||
|
|
@@ -34,6 +33,7 @@ export default function App() { | |
| <Route index element={<Home />} /> | ||
| <Route path="details/:id" element={<MovieDetail />} /> | ||
| <Route path="search" element={<SearchResults />} /> | ||
| <Route path="mypage" element={<MyPage />} /> | ||
| </Route> | ||
|
|
||
| <Route path="/login" element={<LoginPage />} /> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,30 +1,56 @@ | ||
| import { useNavigate } from "react-router-dom"; | ||
| import { useUser } from "@sbCtx/UserContext"; | ||
| import { useBookmarks } from "@/context/BookmarkContext"; | ||
| import { Button } from "."; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 절대 경로로 불러오면 더 좋을 것 같아요 ! |
||
|
|
||
| const baseUrl = "https://image.tmdb.org/t/p/w500"; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. baseUrl은 constants 폴더에 Urls라는 파일 만들어서 관리하면 더 깔끔해질 것 같아요 ! |
||
|
|
||
| export default function MovieCard({ | ||
| title, | ||
| poster_path, | ||
| vote_average, | ||
| size = "md", | ||
| }) { | ||
| const sizeClass = size === "sm" ? "w-40" : size === "md" ? "w-52" : "w-64"; | ||
| export default function MovieCard({ id, title, poster_path, vote_average }) { | ||
| const navigate = useNavigate(); | ||
| const { user } = useUser(); | ||
| const { toggleBookmark, isBookmarked } = useBookmarks(); | ||
|
|
||
| const handleBookmark = (e) => { | ||
| e.stopPropagation(); | ||
|
|
||
| if (!user) { | ||
| alert("로그인이 필요합니다!"); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. alert는 ux 적으로 좋지 않기 때문에 |
||
| return; | ||
| } | ||
|
|
||
| toggleBookmark({ id, title, poster_path, vote_average }); | ||
| }; | ||
|
|
||
| return ( | ||
| <> | ||
| <div | ||
| className={`${sizeClass} bg-white shadow-md hover:shadow-xl rounded-lg | ||
| overflow-hidden cursor-pointer transform hover:scale-105 transition duration-300`} | ||
| <div | ||
| className="relative cursor-pointer group" | ||
| onClick={() => navigate(`/details/${id}`)} | ||
| > | ||
| {/* 이미지 */} | ||
| <img | ||
| src={`${baseUrl}${poster_path}`} | ||
| alt={title} | ||
| className="rounded-lg shadow-md w-full group-hover:opacity-90 transition" | ||
| /> | ||
|
|
||
| {/* 평점 */} | ||
| <span className="absolute bottom-2 left-2 bg-black/70 text-white text-sm px-2 py-1 rounded"> | ||
| ⭐ {vote_average} | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 평점의 경우 |
||
| </span> | ||
|
|
||
| {/* ✅ 북마크 버튼 */} | ||
| <Button | ||
| onClick={handleBookmark} | ||
| className={`absolute top-2 right-2 px-2 py-1 rounded-full text-sm font-bold transition | ||
| ${ | ||
| isBookmarked(id) | ||
| ? "bg-yellow-400 text-black" | ||
| : "bg-white/80 text-gray-700" | ||
| } | ||
| `} | ||
| > | ||
| <div className="w-full aspect-[2/3]"> | ||
| <img | ||
| src={poster_path ? `${baseUrl}${poster_path}` : "/placeholder.png"} | ||
| alt={title} | ||
| className="w-full h-72 object-cover" | ||
| /> | ||
| </div> | ||
| <div className="p-4 flex flex-col justify-between h-28"> | ||
| <h2 className="text-base font-semibold mb-1">{title}</h2> | ||
| <p className="text-sm text-gray-500">⭐ {vote_average}</p> | ||
| </div> | ||
| </div> | ||
| </> | ||
| {isBookmarked(id) ? "★" : "☆"} | ||
| </Button> | ||
| </div> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| export { default as Button } from "./common/Button"; | ||
| export { default as InputField } from "./common/InputField"; | ||
| export { default as NavBar } from "./NavBar"; | ||
| export { default as MovieCard } from "./MovieCard"; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| import { createContext, useContext, useState, useEffect } from "react"; | ||
|
|
||
| export const BookmarkContext = createContext(); | ||
|
|
||
| export function BookmarkProvider({ children }) { | ||
| const [bookmarks, setBookmarks] = useState([]); | ||
|
|
||
| // localStorage에서 복원 | ||
| useEffect(() => { | ||
| const saved = localStorage.getItem("bookmarks"); | ||
| if (saved) setBookmarks(JSON.parse(saved)); | ||
| }, []); | ||
|
|
||
| // 저장 | ||
| useEffect(() => { | ||
| localStorage.setItem("bookmarks", JSON.stringify(bookmarks)); | ||
| }, [bookmarks]); | ||
|
|
||
| const toggleBookmark = (movie) => { | ||
| setBookmarks((prev) => { | ||
| const exists = prev.some((m) => m.id === movie.id); | ||
| if (exists) { | ||
| return prev.filter((m) => m.id !== movie.id); | ||
| } | ||
| return [...prev, movie]; | ||
| }); | ||
| }; | ||
|
|
||
| const removeBookmark = (id) => { | ||
| setBookmarks((prev) => prev.filter((m) => m.id !== id)); | ||
| }; | ||
|
|
||
| const isBookmarked = (id) => bookmarks.some((m) => m.id === id); | ||
|
Comment on lines
+6
to
+33
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 부분은 비즈니스 로직 분리 해주면 좋을 것 같아요 ! |
||
|
|
||
| return ( | ||
| <BookmarkContext.Provider | ||
| value={{ bookmarks, toggleBookmark, isBookmarked, removeBookmark }} | ||
| > | ||
| {children} | ||
| </BookmarkContext.Provider> | ||
| ); | ||
| } | ||
|
|
||
| export const useBookmarks = () => useContext(BookmarkContext); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
components에 barrel 만들어서 사용하면 더 좋을 것 같아요