-
Notifications
You must be signed in to change notification settings - Fork 0
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
Review์ฉ PR #1
base: empty
Are you sure you want to change the base?
Review์ฉ PR #1
Changes from all commits
438bbae
9bcce41
5f81e01
afd7520
e9b46fd
08f435c
752bd48
b096771
51905be
01de1c7
c348690
5445c08
2f7f3ae
486e231
ab9c17c
6c31713
71f0703
3000bd8
a81c704
b1209ba
dc8c0b9
9a85ca7
232393e
4666379
87f6979
b36c384
5acad80
7b878be
2df5c94
54c6d64
4b5d05f
884356a
1162224
1b08f75
eb45bf1
e3b1c20
ca52a21
60d37d3
f6714f1
59a7188
5aefd92
d094f85
1570df4
94cf01a
549a047
efa686c
bc637f4
562235d
be0d995
95132be
168c587
33aa0c3
5916a35
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,3 @@ | ||
{ | ||
"extends": "next/core-web-vitals" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. | ||
|
||
# dependencies | ||
/node_modules | ||
/.pnp | ||
.pnp.js | ||
.yarn/install-state.gz | ||
|
||
# testing | ||
/coverage | ||
|
||
# next.js | ||
/.next/ | ||
/out/ | ||
|
||
# production | ||
/build | ||
|
||
# misc | ||
.DS_Store | ||
*.pem | ||
|
||
# debug | ||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
|
||
# local env files | ||
.env*.local | ||
|
||
# vercel | ||
.vercel | ||
|
||
# typescript | ||
*.tsbuildinfo | ||
next-env.d.ts |
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://github.com/vercel/next.js/tree/canary/packages/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/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. | ||
|
||
## 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/deployment) for more details. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { instance } from ".."; | ||
|
||
export const deleteFollower = async (username: string) => { | ||
const response = await instance.delete(`/user/following/${username}`); | ||
|
||
return response; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { instance } from ".."; | ||
|
||
export const putFollower = async (username: string) => { | ||
const response = await instance.put(`/user/following/${username}`); | ||
|
||
return response; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import axios, { AxiosInstance } from "axios"; | ||
|
||
export const instance: AxiosInstance = axios.create({ | ||
baseURL: process.env.NEXT_PUBLIC_BASE_URL, | ||
headers: { | ||
Accept: "application/vnd.github+json", | ||
"X-GitHub-Api-Version": "2022-11-28", | ||
"If-None-Match": "", | ||
}, | ||
}); | ||
|
||
// token ์ ๋ ฅ ํ ํค๋์ ์ ์ฅํ๋ ํจ์ | ||
export const setInstanceToken = (token: string) => { | ||
instance.defaults.headers.common["Authorization"] = `Bearer ${token}`; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { UserTypes } from "@/types/user/UserTypes"; | ||
import { instance } from ".."; | ||
|
||
const PER_PAGE = 100; | ||
|
||
export const getUserFollowInfo = async () => { | ||
const { data: followingData } = await instance.get<Array<UserTypes>>( | ||
`/user/following?per_page=${PER_PAGE}` | ||
); | ||
|
||
const { data: followersData } = await instance.get<Array<UserTypes>>( | ||
`user/followers?per_page=${PER_PAGE}` | ||
); | ||
|
||
return { followingData, followersData }; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { UserProfileInfoTypes } from "@/types/user/UserTypes"; | ||
import { instance } from ".."; | ||
|
||
export const getUserProfileInfo = async () => { | ||
const { data } = await instance.get<UserProfileInfoTypes>("/user"); | ||
return data; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import TokenInputContainer from "@/components/home/TokenInputContainer.tsx"; | ||
import Link from "next/link"; | ||
import * as styles from "../../styles/home/HomeContainerStyle.css.ts"; | ||
|
||
const HomePage = () => { | ||
return ( | ||
<section className={styles.HomeWrapper}> | ||
<article className={styles.TokenBoxContainer}> | ||
<div className={styles.TokenLinkBox}> | ||
<Link | ||
href={"https://github.com/settings/tokens"} | ||
className={styles.TokenLinkBtn} | ||
target="_blank" | ||
> | ||
Github Token ๋ง๋ค๋ฌ ๊ฐ๊ธฐ | ||
</Link> | ||
<p className={styles.TokenInfoText}> | ||
๏น ํ ํฐ ๋ฐ๊ธ ์ ๊ถํ user(Update ALL user data)๋ฅผ ์ฒดํฌํด์ฃผ์ธ์! | ||
</p> | ||
</div> | ||
<TokenInputContainer /> | ||
</article> | ||
</section> | ||
); | ||
}; | ||
|
||
export default HomePage; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
"use client"; | ||
|
||
import { setInstanceToken } from "@/apis"; | ||
import Error from "@/components/common/Error"; | ||
import FollowList from "@/components/follow-list/FollowList"; | ||
import UserProfile from "@/components/follow-list/UserProfile"; | ||
import FollowListSkeleton from "@/components/follow-list/skeletonUi/FollowListSkeleton"; | ||
import { useGetCombinedUserInfo } from "@/hooks/user/useGetCombinedUserInfo"; | ||
import { getSessionStorageHandler } from "@/utils/getSessionStorageHandler"; | ||
import * as styles from "../../styles/follow-list/FollowListPageContainerStyle.css"; | ||
|
||
const FollowListPage = () => { | ||
const token: string | null = | ||
typeof window !== "undefined" | ||
? getSessionStorageHandler().getItem("token") | ||
: null; | ||
const isHasToken = | ||
typeof window !== "undefined" && | ||
getSessionStorageHandler().hasItem("token"); | ||
|
||
const { isLoading, error, data } = useGetCombinedUserInfo(); | ||
|
||
if (!isHasToken || !token) return <Error />; | ||
setInstanceToken(token); | ||
|
||
const userProfileData = data[0]; | ||
const followData = data[1]; | ||
|
||
return ( | ||
<section className={styles.FollowListPageWrapper}> | ||
{isLoading && <FollowListSkeleton />} | ||
{error && <Error />} | ||
{!isLoading && !error && ( | ||
<UserProfile | ||
userProfile={userProfileData} | ||
followCnt={{ | ||
following: followData.followingData.length, | ||
followers: followData.followersData.length, | ||
}} | ||
/> | ||
)} | ||
{!isLoading && !error && <FollowList followData={followData} />} | ||
</section> | ||
); | ||
}; | ||
|
||
export default FollowListPage; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import type { Metadata } from "next"; | ||
|
||
import Layout from "@/components/Layout"; | ||
import "../styles/globalStyle.css"; | ||
|
||
export const metadata: Metadata = { | ||
title: "github ๋งํ ํ์ง๊ธฐ", | ||
description: "github ํ๋ก์ฐ ์ํ๋ฅผ ๋ณด์ฌ์ฃผ๋ ๋งํ ํ์ง๊ธฐ์ ๋๋ค.", | ||
}; | ||
|
||
export default function RootLayout({ | ||
children, | ||
}: Readonly<{ | ||
children: React.ReactNode; | ||
}>) { | ||
return ( | ||
<html lang="ko"> | ||
<Layout>{children}</Layout> | ||
</html> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { Inter } from "next/font/google"; | ||
import React from "react"; | ||
import * as styles from "../styles/common/LayoutStyle.css"; | ||
import ReactQueryProvider from "./ReactQueryProvider"; | ||
import Header from "./common/Header"; | ||
|
||
const inter = Inter({ subsets: ["latin"] }); | ||
|
||
const Layout = ({ children }: React.PropsWithChildren) => { | ||
return ( | ||
<body className={inter.className}> | ||
<ReactQueryProvider> | ||
<Header /> | ||
<main className={styles.LayoutMainStyle}>{children}</main> | ||
</ReactQueryProvider> | ||
</body> | ||
); | ||
}; | ||
|
||
export default Layout; |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,13 @@ | ||||||
"use client"; | ||||||
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; | ||||||
import React, { useState } from "react"; | ||||||
|
||||||
const ReactQueryProvider = ({ children }: React.PropsWithChildren) => { | ||||||
const [queryClient] = useState(new QueryClient()); | ||||||
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. ASK: ์๋์ ๊ฐ์ ํํ๊ฐ ์๋๋ผ ์์ ์ฝ๋์ฒ๋ผ ์ ์ํ์ ์ด์ ๊ฐ ๋ฐ๋ก ์์๊น์ฉ!?
Suggested change
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. ๋ฃจ๋ฐ์ด ๋ฆฌ๋ทฐ์ ๋จ๊ฒผ๋ฏ์ด Next.js์์๋ queryprovider์์ queryclient ์ ์ ์ ์ฐธ์กฐ๋์ผ์ฑ ์ ์ง๋ฅผ ์ํด useState๋ฅผ ์ฌ์ฉํ๋ค๊ตฌ ํฉ๋๋น!! |
||||||
return ( | ||||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider> | ||||||
); | ||||||
}; | ||||||
|
||||||
export default ReactQueryProvider; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
"use client"; | ||
|
||
import * as styles from "@/styles/common/ErrorStyle.css"; | ||
import { getSessionStorageHandler } from "@/utils/getSessionStorageHandler"; | ||
import { useRouter } from "next/navigation"; | ||
import { useEffect } from "react"; | ||
|
||
const Error = () => { | ||
const router = useRouter(); | ||
|
||
useEffect(() => { | ||
// ์๋ฌ ์ sessionStorage์ ์๋ token ์ ๋ณด ์ญ์ | ||
if (typeof window === undefined) return; | ||
if (!getSessionStorageHandler().hasItem) return; | ||
|
||
getSessionStorageHandler().removeItem("token"); | ||
}, []); | ||
|
||
return ( | ||
<section className={styles.ErrorWrapper}> | ||
<div className={styles.ErrorTxtContainer}> | ||
<h2 className={styles.ErrorTitleTxt}>๐ฅบ Error ๐ฅบ</h2> | ||
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. ใ ใ ใ ใ ใ ใ ใ ใ ์ ์์ฃฝ ํ ใ ใฑใ ใ ๐น |
||
<p className={styles.ErrorDescribTxt}> | ||
๏น์ฌ๋ฐ๋ฅธ token ๊ฐ์ธ์ง ํ์ธํด์ฃผ์ธ์ | ||
</p> | ||
</div> | ||
<button | ||
className={styles.ErrorBackBtn} | ||
type="button" | ||
onClick={() => router.back()} | ||
> | ||
์ด์ ์ผ๋ก | ||
</button> | ||
</section> | ||
); | ||
}; | ||
|
||
export default Error; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import img_github_logo_white from "@/public/images/img_github_logo_white.png"; | ||
import Image from "next/image"; | ||
import Link from "next/link"; | ||
import * as styles from "../../styles/common/HeaderStyle.css"; | ||
|
||
const Header = () => { | ||
return ( | ||
<header className={styles.HeaderWrapper}> | ||
<Link href="/"> | ||
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. ์ค ์ด ๋ถ๋ถ ์ฌ์ฉ์ ๊ฒฝํ ์ธก๋ฉด์์ ์ข์ ๊ฒ ๊ฐ๋ค์! !! ์๊ฐ ๋ชปํ ๋ถ๋ถ์ธ๋ฐ ์ฝ์ฅ ๊ฐ์ ธ๊ฐ๊ฒ ์ต๋๋น ~ |
||
<Image | ||
src={img_github_logo_white} | ||
alt="๊นํ๋ธ-๋ก๊ณ " | ||
width={30} | ||
height={30} | ||
/> | ||
</Link> | ||
<h1 className={styles.HeaderTitle}>๊นํ๋ธ ํ๋ก์ฐ ํ์ง๊ธฐ</h1> | ||
</header> | ||
); | ||
}; | ||
|
||
export default Header; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import { LIST_TYPE } from "@/constants/follow-list/LIST_TYPE"; | ||
import { UserFollowInfoDataTypes } from "@/types/user/UserTypes"; | ||
import { useMemo } from "react"; | ||
import * as styles from "../../styles/follow-list/FollowListStyle.css"; | ||
import FollowListDetailWrapper from "./FollowListDetailWrapper"; | ||
|
||
const FollowList = ({ | ||
followData, | ||
}: { | ||
followData: UserFollowInfoDataTypes; | ||
}) => { | ||
const { followingData, followersData } = followData; | ||
|
||
const getIsMatchInfo = useMemo(() => { | ||
// ๋งํ ์ค์ธ ์ฌ๋๋ค ๋ฆฌ์คํธ | ||
const matchedList = followersData.filter((follower) => { | ||
return followingData.some( | ||
(following) => following.login === follower.login | ||
); | ||
}); | ||
|
||
// ๋๋ฅผ ํ๋ก์ฐ ํ ์ฌ๋๋ค ์ค ๋ด๊ฐ ํ๋ก์ฐ ํ์ง ์์ ์ฌ๋๋ค ๋ฆฌ์คํธ | ||
const unMatchedList = followersData.filter((follower) => { | ||
return !matchedList.includes(follower); | ||
}); | ||
|
||
return [ | ||
{ type: LIST_TYPE.unMatched, list: unMatchedList }, | ||
{ type: LIST_TYPE.matched, list: matchedList }, | ||
]; | ||
Comment on lines
+27
to
+30
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. !!! ๋๋ฌด ๊น๋ํ๊ณ ์ข๋ค์ ๐๐ |
||
}, [followersData, followingData]); | ||
|
||
const matchListData = getIsMatchInfo; | ||
|
||
return ( | ||
<article className={styles.FollowListWrapper}> | ||
{matchListData.map(({ type, list }) => { | ||
return ( | ||
<FollowListDetailWrapper | ||
key={type} | ||
listType={type} | ||
targetMatchList={list} | ||
/> | ||
); | ||
})} | ||
</article> | ||
); | ||
}; | ||
|
||
export default FollowList; |
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.
P5: ๋ฐ๋์ ํ ํฐ์ ๋ฃ์ด์ค์ผ ํ๋๊น
์์ฒญ์ด ๊ฐ๊ธฐ ์ ์ ์ด๊ฑธ ๋บ์ด์์ ํ ํฐ ๊ฐ์ ๋ฃ์ด์ค์ผ๊ฒ ๋ค!
๋ผ๊ณ ์๊ฐํด์, interceptor๋ฅผ ํ์ฉํด์ ๊ตฌํํ๋๋ฐ์..!๋ค์ axios๋ AxiosInstance์ ๋ํด ์ฐฌ์ฐฌํ ์ฐพ์๋ณด๋, ์ด์ฐจํผ ๊ธฐ๋ณธ์ ์ผ๋ก ํ ํฐ์ด ์์ด์ผ ๋์ํ๋ ๋ถ๋ถ์ด๋ผ์ interceptor์ ๊ฐ์ ์์ธ์ ์ธ ์ค์ ์ ํด์ค ํ์์์ด ๊ธฐ๋ณธ๊ฐ(defaults)์ผ๋ก ๋ฃ์ด์ฃผ๋๊ฒ ๋ ๋ง๋ ๊ฒ ๊ฐ๋ค๋ ์๊ฐ์ด ๋ค์์ต๋๋น. . !
๋ช ํํ ๊ธฐ์ค์ด ์๋ค๊ธฐ ๋ณด๋จ ๊ทธ๋ ๊ทธ๋ ์ํฉ์ ์ ํ๋จํ๊ณ ์ ์ ํํ๋๊ฒ ์ค์ํ ๊ฒ ๊ฐ๋ค๋ ๊ฑธ ๋ค์ ํ ๋ฒ ๋๋ผ๊ณ ๊ฐ๋๋ค ..~