-
Notifications
You must be signed in to change notification settings - Fork 1
Feat: 맞팔 탐지 페이지 구현 #39
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
Changes from 30 commits
6c044a8
e295d92
c7ac4a5
7bb1dc8
d5dfc95
1c64f21
29818bf
f2fd152
7abe609
5cc7419
3fb7740
7f1f487
2971dd2
7b5d4da
99d8ff7
911a7d1
3c1228d
23a6e1f
de73982
dd862c3
37bd90d
ad0d747
cdbdbee
34695db
50e04c5
b0ae058
ac15ffa
cd52f5b
9900db8
0d324bd
30ebef1
964fa64
51ef6b3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
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 |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| import { router } from "@/Router"; | ||
| import { GlobalStyles } from "@/common/styles/GlobalStyles"; | ||
| import { router } from "@/router"; | ||
| import { Theme } from "@radix-ui/themes"; | ||
| import "@radix-ui/themes/styles.css"; | ||
| import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; | ||
|
|
@@ -15,7 +15,6 @@ const queryClient = new QueryClient({ | |
| }, | ||
| mutations: { | ||
| retry: 0, | ||
| throwOnError: true, | ||
|
Member
Author
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. mutation은 사용처에서 각각 맞는 에러 처리 방식 사용하기 위해 throwOnError 제거합니다 |
||
| }, | ||
| }, | ||
| }); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| import { apiClient } from "@/common/api"; | ||
|
|
||
| export type FollowDetectType = "mutual" | "follow-only" | "followed-only"; | ||
|
|
||
| export interface FollowDetectUser { | ||
| githubUserId: number; | ||
| username: string; | ||
| profileImgUrl: string; | ||
| } | ||
|
|
||
| export interface FollowDetectResponse { | ||
| userList: FollowDetectUser[]; | ||
| lastPage: boolean; | ||
| totalUserCount: number; | ||
| lastSyncAt: string; | ||
| } | ||
|
|
||
| export async function queryFollowDetect({ | ||
| detectType, | ||
| lastGithubUserId, | ||
| }: { detectType: FollowDetectType; lastGithubUserId?: number }) { | ||
| const poll = async () => { | ||
| const response = await apiClient.get<FollowDetectResponse>(`api/v1/users/me/followings/${detectType}`, { | ||
| searchParams: lastGithubUserId ? { lastGithubUserId } : undefined, | ||
| }); | ||
|
|
||
| if (response.status === 202) { | ||
| await new Promise((resolve) => setTimeout(resolve, 500)); | ||
| return poll(); | ||
| } | ||
|
|
||
| return response; | ||
| }; | ||
|
|
||
| return poll(); | ||
|
Comment on lines
+27
to
+43
Member
Author
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. 맞팔 리스트 조회시 status가 202이면 202가 아닐때까지 polling합니다
Contributor
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. 서버에서 status가 지속적으로 202를 반환한다면 무제한 polling이 발생할것 같은데, 최대 횟수나 타임아웃 등의 제한을 두어서 처리해주어도 좋을것 같아요
Member
Author
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. 너무 좋은데요 ?! 👍 이 부분 백엔드 담당자분과 횟수 제한 어떻게 둘지 논의 먼저 해보겠습니다!
Member
Author
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. |
||
| } | ||
|
|
||
| export async function createFollowUser(githubUserId: FollowDetectUser["githubUserId"]) { | ||
| return apiClient.post<FollowDetectResponse>(`api/v1/follow/${githubUserId}`); | ||
| } | ||
|
|
||
| export async function createFollowRefresh() { | ||
| return apiClient.post<null>("api/v1/users/me/followings/refresh"); | ||
| } | ||
|
|
||
| export async function deleteUnfollowUser(githubUserId: FollowDetectUser["githubUserId"]) { | ||
| return apiClient.delete<FollowDetectResponse>(`api/v1/unfollow/${githubUserId}`); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| import type { FollowDetectType } from "@/Follow/apis/follow"; | ||
| import { colors } from "@/common/styles/theme"; | ||
| import { SegmentedControl } from "@radix-ui/themes"; | ||
|
|
||
| interface Props { | ||
| value: FollowDetectType; | ||
| onChange: (value: FollowDetectType) => void; | ||
| } | ||
|
|
||
| export function DetectTypeSelector({ value, onChange }: Props) { | ||
| return ( | ||
| <SegmentedControl.Root | ||
| defaultValue="mutual" | ||
|
Contributor
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. defaultValue는 현 value에 상관없이 항상 고정으로 넣어주나요?!
Member
Author
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. value에 state값 넣어주면서 지워도 되는데 놓쳤네요! ㅋ-ㅋ 30ebef1 |
||
| radius="large" | ||
| size="3" | ||
| value={value} | ||
| onValueChange={onChange} | ||
| variant="classic" | ||
| css={{ | ||
| position: "sticky", | ||
| top: 0, | ||
| border: `1px solid ${colors.gray4}`, | ||
| boxShadow: "0 0 3px 0 rgba(0, 0, 0, 0.1)", | ||
| }} | ||
| > | ||
| <SegmentedControl.Item value="mutual">맞팔</SegmentedControl.Item> | ||
| <SegmentedControl.Item value="followed-only">상대만 팔로우</SegmentedControl.Item> | ||
| <SegmentedControl.Item value="follow-only">나만 팔로우</SegmentedControl.Item> | ||
| </SegmentedControl.Root> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| import type { FollowDetectResponse, FollowDetectType } from "@/Follow/apis/follow"; | ||
| import { useCreateFollowUser } from "@/Follow/hooks/apis/useCreateFollowUser"; | ||
| import { useDeleteUnfollowUser } from "@/Follow/hooks/apis/useDeleteUnfollowUser"; | ||
| import UserListItemCard from "@/common/components/UserListItemCard"; | ||
| import UserProfileAvatar from "@/common/components/UserProfileAvatar"; | ||
| import { Button, Flex } from "@radix-ui/themes"; | ||
| import { Text } from "@radix-ui/themes"; | ||
| import toast, { Toaster } from "react-hot-toast"; | ||
|
|
||
| interface FollowListProps { | ||
| type: FollowDetectType; | ||
| userList: FollowDetectResponse["userList"]; | ||
| } | ||
|
|
||
| export function FollowList({ type, userList }: FollowListProps) { | ||
| const { mutateAsync: unfollow } = useDeleteUnfollowUser(); | ||
| const { mutateAsync: follow } = useCreateFollowUser(); | ||
|
|
||
| return ( | ||
| <> | ||
| <Flex direction="column" gap="2" css={{ padding: "0 2rem" }}> | ||
| {userList.map((user) => ( | ||
| <UserListItemCard key={user.githubUserId}> | ||
| <Flex justify="between" align="center"> | ||
| <Flex gap="4" align="center"> | ||
| <UserProfileAvatar src={user.profileImgUrl} /> | ||
|
|
||
| <Text size="2" weight="medium"> | ||
| {user.username} | ||
| </Text> | ||
| </Flex> | ||
| <Flex gap="3"> | ||
| {type === "followed-only" ? ( | ||
| <Button | ||
| color="indigo" | ||
| variant="soft" | ||
| size="1" | ||
| css={{ cursor: "pointer" }} | ||
| onClick={async () => { | ||
| try { | ||
| await follow(user.githubUserId); | ||
| toast.success("팔로우에 성공했습니다."); | ||
| } catch (e) { | ||
| toast.error("팔로우에 실패했습니다. 다시 시도해주세요"); | ||
|
Comment on lines
+39
to
+44
Contributor
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. 팔로우/언팔로우시 버튼 onClick 내부 로직이 동일하므로 핸들러로 추상화하고, mutate function이랑 toast 텍스트만 각 버튼에 따라 적용되도록 수정해줘도 좋을것 같아요~
Member
Author
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. 고것도 좋은 것 같네용 👀 그런데 아직은 추상화하지 않아도 되는 정도의 로직이라고 생각해요! 핸들러를 만들게 되면 type에 따라 api 요청 함수 / success / error 토스트 텍스트 분기가 다 들어가야 하다보니 그래서 지금은 이대로 쓰는게 더 직관적이지 않을까 하는 의견입니다!! 나중에 좀 더 공통 처리 로직이나 부가적인 로직이 들어갔을 때 추상화 고려해봐도 좋을 것 같은데 어떠세요 ~?!
Member
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. 저두 지금은 옵션이 2개라 괜찮다고 보긴 하는데 나중에 옵션들이 추가된다면 핸들러로 추상화가 필요하긴 할 것 같아요. 그러면 옵션 개수만큼 분기가 필요해질거라 옵션이 많아지면 그만큼 핸들러 로직이 비대해질수도 있을 것 같아요. const map = {
"followed-only": {
mutate: follow,
success: "팔로우에 성공했습니다.",
error: "팔로우에 실패했습니다. 다시 시도해주세요",
buttonText: "Follow",
},
"following-only": {
mutate: unfollow,
success: "언팔로우에 성공했습니다.",
error: "언팔로우에 실패했습니다. 다시 시도해주세요",
buttonText: "Unfollow",
},
} as const;
const currentAction = map[type];
const handleClick = async (userId: string) => {
try {
await currentAction.mutate(userId);
toast.success(currentAction.success);
} catch {
toast.error(currentAction.error);
}
}; |
||
| } | ||
| }} | ||
| > | ||
| Follow | ||
| </Button> | ||
| ) : ( | ||
| <Button | ||
| color="indigo" | ||
| variant="soft" | ||
| size="1" | ||
| css={{ cursor: "pointer" }} | ||
| onClick={async () => { | ||
| try { | ||
| await unfollow(user.githubUserId); | ||
| toast.success("언팔로우에 성공했습니다."); | ||
| } catch (e) { | ||
| toast.error("언팔로우에 실패했습니다. 다시 시도해주세요"); | ||
| } | ||
| }} | ||
| > | ||
| Unfollow | ||
| </Button> | ||
| )} | ||
| </Flex> | ||
| </Flex> | ||
| </UserListItemCard> | ||
| ))} | ||
| </Flex> | ||
| <Toaster position="bottom-center" /> | ||
| </> | ||
| ); | ||
| } | ||
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.
환경별 env 공유해서 사용하기로 했던것 같은데, 깃에 안올라가게 처리한 이유가 있나요 -?!
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.
요거 제가 긴가민가한데.. ㅎㅎ 지금은 BASE_URL만 있어서 노출되어도 되지만, 생각해보니 노출되면 안되는 시크릿 키 같은걸 들고 있게되면 파일이 올라가면 안되겠더라구요! 그래서 깃에 안올라가도록 처리해줬는데 요 부분 어떻게 생각하시나요 ~?