Skip to content

feat: OG Image 설정 및 모바일 UI/반응형 개선#31

Merged
kim3360 merged 7 commits intomainfrom
refactor/ui-refactoring
Feb 7, 2026
Merged

feat: OG Image 설정 및 모바일 UI/반응형 개선#31
kim3360 merged 7 commits intomainfrom
refactor/ui-refactoring

Conversation

@kangdy25
Copy link
Copy Markdown
Collaborator

@kangdy25 kangdy25 commented Feb 7, 2026

🚀 feat: OG Image 설정 및 모바일 UI/반응형 개선

📝 변경사항

OG (Open Graph) Image 설정

  • 메인 페이지, 모임 생성 완료(Share), 참여(Join) 페이지에 메타데이터를 적용했습니다.
  • join/[id] 페이지의 경우 쿼리스트링(?view=share / ?view=nudge)에 따라 동적으로 다른 OG 이미지가 노출되도록 구현했습니다.
  • 이를 위해 클라이언트 컴포넌트(JoinForm, ShareContent)와 서버 페이지(page.tsx)를 분리했습니다.

링크 공유 로직 개선

  • useShareMeeting 훅을 수정하여 공유 목적(일반 공유, 재촉하기)에 따라 URL에 쿼리 파라미터가 자동으로 붙도록 변경했습니다.

모바일 모달 UI 수정 (NudgeModal, ShareModal)

  • 모바일 환경에서 이미지와 인풋 창이 모달 영역을 뚫고 나가는 문제를 해결했습니다.

메인 페이지 타이포그래피 개선

데스크탑, 태블릿, 모바일 해상도별로 자연스러운 줄바꿈(
)이 적용되도록 수정했습니다.

지도 컴포넌트 사이즈 조절

  • 모바일에서 지도가 화면 너비를 초과하여 가로 스크롤이 생기는 문제를 max-w-full, overflow-hidden을 적용하여 해결했습니다.

✅ 체크리스트

  • 코드 리뷰를 받았습니다
  • 테스트를 완료했습니다
  • 린터 에러가 없습니다
  • 타입 에러가 없습니다
  • 브라우저에서 테스트를 완료했습니다
  • 모바일에서 테스트를 완료했습니다 (해당되는 경우)

📸 스크린샷

UI 변경 사항이 있다면 이미지를 드래그해서 넣어주세요!

💬 리뷰어 전달사항

  • 리뷰어가 특별히 확인해야 할 사항이 있다면 적어주세요.

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 공유 페이지에 공유 URL 표시 및 복사 기능을 가진 공유 콘텐츠 추가
    • 참가 동작을 처리하는 통합 참가 폼 추가
  • UI/UX 개선

    • 페이지별 소셜 미리보기용 메타데이터(OG/Twitter) 생성 개선
    • 모달 이미지 및 카드/히어로 섹션의 반응형 레이아웃 조정
    • 지도 컨테이너의 레이아웃·오버플로우 처리 강화
  • 기타

    • 푸터의 피드백 버튼 및 소셜 아이콘 비활성화

@vercel
Copy link
Copy Markdown

vercel Bot commented Feb 7, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
mingling-frontend Ready Ready Preview, Comment Feb 7, 2026 1:51pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 7, 2026

Warning

Rate limit exceeded

@kangdy25 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 12 minutes and 48 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

Walkthrough

서버 컴포넌트로 join/share 페이지를 전환하고 메타데이터 생성 함수를 추가했으며, 클라이언트 폼 로직을 JoinForm/ShareContent로 분리하고, 모달 훅에 mode 매개변수 도입 및 맵·레이아웃 관련 클래스 조정이 이루어졌습니다.

Changes

Cohort / File(s) Summary
서버 페이지 및 레이아웃
app/join/[id]/page.tsx, app/share/[id]/page.tsx, app/layout.tsx
클라이언트 페이지를 서버 컴포넌트로 전환. generateMetadata 추가으로 OpenGraph/Twitter 메타데이터 동적 생성. metadataBase 설정 추가.
클라이언트 폼 및 공유 UI
components/join/joinForm.tsx, components/share/shareContent.tsx
새로운 클라이언트 컴포넌트 JoinForm(이름/비밀번호/remember 로직, mutation 호출)과 ShareContent(공유 URL 표시/복사/toast) 추가.
모달·공유 훅 변경
components/modal/nudgeModal.tsx, components/modal/shareModal.tsx, hooks/api/query/useShareMeeting.ts
useShareMeeting에 `mode: 'share'
맵 컴포넌트 스타일링
components/map/kakaoMap.tsx, components/map/kakaoMapLine.tsx, components/map/kakaoMapRecommend.tsx
맵 컨테이너에 w-full max-w-full overflow-hidden 등 레이아웃 클래스 추가 및 소폭 클래스/포맷팅 변경 (동작 변화 없음).
페이지 UI/스타일 조정
app/page.tsx, app/meeting/[id]/page.tsx
Hero 및 FeatureCard 크기/레이아웃 조정, 미팅 페이지의 KakaoMap 클래스 및 submit 버튼의 포지셔닝/반응형 동작 변경.
기타 UI 비활성화
components/footer.tsx
피드백 버튼 및 소셜 아이콘 섹션 렌더링 주석 처리(비활성화).

Sequence Diagram

sequenceDiagram
    actor User
    participant Browser
    participant Server
    participant JoinForm as JoinForm (Client)
    participant API as API Server
    participant Storage as LocalStorage

    User->>Browser: 요청 /join/{id}
    Browser->>Server: 페이지 요청
    Server->>Server: generateMetadata(params, searchParams)
    Server->>Server: render Page -> includes JoinForm mount point
    Server-->>Browser: HTML + metadata

    Browser->>JoinForm: mount (client)
    JoinForm->>API: (optional) check login / initial hooks
    User->>JoinForm: 입력(이름, 비밀번호) 및 제출
    JoinForm->>JoinForm: 입력 검증
    JoinForm->>API: useEnterParticipant mutation
    API-->>JoinForm: 성공 / 실패

    alt 성공
        JoinForm->>Storage: setMeetingUserId(...)
        JoinForm->>Browser: navigate to /meeting/{id}
    else 실패
        JoinForm->>Browser: show toast error
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • kim3360
🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive PR 제목이 '변경사항(Refactor/UI refactoring)'은 실제 변경의 일부를 나타내지만, PR의 주요 목표인 OG 메타데이터 설정을 명확히 반영하지 못하고 너무 포괄적입니다. 제목을 더 구체적으로 변경하세요. 예: 'feat: OG 메타데이터 설정 및 모바일 UI 개선' 또는 'refactor: 페이지 구조 분리 및 UI 반응형 개선'
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed PR 설명이 템플릿 구조를 따르고 변경사항, 체크리스트, 스크린샷, 리뷰어 전달사항을 포함하며 모두 완성되어 있습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/ui-refactoring

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

Comment @coderabbitai help to get the list of available commands and usage tips.

@kangdy25 kangdy25 requested a review from kim3360 February 7, 2026 13:35
Copy link
Copy Markdown

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
components/map/kakaoMapLine.tsx (1)

89-98: ⚠️ Potential issue | 🟡 Minor

빈 상태와 정상 렌더링 경로 간 스타일 불일치

Line 89의 빈 상태 컨테이너에는 w-full max-w-full overflow-hidden이 추가되었지만, Line 98의 실제 지도 컨테이너에는 여전히 relative ${className}만 사용하고 있습니다. 모바일 가로 스크롤 방지가 목적이라면 정상 렌더링 경로에도 동일한 클래스를 적용해야 합니다.

♻️ 수정 제안
-    <div className={`relative ${className}`}>
+    <div className={`relative w-full max-w-full overflow-hidden ${className}`}>
🤖 Fix all issues with AI agents
In `@components/join/joinForm.tsx`:
- Around line 114-148: The custom checkbox built around
isRemembered/setIsRemembered (the outer clickable div and inner visual div)
lacks keyboard accessibility; update the outer div to include role="checkbox",
tabIndex={0}, aria-checked={isRemembered}, and an onKeyDown handler that toggles
setIsRemembered when Space or Enter is pressed (mirroring the onClick behavior),
and ensure the visible label span remains associated (e.g., keep it adjacent) so
screen readers announce the control correctly; also preserve existing visual
state logic using isFormValid/isRemembered when applying classes.

In `@components/modal/nudgeModal.tsx`:
- Around line 49-56: Update the Image usage in NudgeModal by changing the alt
text from "모임 링크를 공유해주세요" to a string that reflects the nudge intent (e.g., "아직
입력하지 않은 친구를 재촉해 보세요") in the Image component instance, and rename the referenced
image file from "nudge_modals.jpg" to the consistent singular "nudge_modal.jpg"
(and update any import/path usages) so the <Image ... src="nudge_modal.jpg" ...
alt="아직 입력하지 않은 친구를 재촉해 보세요" /> accurately describes the modal and matches
naming conventions.
🧹 Nitpick comments (8)
app/meeting/[id]/page.tsx (1)

315-320: 모바일에서 제출 버튼 하단 여백 부재 확인 필요

md:absolute 조건부 포지셔닝은 좋은 접근이지만, 모바일(비absolute)에서 버튼 하단에 여백(mb)이 없어 화면 하단에 버튼이 붙을 수 있습니다. Toast 컴포넌트(Line 321)와의 간격도 확인해보세요.

hooks/api/query/useShareMeeting.ts (1)

46-49: 클립보드 복사 실패 시 alert() 대신 Toast 사용 고려

성공 시에는 show()로 Toast를 표시하지만, 실패 시에는 alert()를 사용하고 있습니다. 일관된 UX를 위해 실패 시에도 Toast 또는 별도의 에러 피드백을 사용하는 것이 좋습니다.

components/share/shareContent.tsx (2)

17-18: 로딩 중 빈 화면 표시 문제

isLoadingnull을 반환하면 사용자에게 빈 화면이 잠시 보일 수 있습니다. 간단한 스켈레톤이나 스피너를 표시하면 UX가 개선됩니다.


28-35: 이미지에 적절한 크기 제약 확인 필요

컨테이너가 h-70 w-80로 고정되어 있지만, Image 컴포넌트의 width={360} height={257}은 이와 다릅니다. 컨테이너에 overflow-hidden이 없으므로 특정 상황에서 이미지가 컨테이너를 벗어날 수 있습니다. PR 목적에서 언급된 "모달에서 이미지가 영역을 벗어나는 문제"와 유사한 패턴입니다.

♻️ 수정 제안
-      <section className="mb-9 flex h-70 w-80 max-w-sm items-center justify-center rounded-2xl md:w-90">
+      <section className="mb-9 flex h-70 w-80 max-w-sm items-center justify-center overflow-hidden rounded-2xl md:w-90">
components/join/joinForm.tsx (1)

111-112: 패스워드 입력 필드의 Tailwind 클래스 충돌 가능성

외부에 pl-3 text-center가 있고, 조건부로 pl-0 text-center 또는 pl-3 text-left를 추가하고 있습니다. Tailwind v4에서는 동일 속성의 중복 유틸리티 클래스 간 우선순위가 소스 순서가 아닌 CSS 레이어 순서로 결정되므로, 의도대로 오버라이드가 안 될 수 있습니다. clsx/cn 등으로 조건부 클래스를 깔끔하게 분리하는 것을 권장합니다.

♻️ 조건부 클래스 분리 제안
-            className={`border-gray-2 placeholder:text-gray-3 text-gray-10 focus:border-blue-5 w-full rounded-sm border py-2 pl-3 text-center text-[15px] focus:bg-white focus:outline-none ${password ? 'pl-0 text-center' : 'pl-3 text-left'}`}
+            className={`border-gray-2 placeholder:text-gray-3 text-gray-10 focus:border-blue-5 w-full rounded-sm border py-2 text-[15px] focus:bg-white focus:outline-none ${password ? 'pl-0 text-center' : 'pl-3 text-left'}`}
app/join/[id]/page.tsx (1)

30-42: OG URL 하드코딩 및 description 누락

  1. Line 34의 url이 하드코딩되어 있습니다. app/layout.tsxmetadataBase가 설정되어 있다면, 상대 경로(/join/${id})만으로도 충분합니다. 환경(스테이징/프로덕션)에 따라 달라질 수 있으므로 metadataBase를 활용하는 것이 더 유지보수에 유리합니다.

  2. app/share/[id]/page.tsxdescription을 포함하고 있는데, 이 파일에는 누락되어 있습니다. SNS 미리보기 시 설명 텍스트가 표시되지 않을 수 있습니다.

♻️ 개선 제안
   return {
     title,
+    description: isNudge
+      ? '모임원들이 기다리고 있어요. 출발지를 입력해주세요!'
+      : '링크를 통해 모임에 참여하고 출발지를 등록하세요.',
     openGraph: {
       title,
-      url: `https://www.mingling.kr/join/${id}`, // 실제 도메인으로 변경 권장
+      description: isNudge
+        ? '모임원들이 기다리고 있어요. 출발지를 입력해주세요!'
+        : '링크를 통해 모임에 참여하고 출발지를 등록하세요.',
+      url: `/join/${id}`,
       images: [imageUrl, ...previousImages],
     },
     twitter: {
       card: 'summary_large_image',
       title,
+      description: isNudge
+        ? '모임원들이 기다리고 있어요. 출발지를 입력해주세요!'
+        : '링크를 통해 모임에 참여하고 출발지를 등록하세요.',
       images: [imageUrl],
     },
   };
app/share/[id]/page.tsx (1)

8-27: 메타데이터 구조가 깔끔합니다. URL 하드코딩만 일관성 있게 수정하면 좋겠습니다.

join/[id]/page.tsx와 동일하게 url이 하드코딩되어 있습니다. metadataBase가 설정되어 있다면 상대 경로(/share/${id})로 충분합니다. 또한 join 페이지에서는 parentopenGraph.images를 병합하는데, 이 파일에서는 하지 않고 있습니다. 의도적이라면 괜찮지만, 일관성을 위해 확인해 주세요.

components/modal/shareModal.tsx (1)

21-84: ShareModalNudgeModal의 중복이 큽니다.

두 모달 컴포넌트가 mode, 이미지 경로, 타이틀 텍스트만 다르고 구조가 거의 동일합니다. 공유 모달 컴포넌트로 통합하면 향후 UI 변경 시 한 곳만 수정하면 됩니다.

♻️ 통합 컴포넌트 예시
// components/modal/linkShareModal.tsx
interface LinkShareModalProps {
  isOpen: boolean;
  onClose: () => void;
  meetingId: string;
  mode: 'share' | 'nudge';
}

const MODAL_CONFIG = {
  share: {
    title: '모임 링크를 공유해주세요',
    image: '/images/share_modal.jpg',
    inputName: 'shareLink',
  },
  nudge: {
    title: (
      <>아직 입력하지 않은 친구를 <br /> 재촉해 보세요!</>
    ),
    image: '/images/nudge_modals.jpg',
    inputName: 'NudgeLink',
  },
} as const;

Comment on lines +114 to +148
<div
onClick={() => setIsRemembered(!isRemembered)}
className="flex cursor-pointer items-center gap-2"
>
<div
className={`flex h-5 w-5 items-center justify-center rounded border transition-colors ${
!isRemembered
? 'border-gray-300 bg-white'
: isFormValid
? 'border-blue-500 bg-blue-500'
: 'border-gray-300 bg-gray-300'
}`}
>
{isRemembered && (
<svg
width="8"
height="6"
viewBox="0 0 14 10"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M1 5L4.5 8.5L13 1"
stroke="white"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)}
</div>
<span className={`text-xs font-medium ${isFormValid ? 'text-blue-5' : 'text-gray-5'}`}>
내 정보 기억하기
</span>
</div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

커스텀 체크박스에 키보드 접근성이 누락되어 있습니다.

div 기반의 체크박스 토글에 role="checkbox", tabIndex={0}, onKeyDown (Space/Enter 처리), aria-checked 속성이 없어 키보드 사용자가 이 컨트롤을 조작할 수 없습니다.

♿ 접근성 개선 제안
         <div
           onClick={() => setIsRemembered(!isRemembered)}
           className="flex cursor-pointer items-center gap-2"
+          role="checkbox"
+          aria-checked={isRemembered}
+          tabIndex={0}
+          onKeyDown={(e) => {
+            if (e.key === ' ' || e.key === 'Enter') {
+              e.preventDefault();
+              setIsRemembered(!isRemembered);
+            }
+          }}
         >
📝 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
<div
onClick={() => setIsRemembered(!isRemembered)}
className="flex cursor-pointer items-center gap-2"
>
<div
className={`flex h-5 w-5 items-center justify-center rounded border transition-colors ${
!isRemembered
? 'border-gray-300 bg-white'
: isFormValid
? 'border-blue-500 bg-blue-500'
: 'border-gray-300 bg-gray-300'
}`}
>
{isRemembered && (
<svg
width="8"
height="6"
viewBox="0 0 14 10"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M1 5L4.5 8.5L13 1"
stroke="white"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)}
</div>
<span className={`text-xs font-medium ${isFormValid ? 'text-blue-5' : 'text-gray-5'}`}>
정보 기억하기
</span>
</div>
<div
onClick={() => setIsRemembered(!isRemembered)}
className="flex cursor-pointer items-center gap-2"
role="checkbox"
aria-checked={isRemembered}
tabIndex={0}
onKeyDown={(e) => {
if (e.key === ' ' || e.key === 'Enter') {
e.preventDefault();
setIsRemembered(!isRemembered);
}
}}
>
<div
className={`flex h-5 w-5 items-center justify-center rounded border transition-colors ${
!isRemembered
? 'border-gray-300 bg-white'
: isFormValid
? 'border-blue-500 bg-blue-500'
: 'border-gray-300 bg-gray-300'
}`}
>
{isRemembered && (
<svg
width="8"
height="6"
viewBox="0 0 14 10"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M1 5L4.5 8.5L13 1"
stroke="white"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)}
</div>
<span className={`text-xs font-medium ${isFormValid ? 'text-blue-5' : 'text-gray-5'}`}>
정보 기억하기
</span>
</div>
🤖 Prompt for AI Agents
In `@components/join/joinForm.tsx` around lines 114 - 148, The custom checkbox
built around isRemembered/setIsRemembered (the outer clickable div and inner
visual div) lacks keyboard accessibility; update the outer div to include
role="checkbox", tabIndex={0}, aria-checked={isRemembered}, and an onKeyDown
handler that toggles setIsRemembered when Space or Enter is pressed (mirroring
the onClick behavior), and ensure the visible label span remains associated
(e.g., keep it adjacent) so screen readers announce the control correctly; also
preserve existing visual state logic using isFormValid/isRemembered when
applying classes.

Comment thread components/modal/nudgeModal.tsx
- 피드백 남기기, 인스타/쓰레드 아이콘 제거
@kim3360 kim3360 merged commit a005528 into main Feb 7, 2026
4 checks passed
@kangdy25 kangdy25 deleted the refactor/ui-refactoring branch February 7, 2026 14:05
@kangdy25 kangdy25 changed the title Refactor/UI refactoring feat: OG Image 설정 및 모바일 UI/반응형 개선 Feb 14, 2026
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.

2 participants