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
26 changes: 19 additions & 7 deletions frontend/src/pages/ClubDetailPage/ClubDetailPage.styles.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import styled from 'styled-components';
import { media } from '@/styles/mediaQuery';
import { colors } from '@/styles/theme/colors';
import { transitions } from '@/styles/theme/transitions';

export const Container = styled.div`
width: 100%;
Expand Down Expand Up @@ -57,17 +58,28 @@ export const TabButton = styled.button<{ $active: boolean }>`
border-bottom: 2px solid
${({ $active }) => ($active ? colors.gray[800] : colors.gray[400])};
cursor: pointer;
transition: all 0.2s;

&:hover {
color: ${colors.gray[800]};
border-bottom: 2px solid ${colors.gray[800]};
}
transition:
color ${transitions.duration.normal} ${transitions.easing.easeInOut},
border-color ${transitions.duration.normal} ${transitions.easing.easeInOut};

${media.tablet} {
flex: 1;
width: auto;
}
`;

export const TabContent = styled.div``;
export const TabContent = styled.div`
animation: fadeIn ${transitions.duration.normal}
${transitions.easing.easeInOut};

@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
`;
50 changes: 34 additions & 16 deletions frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useState } from 'react';
import { useParams } from 'react-router-dom';
import { useCallback, useEffect, useState } from 'react';
import { useParams, useSearchParams } from 'react-router-dom';
import Footer from '@/components/common/Footer/Footer';
import Header from '@/components/common/Header/Header';
import { PAGE_VIEW, USER_EVENT } from '@/constants/eventName';
Expand All @@ -13,17 +13,41 @@ import ClubProfileCard from '@/pages/ClubDetailPage/components/ClubProfileCard/C
import * as Styled from './ClubDetailPage.styles';
import ClubDetailFooter from './components/ClubDetailFooter/ClubDetailFooter';

export const TAB_TYPE = {
INTRO: 'intro',
PHOTOS: 'photos',
} as const;

type TabType = (typeof TAB_TYPE)[keyof typeof TAB_TYPE];

const ClubDetailPage = () => {
const [activeTab, setActiveTab] = useState<'intro' | 'photos'>('intro');
const trackEvent = useMixpanelTrack();

const [searchParams, setSearchParams] = useSearchParams();
const tabParam = searchParams.get('tab') as TabType | null;

const activeTab: TabType =
tabParam && Object.values(TAB_TYPE).includes(tabParam)
? tabParam
: TAB_TYPE.INTRO;

const { clubId } = useParams<{ clubId: string }>();
const { isLaptop, isDesktop } = useDevice();
const trackEvent = useMixpanelTrack();

const { data: clubDetail, error } = useGetClubDetail(clubId || '');

useTrackPageView(PAGE_VIEW.CLUB_DETAIL_PAGE, clubDetail?.name, !clubDetail);

const handleIntroTabClick = useCallback(() => {
setSearchParams({ tab: TAB_TYPE.INTRO });
trackEvent(USER_EVENT.CLUB_INTRO_TAB_CLICKED);
}, [setSearchParams, trackEvent]);

const handlePhotosTabClick = useCallback(() => {
setSearchParams({ tab: TAB_TYPE.PHOTOS });
trackEvent(USER_EVENT.CLUB_FEED_TAB_CLICKED);
}, [setSearchParams, trackEvent]);

if (!clubDetail) {
return null;
}
Expand All @@ -49,30 +73,24 @@ const ClubDetailPage = () => {
<Styled.RightSection>
<Styled.TabList>
<Styled.TabButton
$active={activeTab === 'intro'}
onClick={() => {
setActiveTab('intro');
trackEvent(USER_EVENT.CLUB_INTRO_TAB_CLICKED);
}}
$active={activeTab === TAB_TYPE.INTRO}
onClick={handleIntroTabClick}
>
소개 내용
</Styled.TabButton>
<Styled.TabButton
$active={activeTab === 'photos'}
onClick={() => {
setActiveTab('photos');
trackEvent(USER_EVENT.CLUB_FEED_TAB_CLICKED);
}}
$active={activeTab === TAB_TYPE.PHOTOS}
onClick={handlePhotosTabClick}
>
활동사진
</Styled.TabButton>
</Styled.TabList>

<Styled.TabContent>
{activeTab === 'intro' && (
{activeTab === TAB_TYPE.INTRO && (
<ClubIntroContent {...clubDetail.description} />
)}
{activeTab === 'photos' && (
{activeTab === TAB_TYPE.PHOTOS && (
<ClubFeed feed={clubDetail.feeds} clubName={clubDetail.name} />
)}
</Styled.TabContent>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import styled from 'styled-components';
import { media } from '@/styles/mediaQuery';
import { colors } from '@/styles/theme/colors';
import { transitions } from '@/styles/theme/transitions';

export const Container = styled.div`
width: 100%;
Expand All @@ -22,7 +23,7 @@ export const PhotoItem = styled.div`
overflow: hidden;
cursor: pointer;
background-color: ${colors.gray[100]};
transition: all 0.3s ease;
transition: all ${transitions.duration.normal} ${transitions.easing.ease};

&:hover {
transform: translateY(-4px);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import styled from 'styled-components';
import { media } from '@/styles/mediaQuery';
import { colors } from '@/styles/theme/colors';
import { transitions } from '@/styles/theme/transitions';
import { typography } from '@/styles/theme/typography';

const setTypography = (typo: { size: string; weight: number }) => `
Expand Down Expand Up @@ -181,16 +182,24 @@ export const ArrowIcon = styled.svg<{ $isOpen: boolean }>`
width: 24px;
height: 24px;
color: ${colors.gray[400]};
transition: transform 0.3s ease;
transition: transform ${transitions.duration.normal}
${transitions.easing.ease};
transform: ${({ $isOpen }) => ($isOpen ? 'rotate(180deg)' : 'rotate(0deg)')};
flex-shrink: 0;
`;

export const AnswerContainer = styled.div`
padding: 0 20px 20px 20px;
export const AnswerContainer = styled.div<{ $isOpen: boolean }>`
max-height: ${({ $isOpen }) => ($isOpen ? '500px' : '0')};
opacity: ${({ $isOpen }) => ($isOpen ? '1' : '0')};
padding: ${({ $isOpen }) => ($isOpen ? '0 20px 20px 20px' : '0 20px')};
overflow: hidden;
transition:
max-height ${transitions.duration.normal} ${transitions.easing.easeInOut},
opacity ${transitions.duration.normal} ${transitions.easing.easeInOut},
padding ${transitions.duration.normal} ${transitions.easing.easeInOut};

${media.mobile} {
padding: 0 16px 16px 16px;
padding: ${({ $isOpen }) => ($isOpen ? '0 16px 16px 16px' : '0 16px')};
}
`;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,9 @@ const ClubIntroContent = ({
/>
</Styled.ArrowIcon>
</Styled.QuestionRow>
{isOpen && (
<Styled.AnswerContainer>
<Styled.AnswerBox>{faq.answer}</Styled.AnswerBox>
</Styled.AnswerContainer>
)}
<Styled.AnswerContainer $isOpen={isOpen}>
<Styled.AnswerBox>{faq.answer}</Styled.AnswerBox>
</Styled.AnswerContainer>
</Styled.FaqItem>
);
})}
Expand Down
43 changes: 0 additions & 43 deletions frontend/src/pages/ClubDetailPage/mockData.ts

This file was deleted.

1 change: 0 additions & 1 deletion frontend/src/pages/MainPage/MainPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ const MainPage = () => {
});

const clubs = data?.clubs || [];
// const totalCount = data?.totalCount || 0; // ⚠️ 백엔드 업데이트 전까지 임시 주석
const totalCount = data?.totalCount ?? clubs.length;

const isEmpty = !isLoading && clubs.length === 0;
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/styles/theme/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { colors } from './colors';
import { transitions } from './transitions';
import { typography } from './typography';

export const theme = {
colors,
typography,
transitions,
} as const;

export type Theme = typeof theme;
13 changes: 13 additions & 0 deletions frontend/src/styles/theme/transitions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export const transitions = {
duration: {
fast: '0.15s',
normal: '0.3s',
slow: '0.5s',
},
easing: {
ease: 'ease',
easeIn: 'ease-in',
easeOut: 'ease-out',
easeInOut: 'ease-in-out',
},
} as const;