Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
16 changes: 11 additions & 5 deletions src/pages/match/components/match-tab-pannel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import { getColorType } from '@components/card/match-card/utils/get-color-type';
import EmptyState from '@components/ui/empty-state';
import { cn } from '@libs/cn';
import { CLICKABLE_STATUS_MAP } from '@pages/match/constants/matching';
import { getCardColor, statusToCategory } from '@pages/match/utils/match-status';
import { getCardColor, getPendingToast, statusToCategory } from '@pages/match/utils/match-status';
import { ROUTES } from '@routes/routes-config';
import { useMutation } from '@tanstack/react-query';
import { useNavigate } from 'react-router-dom';
import { showErrorToast } from '@/shared/utils/show-error-toast';

type MatchableCardProps = SingleCardProps | GroupCardProps;

Expand All @@ -26,6 +27,12 @@ const MatchTabPanel = ({ cards, filter }: MatchTabPanelProps) => {
filter === '전체' ? cards : cards.filter((card) => statusToCategory(card.status) === filter);

const handleCardClick = async (card: MatchableCardProps) => {
const toastMsg = getPendingToast(card.status, card.type);
if (toastMsg) {
showErrorToast(toastMsg, undefined, false);
return;
}

const query = CLICKABLE_STATUS_MAP[card.status ?? ''];
if (!query) return;

Expand All @@ -39,9 +46,7 @@ const MatchTabPanel = ({ cards, filter }: MatchTabPanelProps) => {
}
};

const isClickable = (status?: string) => {
return Boolean(CLICKABLE_STATUS_MAP[status ?? '']);
};
const isClickable = (status?: string) => Boolean(CLICKABLE_STATUS_MAP[status ?? '']);

return (
<div className="flex-col gap-[0.8rem] px-[1.6rem] py-[2rem]">
Expand All @@ -56,10 +61,11 @@ const MatchTabPanel = ({ cards, filter }: MatchTabPanelProps) => {
<button
key={card.id}
type="button"
onClick={isClickable(card.status) ? () => handleCardClick(card) : undefined}
onClick={() => handleCardClick(card)}
className={cn('w-full', {
'cursor-pointer': isClickable(card.status),
})}
aria-disabled={!isClickable(card.status)}
>
<Card
status={card.status}
Expand Down
27 changes: 21 additions & 6 deletions src/pages/match/hooks/mapMatchData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,35 @@ import type {
GroupCardProps,
SingleCardProps,
} from '@components/card/match-card/types/card';
import { chipVariantOptions } from '@components/chip/styles/chip-variants';
import type { getGroupMatchMate, singleMatchMate } from '@/shared/types/match-types';

const normalizeChipKey = (v?: string) => (v ?? '').replace(/\s/g, '');

const isChipColor = (k: string): k is ChipColor =>
Object.prototype.hasOwnProperty.call(chipVariantOptions.bgColor, k);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

api 응답에서 직관 스타일에 띄어쓰기가 포함되어 '열정 응원러'처럼 와서 그동안 스타일 칩이 나타나지 않는 거였어요! 그래서 이렇게 띄어쓰기에도 대응할 수 있도록 추가했습니다.

Copy link
Contributor

Choose a reason for hiding this comment

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

마찬가지로 utils로 이동하면 좋을 것 같아요!

export const mapSingleMatchData = (mates: singleMatchMate[] = []): SingleCardProps[] => {
return mates.map((mate) => ({
...mate,
type: 'single',
imgUrl: [mate.imgUrl],
chips: [mate.team, mate.style].map((v) => v as ChipColor),
}));
return mates.map((mate) => {
const teamKey = mate.team;
const styleKey = normalizeChipKey(mate.style);

const chips = [teamKey, styleKey].filter(isChipColor);

return {
...mate,
type: 'single',
imgUrl: [mate.imgUrl],
isCreated: Boolean(mate.isCreated),
chips,
};
});
};

export const mapGroupMatchData = (mates: getGroupMatchMate[] = []): GroupCardProps[] => {
return mates.map((mate) => ({
...mate,
type: 'group',
isCreated: Boolean(mate.isCreated),
}));
};
27 changes: 0 additions & 27 deletions src/pages/match/hooks/useMate.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/pages/match/match.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const Match = () => {

const contentMap = {
'1:1': (
<MatchTabPanel key="single" cards={mapSingleMatchData(singleData?.mates)} filter={filter} />
<MatchTabPanel key="single" cards={mapSingleMatchData(singleData?.results)} filter={filter} />
),
그룹: <MatchTabPanel key="group" cards={mapGroupMatchData(groupData?.mates)} filter={filter} />,
};
Expand Down
18 changes: 18 additions & 0 deletions src/pages/match/utils/match-status.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { GroupCardProps, SingleCardProps } from '@components/card/match-card/types/card';

export const statusToCategory = (status?: string): '대기 중' | '완료' | '실패' | '' => {
if (!status) return '';
if (status.includes('매칭 완료')) return '완료';
Expand All @@ -14,3 +16,19 @@ export const getCardColor = (status?: string): 'active' | 'inactive' => {
};

export const fillTabItems = ['전체', '대기 중', '완료', '실패'];

type MatchableCardProps = SingleCardProps | GroupCardProps;

export const getPendingToast = (
status?: string,
type?: MatchableCardProps['type'],
): string | '' => {
if (!status) return '';
if (status === '요청 대기 중') return '메이트의 요청을 기다리는 중입니다.';
if (status === '승인 대기 중') {
return type === 'group'
? '메이트 전원의 승인을 기다리는 중입니다.'
: '메이트의 승인을 기다리는 중입니다.';
}
return '';
};
Comment on lines +22 to +38
Copy link
Contributor Author

Choose a reason for hiding this comment

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

요청 상태에 따라 토스트 메시지를 다르게 보여주는 목적입니다!

7 changes: 7 additions & 0 deletions src/shared/assets/svgs/crown.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,56 @@ import CardProfile from '@components/card/match-card/components/card-profile-ima
import type { CardProps } from '@components/card/match-card/types/card';
import ChipList from '@components/chip/chip-list';
import ChipState from '@components/chip/chip-state/chip-state';
import Icon from '@components/icon/icon';
import { cn } from '@libs/cn';
import { ROUTES } from '@routes/routes-config';
import { matchPath, useLocation } from 'react-router-dom';

const CardHeader = (props: CardProps) => {
const { type } = props;
const { pathname } = useLocation();

const isCreateMatchPage = matchPath(ROUTES.MATCH_CREATE(), pathname);

switch (type) {
const getCrownSpec = (t: CardProps['type']) => {
if (t === 'group') {
return {
box: 'h-[1.2rem] w-[1.2rem]',
pos: 'left-[1.4rem] -bottom-[0.2rem]',
size: 1.2 as const,
};
}
return {
box: 'h-[1.6rem] w-[1.6rem]',
pos: 'right-[0.3rem] -bottom-[0.2rem]',
size: 1.6 as const,
};
};

const renderProfile = (profileType: CardProps['type']) => {
const spec = getCrownSpec(profileType);
return (
<div className="relative isolate">
<CardProfile type={profileType} imgUrl={props.imgUrl} />
{props.isCreated && (
<span
className={cn(
'pointer-events-none absolute z-[var(--z-card-owner)]',
spec.pos,
'grid place-items-center rounded-full shadow-sm',
spec.box,
)}
>
<Icon name="crown" size={spec.size} className="text-owner" aria-hidden />
</span>
)}
</div>
);
};

switch (props.type) {
case 'single':
return (
<div className="flex">
<CardProfile type="single" imgUrl={props.imgUrl} />
{renderProfile('single')}
<div>
<div className="flex items-center gap-[0.8rem] pb-[0.8rem] pl-[1.2rem]">
<div className="body_16_b">{props.nickname}</div>
Expand Down Expand Up @@ -47,7 +83,7 @@ const CardHeader = (props: CardProps) => {
<div className="cap_12_m text-gray-900">
매칭된 인원 {props.count}/{GROUP_MAX}
</div>
<CardProfile type="group" imgUrl={props.imgUrl} />
{renderProfile('group')}
</div>
</div>
{!isCreateMatchPage && (
Expand All @@ -61,7 +97,7 @@ const CardHeader = (props: CardProps) => {
case 'detailed':
return (
<div className="flex gap-[1.2rem]">
<CardProfile type="detailed" imgUrl={props.imgUrl} />
{renderProfile('detailed')}
<div className="flex-col gap-[0.8rem]">
<div className="flex-col gap-[0.4rem]">
<div className="body_16_b">{props.nickname}</div>
Expand All @@ -79,7 +115,7 @@ const CardHeader = (props: CardProps) => {
case 'user':
return (
<div className="flex">
<CardProfile type="user" imgUrl={props.imgUrl} />
{renderProfile('user')}
<div>
<div className="gap-[0.8rem] pb-[0.8rem] pl-[1.2rem]">
<div className="body_16_b">{props.nickname}</div>
Expand All @@ -88,7 +124,7 @@ const CardHeader = (props: CardProps) => {
</div>
</div>
<div className="ml-[1.2rem] flex-row gap-[0.8rem]">
<ChipList names={props.chips} />
<ChipList names={props.chips ?? []} />
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Icon from '@components/icon/icon';
import { cn } from '@libs/cn';
import type { ReactNode } from 'react';

const CardProfile = ({ type, imgUrl = [] }: CardProfileProps) => {
const CardProfile = ({ type, imgUrl }: CardProfileProps) => {
const zIndexClasses = [
'z-[var(--z-card-profile-1)] ml-0',
'-ml-[0.9rem] z-[var(--z-card-profile-2)]',
Expand All @@ -13,30 +13,39 @@ const CardProfile = ({ type, imgUrl = [] }: CardProfileProps) => {
];

if (type === 'group') {
const filledImages = imgUrl.slice(0, 4);
const emptySlots = Array(4 - filledImages.length).fill('');
const urls = Array.isArray(imgUrl) ? imgUrl.slice(0, 4) : [];
const emptyCount = Math.max(0, 4 - urls.length);
const nodes: ReactNode[] = [];

const profileElements: ReactNode[] = [];

filledImages.forEach((url, order) => {
const key = url || `profile-slot-${order}`;
profileElements.push(
urls.forEach((url, order) => {
const hasSrc = typeof url === 'string' && url.length > 0;
nodes.push(
<div
key={key}
key={hasSrc ? url : `profile-slot-${order}`}
className={cn(
'flex items-center justify-center overflow-hidden rounded-full',
zIndexClasses[order],
profileVariants({ type }),
)}
>
<Icon size={2.8} name="profile" className={cn('rounded-full text-gray-black')} />
{hasSrc ? (
<img
src={url}
alt=""
loading="lazy"
decoding="async"
className="h-[2.8rem] w-[2.8rem] rounded-full object-cover"
/>
) : (
<Icon size={2.8} name="profile" className="rounded-full" />
)}
</div>,
);
});

emptySlots.forEach((_, order) => {
const slotIndex = filledImages.length + order;
profileElements.push(
Array.from({ length: emptyCount }).forEach((_, idx) => {
const slotIndex = urls.length + idx;
nodes.push(
<div
key={`empty-slot-${slotIndex}`}
className={cn(
Expand All @@ -50,17 +59,29 @@ const CardProfile = ({ type, imgUrl = [] }: CardProfileProps) => {
);
});

return <div className="flex items-center">{profileElements}</div>;
return <div className="flex items-center">{nodes}</div>;
}

const src = typeof imgUrl === 'string' ? imgUrl : Array.isArray(imgUrl) ? (imgUrl[0] ?? '') : '';

return (
<div className="flex items-center overflow-hidden rounded-full">
<Icon
width={6}
height={6}
name="profile"
className={cn('overflow-hidden rounded-full', profileVariants({ type }))}
/>
{src ? (
<img
src={src}
alt=""
loading="lazy"
decoding="async"
className={cn('overflow-hidden rounded-full object-cover', profileVariants({ type }))}
/>
) : (
<Icon
width={6}
height={6}
name="profile"
className={cn('overflow-hidden rounded-full', profileVariants({ type }))}
/>
)}
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ export const cardVariants = cva('relative w-full rounded-[12px] bg-white', {
export const profileVariants = cva('overflow-hidden rounded-full object-cover', {
variants: {
type: {
single: 'h-[6rem] w-[6rem] border border-gray-900',
group: 'h-[2.8rem] w-[2.8rem] border border-main-600',
detailed: 'h-[8.2rem] w-[8.2rem] border border-gray-900',
user: 'h-[8.2rem] w-[8.2rem] border border-gray-900',
single: 'h-[6rem] w-[6rem]',
group: 'h-[2.8rem] w-[2.8rem]',
detailed: 'h-[8.2rem] w-[8.2rem]',
user: 'h-[8.2rem] w-[8.2rem]',
},
},
defaultVariants: {
Expand Down
Loading