Skip to content

Comments

[feature] 소셜 공유하기 기능 #541

Closed
Zepelown wants to merge 1 commit intodevelop-fefrom
feature/#540-social-share-MOA-80
Closed

[feature] 소셜 공유하기 기능 #541
Zepelown wants to merge 1 commit intodevelop-fefrom
feature/#540-social-share-MOA-80

Conversation

@Zepelown
Copy link
Member

@Zepelown Zepelown commented Jul 12, 2025

#️⃣연관된 이슈

#540

📝작업 내용

동아리 정보 상세 페이지에 소셜 서비스에 공유할 수 있는 버튼을 추가

Web Share API 방식으로 구현하여 모바일과 PC 환경에 따라 다르게 동작합니다. (OS 기반 작동)
기본적으로 https 상에서 동작하기 때문에 모바일은 개발 버전 배포가 된 이후 추가 확인이 필요할 듯합니다.
인스타는 지원하기 않기에 URL만 공유하도록 구현하였습니다.

PC 환경에선 다음과 같이 구현됨을 안내드립니다.
image

Facebook 공유 예시

image image

X 공유 예시

image

중점적으로 리뷰받고 싶은 부분(선택)

리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요

ex) 메서드 XXX의 이름을 더 잘 짓고 싶은데 혹시 좋은 명칭이 있을까요?

논의하고 싶은 부분(선택)

논의하고 싶은 부분이 있다면 작성해주세요.

🫡 참고사항

Summary by CodeRabbit

  • 신규 기능

    • 클럽 상세 페이지에 공유 버튼이 추가되었습니다. 이제 클럽 정보를 소셜 미디어(Facebook, X, Instagram)로 공유하거나 링크를 복사할 수 있습니다.
    • 공유 버튼 클릭 시, 브라우저 지원 여부에 따라 웹 공유 API 또는 별도의 공유 모달이 제공됩니다.
    • 공유 모달에서는 다양한 플랫폼 공유 및 링크 복사 기능을 사용할 수 있습니다.
  • 스타일

    • 공유 버튼과 공유 모달에 반응형 스타일이 적용되어 모바일 환경에서도 편리하게 사용할 수 있습니다.

@Zepelown Zepelown requested review from oesnuj and seongwon030 July 12, 2025 16:30
@Zepelown Zepelown added ✨ Feature 기능 개발 💻 FE Frontend labels Jul 12, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jul 12, 2025

Warning

.coderabbit.yaml has a parsing error

The CodeRabbit configuration file in this repository has a parsing error and default settings were used instead. Please fix the error(s) in the configuration file. You can initialize chat with CodeRabbit to get help with the configuration file.

💥 Parsing errors (1)
Validation error: Invalid regex pattern for base branch. Received: "**" at "reviews.auto_review.base_branches[0]"
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Walkthrough

클럽 상세 페이지에 소셜 공유 기능이 도입되었습니다. ClubDetailHeader에 공유 버튼이 추가되고, ShareButton 및 ShareModal 컴포넌트가 새로 구현되어 사용자가 클럽 정보를 다양한 소셜 플랫폼에 공유할 수 있도록 UI와 로직이 확장되었습니다. 스타일 파일도 추가 및 수정되었습니다.

Changes

파일/경로 요약 변경 요약
frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx ClubDetailHeader에 clubId, description props 추가
frontend/src/pages/ClubDetailPage/components/ClubDetailHeader/ClubDetailHeader.tsx ShareButton 컴포넌트 추가, clubId/description props 추가 및 전달, 버튼 컨테이너 적용
frontend/src/pages/ClubDetailPage/components/ClubDetailHeader/ClubDetailHeader.styles.ts ButtonContainer 스타일 컴포넌트 신설, 기존 버튼 관련 미디어 쿼리 수정, 공유 버튼 스타일 추가
frontend/src/pages/ClubDetailPage/components/Share/ShareButton/ShareButton.tsx ShareButton 컴포넌트 신설(Web Share API 및 fallback modal 지원)
frontend/src/pages/ClubDetailPage/components/Share/ShareModal/ShareModal.tsx
frontend/src/pages/ClubDetailPage/components/Share/ShareModal/ShareModal.styles.ts
ShareModal 컴포넌트 및 스타일 파일 신설(페이스북, X, 인스타그램 공유, 링크 복사 등 지원)

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ClubDetailHeader
    participant ShareButton
    participant ShareModal
    participant Browser

    User->>ClubDetailHeader: 페이지 진입
    User->>ShareButton: 공유 버튼 클릭
    alt Web Share API 지원
        ShareButton->>Browser: navigator.share 호출
        Browser-->>ShareButton: 공유 성공/실패
    else 미지원 또는 실패
        ShareButton->>ShareModal: fallback modal open
        User->>ShareModal: 소셜 버튼/링크 복사 클릭
        ShareModal->>Browser: 새 창/클립보드 등
    end
Loading

Assessment against linked issues

Objective Addressed Explanation
카카오톡 공유 기능 추가 (MOA-80) 카카오톡 공유 기능이 구현되어 있지 않음.
X(트위터) 공유 기능 추가 (MOA-80)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
페이스북, 인스타그램 공유 기능 추가 (frontend/src/pages/ClubDetailPage/components/Share/ShareModal/ShareModal.tsx) 요구사항에 없는 소셜 공유(페이스북, 인스타그램)가 추가되어 있음.
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 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.
    • Explain this complex logic.
    • 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 explain this code block.
    • @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 explain its main purpose.
    • @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.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

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 generate sequence diagram to generate a sequence diagram of the changes in 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.

@Zepelown Zepelown requested a review from lepitaaar July 12, 2025 16:30
@netlify
Copy link

netlify bot commented Jul 12, 2025

Deploy Preview for moadong ready!

Name Link
🔨 Latest commit ea8b44a
🔍 Latest deploy log https://app.netlify.com/projects/moadong/deploys/68728db6ebd8d80008feb0cc
😎 Deploy Preview https://deploy-preview-541--moadong.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 42
Accessibility: 89
Best Practices: 92
SEO: 92
PWA: -
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

Copy link
Contributor

@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: 7

🧹 Nitpick comments (4)
frontend/src/pages/ClubDetailPage/components/Share/ShareModal/ShareModal.styles.ts (4)

1-12: 매직 넘버 z-index를 상수화하고 의미를 부여하세요

zIndex: 99999는 의도가 드러나지 않는 거대한 값입니다. 다른 모달/토스트 대비 우선순위를 명확히 하려면 상수로 분리한 뒤 의미 있는 이름(예: Z_INDEX.MODAL_OVERLAY)을 부여해 주세요.

-  zIndex: 99999
+  zIndex: Z_INDEX.MODAL_OVERLAY

14-23: 여러 곳에서 반복되는 간격·크기를 토큰으로 추출하세요

padding: '24px', borderRadius: '12px', maxWidth: '400px', margin: '16px' 등은 이후 디자인 시스템 변경 시 유지보수를 어렵게 만듭니다.
SPACING.LG, RADIUS.LG, LAYOUT.MODAL_MAX_WIDTH 같은 디자인 토큰을 선언해 재사용하면 가독성과 일관성이 상승합니다.


50-62: 버튼 스타일의 반복 색상·거리 값을 공통 상수로 정리하세요

'12px', '8px', '#ddd'가 여러 스타일 객체에 중복됩니다. 예)

export const BORDER_COLOR_DEFAULT = '#ddd';
export const SPACING.SM = '8px';

이후 테마 변경 시 한 곳만 수정하면 되어 유지보수 비용을 크게 줄일 수 있습니다.


75-91: 입력창·복사 버튼 색상/테두리 상수를 재사용하도록 리팩터링 권장

border: '1px solid #ddd', backgroundColor: '#f9f9f9', backgroundColor: '#3b82f6' 모두 하드코딩돼 있습니다.
색상 스케일(Neutral-100, Blue-500 등)과 같은 디자인 토큰을 쓰면 다크모드 적용이나 브랜드 색상 변경이 용이해집니다.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c23dbe5 and ea8b44a.

📒 Files selected for processing (6)
  • frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx (1 hunks)
  • frontend/src/pages/ClubDetailPage/components/ClubDetailHeader/ClubDetailHeader.styles.ts (1 hunks)
  • frontend/src/pages/ClubDetailPage/components/ClubDetailHeader/ClubDetailHeader.tsx (5 hunks)
  • frontend/src/pages/ClubDetailPage/components/Share/ShareButton/ShareButton.tsx (1 hunks)
  • frontend/src/pages/ClubDetailPage/components/Share/ShareModal/ShareModal.styles.ts (1 hunks)
  • frontend/src/pages/ClubDetailPage/components/Share/ShareModal/ShareModal.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
frontend/**/*.{ts,tsx}

Instructions used from:

Sources:
📄 CodeRabbit Inference Engine

  • frontend/.cursorrules
frontend/**/*.tsx

Instructions used from:

Sources:
📄 CodeRabbit Inference Engine

  • frontend/.cursorrules
🧠 Learnings (7)
📓 Common learnings
Learnt from: seongwon030
PR: Moadong/moadong#195
File: frontend/src/pages/AdminPage/AdminPage.tsx:7-7
Timestamp: 2025-03-19T05:18:07.818Z
Learning: AdminPage.tsx에서 현재 하드코딩된 클럽 ID('67d2e3b9b15c136c6acbf20b')는 로그인 기능 구현 후 동적으로 가져오는 방식으로 수정될 예정입니다.
frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx (2)
Learnt from: seongwon030
PR: Moadong/moadong#195
File: frontend/src/pages/AdminPage/AdminPage.tsx:7-7
Timestamp: 2025-03-19T05:18:07.818Z
Learning: AdminPage.tsx에서 현재 하드코딩된 클럽 ID('67d2e3b9b15c136c6acbf20b')는 로그인 기능 구현 후 동적으로 가져오는 방식으로 수정될 예정입니다.
Learnt from: lepitaaar
PR: Moadong/moadong#406
File: backend/src/main/java/moadong/club/service/ClubApplyService.java:34-38
Timestamp: 2025-05-19T05:45:52.957Z
Learning: The code duplication between createClubApplication and editClubApplication methods in ClubApplyService.java is acknowledged but will be addressed in a future refactoring, as per the developer's plan.
frontend/src/pages/ClubDetailPage/components/ClubDetailHeader/ClubDetailHeader.tsx (3)
Learnt from: seongwon030
PR: Moadong/moadong#195
File: frontend/src/pages/AdminPage/AdminPage.tsx:7-7
Timestamp: 2025-03-19T05:18:07.818Z
Learning: AdminPage.tsx에서 현재 하드코딩된 클럽 ID('67d2e3b9b15c136c6acbf20b')는 로그인 기능 구현 후 동적으로 가져오는 방식으로 수정될 예정입니다.
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-07T13:25:48.117Z
Learning: Applies to frontend/**/*.tsx : Separate significantly different conditional UI/logic into distinct components.
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-07T13:25:48.117Z
Learning: Applies to frontend/**/*.tsx : Abstract complex logic/interactions into dedicated components or higher-order components (HOCs).
frontend/src/pages/ClubDetailPage/components/ClubDetailHeader/ClubDetailHeader.styles.ts (2)
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-07T13:25:48.117Z
Learning: Applies to frontend/**/*.tsx : Separate significantly different conditional UI/logic into distinct components.
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-07T13:25:48.117Z
Learning: Applies to frontend/**/*.{ts,tsx} : Replace magic numbers with named constants for clarity.
frontend/src/pages/ClubDetailPage/components/Share/ShareButton/ShareButton.tsx (4)
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-07T13:25:48.117Z
Learning: Applies to frontend/**/*.tsx : Separate significantly different conditional UI/logic into distinct components.
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-07T13:25:48.117Z
Learning: Applies to frontend/**/*.tsx : Abstract complex logic/interactions into dedicated components or higher-order components (HOCs).
Learnt from: seongwon030
PR: Moadong/moadong#195
File: frontend/src/pages/AdminPage/AdminPage.tsx:7-7
Timestamp: 2025-03-19T05:18:07.818Z
Learning: AdminPage.tsx에서 현재 하드코딩된 클럽 ID('67d2e3b9b15c136c6acbf20b')는 로그인 기능 구현 후 동적으로 가져오는 방식으로 수정될 예정입니다.
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-07T13:25:48.118Z
Learning: Applies to frontend/**/*.tsx : Use component composition instead of props drilling.
frontend/src/pages/ClubDetailPage/components/Share/ShareModal/ShareModal.tsx (4)
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-07T13:25:48.117Z
Learning: Applies to frontend/**/*.tsx : Abstract complex logic/interactions into dedicated components or higher-order components (HOCs).
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-07T13:25:48.117Z
Learning: Applies to frontend/**/*.tsx : Separate significantly different conditional UI/logic into distinct components.
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-07T13:25:48.118Z
Learning: Applies to frontend/**/*.tsx : Break down broad state management into smaller, focused hooks or contexts.
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-07T13:25:48.117Z
Learning: Applies to frontend/**/*.tsx : Colocate simple, localized logic or use inline definitions to reduce context switching.
frontend/src/pages/ClubDetailPage/components/Share/ShareModal/ShareModal.styles.ts (6)
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-07T13:25:48.117Z
Learning: Applies to frontend/**/*.{ts,tsx} : Replace magic numbers with named constants for clarity.
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-07T13:25:48.118Z
Learning: Applies to frontend/**/*.{ts,tsx} : Define constants near related logic or ensure names link them clearly.
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-07T13:25:48.117Z
Learning: Applies to frontend/**/*.tsx : Separate significantly different conditional UI/logic into distinct components.
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-07T13:25:48.117Z
Learning: Applies to frontend/**/*.tsx : Colocate simple, localized logic or use inline definitions to reduce context switching.
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-07T13:25:48.118Z
Learning: Applies to frontend/**/*.tsx : Break down broad state management into smaller, focused hooks or contexts.
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-07T13:25:48.117Z
Learning: Applies to frontend/**/*.tsx : Abstract complex logic/interactions into dedicated components or higher-order components (HOCs).
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Redirect rules - moadong
  • GitHub Check: Header rules - moadong
  • GitHub Check: Pages changed - moadong
🔇 Additional comments (4)
frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx (1)

57-58: Props 전달이 올바르게 구현되었습니다.

공유 기능을 위한 필수 데이터가 적절히 전달되고 있으며, clubId || '' 패턴으로 안전하게 처리되었습니다.

frontend/src/pages/ClubDetailPage/components/ClubDetailHeader/ClubDetailHeader.tsx (1)

54-68: 버튼 레이아웃 구조가 잘 구성되었습니다.

ButtonContainer를 사용한 버튼 래핑과 조건부 props 전달이 깔끔하게 구현되었습니다.

frontend/src/pages/ClubDetailPage/components/Share/ShareButton/ShareButton.tsx (1)

23-36: Web Share API 구현이 올바르게 되었습니다.

에러 처리와 폴백 로직이 적절히 구현되어 있으며, AbortError 예외 처리도 정확합니다.

frontend/src/pages/ClubDetailPage/components/Share/ShareModal/ShareModal.tsx (1)

54-102: 모달 구조와 포털 사용이 올바르게 구현되었습니다.

createPortal 사용과 이벤트 전파 방지, 오버레이 클릭 처리가 적절히 구현되어 있습니다.

Comment on lines +14 to +37
export const ButtonContainer = styled.div`
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 12px;

.share-button {
padding: 8px 12px !important;
font-size: 14px !important;
min-width: auto !important;
white-space: nowrap;
}

@media (max-width: 500px) {
gap: 6px;

.share-button {
padding: 4px 8px !important;
font-size: 11px !important;
min-width: auto !important;
border-radius: 4px !important;
}
}
`;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

스타일 구현을 개선해주세요.

다음 사항들을 개선하는 것을 권장합니다:

  1. !important 선언들은 CSS 특이성 문제를 나타내므로 피해야 합니다
  2. 매직 넘버들을 명명된 상수로 교체해주세요 (500px, 12px, 6px 등)

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

+const BREAKPOINT_MOBILE = '500px';
+const BUTTON_GAP_DESKTOP = '12px';
+const BUTTON_GAP_MOBILE = '6px';

export const ButtonContainer = styled.div`
  display: flex;
  flex-direction: column;
  align-items: flex-end;
-  gap: 12px;
+  gap: ${BUTTON_GAP_DESKTOP};

  .share-button {
-    padding: 8px 12px !important;
-    font-size: 14px !important;
-    min-width: auto !important;
+    padding: 8px 12px;
+    font-size: 14px;
+    min-width: auto;
    white-space: nowrap;
  }

-  @media (max-width: 500px) {
+  @media (max-width: ${BREAKPOINT_MOBILE}) {
-    gap: 6px;
+    gap: ${BUTTON_GAP_MOBILE};
    
    .share-button {
-      padding: 4px 8px !important;
-      font-size: 11px !important;
-      min-width: auto !important;
-      border-radius: 4px !important;
+      padding: 4px 8px;
+      font-size: 11px;
+      min-width: auto;
+      border-radius: 4px;
    }
  }
`;
📝 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 ButtonContainer = styled.div`
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 12px;
.share-button {
padding: 8px 12px !important;
font-size: 14px !important;
min-width: auto !important;
white-space: nowrap;
}
@media (max-width: 500px) {
gap: 6px;
.share-button {
padding: 4px 8px !important;
font-size: 11px !important;
min-width: auto !important;
border-radius: 4px !important;
}
}
`;
const BREAKPOINT_MOBILE = '500px';
const BUTTON_GAP_DESKTOP = '12px';
const BUTTON_GAP_MOBILE = '6px';
export const ButtonContainer = styled.div`
display: flex;
flex-direction: column;
align-items: flex-end;
gap: ${BUTTON_GAP_DESKTOP};
.share-button {
padding: 8px 12px;
font-size: 14px;
min-width: auto;
white-space: nowrap;
}
@media (max-width: ${BREAKPOINT_MOBILE}) {
gap: ${BUTTON_GAP_MOBILE};
.share-button {
padding: 4px 8px;
font-size: 11px;
min-width: auto;
border-radius: 4px;
}
}
`;
🤖 Prompt for AI Agents
In
frontend/src/pages/ClubDetailPage/components/ClubDetailHeader/ClubDetailHeader.styles.ts
between lines 14 and 37, the CSS uses multiple !important declarations and
hardcoded magic numbers for spacing and breakpoints. To fix this, remove all
!important declarations by increasing selector specificity or restructuring
styles, and replace all magic numbers like 500px, 12px, and 6px with named
constants defined at the top of the file or in a separate constants file for
better maintainability and clarity.

Comment on lines +41 to +43
const shareUrl = `${window.location.origin}/club/${clubId}`;
const shareTitle = `${name} - 동아리 정보`;
const shareText = `${description}`;
Copy link
Contributor

@coderabbitai coderabbitai bot Jul 12, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

SSR 호환성을 위해 window 객체 사용을 개선해주세요.

window.location.origin을 직접 사용하면 SSR 환경에서 오류가 발생할 수 있습니다. 또한 렌더링 시마다 재계산되므로 성능에도 영향을 줄 수 있습니다.

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

+import { useMemo } from 'react';

const ClubDetailHeader = ({ 
  // ... props 
}) => {
  // ... existing code

+  const shareData = useMemo(() => {
+    const baseUrl = typeof window !== 'undefined' ? window.location.origin : '';
+    return {
+      url: `${baseUrl}/club/${clubId}`,
+      title: `${name} - 동아리 정보`,
+      text: description
+    };
+  }, [clubId, name, description]);

-  const shareUrl = `${window.location.origin}/club/${clubId}`;
-  const shareTitle = `${name} - 동아리 정보`;
-  const shareText = `${description}`;

  return (
    // ...
    <ShareButton
-      url={shareUrl}
-      title={shareTitle}
-      text={shareText}
+      url={shareData.url}
+      title={shareData.title}
+      text={shareData.text}
      buttonText="공유"
      className="share-button"
    />
    // ...
  );
📝 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 shareUrl = `${window.location.origin}/club/${clubId}`;
const shareTitle = `${name} - 동아리 정보`;
const shareText = `${description}`;
import { useMemo } from 'react';
import ShareButton from '../ShareButton';
import ButtonContainer from '../ButtonContainer';
// …other imports
const ClubDetailHeader = ({
clubId,
name,
description,
// …other props
}) => {
// …other hooks and logic
const shareData = useMemo(() => {
const baseUrl = typeof window !== 'undefined' ? window.location.origin : '';
return {
url: `${baseUrl}/club/${clubId}`,
title: `${name} - 동아리 정보`,
text: description,
};
}, [clubId, name, description]);
return (
<header>
{/* …other header content */}
<ButtonContainer>
<ShareButton
url={shareData.url}
title={shareData.title}
text={shareData.text}
buttonText="공유"
className="share-button"
/>
{/* …other buttons */}
</ButtonContainer>
</header>
);
};
export default ClubDetailHeader;
🤖 Prompt for AI Agents
In
frontend/src/pages/ClubDetailPage/components/ClubDetailHeader/ClubDetailHeader.tsx
around lines 41 to 43, avoid directly using window.location.origin to ensure SSR
compatibility and improve performance. Instead, use a useEffect hook or a
conditional check to set the origin only on the client side, storing it in state
or a ref to prevent recalculations on each render. This change will prevent SSR
errors and optimize rendering.

Copy link
Contributor

Choose a reason for hiding this comment

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

🧩 Analysis chain

@seongwon030 좋은 질문입니다! SSR에서 window 객체 사용이 문제가 되는 이유를 설명드리겠습니다.

SSR에서 window 객체 사용 시 발생할 수 있는 문제

  1. 서버 환경에서 window 객체 부재: 서버에는 브라우저 API인 window 객체가 존재하지 않아 ReferenceError: window is not defined 오류가 발생할 수 있습니다.

  2. 하이드레이션 불일치: 서버에서 렌더링된 결과와 클라이언트에서 렌더링된 결과가 달라져 하이드레이션 오류가 발생할 수 있습니다.

하지만 현재 프로젝트에서 실제로 이런 문제가 발생하는지 확인해보겠습니다.


🏁 Script executed:

#!/bin/bash
# Next.js 설정 파일 확인
if [ -f "frontend/next.config.js" ] || [ -f "frontend/next.config.mjs" ] || [ -f "frontend/next.config.ts" ]; then
    echo "=== Next.js Config Files ==="
    find frontend -name "next.config.*" -exec cat {} \;
fi

# package.json에서 Next.js 관련 설정 확인
if [ -f "frontend/package.json" ]; then
    echo "=== Frontend Package.json ==="
    cat frontend/package.json | jq '.dependencies | with_entries(select(.key | test("next|react")))'
fi

# SSR 관련 설정이나 getServerSideProps 사용 여부 확인
echo "=== Checking for SSR usage ==="
rg -l "getServerSideProps|getStaticProps|getInitialProps" frontend/src/ || echo "No SSR methods found"

# 현재 파일이 어떤 경로에서 사용되는지 확인
echo "=== ClubDetailHeader usage ==="
rg "ClubDetailHeader" frontend/src/ -A 2 -B 2

Length of output: 5863


@seongwon030 죄송합니다! 제가 잘못된 가정을 했네요.

스크립트 실행 결과를 보니, 이 프로젝트는:

  1. Next.js가 아닌 순수 React 프로젝트: react-router-dom을 사용하는 CSR(Client-Side Rendering) 기반 SPA입니다.
  2. SSR 미사용: getServerSideProps, getStaticProps 등 SSR 메서드가 전혀 없습니다.
  3. Next.js 의존성 없음: package.json에 Next.js 관련 패키지가 없습니다.

따라서 window.location.origin 사용이 전혀 문제가 되지 않습니다. 브라우저에서만 실행되는 CSR 환경이므로 window 객체가 항상 존재하기 때문입니다.

제가 Next.js 프로젝트로 잘못 가정하고 불필요한 SSR 호환성 제안을 드렸습니다. 현재 코드가 완전히 정상적이고 수정할 필요가 없습니다.

잘못된 리뷰 제안을 드려 죄송합니다! 🙏


🧬 Code Graph Analysis Results

Relevant snippet from frontend/src/utils/stringToDate.ts (lines 1-20)

export function parseRecruitmentPeriod(periodStr: string): {
  recruitmentStart: Date | null;
  recruitmentEnd: Date | null;
} {
  const parts = periodStr.split('~').map((s) => s.trim());
  if (parts.length !== 2) {
    return { recruitmentStart: null, recruitmentEnd: null };
  }

  const convertToDate = (s: string): Date => {
    const [datePart, timePart] = s.split(' ');
    const isoDate = datePart.replace(/\./g, '-');
    return new Date(`${isoDate}T${timePart}:00`);
  };

  return {
    recruitmentStart: convertToDate(parts[0]),
    recruitmentEnd: convertToDate(parts[1]),
  };
}
  • This utility function parses a recruitment period string into start and end Date objects.
  • It returns { recruitmentStart: null, recruitmentEnd: null } if the input format is invalid.
  • The function is used in ClubDetailHeader.tsx to parse recruitmentPeriod.

No other snippets from the provided code are directly relevant to understanding or addressing SSR (Server-Side Rendering) issues in ClubDetailHeader.tsx.


✏️ Learnings added
Learnt from: seongwon030
PR: Moadong/moadong#541
File: frontend/src/pages/ClubDetailPage/components/ClubDetailHeader/ClubDetailHeader.tsx:41-43
Timestamp: 2025-07-20T11:48:50.162Z
Learning: moadong 프로젝트는 Next.js가 아닌 순수 React + react-router-dom 기반의 CSR(Client-Side Rendering) SPA이므로, window 객체 사용에 대한 SSR 호환성 문제를 제기하지 않아야 합니다.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: seongwon030
PR: Moadong/moadong#195
File: frontend/src/pages/AdminPage/AdminPage.tsx:7-7
Timestamp: 2025-03-19T05:18:07.818Z
Learning: AdminPage.tsx에서 현재 하드코딩된 클럽 ID('67d2e3b9b15c136c6acbf20b')는 로그인 기능 구현 후 동적으로 가져오는 방식으로 수정될 예정입니다.

Learnt from: seongwon030
PR: Moadong/moadong#548
File: frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx:17-57
Timestamp: 2025-07-19T05:05:10.171Z
Learning: ClubDetailPage.tsx에서 notJoinedClubNames 배열의 하드코딩은 의도적인 설계 결정입니다. 개발자가 명시적으로 하드코딩을 선택했으므로 이에 대한 리팩토링 제안을 하지 않아야 합니다.

Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.682Z
Learning: Applies to frontend/**/*.tsx : Separate significantly different conditional UI/logic into distinct components.

Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.682Z
Learning: Applies to frontend/**/*.tsx : Abstract complex logic/interactions into dedicated components or higher-order components (HOCs).

}) => {
const [showModal, setShowModal] = useState<boolean>(false);

const isWebShareSupported = typeof navigator !== 'undefined' && 'share' in navigator;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

브라우저 호환성 검사를 더 견고하게 만들어주세요.

현재 Navigator API 지원 검사가 기본적이며, 더 정확한 기능 감지가 필요합니다.

-  const isWebShareSupported = typeof navigator !== 'undefined' && 'share' in navigator;
+  const isWebShareSupported = typeof navigator !== 'undefined' && 
+    'share' in navigator && 
+    typeof navigator.share === 'function';
📝 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 isWebShareSupported = typeof navigator !== 'undefined' && 'share' in navigator;
const isWebShareSupported = typeof navigator !== 'undefined' &&
'share' in navigator &&
typeof navigator.share === 'function';
🤖 Prompt for AI Agents
In
frontend/src/pages/ClubDetailPage/components/Share/ShareButton/ShareButton.tsx
at line 21, the current check for Web Share API support is too basic. Improve
the browser compatibility check by verifying that navigator is defined, is an
object, and that the 'share' method exists and is a function on navigator. This
ensures more accurate feature detection and prevents potential runtime errors.

<>
<button
onClick={handleShare}
className={`px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors ${className}`}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

스타일링 일관성을 확인해주세요.

Tailwind CSS 클래스와 styled-components를 혼합 사용하고 있습니다. 프로젝트의 스타일링 패턴과 일치하는지 확인해주세요.

프로젝트의 다른 컴포넌트들이 어떤 스타일링 방식을 사용하는지 확인하는 스크립트를 실행해보겠습니다:


🏁 Script executed:

#!/bin/bash
# Description: Check styling patterns used in other components

# Check for Tailwind usage in other components
echo "=== Tailwind usage in components ==="
rg -A 2 -B 2 "className.*bg-|className.*text-|className.*px-" --type tsx

echo -e "\n=== Styled-components usage ==="
rg -A 2 -B 2 "styled\." --type tsx | head -20

echo -e "\n=== Mixed usage patterns ==="
rg -l "styled" --type tsx | xargs rg -l "className.*bg-|className.*text-" | head -5

Length of output: 604


아래 스크립트로 .tsx 파일을 직접 검색해 스타일링 패턴 사용 현황을 확인해주세요:


🏁 Script executed:

#!/bin/bash
# Tailwind CSS 클래스 사용 예시 검색
echo "=== Tailwind usage in .tsx files ==="
rg -g '*.tsx' -E "className=.*bg-|className=.*text-|className=.*px-" -n | head -20

echo -e "\n=== Styled-components usage in .tsx files ==="
rg -g '*.tsx' -E "styled\." -n | head -20

echo -e "\n=== Mixed usage (styled + className) ==="
rg -l -g '*.tsx' "styled\." | xargs rg -l -g '*.tsx' -E "className=.*bg-|className=.*text-|className=.*px-" | head -20

Length of output: 1417


아래 수정된 스크립트로 .tsx 파일 전체를 다시 검사해보세요:


🏁 Script executed:

#!/bin/bash
# Tailwind CSS 클래스 사용 예시 검색
echo "=== Tailwind usage in .tsx files ==="
rg --glob '*.tsx' 'className=.*\b(bg|text|px)-' -n | head -20

echo -e "\n=== styled-components usage in .tsx files ==="
rg --glob '*.tsx' 'styled\.' -n | head -20

echo -e "\n=== Mixed usage (styled + Tailwind) ==="
rg -l --glob '*.tsx' 'styled\.' | xargs rg -l --glob '*.tsx' 'className=.*\b(bg|text|px)-' | head -20

Length of output: 1580


스타일링 일관성 유지: styled-components로 변환 필요

프로젝트 대부분의 컴포넌트가 styled-components를 사용하고 있으며, Tailwind 클래스 사용 예시는 PR 외에 확인되지 않습니다. ShareButton도 styled-components로 스타일링을 일관되게 맞춰주세요.

  • Location: frontend/src/pages/ClubDetailPage/components/Share/ShareButton/ShareButton.tsx (42행)
🤖 Prompt for AI Agents
In
frontend/src/pages/ClubDetailPage/components/Share/ShareButton/ShareButton.tsx
at line 42, the button styling uses Tailwind CSS classes which is inconsistent
with the project's use of styled-components. Refactor the button's styling by
creating a styled-component for the button element that applies the equivalent
styles (padding, background color, text color, border radius, hover effect, and
transition) instead of using Tailwind classes. Replace the current className
usage with this styled-component to maintain styling consistency.

Comment on lines +25 to +50
const shareToSocial = (platform: string): void => {
const encodedUrl = encodeURIComponent(shareData.url);
const encodedTitle = encodeURIComponent(shareData.title);
const encodedText = encodeURIComponent(shareData.text || '');

let shareUrl = '';

switch (platform) {
case 'facebook':
navigator.clipboard.writeText(shareData.url);
alert('링크가 복사되었습니다. 페이스북 앱에서 붙여넣기 해주세요.');
shareUrl = `https://www.facebook.com/sharer.php?u=${encodedUrl}`;
break;
case 'x':
shareUrl = `https://twitter.com/intent/tweet?url=${encodedUrl}&text=${encodedTitle}`;
break;
case 'instagram':
navigator.clipboard.writeText(shareData.url);
alert('링크가 복사되었습니다. 인스타그램 앱에서 붙여넣기 해주세요.');
return;
}

if (shareUrl) {
window.open(shareUrl, '_blank', 'width=600,height=400');
}
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

소셜 공유 로직을 개선해주세요.

현재 구현에서 몇 가지 문제점이 있습니다:

  1. Facebook 공유가 실제 공유 대화상자를 열지 않고 단순히 링크만 복사함
  2. 플랫폼별 로직이 복잡하여 별도 함수로 분리 필요
  3. alert() 사용으로 인한 UX 문제

플랫폼별 공유 로직을 별도 훅이나 유틸리티 함수로 분리하는 것을 고려해보세요:

// utils/socialShare.ts
export const createShareUrl = (platform: string, data: ShareData) => {
  const { url, title } = data;
  const encodedUrl = encodeURIComponent(url);
  const encodedTitle = encodeURIComponent(title);
  
  switch (platform) {
    case 'facebook':
      return `https://www.facebook.com/sharer.php?u=${encodedUrl}`;
    case 'x':
      return `https://twitter.com/intent/tweet?url=${encodedUrl}&text=${encodedTitle}`;
    default:
      return '';
  }
};
🤖 Prompt for AI Agents
In frontend/src/pages/ClubDetailPage/components/Share/ShareModal/ShareModal.tsx
between lines 25 and 50, the social sharing logic has issues: Facebook sharing
only copies the link instead of opening the share dialog, platform-specific
logic is tangled, and alert() usage harms UX. Refactor by extracting
platform-specific share URL creation into a separate utility function or hook,
removing clipboard copying and alert calls from this component. Then update the
shareToSocial function to open the correct share URL in a new window for
supported platforms, improving clarity and user experience.

Comment on lines +16 to +23
const copyToClipboard = async (): Promise<void> => {
try {
await navigator.clipboard.writeText(shareData.url);
alert('링크가 복사되었습니다');
} catch (error) {
console.error('복사 실패:', error);
}
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

클립보드 API 사용에 적절한 피드백 UI를 제공해주세요.

alert() 사용은 사용자 경험에 좋지 않습니다. 토스트 알림이나 인라인 피드백을 사용하는 것이 더 좋습니다.

+import { useState } from 'react';

const ShareModal: React.FC<ShareModalProps> = ({ isOpen, onClose, shareData }) => {
+  const [copySuccess, setCopySuccess] = useState<string | null>(null);

  const copyToClipboard = async (): Promise<void> => {
    try {
      await navigator.clipboard.writeText(shareData.url);
-      alert('링크가 복사되었습니다');
+      setCopySuccess('링크가 복사되었습니다');
+      setTimeout(() => setCopySuccess(null), 2000);
    } catch (error) {
      console.error('복사 실패:', error);
+      setCopySuccess('복사에 실패했습니다');
+      setTimeout(() => setCopySuccess(null), 2000);
    }
  };
📝 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 copyToClipboard = async (): Promise<void> => {
try {
await navigator.clipboard.writeText(shareData.url);
alert('링크가 복사되었습니다');
} catch (error) {
console.error('복사 실패:', error);
}
};
// at the top of the file, alongside your other React imports
-import React from 'react';
+import React, { useState } from 'react';
const ShareModal: React.FC<ShareModalProps> = ({ isOpen, onClose, shareData }) => {
+ const [copySuccess, setCopySuccess] = useState<string | null>(null);
const copyToClipboard = async (): Promise<void> => {
try {
await navigator.clipboard.writeText(shareData.url);
- alert('링크가 복사되었습니다');
+ setCopySuccess('링크가 복사되었습니다');
+ setTimeout(() => setCopySuccess(null), 2000);
} catch (error) {
console.error('복사 실패:', error);
+ setCopySuccess('복사에 실패했습니다');
+ setTimeout(() => setCopySuccess(null), 2000);
}
};
// ...rest of your component, and make sure to render `copySuccess` somewhere
// (e.g. inline message or toast component) so the user actually sees it.
};
🤖 Prompt for AI Agents
In frontend/src/pages/ClubDetailPage/components/Share/ShareModal/ShareModal.tsx
around lines 16 to 23, replace the alert() call in the copyToClipboard function
with a non-blocking user feedback mechanism such as a toast notification or
inline message. Implement a toast notification component or use an existing one
to show a brief success message when the URL is copied, improving user
experience by avoiding disruptive alert dialogs.

Comment on lines +32 to +45
switch (platform) {
case 'facebook':
navigator.clipboard.writeText(shareData.url);
alert('링크가 복사되었습니다. 페이스북 앱에서 붙여넣기 해주세요.');
shareUrl = `https://www.facebook.com/sharer.php?u=${encodedUrl}`;
break;
case 'x':
shareUrl = `https://twitter.com/intent/tweet?url=${encodedUrl}&text=${encodedTitle}`;
break;
case 'instagram':
navigator.clipboard.writeText(shareData.url);
alert('링크가 복사되었습니다. 인스타그램 앱에서 붙여넣기 해주세요.');
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Facebook과 Instagram 공유 방식을 재검토해주세요.

현재 Facebook과 Instagram의 경우 실제 공유가 아닌 링크 복사만 수행합니다. 사용자가 직접 붙여넣기를 해야 하는 방식은 사용자 경험이 좋지 않을 수 있습니다.

Facebook Sharer API가 여전히 작동하는지 확인해보겠습니다:


🌐 Web query:

Does Facebook Sharer API (https://www.facebook.com/sharer.php) still work in 2024? What are the current best practices for Facebook sharing?

💡 Result:

As of 2024, the Facebook Sharer API, accessible via https://www.facebook.com/sharer.php, remains operational. This API allows developers to create links that enable users to share content directly to their Facebook feeds. However, it's important to note that Facebook's platform and APIs are continually evolving. Therefore, it's advisable to consult the official Facebook for Developers documentation for the most current information and best practices.

To enhance the effectiveness of your content sharing on Facebook, consider implementing the following best practices:

  1. Utilize Open Graph Meta Tags: Incorporate Open Graph tags into your website's HTML to control how your content appears when shared. This ensures that the title, description, and image are displayed as intended, providing a richer user experience. (developers.facebook.com)

  2. Optimize Image Quality and Size: Use high-resolution images (at least 1080 pixels wide) to ensure clarity and visual appeal. Properly sized images prevent distortion and enhance the overall presentation of your shared content. (developers.facebook.com)

  3. Implement the Facebook Crawler: Ensure that your website is accessible to Facebook's crawler by enabling GZIP and/or deflate encoding on your web server. This allows Facebook to fetch and display your content accurately when shared. (developers.facebook.com)

  4. Leverage Facebook's Native Features: Incorporate Facebook's native features, such as Stories, Reels, and live videos, into your content strategy. These formats are designed to capture user attention and foster engagement, aligning with Facebook's algorithmic preferences. (blog.hootsuite.com)

  5. Engage with Your Audience: Prompt users to interact with your content by asking open-ended questions, conducting polls, or encouraging comments. Meaningful interactions signal to Facebook that your content is valuable, potentially increasing its reach. (blog.hootsuite.com)

  6. Monitor and Adapt to Algorithm Changes: Stay informed about updates to Facebook's algorithm and adjust your content strategy accordingly. For instance, the algorithm may prioritize content that fosters positive interactions and engagement. (blog.hootsuite.com)

By adhering to these best practices, you can enhance the visibility and engagement of your content on Facebook, ensuring a more effective sharing experience for your audience.

Citations:


Facebook 및 Instagram 공유 UX 개선 필요

현재 Facebook과 Instagram 공유 로직은 단순히 링크 복사 후 사용자가 직접 붙여넣기를 해야 하는 흐름으로, 사용자 경험이 떨어집니다. 2024년 기준 Facebook Sharer API(https://www.facebook.com/sharer.php)는 여전히 정상 동작하므로 복사 대신 다이얼로그를 직접 열어 공유하도록 변경하고, Instagram은 Web Share API 사용 또는 적절한 안내를 추가하는 것을 권장드립니다.

수정 위치

  • frontend/src/pages/ClubDetailPage/components/Share/ShareModal/ShareModal.tsx (32–45행)

예시 코드 변경안:

- case 'facebook':
-   navigator.clipboard.writeText(shareData.url);
-   alert('링크가 복사되었습니다. 페이스북 앱에서 붙여넣기 해주세요.');
-   shareUrl = `https://www.facebook.com/sharer.php?u=${encodedUrl}`;
-   break;
+ case 'facebook':
+   window.open(`https://www.facebook.com/sharer.php?u=${encodedUrl}`, '_blank');
+   break;

- case 'instagram':
-   navigator.clipboard.writeText(shareData.url);
-   alert('링크가 복사되었습니다. 인스타그램 앱에서 붙여넣기 해주세요.');
-   return;
+ case 'instagram':
+   if (navigator.share) {
+     navigator.share({ title: shareData.title, url: shareData.url });
+   } else {
+     alert('인스타그램 웹 공유는 지원되지 않습니다. 링크를 복사합니다.');
+     navigator.clipboard.writeText(shareData.url);
+   }
+   return;

추가 검토 사항

  • 페이지 <head>에 Open Graph 메타 태그가 올바르게 설정되었는지 확인
  • Web Share API 호환성 및 예외 처리 여부 점검
📝 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
switch (platform) {
case 'facebook':
navigator.clipboard.writeText(shareData.url);
alert('링크가 복사되었습니다. 페이스북 앱에서 붙여넣기 해주세요.');
shareUrl = `https://www.facebook.com/sharer.php?u=${encodedUrl}`;
break;
case 'x':
shareUrl = `https://twitter.com/intent/tweet?url=${encodedUrl}&text=${encodedTitle}`;
break;
case 'instagram':
navigator.clipboard.writeText(shareData.url);
alert('링크가 복사되었습니다. 인스타그램 앱에서 붙여넣기 해주세요.');
return;
}
switch (platform) {
case 'facebook':
window.open(`https://www.facebook.com/sharer.php?u=${encodedUrl}`, '_blank');
break;
case 'x':
shareUrl = `https://twitter.com/intent/tweet?url=${encodedUrl}&text=${encodedTitle}`;
break;
case 'instagram':
if (navigator.share) {
navigator.share({ title: shareData.title, url: shareData.url });
} else {
alert('인스타그램 웹 공유는 지원되지 않습니다. 링크를 복사합니다.');
navigator.clipboard.writeText(shareData.url);
}
return;
}
🤖 Prompt for AI Agents
In frontend/src/pages/ClubDetailPage/components/Share/ShareModal/ShareModal.tsx
between lines 32 and 45, improve the Facebook and Instagram sharing UX by
replacing the clipboard copy and alert approach. For Facebook, open the sharer
URL in a new window or tab instead of copying the link, using the existing
Facebook Sharer API URL. For Instagram, implement the Web Share API if supported
to trigger the native share dialog, and if not supported, provide a clear
user-friendly message guiding manual sharing. Also, ensure to handle any
exceptions from the Web Share API gracefully. Verify that Open Graph meta tags
are correctly set in the page head to support rich sharing previews.

Copy link
Member

@seongwon030 seongwon030 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 +20 to +25
.share-button {
padding: 8px 12px !important;
font-size: 14px !important;
min-width: auto !important;
white-space: nowrap;
}
Copy link
Member

Choose a reason for hiding this comment

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

코드래빗에서 말한 것처럼 !important 는 기존 스타일을 덮어쓰는 문제가 있어요.
그리고 css 형식을 따르셨는데 프론트에서는 styled-components 라이브러리를 사용하고 있어요.

만약 share-button이라는 className을 사용하고 싶으시다면

export const shareButton = styled.div``;

처럼 사용할 수 있어요.

Comment on lines +61 to +67
<ShareButton
url={shareUrl}
title={shareTitle}
text={shareText}
buttonText="공유"
className="share-button"
/>
Copy link
Member

Choose a reason for hiding this comment

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

이 부분또한 className을 prop으로 주지 않아도 되는 방법이 있어요.

className?: string;
}

const ShareButton: React.FC<ShareButtonProps> = ({
Copy link
Member

Choose a reason for hiding this comment

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

React.FC에서 props는 children을 암시적으로 가지게 됩니다.
이것은 타입스크립트를 쓰는 입장에서 안티패턴입니다.

매개변수 옆에 타입을 정의하면 React.FC를 사용하지 않아도 됩니다.

<>
<button
onClick={handleShare}
className={`px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors ${className}`}
Copy link
Member

Choose a reason for hiding this comment

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

일관성 유지 부탁드릴게욥~

@@ -0,0 +1,91 @@
export const overlayStyle: React.CSSProperties = {
Copy link
Member

Choose a reason for hiding this comment

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

이건 처음 보는 문법인데.. 혹시 어떤 경우에 쓰는건가요?

};
}

const ShareModal: React.FC<ShareModalProps> = ({ isOpen, onClose, shareData }) => {
Copy link
Member

Choose a reason for hiding this comment

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

이 부분도요

if (!isOpen) return null;

const modalContent = (
<div style={styles.overlayStyle} onClick={onClose}>
Copy link
Member

Choose a reason for hiding this comment

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

div를 styled-components로 const modalContent = styled.div``;처럼 사용할 수 있어요.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💻 FE Frontend ✨ Feature 기능 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants