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
5 changes: 3 additions & 2 deletions design-system/ui/buttons/IconButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,18 @@ export interface IconButtonProps {
onClick: (event: React.MouseEvent<HTMLButtonElement>) => void;
size?: 'small' | 'medium' | 'large';
iconClassName?: string;
className?: string;
}

const IconButton = ({ size = 'medium', iconPath, onClick, iconClassName }: IconButtonProps) => {
const IconButton = ({ size = 'medium', iconPath, onClick, iconClassName, className }: IconButtonProps) => {
const sizeClasses = {
small: 'w-8 h-8',
medium: 'w-10 h-10',
large: 'w-14 h-14',
};

return (
<button className={`inline-flex items-center justify-center ${sizeClasses[size]}`} onClick={onClick}>
<button className={`inline-flex items-center justify-center ${sizeClasses[size]} ${className}`} onClick={onClick}>
<div className={`flex items-center justify-center w-1/2 h-1/2 ${iconClassName}`}>{iconPath}</div>
</button>
);
Expand Down
67 changes: 54 additions & 13 deletions design-system/ui/texts/Countdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,68 @@ import { flexCenter } from '../../styles/flex';
interface CountdownProps {
children: React.ReactNode; // 버튼 내부 컨텐츠
isChecked: boolean; // 활성화 여부
status?: 'PROGRESS' | 'COMPLETE' | 'DELETED'; // 이벤트 상태
}

const Countdown = ({ children, isChecked }: CountdownProps) => {
// Active와 Inactive 스타일 분기
const activeStyles = `
border-deDayText text-deDayText bg-deDayBg
`;

const inactiveStyles = `
border-deDayTextDark text-deDayTextDark bg-deDayBgLight
`;

const Countdown = ({ children, isChecked, status }: CountdownProps) => {
const baseStyles = `
h-5 sm:h-4 md:h-5 w-12 px-2 py-1 rounded-[2px] text-11
border-[0.1px] font-medium ${flexCenter}
`;

const isEnded = children === 'false';
const displayText = isEnded ? '종료' : children;
// status 기반 스타일 분기
const getStyles = () => {
if (status) {
switch (status) {
case 'PROGRESS':
return `
border-deDayText text-deDayText bg-deDayBg
`;
case 'COMPLETE':
return `
border-eventEndText text-eventEndText bg-eventEndBg
`;
case 'DELETED':
return `
border-deDayTextDark text-deDayTextDark bg-deDayBgLight
`;
default:
return `
border-deDayText text-deDayText bg-deDayBg
`;
}
}

// status가 없을 때 기존 로직 (children 기반)
const isEnded = children === 'false';
return isChecked && !isEnded
? `border-deDayText text-deDayText bg-deDayBg`
: `border-deDayTextDark text-deDayTextDark bg-deDayBgLight`;
};

// status에 따른 displayText 결정
let displayText: React.ReactNode;
if (status) {
switch (status) {
case 'PROGRESS':
displayText = '진행중';
break;
case 'COMPLETE':
displayText = '종 료';
break;
case 'DELETED':
displayText = '무 효';
break;
default:
displayText = children;
}
} else {
// status가 없을 때 기존 로직
const isEnded = children === 'false';
displayText = isEnded ? '종료' : children;
}

return <button className={`${baseStyles} ${isChecked && !isEnded ? activeStyles : inactiveStyles}`}>{displayText}</button>
return <button className={`${baseStyles} ${getStyles()}`}>{displayText}</button>
};

export default Countdown;
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@tanstack/react-query-devtools": "^5.76.1",
"autoprefixer": "^10.4.20",
"axios": "^1.8.1",
"classnames": "^2.5.1",
"date-fns": "^4.1.0",
"framer-motion": "^12.4.7",
"js-cookie": "^3.0.5",
Expand All @@ -34,6 +35,7 @@
"react-kakao-maps-sdk": "^1.1.27",
"react-quill": "^2.0.0",
"react-router-dom": "^7.0.1",
"react-virtuoso": "^4.13.0",
"recharts": "^2.15.3",
"storybook": "^8.4.6",
"swiper": "^11.2.1",
Expand Down
6 changes: 4 additions & 2 deletions src/features/home/ui/EventSliderSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,14 @@ const EventSliderSection = ({ title, events }: EventSliderSectionProps) => {
</div>
{startIndex !== 0 && (
<IconButton
iconPath={<img src={leftButton} alt="왼쪽 버튼" className="absolute top-1/2 left-0.5" />}
className="absolute top-1/2 left-0.5"
iconPath={<img src={leftButton} alt="왼쪽 버튼" />}
onClick={() => handlePrev(setStartIndex, startIndex, events.length)}
/>
)}
<IconButton
iconPath={<img src={rightButton} alt="오른쪽 버튼" className="absolute top-1/2 right-0.5" />}
className="absolute top-1/2 right-0.5"
iconPath={<img src={rightButton} alt="오른쪽 버튼" />}
onClick={() => handleNext(setStartIndex, startIndex, events.length)}
/>
</div>
Expand Down
8 changes: 5 additions & 3 deletions src/features/home/ui/EventTags.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ const EventTags = () => {

return (
<>
<EventSliderSection title="최신 이벤트" events={latestEvents} />
<EventSliderSection title="요즘 뜨는 이벤트" events={trendingEvents} />
<EventSliderSection title="곧 이벤트가 마감돼요! ⏰" events={closingSoonEvents} />
<div className="grid grid-cols-1 gap-10 mb-5">
<EventSliderSection title="최신 이벤트" events={latestEvents} />
<EventSliderSection title="요즘 뜨는 이벤트" events={trendingEvents} />
<EventSliderSection title="곧 이벤트가 마감돼요! ⏰" events={closingSoonEvents} />
</div>
</>
);
};
Expand Down
8 changes: 5 additions & 3 deletions src/features/ticket/model/Order.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ export interface OrderTicketResponse {
bannerImageUrl: string;
title: string;
hostChannelName: string;
address: string;
startDate: string;
remainDays: string;
hashtags: string[];
endDate: string;
address: string;
onlineType: OnlineType;
hashtags: string[];
remainDays: string;
status: 'PROGRESS' | 'COMPLETE' | 'DELETED';
};
ticketQrCode: string;
ticketName: string;
Expand Down
40 changes: 37 additions & 3 deletions src/pages/menu/ui/MyTicketPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ import { OrderTicketResponse } from '../../../features/ticket/model/Order';
import EmailDeleteModal from '../../../widgets/dashboard/ui/email/EmailDeleteModal';
import TertiaryButton from '../../../../design-system/ui/buttons/TertiaryButton';
import useAuthStore from '../../../app/provider/authStore';
import TextModal from '../../../shared/ui/TextModal';

const MyTicketPage = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
const [pendingTicket, setPendingTicket] = useState<OrderTicketResponse | null>(null);
const [selectedTicket, setSelectedTicket] = useState<OrderTicketResponse | null>(null);
const [isCancelMode, setIsCancelMode] = useState(false);
const [selectedIds, setSelectedIds] = useState<number[]>([]);
Expand All @@ -25,6 +27,9 @@ const MyTicketPage = () => {
const { mutate: cancelTicket } = useCancelTicket();
const isLoggedIn = useAuthStore(state => state.isLoggedIn);

const [isDoneEventModalOpen, setIsDoneEventModalOpen] = useState(false);
const [eventModalText, setEventModalText] = useState('');

const handleCancelButtonClick = () => {
if (isCancelMode) {
if (selectedIds.length === 0) {
Expand Down Expand Up @@ -58,17 +63,38 @@ const MyTicketPage = () => {
if (isCancelMode) {
setSelectedIds(prev => (prev.includes(ticket.orderId) ? prev.filter(id => id !== ticket.orderId) : [...prev, ticket.orderId]));
} else {
setSelectedTicket(ticket);
setSelectedTicket(null);
setPendingTicket(ticket);
setIsModalOpen(true);
}
};

useEffect(() => {
if (data?.result) {
setTickets(data.result);
setTickets(data.result);
}
}, [data]);

useEffect(() => {
if (pendingTicket) {
setSelectedTicket(pendingTicket);
setPendingTicket(null);
}
}, [pendingTicket]);

useEffect(() => {
if (selectedTicket) {
if (selectedTicket.event.status === 'COMPLETE') {
setEventModalText('이벤트가 종료되었습니다.');
setIsDoneEventModalOpen(true);
} else if (selectedTicket.event.status === 'DELETED') {
setEventModalText('호스트가 이벤트를 삭제했습니다.');
setIsDoneEventModalOpen(true);
}
}
}, [selectedTicket]);

console.log('isDoneEventModalOpen', isDoneEventModalOpen);
return (
<TicketHostLayout image={TicketLogo} centerContent="내 티켓" ticketPage={true} isCancelMode={isCancelMode}>
{tickets.length > 0 && (
Expand Down Expand Up @@ -107,6 +133,7 @@ const MyTicketPage = () => {
eventDate={ticket.event.startDate}
location={ticket.event.address}
hashtags={ticket.event.hashtags}
status={ticket.event.status}
onClick={() => handleEventCardClick(ticket)}
className={`transition-transform duration-200 ${isCancelMode && selectedIds.includes(ticket.orderId) ? 'scale-95 border-2 border-pink-400' : ''
}`}
Expand Down Expand Up @@ -134,7 +161,7 @@ const MyTicketPage = () => {
)}
</div>

{isModalOpen && selectedTicket && (
{isModalOpen && selectedTicket && (selectedTicket.event.status !== 'DELETED' && selectedTicket.event.status !== 'COMPLETE') && (
<div className="fixed inset-0 z-50 bg-black bg-opacity-60 flex items-center justify-center">
<QrModal
isChecked={true}
Expand All @@ -155,6 +182,13 @@ const MyTicketPage = () => {
</div>
)}

{isDoneEventModalOpen && selectedTicket && (
<TextModal isOpen={isDoneEventModalOpen} onClick={() => setIsDoneEventModalOpen(false)}>
{eventModalText}
</TextModal>
)
}

{isDeleteModalOpen && (
<EmailDeleteModal
mainText={`총 ${selectedIds.length}개의 티켓을 취소하시겠습니까? 취소 후에는 복구가 불가능합니다.`}
Expand Down
6 changes: 5 additions & 1 deletion src/shared/types/api/http-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,11 @@ axiosClient.interceptors.response.use(
originalRequest._retry = true;

try {
await axios.post(`${import.meta.env.VITE_API_BASE_URL}/api/v1/oauth/reissue`, {}, { withCredentials: true });
await axios.post(
`${import.meta.env.VITE_API_BASE_URL}/api/v1/oauth/reissue`,
{},
{ withCredentials: true }
);
// 새 토큰이 쿠키에 재설정되었으므로 원래 요청 재시도
return axiosClient(originalRequest);
} catch (refreshError: unknown) {
Expand Down
18 changes: 6 additions & 12 deletions src/shared/ui/EventCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import deleteButton from '../../../public/assets/menu/Delete.svg';
import { useState } from 'react';
import DeleteConfirmModal from '../../widgets/host/DeleteConfirmModal';
import { useEventDeletion } from '../../entities/event/hook/useEventHook';
import HashtagCarousel from './HashtagCarousel';

interface EventCardProps {
id: number;
Expand All @@ -25,6 +26,7 @@ interface EventCardProps {
isDelete?: boolean;
onDeleteSuccess?: (eventId: number) => void;
onlineType?: 'OFFLINE' | 'ONLINE';
status?: 'PROGRESS' | 'COMPLETE' | 'DELETED';
aspectRatio?: string;
}

Expand All @@ -43,6 +45,7 @@ const EventCard = ({
isDelete = false,
onDeleteSuccess,
onlineType,
status,
aspectRatio = 'md:aspect-[3/4.3] sm:aspect-[3/5]'
}: EventCardProps) => {
const navigate = useNavigate();
Expand All @@ -69,7 +72,7 @@ const EventCard = ({
<h2 className="md:max-w-[130px] sm:max-w-[130px] text-sm font-semibold truncate overflow-hidden">{eventTitle}</h2>
{dDay !== 'false' && (
<div className="sm:max-w-15 md:max-w-15">
<Countdown isChecked>{dDay}</Countdown>
<Countdown isChecked status={status}>{dDay}</Countdown>
</div>
)}
</div>
Expand All @@ -92,19 +95,10 @@ const EventCard = ({
{children}
{/* 해시태그 */}
{hashtags && (
<div className="flex flex-wrap w-full h-6 mt-2 overflow-hidden text-xs font-semibold text-gray-700 whitespace-nowrap">
{(hashtags ?? []).map((tag, index) => (
<span
key={index}
className="flex items-center justify-center h-6 px-2 mr-2 bg-gray-200 rounded last:mr-0"
>
{tag}
</span>
))}
</div>
<HashtagCarousel hashtagSlides={hashtags} onClick={e => e.stopPropagation()} />
)}

{/* 대시보드 버튼 */}
{/* 대시보드 버튼 */}
{isHostPage && (
<div className="flex justify-between items-center h-7">
<TertiaryButton
Expand Down
Loading