Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat] 홈 화면 플로우 구현 #71

Merged
merged 21 commits into from
Mar 18, 2025
Merged

[Feat] 홈 화면 플로우 구현 #71

merged 21 commits into from
Mar 18, 2025

Conversation

semnil5202
Copy link
Collaborator

@semnil5202 semnil5202 commented Mar 8, 2025

📌 개요

  • 홈 화면 및 명함 링크 복사 기능을 도입합니다.

📋 변경사항

  • 카드 스와이핑
  • 링크 복사 기능
  • API 연동
  • URL 연동

화면

2025-03-17.01.44.58.mov

✅ 체크사항

  • 기능이 정상적으로 동작하는지 확인
  • 코드 스타일 및 규칙 준수 확인
  • UI가 변경된 경우 스크린샷 첨부 여부 확인

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • 홈 페이지 레이아웃이 강화되어 새로운 배경, 헤더, 카드 및 클립보드 기능이 도입되었습니다.
    • 명함 링크 복사 버튼과 새로운 아이콘이 추가되어 빠르고 직관적인 링크 복사가 가능합니다.
    • 알림 토스트가 중앙 상단에 표시되어 사용자 피드백이 개선되었습니다.
    • 추가 카드 화면의 배경이 동적 SVG 그래픽으로 업그레이드되었습니다.
    • 새로운 HomeBackground, ClipboardContainer, HeaderContainer, AddCardBackground, 및 CopyLinkIcon 컴포넌트가 추가되었습니다.
    • 새로운 useClipboard 훅이 도입되어 클립보드 기능이 향상되었습니다.
  • Style

    • 내비게이션 바가 화면 하단에 고정되어 보다 일관된 탐색 환경을 제공합니다.

Copy link

coderabbitai bot commented Mar 8, 2025

📝 Walkthrough

Walkthrough

이번 PR은 프로젝트의 의존성과 UI 구조를 정리하는 변경 사항을 포함합니다. package.jsontailwind.config.ts에서 @tailwindcss/line-clamp 관련 의존성이 제거되었으며, 메인 페이지의 레이아웃이 재구성되었습니다. 이에 따라 새로운 UI 컴포넌트(예: Toaster, HomeBackground, CopyLinkIcon, ClipboardContainer, HeaderContainer)와 커스텀 후크(useClipboard)가 추가되었고, 기존 배경 컴포넌트가 SVG 기반으로 개선되었습니다. 또한 불필요한 React Query 관련 sample 파일이 삭제되었습니다.

Changes

파일 경로(s) 변경 내용 요약
package.json, tailwind.config.ts @tailwindcss/line-clamp 의존성 제거 및 Tailwind CSS 구성 업데이트
src/app/page.tsx, src/shared/ui/Navigation/index.tsx 메인 페이지 레이아웃 재구성: Toaster 추가, 내비게이션의 위치를 relative에서 fixed (bottom-0)로 변경
src/features/home/components/BusinessCard/AddCard.tsx, src/features/home/components/BusinessCard/Background/AddCardBackground.tsx AddCardBackground 컴포넌트가 SVG 기반으로 변경되고 배경 투명도 수정
src/features/home/components/BusinessCard/Background/HomeBackground.tsx, src/features/home/components/icons/CopyLinkIcon.tsx, src/features/home/containers/ClipboardContainer.tsx, src/features/home/containers/HeaderContainer.tsx, src/features/home/hooks/useClipboard.ts 새로운 UI 및 기능 컴포넌트 추가: 배경, 아이콘, 클립보드 복사 기능, 헤더 컴포넌트 및 관련 후크 도입
src/features/home/hooks/sample.ts React Query 관련 샘플 코드(함수, 상수, 후크 등) 전면 삭제

Sequence Diagram(s)

sequenceDiagram
    participant U as 사용자
    participant CC as ClipboardContainer
    participant UC as useClipboard
    participant API as Clipboard API
    participant T as Toaster

    U->>CC: 복사 버튼 클릭
    CC->>UC: handleCopy 호출
    UC->>API: window.clipboard.writeText 호출
    API-->>UC: 복사 완료 응답
    UC-->>CC: isCopied 상태 업데이트
    CC->>T: 성공 토스트 표시
Loading

Suggested labels

🚀 feature

Suggested reviewers

  • jangwonyoon
  • youngju6143
  • JaeIn1
  • evenway2025

Tip

⚡🧪 Multi-step agentic review comment chat (experimental)
  • We're introducing multi-step agentic chat in review comments. This experimental feature enhances review discussions with the CodeRabbit agentic chat by enabling advanced interactions, including the ability to create pull requests directly from comments.
    - To enable this feature, set early_access to true under in the settings.

📜 Recent review details

Configuration used: .coderabbit.yml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 905e0e0 and a6766a7.

📒 Files selected for processing (3)
  • src/app/page.tsx (1 hunks)
  • src/features/home/components/BusinessCard/AddCard.tsx (1 hunks)
  • src/features/home/hooks/prefetches/useCardPrefetch.ts (2 hunks)
🧰 Additional context used
🧬 Code Definitions (3)
src/features/home/components/BusinessCard/AddCard.tsx (2)
src/features/home/components/BusinessCard/Background/AddCardBackground.tsx (1) (1)
  • AddCardBackground (3:15)
src/features/home/components/icons/CardPlusIcon.tsx (1) (1)
  • CardPlusIcon (3:11)
src/app/page.tsx (6)
src/features/home/components/BusinessCard/Background/HomeBackground.tsx (1) (1)
  • HomeBackground (5:34)
src/features/home/containers/HeaderContainer.tsx (1) (1)
  • HeaderContainer (3:9)
src/features/home/containers/CardContainer.tsx (1) (1)
  • CardContainer (18:49)
src/features/home/containers/ClipboardContainer.tsx (1) (1)
  • ClipboardContainer (10:31)
src/shared/ui/Navigation/index.tsx (1) (1)
  • Navbar (19:53)
src/shared/ui/Toast/sonner.tsx (1) (1)
  • Toaster (27:27)
src/features/home/hooks/prefetches/useCardPrefetch.ts (1)
src/features/home/hooks/queries/useCardQuery.ts (1) (1)
  • MY_CARD_QUERY_KEY (8:8)
🔇 Additional comments (10)
src/features/home/hooks/prefetches/useCardPrefetch.ts (5)

6-6: 가져오는 쿼리 키가 업데이트되었습니다.

이제 'CARD_QUERY_KEY' 대신 'MY_CARD_QUERY_KEY'를 가져오도록 변경되었습니다. 이 변경은 서버에서 카드 데이터를 가져올 때 사용하는 쿼리 키의 일관성을 유지하기 위한 것으로 보입니다.


8-8: 인터페이스가 간소화되었습니다.

_getCardPrefetch 함수에서 params 매개변수가 제거되었습니다. 이는 함수를 더 단순하게 만들고 불필요한 파라미터를 제거하여 코드의 명확성을 높입니다.


18-18: 공개 함수의 인터페이스가 간소화되었습니다.

getCardPrefetch 함수에서도 params 매개변수가 제거되었습니다. 이는 상위 함수의 간소화와 일관되며, API 호출 방식의 단순화를 반영합니다.


20-20: 쿼리 키가 개선되었습니다.

쿼리 키가 'MY_CARD_QUERY_KEY'로 변경되었습니다. 이 변경은 더 명확한 의미를 가지며, useCardQuery 훅과의 일관성을 유지합니다.


21-21: 함수 호출이 업데이트되었습니다.

_getCardPrefetch 함수 호출이 업데이트되어 params 인자 없이 cookie만 전달합니다. 이는 함수 시그니처 변경과 일치하며 코드 흐름이 일관되게 유지됩니다.

src/features/home/components/BusinessCard/AddCard.tsx (2)

1-2: 적절한 import 구문 사용

Next.js의 Link 컴포넌트와 AddCardBackground 컴포넌트를 적절히 임포트하여 사용하고 있습니다. 이전 리뷰에서 지적된 import 경로 오타도 수정되었네요.

Also applies to: 7-8


11-18: UX 개선을 위한 Link 컴포넌트 활용 👍

div 대신 Link 컴포넌트를 사용하여 카드 추가 기능을 구현한 것은 좋은 접근입니다. 사용자가 전체 영역을 클릭할 수 있어 사용성이 향상됩니다. 배경 불투명도를 0.2에서 0.1로 줄인 것도 디자인적으로 자연스러워 보입니다.

AddCardBackground를 절대 위치로 설정하는 방식도 적절합니다. 컴포넌트의 시각적 계층 구조가 명확히 표현되었습니다.

src/app/page.tsx (3)

4-11: 구조화된 컴포넌트 분리와 명확한 임포트

다양한 컨테이너 컴포넌트들을 분리하여 각 역할에 맞게 임포트하고 있습니다. 이렇게 관심사를 분리하는 것은 코드의 가독성과 유지보수성을 높이는 좋은 방법입니다. 특히 Toaster 컴포넌트를 추가하여 사용자 피드백을 제공하는 것은 UX 측면에서 좋은 개선사항입니다.


14-25: 레이아웃 구조 개선 및 컴포넌트 계층 구조의 명확한 설계

전체 홈 화면의 레이아웃을 체계적으로 구성했습니다. 특히:

  1. HomeBackground를 z-index로 배치하여 시각적 계층을 분명히 한 점
  2. HeaderContainer, CardContainer, ClipboardContainer 등으로 컴포넌트를 분리하여 관심사를 명확히 구분한 점
  3. 고정된 최대 너비(max-w-[600px])를 설정하여 다양한 화면 크기에서도 일관된 UI를 제공하는 점
  4. Toaster를 최상위에 배치하여 알림 메시지가 다른 요소에 영향을 받지 않도록 한 점

이런 구조적 개선은 확장성과 유지보수성을 크게 향상시킵니다. 이전 리뷰에서 언급된 것처럼 Container로 감싸는 패턴이 코드를 훨씬 깔끔하게 만들었습니다.


18-21: 명함 관련 기능의 효과적인 그룹화

카드 표시와 링크 복사 기능을 하나의 섹션으로 그룹화한 것은 논리적으로 관련 기능들을 연결시키는 좋은 접근입니다. 카드 스와이핑과 링크 복사 기능이 PR 목표에 명시된 내용과 일치하며, 사용자 경험 측면에서도 자연스러운 흐름을 제공합니다.

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@semnil5202 semnil5202 self-assigned this Mar 13, 2025
@semnil5202 semnil5202 marked this pull request as ready for review March 16, 2025 16:26
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (7)
src/features/home/containers/HeaderContainer.tsx (1)

10-10: 불필요한 빈 줄을 제거하세요.

파일 끝에 있는 불필요한 빈 줄을 제거하는 것이 좋습니다.

  );
};
-
src/features/home/components/icons/CopyLinkIcon.tsx (1)

1-20: SVG 아이콘 구현이 잘 되었습니다.

CopyLinkIcon 컴포넌트가 SVG 표준을 잘 따르고 있으며 깔끔하게 구현되었습니다. clipPath를 사용하여 아이콘 영역을 적절히 제한하고 있습니다.

개선 제안: 아이콘의 크기나 색상을 커스터마이징할 수 있도록 props를 받도록 확장하면 더 유연하게 사용할 수 있을 것입니다.

- export const CopyLinkIcon = () => {
+ export const CopyLinkIcon = ({ width = 16, height = 16, color = "white", ...props }) => {
  return (
-   <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+   <svg width={width} height={height} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
      <g clipPath="url(#clip0_994_20373)">
        <path
          d="M8.79335 5.79202C9.22657 5.99886 9.60382 6.30664 9.89342 6.68951C10.183 7.07238 10.3765 7.51916 10.4576 7.99232C10.5388 8.46548 10.5052 8.9512 10.3596 9.40867C10.2141 9.86614 9.96093 10.282 9.62135 10.6214L6.62135 13.6213C6.05874 14.184 5.29567 14.5 4.50002 14.5C3.70436 14.5 2.9413 14.184 2.37869 13.6213C1.81607 13.0587 1.5 12.2957 1.5 11.5C1.5 10.7044 1.81607 9.9413 2.37869 9.37868L3.55002 8.20735M12.45 7.79268L13.6213 6.62135C14.184 6.05874 14.5 5.29567 14.5 4.50002C14.5 3.70436 14.184 2.9413 13.6213 2.37869C13.0587 1.81607 12.2957 1.5 11.5 1.5C10.7044 1.5 9.9413 1.81607 9.37868 2.37869L6.37868 5.37868C6.03911 5.71802 5.78593 6.13389 5.64041 6.59137C5.49488 7.04884 5.46127 7.53456 5.5424 8.00772C5.62352 8.48087 5.81701 8.92765 6.10661 9.31053C6.39621 9.6934 6.77347 10.0012 7.20669 10.208"
-         stroke="white"
+         stroke={color}
          strokeWidth="1.5"
          strokeLinecap="round"
          strokeLinejoin="round"
        />
      </g>
      <defs>
        <clipPath id="clip0_994_20373">
          <rect width="16" height="16" fill="white" />
        </clipPath>
      </defs>
    </svg>
  );
};
src/features/home/containers/ClipboardContainer.tsx (1)

18-31: 불필요한 프래그먼트와 접근성 개선 필요

  1. 컴포넌트가 단일 div만 포함하고 있어 프래그먼트(<>...</>)가 불필요합니다.
  2. 버튼에 접근성 속성(aria-label)이 없습니다.

다음과 같이 개선할 수 있습니다:

return (
-  <>
    <div className="mt-6 flex w-full items-center justify-center">
      <button
        className="bg flex h-[40px] w-[252px] items-center justify-center gap-1 rounded-full bg-primary"
        onClick={onClickCopyClipboard}
+       aria-label="내 명함 링크 복사하기"
      >
        <CopyLinkIcon />
        <Typography variant="body-4">내 명함 링크 복사하기</Typography>
      </button>
    </div>
-  </>
);
src/features/home/components/BusinessCard/Background/HomeBackground.tsx (2)

8-10: 반응형 디자인에 대한 브라우저 호환성 고려 필요

h-dvh는 최신 CSS 기능으로, 오래된 브라우저에서는 지원되지 않을 수 있습니다.

호환성을 위해 폴백(fallback) 값을 제공하는 것이 좋습니다:

- <div {...restProps} className={cn('relative h-dvh w-full', className)}>
+ <div {...restProps} className={cn('relative h-screen h-dvh w-full', className)}>

이렇게 하면 h-dvh를 지원하지 않는 브라우저에서도 h-screen을 사용할 수 있습니다.


11-31: SVG의 접근성 및 성능에 대한 개선 필요

SVG에 aria-hidden 속성이 없고, 불필요하게 고정된 마진 값(ml-[44px], mt-[88px])을 사용하고 있습니다. 이는 다양한 화면 크기에서 문제를 일으킬 수 있습니다.

다음과 같이 개선할 수 있습니다:

<svg
-  className="ml-[44px] mt-[88px]"
+  className="absolute left-[10%] top-[15%]"
  width="340"
  height="354"
  viewBox="0 0 340 354"
  fill="none"
+  aria-hidden="true"
  xmlns="http://www.w3.org/2000/svg"
>

이렇게 하면 다양한 화면 크기에서도 SVG의 상대적 위치가 유지되며, 스크린 리더에서는 무시됩니다.

src/app/page.tsx (1)

14-25: 레이아웃 구조가 절대 위치에 과도하게 의존하고 있습니다.

현재 레이아웃이 absolute 포지셔닝에 크게 의존하고 있어 향후 유지보수나 확장 시 문제가 발생할 수 있습니다. 또한 translate 변환을 사용한 중앙 정렬은 성능에 영향을 줄 수 있습니다.

좀 더 유연하고 선언적인 레이아웃 구조를 사용하는 것이 좋습니다:

<>
-  <div className="relative mx-auto h-dvh w-full max-w-[600px] justify-center">
-    <HomeBackground className="absolute" />
-    <HeaderContainer />
-    <section className="absolute left-1/2 top-1/2 h-auto w-full -translate-x-1/2 -translate-y-1/2 pb-20">
-      <CardContainer />
-      <ClipboardContainer />
-    </section>
-    <Navbar />
+  <div className="relative mx-auto h-screen h-dvh w-full max-w-[600px] flex flex-col">
+    <HomeBackground className="absolute inset-0" />
+    <HeaderContainer className="z-10" />
+    <section className="flex-1 flex flex-col justify-center items-center pb-20 z-10">
+      <CardContainer />
+      <ClipboardContainer />
+    </section>
+    <Navbar className="z-10" />
  </div>
  <Toaster position="top-center" />
</>

이렇게 하면 Flexbox를 활용하여 더 안정적인 레이아웃을 구성할 수 있으며, z-index를 명시적으로 지정하여 레이어 순서를 명확히 할 수 있습니다.

src/features/home/components/BusinessCard/Background/AddCradBackground.tsx (1)

1-15: SVG 배경 컴포넌트 구현이 잘 되었습니다만 몇 가지 개선 사항이 있습니다.

SVG 기반 배경 구현은 좋은 접근이지만, 다음과 같은 개선사항을 고려해보세요:

  1. 파일명에 오타가 있습니다: AddCradBackground.tsx가 아닌 AddCardBackground.tsx로 수정해야 합니다.
  2. 접근성 측면에서, 이 SVG가 장식용이라면 aria-hidden="true"를 추가하는 것이 좋습니다.
  3. SVG path의 좌표가 음수 값(-49)을 포함하고 있어 일부 환경에서 잘려 보일 수 있습니다. viewBox 설정을 조정하여 전체 그래픽이 제대로 표시되도록 해보세요.
- <svg width="123" height="137" viewBox="0 0 123 137" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
+ <svg width="123" height="137" viewBox="0 0 123 186" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" {...props}>
📜 Review details

Configuration used: .coderabbit.yml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 66d3062 and 4a910d8.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (12)
  • package.json (0 hunks)
  • src/app/page.tsx (1 hunks)
  • src/features/home/components/BusinessCard/AddCard.tsx (1 hunks)
  • src/features/home/components/BusinessCard/Background/AddCradBackground.tsx (1 hunks)
  • src/features/home/components/BusinessCard/Background/HomeBackground.tsx (1 hunks)
  • src/features/home/components/icons/CopyLinkIcon.tsx (1 hunks)
  • src/features/home/containers/ClipboardContainer.tsx (1 hunks)
  • src/features/home/containers/HeaderContainer.tsx (1 hunks)
  • src/features/home/hooks/sample.ts (0 hunks)
  • src/features/home/hooks/useClipboard.ts (1 hunks)
  • src/shared/ui/Navigation/index.tsx (1 hunks)
  • tailwind.config.ts (1 hunks)
💤 Files with no reviewable changes (2)
  • package.json
  • src/features/home/hooks/sample.ts
🔇 Additional comments (8)
tailwind.config.ts (1)

140-140:

✅ Verification successful

@tailwindcss/line-clamp 플러그인 제거 확인

@tailwindcss/line-clamp 플러그인이 제거되었습니다. Tailwind CSS v3.3.0 이상을 사용 중이라면 이 기능은 이미 코어에 통합되어 있어 플러그인이 필요 없습니다. 그러나 v3.3.0 미만을 사용 중이거나 기존에 이 플러그인의 line-clamp 유틸리티(line-clamp-2, line-clamp-3 등)를 사용하던 컴포넌트가 있다면 영향을 받을 수 있습니다.


🏁 Script executed:

#!/bin/bash
# 현재 tailwind CSS 버전을 확인하고 line-clamp 유틸리티 사용 여부 검사

# tailwind 버전 확인
echo "Tailwind CSS 버전 확인:"
cat package.json | grep tailwindcss

# line-clamp 유틸리티 사용 검사
echo -e "\nline-clamp 유틸리티 사용 컴포넌트 검사:"
rg "line-clamp-[0-9]" --type ts --type tsx

Length of output: 342


검증 결과: @tailwindcss/line-clamp 플러그인 제거 유효

  • Tailwind CSS 버전이 현재 ^3.4.1로 확인되었으며, 이는 플러그인 기능이 코어에 통합된 버전입니다.

  • 기존 검증 스크립트에서 TSX 파일 처리 오류가 발생했으므로, TS 및 TSX 파일을 모두 대상으로 하는 재검증 스크립트를 실행해보세요. 예시 스크립트:

    #!/bin/bash
    echo "line-clamp 유틸리티 사용 컴포넌트 검사:"
    rg "line-clamp-[0-9]" -g "*.ts" -g "*.tsx" || true
  • 이 재검증 결과 코드베이스 내에서 line-clamp-[0-9] 패턴을 사용하는 컴포넌트가 발견되지 않는다면, 플러그인 제거가 올바르게 적용된 것으로 판단됩니다.

src/features/home/containers/HeaderContainer.tsx (1)

1-9: 컴포넌트 구현이 잘 되었습니다.

HeaderContainer 컴포넌트가 간결하고 명확하게 구현되었습니다. 단일 책임 원칙을 잘 따르고 있습니다.

고려할 점:

  1. <section> 요소에 스타일링이나 className이 없는데, 의도적인 것인지 확인해보세요.
  2. 향후 재사용성을 고려하여 props를 받을 수 있도록 확장하는 것을 고려해볼 수 있습니다.
src/shared/ui/Navigation/index.tsx (1)

32-32: 네비게이션 바 포지셔닝 변경이 적절합니다.

네비게이션 바를 relative에서 fixed bottom-0으로 변경한 것은 모바일 중심 UI에 매우 적합합니다. 사용자가 스크롤하더라도 항상 하단에 네비게이션 바가 고정되어 접근성이 향상됩니다. z-index 값(100)을 추가한 것도 다른 요소들과의 적절한 계층 구조를 보장해 줍니다.

src/features/home/components/BusinessCard/AddCard.tsx (1)

9-10: 배경 구현이 개선되었습니다.

배경 투명도를 0.2에서 0.1로 변경하고 전용 배경 컴포넌트를 추가한 것은 디자인 개선에 도움이 됩니다. absolute 포지셔닝을 사용하여 층위를 적절히 구성한 점도 좋습니다.

해당 UI가 디자이너의 의도와 일치하는지 확인해보세요. PR 설명에 "design elements that need further development"가 언급되어 있으므로, 디자인팀과 협의가 필요할 수 있습니다.

src/features/home/containers/ClipboardContainer.tsx (1)

1-9: 클라이언트 컴포넌트와 의존성이 명확하게 정의되어 있습니다.

'use client' 지시문을 사용하고 필요한 의존성들을 잘 가져왔습니다. toast 알림을 위한 sonner, 타이포그래피 컴포넌트, 아이콘 및 클립보드 기능을 위한 훅이 모두 적절하게 임포트되었습니다.

src/features/home/components/BusinessCard/Background/HomeBackground.tsx (1)

1-7: 컴포넌트 속성 타입이 적절히 정의되어 있습니다.

React의 HTMLAttributes를 활용해 컴포넌트 props의 타이핑을 잘 구현했습니다. 또한 className을 확장 가능하도록 cn 유틸리티를 사용한 점이 좋습니다.

src/features/home/hooks/useClipboard.ts (1)

1-5: 훅의 기본 구조가 잘 설계되었습니다.

useState를 활용하여 클립보드 상태를 관리하는 기본 구조가 잘 설계되었습니다.

src/app/page.tsx (1)

4-11: 컴포넌트 임포트가 적절히 구성되었습니다.

필요한 모든 컴포넌트들이 명확하게 임포트되어 있습니다. Toaster 및 새로 추가된 홈 화면 관련 컴포넌트들이 잘 정의되어 있습니다.

Comment on lines +10 to +17
export const ClipboardContainer = () => {
const { handleCopy } = useClipboard();

const onClickCopyClipboard = () => {
handleCopy();
toast.success('명함 링크를 클립보드에 복사했어요.');
};

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

클립보드 기능 구현에 에러 처리가 누락되었습니다.

handleCopy 함수 호출 시 클립보드 API가 실패할 경우에 대한 에러 처리가 되어 있지 않습니다. 특히 일부 브라우저나 권한이 없는 경우 문제가 발생할 수 있습니다.

다음과 같이 에러 처리를 추가하는 것을 권장합니다:

const onClickCopyClipboard = () => {
-  handleCopy();
-  toast.success('명함 링크를 클립보드에 복사했어요.');
+  try {
+    handleCopy();
+    toast.success('명함 링크를 클립보드에 복사했어요.');
+  } catch (error) {
+    toast.error('클립보드 복사에 실패했습니다. 다시 시도해주세요.');
+    console.error('클립보드 복사 실패:', error);
+  }
};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const ClipboardContainer = () => {
const { handleCopy } = useClipboard();
const onClickCopyClipboard = () => {
handleCopy();
toast.success('명함 링크를 클립보드에 복사했어요.');
};
export const ClipboardContainer = () => {
const { handleCopy } = useClipboard();
const onClickCopyClipboard = () => {
try {
handleCopy();
toast.success('명함 링크를 클립보드에 복사했어요.');
} catch (error) {
toast.error('클립보드 복사에 실패했습니다. 다시 시도해주세요.');
console.error('클립보드 복사 실패:', error);
}
};
// ... other component logic
};

Comment on lines +6 to +9
const handleCopy = () => {
navigator.clipboard.writeText(window.location.href).then(() => setIsCopied(true));
setTimeout(() => setIsCopied(false), 1500);
};
Copy link

@coderabbitai coderabbitai bot Mar 16, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue

클립보드 API 사용 시 에러 처리 및 타이밍 문제

현재 구현에는 두 가지 주요 문제가 있습니다:

  1. 클립보드 API 실패 시 에러 처리가 없습니다.
  2. setTimeout이 Promise의 성공 여부와 관계없이 항상 실행됩니다.

더 견고한 구현을 위해 다음과 같이 수정하세요:

const handleCopy = () => {
-  navigator.clipboard.writeText(window.location.href).then(() => setIsCopied(true));
-  setTimeout(() => setIsCopied(false), 1500);
+  navigator.clipboard.writeText(window.location.href)
+    .then(() => {
+      setIsCopied(true);
+      setTimeout(() => setIsCopied(false), 1500);
+    })
+    .catch((error) => {
+      console.error('클립보드 복사 실패:', error);
+      throw error; // 호출자가 에러를 처리할 수 있도록 재발생
+    });
};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleCopy = () => {
navigator.clipboard.writeText(window.location.href).then(() => setIsCopied(true));
setTimeout(() => setIsCopied(false), 1500);
};
const handleCopy = () => {
navigator.clipboard.writeText(window.location.href)
.then(() => {
setIsCopied(true);
setTimeout(() => setIsCopied(false), 1500);
})
.catch((error) => {
console.error('클립보드 복사 실패:', error);
throw error; // 호출자가 에러를 처리할 수 있도록 재발생
});
};

Copy link
Collaborator

Choose a reason for hiding this comment

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

@semnil5202 저도 이 의견에 동의합니다. 에러가 발생할 수 있는 포인트에 try catch 문으로 감싸주어 예외 처리를 하면 좋을거 같습니다. 물론 throw로 error를 던지는 부분은 고려 대상이긴합니다.

Copy link

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Member

Choose a reason for hiding this comment

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

handleError와 같은 공통 에러처리 함수를 만들면 편리하게 사용 가능할 것 같아요.

try {
 // ...
} catch (e) {
  handleError(e);
}

handleError의 경우, 개발 환경에서만 throw로 디버깅을 도울 수 있게 하고,
live환경에서는 return 되어 동작에 방해가 없도록 하면 좋을 것 같습니다. (또는 로그로만 에러를 확인할 수 있게 할 수 있겠네요)

Copy link
Collaborator

Choose a reason for hiding this comment

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

좋은 관점이네요~~ 👍

Comment on lines +3 to +12
export const useClipboard = () => {
const [isCopied, setIsCopied] = useState(false);

const handleCopy = () => {
navigator.clipboard.writeText(window.location.href).then(() => setIsCopied(true));
setTimeout(() => setIsCopied(false), 1500);
};

return { isCopied, handleCopy };
};
Copy link

@coderabbitai coderabbitai bot Mar 16, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

브라우저 호환성 검사 필요

일부 브라우저나 환경(특히 오래된 버전이나 보안 제한이 있는 환경)에서는 navigator.clipboard API가 지원되지 않을 수 있습니다.

브라우저 호환성을 확인하고 폴백 메커니즘을 추가하는 것이 좋습니다:

export const useClipboard = () => {
  const [isCopied, setIsCopied] = useState(false);

  const handleCopy = () => {
+    // 클립보드 API 지원 확인
+    if (!navigator.clipboard) {
+      console.error('클립보드 API가 지원되지 않습니다.');
+      // 폴백: 텍스트 선택 및 복사 시도
+      try {
+        const tempInput = document.createElement('input');
+        tempInput.value = window.location.href;
+        document.body.appendChild(tempInput);
+        tempInput.select();
+        document.execCommand('copy');
+        document.body.removeChild(tempInput);
+        setIsCopied(true);
+        setTimeout(() => setIsCopied(false), 1500);
+        return;
+      } catch (err) {
+        console.error('폴백 복사 메커니즘 실패:', err);
+        throw new Error('클립보드 복사가 지원되지 않습니다.');
+      }
+    }
+
     navigator.clipboard.writeText(window.location.href)
       .then(() => {
         setIsCopied(true);
         setTimeout(() => setIsCopied(false), 1500);
       })
       .catch((error) => {
         console.error('클립보드 복사 실패:', error);
         throw error;
       });
  };

  return { isCopied, handleCopy };
};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const useClipboard = () => {
const [isCopied, setIsCopied] = useState(false);
const handleCopy = () => {
navigator.clipboard.writeText(window.location.href).then(() => setIsCopied(true));
setTimeout(() => setIsCopied(false), 1500);
};
return { isCopied, handleCopy };
};
export const useClipboard = () => {
const [isCopied, setIsCopied] = useState(false);
const handleCopy = () => {
// 클립보드 API 지원 확인
if (!navigator.clipboard) {
console.error('클립보드 API가 지원되지 않습니다.');
// 폴백: 텍스트 선택 및 복사 시도
try {
const tempInput = document.createElement('input');
tempInput.value = window.location.href;
document.body.appendChild(tempInput);
tempInput.select();
document.execCommand('copy');
document.body.removeChild(tempInput);
setIsCopied(true);
setTimeout(() => setIsCopied(false), 1500);
return;
} catch (err) {
console.error('폴백 복사 메커니즘 실패:', err);
throw new Error('클립보드 복사가 지원되지 않습니다.');
}
}
navigator.clipboard.writeText(window.location.href)
.then(() => {
setIsCopied(true);
setTimeout(() => setIsCopied(false), 1500);
})
.catch((error) => {
console.error('클립보드 복사 실패:', error);
throw error;
});
};
return { isCopied, handleCopy };
};

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

이 부분 적용해두겠습니다. 추가로 URL 복사가 홈이 아니라 -> 사용자별 명함 링크로 변경되어야함

Copy link

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for code related to business card links or user-specific URLs
rg -A 5 -B 5 "business card link|명함 링크|user.*link|user.*card" --ignore-case

Length of output: 1810


🏁 Script executed:

#!/bin/bash
# Look for more details about user cards and routing patterns
rg -A 5 -B 5 "user.*(card|profile|detail)" --ignore-case

# Search for route definitions for user cards or profiles
rg -A 5 -B 5 "(route|path).*user|user.*(route|path)" --ignore-case

# Search for API endpoints related to user cards
rg -A 3 -B 3 "api.*user|user.*api" --ignore-case

Length of output: 4727


@semnil5202 window.location.href를 사용하는 대신 사용자별 명함 링크를 복사하도록 useClipboard 훅을 수정해야겠네요. 다음과 같이 수정하는 것을 제안합니다:

// useClipboard.ts
export const useClipboard = () => {
  const [isCopied, setIsCopied] = useState(false);

  const handleCopy = (url?: string) => {
    const textToCopy = url || window.location.href;
    
    // 클립보드 API 지원 확인
    if (!navigator.clipboard) {
      console.error('클립보드 API가 지원되지 않습니다.');
      // 폴백: 텍스트 선택 및 복사 시도
      try {
        const tempInput = document.createElement('input');
        tempInput.value = textToCopy;
        document.body.appendChild(tempInput);
        tempInput.select();
        document.execCommand('copy');
        document.body.removeChild(tempInput);
        setIsCopied(true);
        setTimeout(() => setIsCopied(false), 1500);
        return;
      } catch (err) {
        console.error('폴백 복사 메커니즘 실패:', err);
        throw new Error('클립보드 복사가 지원되지 않습니다.');
      }
    }

    navigator.clipboard.writeText(textToCopy)
      .then(() => {
        setIsCopied(true);
        setTimeout(() => setIsCopied(false), 1500);
      })
      .catch((error) => {
        console.error('클립보드 복사 실패:', error);
        throw error;
      });
  };

  return { isCopied, handleCopy };
};

그리고 ClipboardContainer에서는 다음과 같이 사용합니다:

// ClipboardContainer.tsx의 onClickCopyClipboard 함수 수정
const onClickCopyClipboard = () => {
  // 사용자 ID 또는 필요한 파라미터로 URL 생성
  const userCardUrl = `${window.location.origin}/users/{userId}/card`; // 실제 URL 구조에 맞게 수정 필요
  handleCopy(userCardUrl);
  toast.success('명함 링크를 클립보드에 복사했어요.');
};

사용자별 명함 링크 URL 패턴은 프로젝트의 라우팅 구조에 따라 적절히 수정해야 합니다.

Copy link
Collaborator

@youngju6143 youngju6143 left a comment

Choose a reason for hiding this comment

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

너무 수고 많으셨어요🤩 궁금한 점은 코멘트로 남겨두었어요!

<HeaderContainer />
<section className="absolute left-1/2 top-1/2 h-auto w-full -translate-x-1/2 -translate-y-1/2 pb-20">
<CardContainer />
<ClipboardContainer />
Copy link
Collaborator

Choose a reason for hiding this comment

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

오... 확실히 Container로 한 번 감싸주니까 코드도 엄청 깔끔해지는군요..! 참고할게요!!

Copy link
Collaborator

Choose a reason for hiding this comment

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

현재 CopyLinkIcon이 svg인데, 이를 컴포넌트로 만든 이유를 알 수 있을까요? 만약 특별한 이유가 없다면, 아이콘을 public/icons와 같은 폴더에 넣고 필요할 때 import 해서 사용하는 방식이 더 효율적일 수 있을 것 같아서요!

Copy link
Member

Choose a reason for hiding this comment

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

@youngju6143 개인적으로 svg 파일을 정적으로 사용할 때, 동적으로 사용할 때의 장단점이 명확한 것 같아요.
상황에 따라 취사선택하면 좋을 것 같습니다.

icons/와 같은 디렉토리에 넣어놓고 정적으로 파일을 Import한다면, 불필요 번들 크기를 줄일 수 있고 렌더링 성능이 약간이나마 개선될 수 있을 것 같아요.
컴포넌트로써 동적으로 사용하게 된다면 스타일과 같은 요소들의 커스터마이징이 용이하고, 컴포넌트의 특성을 활용하여 다양하게 재조합하여 사용할 수 있습니다.

제 경우에는 복잡한 로고 같은 svg 파일은 정적으로, 화살표나 아이콘 같이 다양한 상황에서 사용되는 아이콘은 컴포넌트로 만들어 사용합니다.
예시로 화살표 아이콘은 컴포넌트로 만들어 direction이나 fill, stroke 등을 바꿔가면서 사용했던 것 같아요.

Copy link
Collaborator

@jangwonyoon jangwonyoon left a comment

Choose a reason for hiding this comment

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

고생하셨습니다~~

Comment on lines +6 to +9
const handleCopy = () => {
navigator.clipboard.writeText(window.location.href).then(() => setIsCopied(true));
setTimeout(() => setIsCopied(false), 1500);
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

@semnil5202 저도 이 의견에 동의합니다. 에러가 발생할 수 있는 포인트에 try catch 문으로 감싸주어 예외 처리를 하면 좋을거 같습니다. 물론 throw로 error를 던지는 부분은 고려 대상이긴합니다.

@JaeIn1
Copy link
Collaborator

JaeIn1 commented Mar 17, 2025

너무 고생하셨습니다!! 👍👍

Copy link
Member

@Jungjjeong Jungjjeong left a comment

Choose a reason for hiding this comment

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

👍

Comment on lines +6 to +9
const handleCopy = () => {
navigator.clipboard.writeText(window.location.href).then(() => setIsCopied(true));
setTimeout(() => setIsCopied(false), 1500);
};
Copy link
Member

Choose a reason for hiding this comment

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

handleError와 같은 공통 에러처리 함수를 만들면 편리하게 사용 가능할 것 같아요.

try {
 // ...
} catch (e) {
  handleError(e);
}

handleError의 경우, 개발 환경에서만 throw로 디버깅을 도울 수 있게 하고,
live환경에서는 return 되어 동작에 방해가 없도록 하면 좋을 것 같습니다. (또는 로그로만 에러를 확인할 수 있게 할 수 있겠네요)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
src/features/home/types/index.ts (3)

1-1: 타입 이름에 오타가 있습니다.

타입 이름 JopType은 오타로 보입니다. 문맥상 JobType이 더 적절해 보입니다.

-export type JopType = 'designer' | 'developer';
+export type JobType = 'designer' | 'developer';

이 변경사항이 적용되면 Card 타입의 previewInfoType 프로퍼티의 타입도 업데이트해야 합니다.


34-46: 중복된 타입 구조를 통합하는 것이 좋습니다.

ProjectContent 타입이 동일한 구조를 가지고 있습니다. 코드 중복을 줄이기 위해 공통 타입을 만들어 재사용하는 것이 좋습니다.

+type MediaItem = {
+  title: string;
+  link: string;
+  imageUrl: string;
+  description: string;
+};
+
-type Project = {
-  title: string;
-  link: string;
-  imageUrl: string;
-  description: string;
-};
+type Project = MediaItem;
 
-type Content = {
-  title: string;
-  link: string;
-  imageUrl: string;
-  description: string;
-};
+type Content = MediaItem;

48-51: SNS 타입의 'type' 필드를 더 구체적으로 정의해보세요.

SNS 타입의 type 필드는 특정 값으로 제한될 수 있습니다. 가능한 SNS 유형을 정의하면 타입 안전성이 더 향상됩니다.

+type SNSType = 'instagram' | 'facebook' | 'twitter' | 'linkedin' | 'other';
+
 type SNS = {
-  type: string;
+  type: SNSType;
   link: string;
 };
📜 Review details

Configuration used: .coderabbit.yml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 08e7708 and 905e0e0.

📒 Files selected for processing (3)
  • src/features/home/hooks/queries/useCardQuery.ts (1 hunks)
  • src/features/home/types/index.ts (1 hunks)
  • src/shared/ui/Navigation/index.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/shared/ui/Navigation/index.tsx
🔇 Additional comments (6)
src/features/home/types/index.ts (2)

3-10: API 응답 구조를 잘 정의했습니다.

API 응답 구조를 명확하게 정의하여 타입 안전성을 높였습니다. 백엔드 API와의 일관성을 유지하는 것이 중요합니다.


12-23: 카드 타입 구조가 잘 정의되었습니다.

카드 데이터를 위한 타입 정의가 상세하게 되어 있습니다. previewInfoType이 앞서 정의한 JopType을 참조하고 있으므로, JopTypeJobType으로 수정할 경우 이 부분도 함께 수정해야 합니다.

src/features/home/hooks/queries/useCardQuery.ts (4)

6-6: 새로운 타입 임포트가 적절하게 추가되었습니다.

MyCardDto 타입을 임포트하여 API 응답에 대한 타입 안전성을 향상시켰습니다.


8-8: 쿼리 키 이름 변경이 적절합니다.

쿼리 키 이름을 MY_CARD_QUERY_KEY로 변경하여 목적이 더 명확해졌습니다.


10-11: 함수 서명 간소화와 타입 지정이 잘 되었습니다.

_getCard 함수에서 불필요한 매개변수를 제거하고 MyCardDto 타입을 적용하여 코드가 간결해졌습니다.


16-20: 모의 데이터 사용에 대한 고려가 필요합니다.

현재 코드는 실제 API 호출을 수행하지만 결과는 무시하고 모의 데이터(CARD_MOCK)를 반환합니다. 이것이 개발 목적으로 의도된 것인지 확인하고, 실제 배포 전에 모의 데이터를 실제 데이터로 대체해야 합니다.

또한 API 호출 실패 시 오류 처리 로직이 없습니다. 다음과 같이 개선할 수 있습니다:

 export const useCardQuery = () => {
-  const { data: _ } = useQuery({
+  const { data, error, isLoading } = useQuery({
     queryKey: [MY_CARD_QUERY_KEY],
     queryFn: () => _getCard(),
+    enabled: process.env.NODE_ENV !== 'development', // 개발 환경에서만 API 호출 비활성화
   });
 
-  return { data: CARD_MOCK };
+  // 개발 환경에서는 모의 데이터 사용, 그 외에는 실제 API 데이터 사용
+  return { 
+    data: process.env.NODE_ENV === 'development' ? CARD_MOCK : data,
+    error,
+    isLoading 
+  };
 };

@semnil5202 semnil5202 merged commit e42da69 into develop Mar 18, 2025
2 checks passed
@semnil5202 semnil5202 deleted the feat/#51 branch March 18, 2025 13:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants