From 27ab63cccdda4ec06bfdf972259224374405b328 Mon Sep 17 00:00:00 2001
From: Jiyoung Jung <72294509+Jungjjeong@users.noreply.github.com>
Date: Thu, 29 Aug 2024 01:08:11 +0900
Subject: [PATCH 1/5] =?UTF-8?q?refactor:=20follow=20virtual=20list,=20memb?=
=?UTF-8?q?er=20type=20=EA=B3=B5=ED=86=B5=EC=9C=BC=EB=A1=9C=20=EC=9D=B4?=
=?UTF-8?q?=EB=8F=99?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
components/molecules/index.ts | 2 +-
.../{profile-list-item => profile-list}/index.ts | 1 +
.../profile-list-item.tsx | 12 ++++++------
.../molecules/profile-list/profile-list.tsx | 15 +++++++--------
features/follow/components/index.ts | 1 -
features/follow/sections/follower-section.tsx | 5 +++--
features/follow/sections/following-section.tsx | 5 +++--
features/follow/types/index.ts | 10 ++--------
types/index.ts | 1 +
types/member-type.ts | 6 ++++++
10 files changed, 30 insertions(+), 28 deletions(-)
rename components/molecules/{profile-list-item => profile-list}/index.ts (53%)
rename components/molecules/{profile-list-item => profile-list}/profile-list-item.tsx (87%)
rename features/follow/components/follow-virtual-list.tsx => components/molecules/profile-list/profile-list.tsx (70%)
delete mode 100644 features/follow/components/index.ts
create mode 100644 types/index.ts
create mode 100644 types/member-type.ts
diff --git a/components/molecules/index.ts b/components/molecules/index.ts
index 5c483c5f..f1f1abe8 100644
--- a/components/molecules/index.ts
+++ b/components/molecules/index.ts
@@ -5,7 +5,7 @@ export * from './header-bar';
export * from './infinite-scroller';
export * from './modal';
export * from './page-modal';
-export * from './profile-list-item';
+export * from './profile-list';
export * from './record-mark';
export * from './search-bar';
export * from './tab';
diff --git a/components/molecules/profile-list-item/index.ts b/components/molecules/profile-list/index.ts
similarity index 53%
rename from components/molecules/profile-list-item/index.ts
rename to components/molecules/profile-list/index.ts
index 232a412e..a764c7f8 100644
--- a/components/molecules/profile-list-item/index.ts
+++ b/components/molecules/profile-list/index.ts
@@ -1 +1,2 @@
+export * from './profile-list';
export * from './profile-list-item';
diff --git a/components/molecules/profile-list-item/profile-list-item.tsx b/components/molecules/profile-list/profile-list-item.tsx
similarity index 87%
rename from components/molecules/profile-list-item/profile-list-item.tsx
rename to components/molecules/profile-list/profile-list-item.tsx
index 275e91f9..b2b20086 100644
--- a/components/molecules/profile-list-item/profile-list-item.tsx
+++ b/components/molecules/profile-list/profile-list-item.tsx
@@ -1,18 +1,18 @@
import Link from 'next/link';
import { Button, Image } from '@/components/atoms';
-import { ProfileFollowContent } from '@/features/follow';
import { css } from '@/styled-system/css';
import { flex } from '@/styled-system/patterns';
+import { MemberProfile } from '@/types';
type FollowListItem = {
isFollow: boolean;
onClick?: () => void;
onClickFollow?: () => void;
-} & ProfileFollowContent;
+} & MemberProfile;
export const ProfileListItem = ({
memberId,
- name,
+ nickname,
introduction,
profileImageUrl,
isFollow,
@@ -22,7 +22,7 @@ export const ProfileListItem = ({
-
{name}
-
{introduction}
+
{nickname}
+ {introduction &&
{introduction}
}
diff --git a/features/follow/components/follow-virtual-list.tsx b/components/molecules/profile-list/profile-list.tsx
similarity index 70%
rename from features/follow/components/follow-virtual-list.tsx
rename to components/molecules/profile-list/profile-list.tsx
index a80255b6..bfe282c7 100644
--- a/features/follow/components/follow-virtual-list.tsx
+++ b/components/molecules/profile-list/profile-list.tsx
@@ -1,18 +1,17 @@
+'use client';
+
import React from 'react';
import { Virtuoso } from 'react-virtuoso';
-import { ProfileListItem } from '@/components/molecules';
+import { MemberProfile } from '@/types';
-import { ProfileFollowContent } from '../types';
+import { ProfileListItem } from './profile-list-item';
-type FollowVirtualList = {
- data: ProfileFollowContent[];
+type ProfileList = {
+ data: MemberProfile[];
fetchNextData: () => void;
};
-export const FollowVirtualList = ({
- data,
- fetchNextData,
-}: FollowVirtualList) => {
+export const ProfileList = ({ data, fetchNextData }: ProfileList) => {
const handleRangeChanged = (range: { endIndex: number }) => {
const currentContentsLastIndex = data.length - 1;
if (range.endIndex >= currentContentsLastIndex - 3) {
diff --git a/features/follow/components/index.ts b/features/follow/components/index.ts
deleted file mode 100644
index 49cad4d9..00000000
--- a/features/follow/components/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './follow-virtual-list';
diff --git a/features/follow/sections/follower-section.tsx b/features/follow/sections/follower-section.tsx
index 48416091..9f8b9214 100644
--- a/features/follow/sections/follower-section.tsx
+++ b/features/follow/sections/follower-section.tsx
@@ -1,7 +1,8 @@
'use client';
+import { ProfileList } from '@/components/molecules';
+
import { useFollowerList } from '../apis';
-import { FollowVirtualList } from '../components';
export const FollowerSection = ({ id }: { id: number }) => {
const { flattenData, hasNextPage, isFetchingNextPage, fetchNextPage } =
@@ -13,5 +14,5 @@ export const FollowerSection = ({ id }: { id: number }) => {
}
};
- return ;
+ return ;
};
diff --git a/features/follow/sections/following-section.tsx b/features/follow/sections/following-section.tsx
index d588b31b..d4243bb4 100644
--- a/features/follow/sections/following-section.tsx
+++ b/features/follow/sections/following-section.tsx
@@ -1,7 +1,8 @@
'use client';
+import { ProfileList } from '@/components/molecules';
+
import { useFollowingList } from '../apis';
-import { FollowVirtualList } from '../components';
export const FollowingSection = ({ id }: { id: number }) => {
const { flattenData, hasNextPage, isFetchingNextPage, fetchNextPage } =
@@ -13,5 +14,5 @@ export const FollowingSection = ({ id }: { id: number }) => {
}
};
- return ;
+ return ;
};
diff --git a/features/follow/types/index.ts b/features/follow/types/index.ts
index 231948d5..bb8dfe1d 100644
--- a/features/follow/types/index.ts
+++ b/features/follow/types/index.ts
@@ -1,16 +1,10 @@
import { Response } from '@/apis';
+import { MemberProfile } from '@/types';
export type FollowTab = 'follow' | 'following';
-export type ProfileFollowContent = {
- memberId: number;
- name: string;
- profileImageUrl: string;
- introduction: string;
-};
-
export type ProfileFollow = Response<{
- contents: ProfileFollowContent[];
+ contents: MemberProfile[];
cursorId: number;
hasNext: boolean;
}>;
diff --git a/types/index.ts b/types/index.ts
new file mode 100644
index 00000000..efb0245a
--- /dev/null
+++ b/types/index.ts
@@ -0,0 +1 @@
+export * from './member-type';
diff --git a/types/member-type.ts b/types/member-type.ts
new file mode 100644
index 00000000..0b1c9adc
--- /dev/null
+++ b/types/member-type.ts
@@ -0,0 +1,6 @@
+export type MemberProfile = {
+ memberId: number;
+ nickname: string;
+ profileImageUrl?: string;
+ introduction?: string;
+};
From 73af958c5f7e6269ffc004a351adc54ac94488fe Mon Sep 17 00:00:00 2001
From: Jiyoung Jung <72294509+Jungjjeong@users.noreply.github.com>
Date: Thu, 29 Aug 2024 01:11:58 +0900
Subject: [PATCH 2/5] =?UTF-8?q?feat:=20profile=20search=20page=EB=A5=BC=20?=
=?UTF-8?q?=EC=9C=84=ED=95=9C=20api,=20section,=20component=EB=A5=BC=20?=
=?UTF-8?q?=EC=B6=94=EA=B0=80=ED=95=A9=EB=8B=88=EB=8B=A4?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/api/member/search/route.ts | 17 +++++++
features/profile-search/apis/index.ts | 1 +
.../apis/use-profile-search.tsx | 38 ++++++++++++++++
.../components/empty-keyword.tsx | 18 ++++++++
.../components/empty-search-result.tsx | 31 +++++++++++++
features/profile-search/components/index.ts | 2 +
features/profile-search/index.ts | 2 +
features/profile-search/sections/index.ts | 2 +
.../sections/search-bar-section.tsx | 45 +++++++++++++++++++
.../sections/search-result-section.tsx | 38 ++++++++++++++++
features/profile-search/types/index.ts | 8 ++++
11 files changed, 202 insertions(+)
create mode 100644 app/api/member/search/route.ts
create mode 100644 features/profile-search/apis/index.ts
create mode 100644 features/profile-search/apis/use-profile-search.tsx
create mode 100644 features/profile-search/components/empty-keyword.tsx
create mode 100644 features/profile-search/components/empty-search-result.tsx
create mode 100644 features/profile-search/components/index.ts
create mode 100644 features/profile-search/index.ts
create mode 100644 features/profile-search/sections/index.ts
create mode 100644 features/profile-search/sections/search-bar-section.tsx
create mode 100644 features/profile-search/sections/search-result-section.tsx
create mode 100644 features/profile-search/types/index.ts
diff --git a/app/api/member/search/route.ts b/app/api/member/search/route.ts
new file mode 100644
index 00000000..4e6f5d25
--- /dev/null
+++ b/app/api/member/search/route.ts
@@ -0,0 +1,17 @@
+import { NextRequest, NextResponse } from 'next/server';
+
+import { fetchData } from '@/apis/fetch-data';
+import { ProfileSearch } from '@/features/profile-search';
+
+export async function GET(request: NextRequest) {
+ const { searchParams } = new URL(request.url);
+ const cursorId = searchParams.get('cursorId') ?? '';
+ const nameQuery = searchParams.get('nameQuery') ?? '';
+
+ const data = await fetchData(
+ `/member/search?nameQuery=${nameQuery}&cursorId=${cursorId}`,
+ 'GET',
+ );
+
+ return NextResponse.json(data);
+}
diff --git a/features/profile-search/apis/index.ts b/features/profile-search/apis/index.ts
new file mode 100644
index 00000000..16495410
--- /dev/null
+++ b/features/profile-search/apis/index.ts
@@ -0,0 +1 @@
+export * from './use-profile-search';
diff --git a/features/profile-search/apis/use-profile-search.tsx b/features/profile-search/apis/use-profile-search.tsx
new file mode 100644
index 00000000..92b2cd5d
--- /dev/null
+++ b/features/profile-search/apis/use-profile-search.tsx
@@ -0,0 +1,38 @@
+'use client';
+
+import { useInfiniteQuery } from '@tanstack/react-query';
+
+import { ProfileSearch } from '../types';
+
+const fetchProfile = async (nameQuery: string, cursorId?: number) => {
+ const res = await fetch(
+ `/api/member/search?nameQuery=${nameQuery}&cursorId=${cursorId ?? ''}`,
+ {
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ },
+ );
+
+ return res.json();
+};
+
+export const useProfileSearch = (nameQuery: string) => {
+ const query = useInfiniteQuery({
+ queryKey: ['useProfileSearch', nameQuery],
+ queryFn: ({ pageParam }) => fetchProfile(nameQuery, pageParam as number),
+ initialPageParam: undefined,
+ getNextPageParam: (lastPage) =>
+ lastPage?.data?.hasNext ? lastPage?.data?.cursorId : undefined,
+ enabled: !!nameQuery?.length,
+ });
+
+ const flattenData =
+ query.data?.pages.flatMap(({ data }) => data?.memberInfoResponses ?? []) ??
+ [];
+
+ return {
+ ...query,
+ flattenData,
+ };
+};
diff --git a/features/profile-search/components/empty-keyword.tsx b/features/profile-search/components/empty-keyword.tsx
new file mode 100644
index 00000000..933cef44
--- /dev/null
+++ b/features/profile-search/components/empty-keyword.tsx
@@ -0,0 +1,18 @@
+import { css } from '@/styled-system/css';
+
+export const EmptyKeyword = () => {
+ return (
+
+ 친구를 팔로우하고
+
+ 서로의 기록에 응원을 보내보세요.
+
+ );
+};
+
+const containerStyle = css({
+ m: '80px auto 0px',
+ textStyle: 'body2.normal',
+ color: 'text.alternative',
+ textAlign: 'center',
+});
diff --git a/features/profile-search/components/empty-search-result.tsx b/features/profile-search/components/empty-search-result.tsx
new file mode 100644
index 00000000..ffaf0618
--- /dev/null
+++ b/features/profile-search/components/empty-search-result.tsx
@@ -0,0 +1,31 @@
+import { css } from '@/styled-system/css';
+import { flex } from '@/styled-system/patterns';
+
+export const EmptySearchResult = ({ keyword }: { keyword: string }) => {
+ return (
+
+
‘{keyword}‘ 유저가 없어요.
+
+ 마이페이지에서 내 프로필을 공유할 수 있어요
+
+
+ );
+};
+
+const containerStyle = flex({
+ direction: 'column',
+ gap: '4px',
+ m: '80px auto 0px',
+ align: 'center',
+});
+
+const titleStyle = css({
+ textStyle: 'heading6',
+ fontWeight: 'medium',
+ color: 'text.normal',
+});
+
+const descriptionStyle = css({
+ color: 'text.alternative',
+ fontWeight: 'regular',
+});
diff --git a/features/profile-search/components/index.ts b/features/profile-search/components/index.ts
new file mode 100644
index 00000000..9b7802a9
--- /dev/null
+++ b/features/profile-search/components/index.ts
@@ -0,0 +1,2 @@
+export * from './empty-keyword';
+export * from './empty-search-result';
diff --git a/features/profile-search/index.ts b/features/profile-search/index.ts
new file mode 100644
index 00000000..d86c3a84
--- /dev/null
+++ b/features/profile-search/index.ts
@@ -0,0 +1,2 @@
+export * from './sections';
+export * from './types';
diff --git a/features/profile-search/sections/index.ts b/features/profile-search/sections/index.ts
new file mode 100644
index 00000000..e6a839a4
--- /dev/null
+++ b/features/profile-search/sections/index.ts
@@ -0,0 +1,2 @@
+export * from './search-bar-section';
+export * from './search-result-section';
diff --git a/features/profile-search/sections/search-bar-section.tsx b/features/profile-search/sections/search-bar-section.tsx
new file mode 100644
index 00000000..64a48f4d
--- /dev/null
+++ b/features/profile-search/sections/search-bar-section.tsx
@@ -0,0 +1,45 @@
+'use client';
+
+import { debounce } from 'lodash';
+import { useRouter } from 'next/navigation';
+import { useCallback, useEffect, useState } from 'react';
+
+import { SearchBar } from '@/components/molecules';
+import { css } from '@/styled-system/css';
+
+export const SearchBarSection = ({ keyword }: { keyword: string }) => {
+ const router = useRouter();
+ const [searchKeyword, setSearchKeyword] = useState(keyword);
+
+ const handleChangeKeyword = debounce((keyword: string) => {
+ setSearchKeyword(keyword);
+ }, 400);
+
+ const setKeywordParams = useCallback(
+ (keyword: string) => {
+ const params = new URL(window.location.href);
+ params.searchParams.set('keyword', keyword);
+
+ router.replace(params.toString());
+ },
+ [router],
+ );
+
+ useEffect(() => {
+ setKeywordParams(searchKeyword);
+ }, [searchKeyword, setKeywordParams]);
+
+ return (
+
+
+
+ );
+};
+
+const containerStyle = css({
+ p: '8px 20px',
+});
diff --git a/features/profile-search/sections/search-result-section.tsx b/features/profile-search/sections/search-result-section.tsx
new file mode 100644
index 00000000..05af9711
--- /dev/null
+++ b/features/profile-search/sections/search-result-section.tsx
@@ -0,0 +1,38 @@
+'use client';
+
+import { ProfileList } from '@/components/molecules';
+import { flex } from '@/styled-system/patterns';
+
+import { useProfileSearch } from '../apis/use-profile-search';
+import { EmptyKeyword, EmptySearchResult } from '../components';
+
+export const SearchResultSection = ({ keyword }: { keyword: string }) => {
+ const {
+ flattenData,
+ hasNextPage,
+ isFetchingNextPage,
+ fetchNextPage,
+ isFetching,
+ } = useProfileSearch(keyword);
+
+ const fetchNextData = () => {
+ if (hasNextPage && !isFetchingNextPage) {
+ void fetchNextPage();
+ }
+ };
+
+ if (!keyword.length) return ;
+ if (flattenData.length === 0 && !isFetching)
+ return ;
+ return (
+
+ );
+};
+
+const containerStyle = flex({
+ direction: 'column',
+ gap: '12px',
+ p: '16px 20px',
+});
diff --git a/features/profile-search/types/index.ts b/features/profile-search/types/index.ts
new file mode 100644
index 00000000..9b158466
--- /dev/null
+++ b/features/profile-search/types/index.ts
@@ -0,0 +1,8 @@
+import { Response } from '@/apis';
+import { MemberProfile } from '@/types';
+
+export type ProfileSearch = Response<{
+ memberInfoResponses: MemberProfile[];
+ cursorId: number;
+ hasNext: boolean;
+}>;
From 18cbe6e1b83c4343b0e8176b54aba2bcdc11a0a7 Mon Sep 17 00:00:00 2001
From: Jiyoung Jung <72294509+Jungjjeong@users.noreply.github.com>
Date: Thu, 29 Aug 2024 01:16:03 +0900
Subject: [PATCH 3/5] =?UTF-8?q?feat:=20profile=20search=20=ED=8E=98?=
=?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EA=B5=AC=ED=98=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/profile/search/page.tsx | 55 +++++++++++++++++++++++++++++++++++++
1 file changed, 55 insertions(+)
create mode 100644 app/profile/search/page.tsx
diff --git a/app/profile/search/page.tsx b/app/profile/search/page.tsx
new file mode 100644
index 00000000..ecaf2a0b
--- /dev/null
+++ b/app/profile/search/page.tsx
@@ -0,0 +1,55 @@
+import dynamic from 'next/dynamic';
+
+import { LeftArrowIcon } from '@/components/atoms';
+import { HeaderBar } from '@/components/molecules';
+
+const DynamicBackButton = dynamic(
+ () => import('@/components/molecules').then(({ BackButton }) => BackButton),
+ {
+ ssr: false,
+ loading: () => ,
+ },
+);
+
+const DynamicSearchBarSection = dynamic(
+ () =>
+ import('@/features/profile-search').then(
+ ({ SearchBarSection }) => SearchBarSection,
+ ),
+ {
+ ssr: false,
+ },
+);
+
+const DynamicSearchResultSection = dynamic(
+ () =>
+ import('@/features/profile-search').then(
+ ({ SearchResultSection }) => SearchResultSection,
+ ),
+ {
+ ssr: false,
+ },
+);
+
+export default function ProfileSearch({
+ searchParams,
+}: {
+ searchParams: { keyword: string };
+}) {
+ const { keyword = '' } = searchParams;
+
+ return (
+ <>
+
+
+
+
+ 친구 찾기
+
+
+
+
+
+ >
+ );
+}
From e3db5b74dd23c25d02153a1b876cb59ac01a2a02 Mon Sep 17 00:00:00 2001
From: Jiyoung Jung <72294509+Jungjjeong@users.noreply.github.com>
Date: Thu, 29 Aug 2024 01:50:15 +0900
Subject: [PATCH 4/5] =?UTF-8?q?feat:=20profile=20image=20component=20?=
=?UTF-8?q?=EA=B5=AC=ED=98=84=20=EB=B0=8F=20=EC=A0=81=EC=9A=A9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
components/molecules/index.ts | 1 +
components/molecules/profile-image/index.ts | 1 +
.../molecules/profile-image/profile-image.tsx | 24 ++++++++++++++++++
.../profile-list/profile-list-item.tsx | 6 +++--
.../components/cheer-modal-item.tsx | 4 +--
public/images/default-profile/blue-hat.png | Bin 20344 -> 52529 bytes
public/images/default-profile/green-hat.png | Bin 20424 -> 53144 bytes
public/images/default-profile/orange-hat.png | Bin 21068 -> 54810 bytes
public/images/default-profile/yellow-hat.png | Bin 20102 -> 52003 bytes
9 files changed, 32 insertions(+), 4 deletions(-)
create mode 100644 components/molecules/profile-image/index.ts
create mode 100644 components/molecules/profile-image/profile-image.tsx
diff --git a/components/molecules/index.ts b/components/molecules/index.ts
index f1f1abe8..cf445732 100644
--- a/components/molecules/index.ts
+++ b/components/molecules/index.ts
@@ -5,6 +5,7 @@ export * from './header-bar';
export * from './infinite-scroller';
export * from './modal';
export * from './page-modal';
+export * from './profile-image';
export * from './profile-list';
export * from './record-mark';
export * from './search-bar';
diff --git a/components/molecules/profile-image/index.ts b/components/molecules/profile-image/index.ts
new file mode 100644
index 00000000..44545fa9
--- /dev/null
+++ b/components/molecules/profile-image/index.ts
@@ -0,0 +1 @@
+export * from './profile-image';
diff --git a/components/molecules/profile-image/profile-image.tsx b/components/molecules/profile-image/profile-image.tsx
new file mode 100644
index 00000000..9cf1b23a
--- /dev/null
+++ b/components/molecules/profile-image/profile-image.tsx
@@ -0,0 +1,24 @@
+'use client';
+
+import { ImageProps } from 'next/image';
+import React, { useMemo } from 'react';
+
+import {
+ defaultProfileImages,
+ ProfileIndexType,
+} from '@/public/images/default-profile';
+
+import { Image } from '../../atoms';
+
+export const ProfileImage = ({
+ src,
+ alt = 'profile image',
+ ...props
+}: ImageProps) => {
+ const imageSrc = useMemo(() => {
+ const profileImage = defaultProfileImages[Number(src) as ProfileIndexType];
+ return profileImage ?? src;
+ }, [src]);
+
+ return ;
+};
diff --git a/components/molecules/profile-list/profile-list-item.tsx b/components/molecules/profile-list/profile-list-item.tsx
index b2b20086..a9ba371c 100644
--- a/components/molecules/profile-list/profile-list-item.tsx
+++ b/components/molecules/profile-list/profile-list-item.tsx
@@ -1,10 +1,12 @@
import Link from 'next/link';
-import { Button, Image } from '@/components/atoms';
+import { Button } from '@/components/atoms';
import { css } from '@/styled-system/css';
import { flex } from '@/styled-system/patterns';
import { MemberProfile } from '@/types';
+import { ProfileImage } from '../profile-image';
+
type FollowListItem = {
isFollow: boolean;
onClick?: () => void;
@@ -21,7 +23,7 @@ export const ProfileListItem = ({