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
4 changes: 2 additions & 2 deletions apps/web/src/api/comments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type CommentListItemRes = {
content: string;
createdAt: string;
updatedAt: string;
authorInfo: AuthorInfoRes; // ✅ 추가
authorInfo: AuthorInfoRes;
};

type CommentListRes = {
Expand All @@ -30,7 +30,7 @@ type CommentCreateRes = {
content: string;
createdAt: string;
updatedAt: string;
authorInfo: AuthorInfoRes; // ✅ 생성 응답에서도 내려온다고 가정
authorInfo: AuthorInfoRes;
};

type CommentUpdateRes = {
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/layout/AppLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export default function AppLayout({
title,
tabs,
children,
topOffset = 72,
topOffset = 59,
onBack,
}: {
title: string;
Expand Down
34 changes: 8 additions & 26 deletions apps/web/src/pages/Community/_components/CommunityAppLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,27 @@
import AppLayout from '@/components/layout/AppLayout';
import CommunityNavigator from '@/pages/Community/_components/CommunityNavigator';
import type { NavKey } from '@/types/community';
import styled from '@emotion/styled';

type BaseProps = React.ComponentProps<typeof AppLayout>;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Props 타입에서 더 이상 사용되지 않는 activeNav가 제거되었어야 하지만, 이전 코드(type Props = BaseProps & { activeNav?: NavKey };)에서 NavKey 타입이 types/community에서 import 되고 있었습니다. 현재 코드에서는 NavKey import가 제거되었지만, CommunityNavigator 내부에서 NavKey를 export하고 있으므로, 이 컴포넌트의 Props 타입도 CommunityNavigator에서 export한 NavKey를 사용하는 것이 타입 안정성 측면에서 더 명확할 수 있습니다. 현재 구조 변경으로 인해 이 prop이 필요없어졌지만, 추후 비슷한 패턴을 사용할 때 참고하시면 좋을 것 같습니다.

type Props = BaseProps & { activeNav?: NavKey };

const TAB_BAR_HEIGHT = 60;
type Props = BaseProps;

export default function CommunityAppLayout({ children, ...rest }: Props) {
return (
<>
<ScrollContainer>
<InnerScroll paddingBottom={TAB_BAR_HEIGHT}>
<AppLayout {...rest}>{children}</AppLayout>
</InnerScroll>
</ScrollContainer>
<FixedNav>
<ScrollContainer>
<AppLayout {...rest}>
<CommunityNavigator />
</FixedNav>
</>
<Content>{children}</Content>
</AppLayout>
</ScrollContainer>
);
}

const ScrollContainer = styled.div`
min-height: 100vh;
/* iOS 부드러운 스크롤 */
-webkit-overflow-scrolling: touch;
`;

const InnerScroll = styled.div<{ paddingBottom: number }>`
const Content = styled.div`
overflow-y: auto;
/* AppLayout이 내부에서 height를 쓰더라도 여기가 스크롤을 맡습니다 */
max-height: 100vh;
padding-bottom: ${({ paddingBottom }) => `${paddingBottom}px`};
`;

const FixedNav = styled.div`
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 50; /* 컨텐츠 위에 오도록 */
max-height: 100%;
`;
96 changes: 47 additions & 49 deletions apps/web/src/pages/Community/_components/CommunityNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { useNavigate } from 'react-router-dom';
import { useCommunityStore } from '@/stores/communityStore';

const PATH_BY_KEY = {
home: '/community',
photo: '/community/feed/proof',
route: '/community/feed/share',
run: '/community/feed/mate',
photo: '/community/feed/proof',
all: '/community',
} as const;

type NavKey = keyof typeof PATH_BY_KEY;
export type NavKey = keyof typeof PATH_BY_KEY;

export default function CommunityNavigator() {
const navigate = useNavigate();
Expand All @@ -22,71 +22,69 @@ export default function CommunityNavigator() {
};

return (
<Container>
<NavItem
active={active === 'home'}
aria-current={active === 'home' ? 'page' : undefined}
onClick={go('home')}
>
<i className="ri-home-4-fill" />
<span>홈</span>
</NavItem>

<NavItem
active={active === 'photo'}
aria-current={active === 'photo' ? 'page' : undefined}
onClick={go('photo')}
>
<i className="ri-camera-2-fill" />
<span>인증샷</span>
</NavItem>

<NavItem
<Container role="tablist" aria-label="커뮤니티 내비게이션">
<TabButton
role="tab"
aria-selected={active === 'route'}
active={active === 'route'}
aria-current={active === 'route' ? 'page' : undefined}
onClick={go('route')}
>
<i className="ri-route-line" />
<span>경로</span>
</NavItem>

<NavItem
경로
</TabButton>
<TabButton
role="tab"
aria-selected={active === 'run'}
active={active === 'run'}
aria-current={active === 'run' ? 'page' : undefined}
onClick={go('run')}
>
<i className="ri-run-fill" />
<span>메이트</span>
</NavItem>
메이트
</TabButton>
<TabButton
role="tab"
aria-selected={active === 'photo'}
active={active === 'photo'}
onClick={go('photo')}
>
인증샷
</TabButton>
<TabButton
role="tab"
aria-selected={active === 'all'}
active={active === 'all'}
onClick={go('all')}
>
전체보기
</TabButton>
</Container>
);
}

/** 상단 고정(헤더 바로 아래) */
const Container = styled.nav`
position: fixed;
bottom: 0;
left: 0;
right: 0;
position: sticky;
top: 0; /* AppLayout 헤더가 fixed이면 필요에 따라 간격 조절 */
z-index: ${({ theme }) => theme.zIndex.header};
display: grid;
grid-template-columns: repeat(4, 1fr);
align-items: center;
border-top: 1px solid ${({ theme }) => theme.colors.border};
gap: 0;
background: ${({ theme }) => theme.colors.surface};
padding: 6px 0;
z-index: ${({ theme }) => theme.zIndex.header};
border-bottom: 1px solid ${({ theme }) => theme.colors.border};
`;

const NavItem = styled.button<{ active?: boolean }>`
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2px;
const TabButton = styled.button<{ active?: boolean }>`
${({ theme }) => theme.typography.small};
height: 36px;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

TabButton의 높이로 하드코딩된 36px는 매직 넘버입니다. 1 이 값은 theme.ts에 상수로 정의하고 (예: theme.sizes.tabHeight) 참조하는 것이 좋습니다. 이렇게 하면 디자인 시스템의 일관성을 유지하고 향후 변경이 용이해집니다.

Style Guide References

Footnotes

  1. 매직 넘버(의미를 알 수 없는 숫자)는 코드 가독성과 유지보수성을 해치므로, 이름 있는 상수로 치환해야 합니다.

background: ${({ theme, active }) =>
active ? theme.colors.surfaceAlt : 'transparent'};
color: ${({ theme, active }) =>
active ? theme.colors.primary : theme.colors.subtext};
i {
font-size: 20px;
line-height: 1;
transition:
background 0.15s ease,
color 0.15s ease,
border-color 0.15s ease;
Comment on lines +82 to +85

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

transition 속성에 사용된 0.15s는 하드코딩된 값입니다. 1 theme.ts에 정의된 transition 값을 (예: theme.transition.fast) 사용하면 앱 전체의 애니메이션 속도를 일관되게 관리할 수 있습니다.

  transition:
    background ${({ theme }) => theme.transition.fast},
    color ${({ theme }) => theme.transition.fast},
    border-color ${({ theme }) => theme.transition.fast};

Style Guide References

Footnotes

  1. 재사용 가능한 값들은 테마 또는 상수 파일에서 관리하여 일관성을 유지하고 재사용성을 높여야 합니다.


&:hover {
background: ${({ theme }) => theme.colors.surfaceAlt};
}
`;
2 changes: 1 addition & 1 deletion apps/web/src/types/community.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ export interface Comment {
authorInfo?: AuthorObj;
}

export type NavKey = 'home' | 'photo' | 'route' | 'run';
export type NavKey = 'home' | 'photo' | 'route' | 'run' | 'all';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

NavKey 타입에 더 이상 사용되지 않는 'home'이 남아있습니다. CommunityNavigator.tsx에서는 'home''all'로 대체되었습니다. 이 불일치로 인해 communityStore의 초기 상태(activeNav: 'home')가 유효하지 않게 되어, 커뮤니티 페이지 첫 로드 시 네비게이션 바의 활성 상태가 올바르게 표시되지 않는 등 예기치 않은 동작을 유발할 수 있습니다. NavKey에서 'home'을 제거하고, communityStore.ts의 초기 상태를 'all'과 같은 유효한 값으로 변경해야 합니다.

Suggested change
export type NavKey = 'home' | 'photo' | 'route' | 'run' | 'all';
export type NavKey = 'photo' | 'route' | 'run' | 'all';