-
Notifications
You must be signed in to change notification settings - Fork 8
[6주차] Team STORIX 김윤성&이채연 과제 제출합니다. #12
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: main
Are you sure you want to change the base?
Changes from all commits
2ab4a9a
ba29ef6
7075e35
4d18414
510fa94
8d29a32
a1c0056
d9d1cac
0e8e6f5
d5bd91b
db0c147
f1b45fe
2eda989
df73c66
c4fe6cf
31a7f5b
f8ad975
675b746
11c2bfe
a907909
0707199
896540d
16e2a2c
c5b124b
f3caa87
9cd2659
9206590
f391696
4a21459
7040f5e
3b25b59
8f81f6c
2bc8151
a2eed46
cfdd0ab
69323ad
157e84e
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,41 @@ | ||
| # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. | ||
|
|
||
| # dependencies | ||
| /node_modules | ||
| /.pnp | ||
| .pnp.* | ||
| .yarn/* | ||
| !.yarn/patches | ||
| !.yarn/plugins | ||
| !.yarn/releases | ||
| !.yarn/versions | ||
|
|
||
| # testing | ||
| /coverage | ||
|
|
||
| # next.js | ||
| /.next/ | ||
| /out/ | ||
|
|
||
| # production | ||
| /build | ||
|
|
||
| # misc | ||
| .DS_Store | ||
| *.pem | ||
|
|
||
| # debug | ||
| npm-debug.log* | ||
| yarn-debug.log* | ||
| yarn-error.log* | ||
| .pnpm-debug.log* | ||
|
|
||
| # env files (can opt-in for committing if needed) | ||
| .env* | ||
|
|
||
| # vercel | ||
| .vercel | ||
|
|
||
| # typescript | ||
| *.tsbuildinfo | ||
| next-env.d.ts |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| { | ||
| "tabWidth": 2, | ||
| "singleQuote": true, | ||
| "printWidth": 80, | ||
| "semi": true, | ||
| "bracketSpacing": true, | ||
| "objectWrap": "preserve", | ||
| "bracketSameLine": false, | ||
| "arrowParens": "always" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). | ||
|
|
||
| ## Getting Started | ||
|
|
||
| First, run the development server: | ||
|
|
||
| ```bash | ||
| npm run dev | ||
| # or | ||
| yarn dev | ||
| # or | ||
| pnpm dev | ||
| # or | ||
| bun dev | ||
| ``` | ||
|
|
||
| Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. | ||
|
|
||
| You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. | ||
|
|
||
| This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. | ||
|
|
||
| ## Learn More | ||
|
|
||
| To learn more about Next.js, take a look at the following resources: | ||
|
|
||
| - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. | ||
| - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. | ||
|
|
||
| You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! | ||
|
|
||
| ## Deploy on Vercel | ||
|
|
||
| The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. | ||
|
|
||
| Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| // app/(frame)/detail/[media]/[id]/page.tsx | ||
| import Detail from '@components/detail/detail'; | ||
|
|
||
| type Params = { | ||
| params: Promise<{ media: 'movie' | 'tv'; id: string }>; | ||
| }; | ||
|
|
||
| async function fetchTmdbDetail(media: 'movie' | 'tv', id: string) { | ||
| const token = | ||
| process.env.TMDB_ACCESS_TOKEN || | ||
| process.env.NEXT_PUBLIC_TMDB_ACCESS_TOKEN || | ||
| ''; | ||
| const url = `https://api.themoviedb.org/3/${media}/${id}?language=eng-US`; | ||
|
|
||
| const res = await fetch(url, { | ||
|
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이나 엑세스 토큰을 HTTP 헤더에 넣은 기본 함수를 따로 생성하고, 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. API 모듈 분리 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. 확인해보니 tmdb.ts에 tmdbFetch라는 함수로 기본함수를 만들어 놓으셨는데, |
||
| headers: { | ||
| Authorization: `Bearer ${token}`, | ||
| 'Content-Type': 'application/json;charset=utf-8', | ||
| }, | ||
| cache: 'no-store', | ||
| }); | ||
| if (!res.ok) { | ||
| throw new Error(`TMDB detail ${media}/${id} ${res.status}`); | ||
| } | ||
| return res.json(); | ||
|
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. axios 라이브러리 적용하면 코드가 더 간단해질 수 있을 거 같아요 |
||
| } | ||
|
Comment on lines
+8
to
+26
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. 주완 님이 리뷰남겨주신 것처럼 해당 API 호출은 api 폴더 내에서 함수로 정의해주시면 더 좋을 것 같습니다! |
||
|
|
||
| export default async function Page({ params }: Params) { | ||
| const { media: mediaParam, id } = await params; | ||
| const media = mediaParam === 'tv' ? 'tv' : 'movie'; | ||
| try { | ||
| const item = await fetchTmdbDetail(media, id); | ||
| return ( | ||
| <main> | ||
| <Detail item={item} /> | ||
| </main> | ||
| ); | ||
| } catch (err) { | ||
| return ( | ||
| <main className="text-white p-6"> | ||
| <h1 className="text-xl font-semibold"> | ||
| 상세 정보를 불러올 수 없습니다. | ||
| </h1> | ||
| <p className="mt-2 text-white/80 text-sm"> | ||
| 잠시 후 다시 시도해 주세요. | ||
| </p> | ||
| </main> | ||
| ); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| // src/app/pages/home/layout.tsx | ||
| import type { ReactNode } from 'react'; | ||
| import Header from '@home/header'; | ||
|
|
||
| export default function HomeLayout({ children }: { children: ReactNode }) { | ||
| return ( | ||
| <> | ||
| {/* 헤더: 오버레이 */} | ||
| <div className="absolute top-3 left-0 right-0 z-30"> | ||
| <Header /> | ||
| </div> | ||
| {children} | ||
| </> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| // src/app/pages/home/page.tsx | ||
| import Section from '@home/section'; | ||
| import Hero from '@home/hero'; | ||
| import CardRow from '@home/cardrow'; | ||
| import HeroOverlay from '@home/herooverlay'; | ||
| import Previews from '@home/previews'; | ||
|
|
||
| // 기존 섹션들 + 새 섹션들 임포트 | ||
| import { | ||
| getPopularOnNetflix, | ||
| getTrendingNow, | ||
| getTop10InKoreaToday, | ||
| getKoreanMovies, | ||
| getNetflixOriginals, | ||
| getNewReleases, | ||
| } from '@/lib/tmdb'; | ||
|
|
||
| export default async function Page() { | ||
| // My List는 아직 저장소 연동이 없으니 비워둠(섹션은 보이되, 내용 없으면 표시문구가 나옴) | ||
| const myList: any[] = []; | ||
|
|
||
| const [pPopular, pTrending, pTop10, pKrMovies, pOriginals, pNew] = | ||
| await Promise.allSettled([ | ||
| getPopularOnNetflix('movie'), | ||
| getTrendingNow(), | ||
| getTop10InKoreaToday(), | ||
| getKoreanMovies(), | ||
| getNetflixOriginals(), | ||
| getNewReleases(), | ||
| ]); | ||
|
|
||
| const popularNetflix = | ||
| pPopular.status === 'fulfilled' ? (pPopular.value.results ?? []) : []; | ||
| const trending = | ||
| pTrending.status === 'fulfilled' ? (pTrending.value.results ?? []) : []; | ||
| const top10 = | ||
| pTop10.status === 'fulfilled' ? (pTop10.value.results ?? []) : []; | ||
| const koreanMovies = | ||
| pKrMovies.status === 'fulfilled' ? (pKrMovies.value.results ?? []) : []; | ||
| const netflixOriginals = | ||
| pOriginals.status === 'fulfilled' ? (pOriginals.value.results ?? []) : []; | ||
| const newReleases = | ||
| pNew.status === 'fulfilled' ? (pNew.value.results ?? []) : []; | ||
|
|
||
| // Hero 후보: 트렌딩에서 하나, 없으면 다른 섹션에서 | ||
| const heroItem = | ||
| trending[0] ?? | ||
| popularNetflix[0] ?? | ||
| koreanMovies[0] ?? | ||
| netflixOriginals[0] ?? | ||
| newReleases[0] ?? | ||
| top10[0]; | ||
|
|
||
| const heroRank = heroItem | ||
| ? top10.findIndex((x: any) => x.id === (heroItem as any).id) + 1 || | ||
| undefined | ||
| : undefined; | ||
|
|
||
| return ( | ||
| <> | ||
| {/* Hero는 이미지만 (상단부터 415px) */} | ||
| <Hero item={heroItem} /> | ||
| <HeroOverlay rank={heroRank} /> | ||
|
|
||
| {/* 헤더에 가리지 않도록 Hero 이후부터만 여백 */} | ||
| <div className="mt-14 px-6 space-y-10"> | ||
| <Section title="Previews"> | ||
| <Previews items={trending.length ? trending : popularNetflix} /> | ||
| </Section> | ||
|
|
||
| <Section title="Continue Watching"> | ||
| <CardRow items={trending} /> | ||
| </Section> | ||
|
|
||
| <Section title="Popular on Netflix"> | ||
| <CardRow items={popularNetflix} /> | ||
| </Section> | ||
|
|
||
| <Section title="Trending Now"> | ||
| <CardRow items={trending} /> | ||
| </Section> | ||
|
|
||
| <Section title="Top 10 in Korea Today"> | ||
| <CardRow items={top10} numbered /> | ||
| </Section> | ||
|
|
||
| <Section title="Korean Movies"> | ||
| <CardRow items={koreanMovies} /> | ||
| </Section> | ||
|
|
||
| <Section title="Netflix Originals"> | ||
| <CardRow items={netflixOriginals} /> | ||
| </Section> | ||
|
|
||
| <Section title="New Releases"> | ||
| <CardRow items={newReleases} /> | ||
| </Section> | ||
| </div> | ||
| </> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| import Menu from '@home/menu'; | ||
|
|
||
| export default function FrameLayout({ | ||
| children, | ||
| }: { | ||
| children: React.ReactNode; | ||
| }) { | ||
| return ( | ||
| <div className="w-full h-dvh bg-[gray] flex items-start justify-center"> | ||
| <div className="relative w-[375px] h-dvh max-w-[100vh] text-white bg-black"> | ||
| <section | ||
| aria-label="메인 화면" | ||
| className=" | ||
| absolute inset-0 | ||
| overflow-y-auto overscroll-y-contain | ||
| [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden | ||
| pt-0 | ||
| pb-[calc(48px+24px+env(safe-area-inset-bottom))] | ||
| " | ||
| > | ||
| {children} | ||
| </section> | ||
|
|
||
| <div className="absolute left-0 right-0 bottom-[24px] z-20 flex justify-center"> | ||
| <Menu /> | ||
| </div> | ||
|
|
||
| <div | ||
| className="absolute left-0 right-0 bottom-0 h-6 z-10 bg-black" | ||
| aria-hidden | ||
| > | ||
| <img | ||
| src="/home-indicator.svg" | ||
| alt="" | ||
| className="h-full w-auto mx-auto select-none pointer-events-none" | ||
| /> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } |
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/detail/Detail 이런식으로 수정해야할 거 같습니다