Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions apps/web/src/entities/user/model/user.model.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { RequiredDeep } from 'type-fest';

import type { components, operations } from '@/shared/api/apiTypes';
import type { ScheduleCalendarItem } from '@/shared/types/schedule';

Expand All @@ -6,8 +8,9 @@ import type { ScheduleCalendarItem } from '@/shared/types/schedule';
export type MyInfoResponse = components['schemas']['MyInfoResponse'];
export type CrewMemberListResponse =
components['schemas']['CrewMemberListResponse'];
export type CrewMemberDetailResponse =
components['schemas']['CrewMemberDetailResponse'];
export type CrewMemberDetailResponse = RequiredDeep<
components['schemas']['CrewMemberDetailResponse']
>;

export type MyAttendanceCalendarResponse = ScheduleCalendarItem[];
export type MyAttendanceCalendarRequest = NonNullable<
Expand Down
4 changes: 1 addition & 3 deletions apps/web/src/features/order/model/useOrder.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { useActivityParams } from '@stackflow/react';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useMutation, useQuery } from '@tanstack/react-query';
import { useState } from 'react';

import { PAYMENT_METHOD_MAP } from '@/shared/constants/order';
import { parseOrderParams } from '@/shared/lib/orderParams';
import type { OrderPageParams } from '@/shared/lib/orderParams';
import { memberQueries } from '@/shared/queries';
import { orderQueries } from '@/shared/queries/order';
import { toastError } from '@/shared/ui/toast';

Expand All @@ -24,7 +23,6 @@ export function useOrder(options: UseOrderOptions = {}) {
const params = useActivityParams<OrderPageParams>();
const { skuId, quantity, cartItemIds } = parseOrderParams(params);
const isDirectOrder = skuId > 0;
const queryClient = useQueryClient();

const [selectedPaymentCode, setSelectedPaymentCode] = useState<
string | undefined
Expand Down
27 changes: 27 additions & 0 deletions apps/web/src/pages/mypage/styles/MemberManagePage.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,30 @@ export const sentinel = style({
height: 1,
width: '100%',
});

export const pullIndicator = style({
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
paddingTop: 8,
paddingBottom: 8,
gap: 4,
});

export const pullHint = style([
typography.body.b2,
{
color: vars.colors.gray70,
textAlign: 'center',
},
]);
Comment on lines +50 to +56
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

❌ 위반 사항: 중복 스타일

pullHint 스타일은 바로 아래에 정의된 pullText 스타일과 완전히 동일합니다. 스타일 가이드의 중복 스타일 제거 규칙에 따라 하나로 통합해야 합니다.

🔧 개선 제안

pullHint를 제거하고, 이 스타일을 사용하는 곳에서는 pullText를 대신 사용하도록 변경하는 것을 제안합니다. 이렇게 하면 코드 중복을 줄이고 유지보수성을 높일 수 있습니다.

References
  1. /src//*.css.ts 경로의 파일은 중복 스타일을 제거해야 합니다. (link)


export const pullText = style([
typography.body.b2,
{
color: vars.colors.gray70,
textAlign: 'center',
},
]);
85 changes: 63 additions & 22 deletions apps/web/src/pages/mypage/ui/MemberManagePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
useQuery,
useQueryClient,
} from '@tanstack/react-query';
import { useState } from 'react';
import { useCallback, useState } from 'react';

import * as styles from '@/pages/mypage/styles/MemberManagePage.css';

Expand All @@ -20,9 +20,11 @@ import { RequestList } from '@/features/crew-manage/ui';

import { MEMBER_ROLE } from '@/shared/constants/member-role';
import { useInfiniteScroll } from '@/shared/lib/useInfiniteScroll';
import { usePullToRefresh } from '@/shared/lib/usePullToRefresh';
import { memberQueries } from '@/shared/queries';
import { BackButton } from '@/shared/ui/button';
import { AppLayout } from '@/shared/ui/layout';
import { spinner as pageLoaderSpinner } from '@/shared/ui/loading/PageLoader.css.ts';

import type { MemberRequestItem } from '@/entities/crew/model/crew.types';
import type { MemberItem } from '@/entities/user/model';
Expand All @@ -40,44 +42,66 @@ export function MemberManagePage({ params }: { params?: { id?: string } }) {
fetchNextPage,
hasNextPage,
isFetchingNextPage,
refetch: refetchMembers,
} = useInfiniteQuery({
...memberQueries.crewMembersQuery(crewId),
enabled: crewId > 0 && activeTab === 'member',
});

const { scrollRef, bottomSentinelRef } = useInfiniteScroll({
hasNextPage: hasNextPage ?? false,
isFetchingNextPage,
fetchNextPage,
});
const { scrollRef: infiniteScrollRef, bottomSentinelRef } = useInfiniteScroll(
{
hasNextPage: hasNextPage ?? false,
isFetchingNextPage,
fetchNextPage,
}
);

const members: MemberItem[] =
membersData?.pages.flatMap(
(page) =>
page.result?.content.map((member) => ({
id: member.id ?? 0,
memberId: member.memberId ?? member.id ?? 0,
nickname: member.nickname ?? '',
profileImageUrl: member.profileImageUrl ?? '',
role: member.role ?? 'MEMBER',
joinedDate: member.joinedDate ?? '',
page.result.content?.map((member) => ({
id: member.id!,
memberId: member.memberId!,
nickname: member.nickname!,
profileImageUrl: member.profileImageUrl!,
role: member.role!,
joinedDate: member.joinedDate!,
})) ?? []
) ?? [];

const totalCount = membersData?.pages[0]?.result?.totalCount;

const { data: joinRequestsData } = useQuery({
const { data: joinRequestsData, refetch: refetchRequests } = useQuery({
...memberQueries.joinRequestsQuery(crewId),
enabled: crewId > 0,
});

const requests = joinRequestsData?.result ?? [];

if (isLoading || !myInfoData?.result) {
return <></>;
}
const {
scrollRef: pullToRefreshScrollRef,
isRefreshing: isPullRefreshing,
pullProgress,
} = usePullToRefresh({
enabled: crewId > 0,
onRefresh: async () => {
if (crewId <= 0) return;

if (activeTab === 'member') {
await refetchMembers();
} else {
await refetchRequests();
}
},
});

const isLeader = myInfo?.crewMemberRole === MEMBER_ROLE.LEADER;
const setMainContainerRef = useCallback(
(el: HTMLDivElement | null) => {
pullToRefreshScrollRef.current = el;
infiniteScrollRef.current = activeTab === 'member' ? el : null;
},
[activeTab, infiniteScrollRef, pullToRefreshScrollRef]
);

const queryClient = useQueryClient();

Expand All @@ -90,6 +114,12 @@ export function MemberManagePage({ params }: { params?: { id?: string } }) {
},
});

if (isLoading || !myInfoData?.result) {
return <></>;
}

const isLeader = myInfo?.crewMemberRole === MEMBER_ROLE.LEADER;

const handleDeleteMember = isLeader
? (targetMemberId: number) =>
deleteMutation.mutate({ crewId, targetMemberId })
Expand All @@ -106,10 +136,21 @@ export function MemberManagePage({ params }: { params?: { id?: string } }) {
requestCount={requests.length}
/>
</div>
<div
className={styles.mainContainer}
ref={activeTab === 'member' ? scrollRef : undefined}
>
<div className={styles.mainContainer} ref={setMainContainerRef}>
{(isPullRefreshing || pullProgress > 0) && (
<div className={styles.pullIndicator} aria-hidden>
{isPullRefreshing ? (
<>
<div className={pageLoaderSpinner} />
<p className={styles.pullText}>새로고침 중</p>
</>
) : (
<p className={styles.pullHint}>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

🔧 개선 제안: 중복 스타일 사용 개선

MemberManagePage.css.ts 파일의 pullHint 스타일은 pullText와 중복되므로 제거하는 것이 좋습니다. 중복을 없애고 일관성을 유지하기 위해 pullText 스타일을 사용해주세요.

Suggested change
<p className={styles.pullHint}>
<p className={styles.pullText}>

{pullProgress >= 1 ? '놓으면 새로고침' : '당겨서 새로고침'}
</p>
)}
</div>
)}
{activeTab === 'request' ? (
<RequestListView crewId={crewId} requests={requests} />
) : (
Expand Down
Loading
Loading