Skip to content

Dev#32

Merged
ark1st merged 29 commits into
mainfrom
dev
Mar 2, 2026
Merged

Dev#32
ark1st merged 29 commits into
mainfrom
dev

Conversation

@ark1st
Copy link
Copy Markdown
Collaborator

@ark1st ark1st commented Mar 2, 2026

📋 작업 내용

  • 자체 SVG 아이콘 렌더링 적용: Lucide 라이브러리를 부분 제거하고, 자체 에셋(static/images/icons/)을 이용해 헤더, 톡 페이지, 로그인 모달 내 아이콘을 커스텀 SVG <img> 태그로 전면 교체
  • 결산 내용 및 운영팀 한마디 상세 페이지 레이아웃 보완: 라벨 텍스트(획득 일자, 직책, 상세 내용)에 고정 너비(w-[72px]), 좌측 정렬, 줄바꿈 방지(whitespace-nowrap), 축소 방지(shrink-0)를 적용하여 텍스트 길이에 따른 UI 찌그러짐 완벽 방지
  • 메생결산 톡 네비게이션(Top Bar) 개선: 좌측 뒤로 가기 요소와 우측 로그아웃 버튼 존재 여부와 무관하게 "메생결산 톡" 타이틀이 화면 정중앙에 위치하도록 absolute text-center 제어 및 배경 분리
  • 단풍바람 로그인 팝업 및 Input Box 접근성/UI 강화:
    • 이름/학번 InputBox 포커스 시 브라우저 기본 이중 아웃라인 버그(focus:outline-none focus:ring-0) 제거 및 내부 배경색 유지, 테두리만 전환되도록 픽스
    • 모달 팝업 상단 "단풍바람" 텍스트 타이틀을 logo-text-white.svg 이미지로 교체하고 그림자 효과 제거 요소 간격(Gap, Margin) 미세 조정
    • 이름 저장 커스텀 체크박스(<label> 확장) 적용을 통한 클릭 접근성(상호작용) 토글 이슈 해결 및 check-enable/disable.svg 상태 동기화
  • 카카오 로그인 기능 임시 제외: 이번 프로젝트 스코프에서 제외됨에 따라 모달 및 메인 로그인 페이지 내 Kauth 연동 로직과 관련 UI 버튼 전체 주석 처리

🎯 관련 이슈

Closes #(이슈번호를 입력해주세요)

🤖 사용한 Prompt

  • "요청하신 디렉터리에 있던 13개의 SVG 아이콘들을 기존 Lucide 아이콘 대신 각 UI 컴포넌트에 알맞게 교체 배치해라."
  • "msg/[id] 페이지 상세 내용 라벨이 우측 긴 텍스트에 의해 찌그러지지 않도록 flex-shrink-0를 적용하고, w-[72px] 고정 너비 및 텍스트 좌측 정렬, 줄바꿈 방지를 걸어라."
  • "로그인 모달 내 학번 Input Box의 X 버튼을 숨기고, 요소 간 간격을 4px, 8px 등으로 UI 통일성을 지켜 조정하라."
  • "탑바의 '메생결산 톡' 제목이 수평 정중앙에 오도록 absolute 맵핑하고, 우측에 로그인 상태일 때만 렌더링되는 '로그아웃' 버튼을 추가하라."
  • "Input Box 포커스 시 기존 주황색 배경은 유지하되 1px 테두리 두께만 흰색으로 즉각 변화하게 만들고, 이중 링(ring) 아웃라인 발생 버그를 없애라."
  • "이름 저장 체크박스의 아이콘/클릭 영역 접근성을 높이도록 label 래핑 구조를 개선하여 텍스트나 아이콘 부분을 눌러도 상태가 정상 변경되도록 수정하라."
  • "기획 상 이번 구현에서 제외된 카카오 로그인 관련 버튼 UI와 스크립트 함수 로직들을 프론트엔드 코드에서 주석 처리해라."

✅ 체크리스트

  • 로컬에서 테스트 완료
  • 타입 에러 없음 (npm run check)
  • Linter 통과
  • 모바일 반응형 확인
  • 코드 리뷰 요청 완료

📸 스크린샷 (선택)

💬 특이사항

  • 카카오 로그인 관련 로직(handleKakaoLogin) 및 프론트 버튼 UI는 파일 내부에 주석 형태로 보존해 두었습니다. (필요 시 추후 주석만 해제해 재활용 가능)
  • 모바일 뷰포트에서 InputBox 및 버튼 컴포넌트 터치 시 모바일 브라우저의 기본 음영(Highlight)이 겹치지 않게 조치하였습니다.

리뷰어를 위한 가이드

  • 요구사항을 모두 충족하는가?
  • 코드 품질이 적절한가?
  • 성능 이슈는 없는가?
  • 문서화가 필요한가?

Hyunseong0303 and others added 29 commits March 1, 2026 02:32
…mprove

# Conflicts:
#	dpbr_front/app/src/lib/components/BottomSheetLogin.svelte
feat: add paginated API clients and infinite scroll UI
…-newline

fix: member detail fallback and team-message newline rendering
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 2, 2026

📝 Walkthrough

Summary by CodeRabbit

새로운 기능

  • 무한 스크롤 기반 캐릭터 및 결산 기록 페이지 네이션 추가

버그 수정

  • 캐릭터 카드 레이아웃 렌더링 개선

스타일

  • 전체 페이지 UI 정렬 및 간격 개선
  • 로그인 팝업 에러 메시지 인라인 표시
  • 로그인 입력 필드 자동완성 비활성화
  • 아이콘 및 비주얼 업데이트
  • 사이드바 푸터 UI 단순화

Walkthrough

페이지네이션 API 엔드포인트를 추가하고 무한 스크롤 기능을 구현하며, 여러 컴포넌트의 UI/스타일을 업데이트하고, 로그인 오류 처리 플로우를 개선하고, 멤버 저장 페이지 레이아웃을 재설계합니다.

Changes

Cohort / File(s) Summary
API 페이지네이션
dpbr_front/app/src/lib/api.ts
새로운 페이지네이션 응답 인터페이스 및 getCharactersPaginated, getSettlementsByCharacterIdPaginated 엔드포인트 추가. 기존 함수들을 리팩토링하여 공통 매핑 헬퍼(mapCharacterResponse, mapSettlementResponse) 사용.
무한 스크롤 구현
dpbr_front/app/src/routes/+page.svelte, dpbr_front/app/src/routes/member/[id]/+page.svelte
클라이언트 측 페이지네이션 상태 및 IntersectionObserver 기반 무한 스크롤 로직 추가. 캐릭터와 정산 데이터의 점진적 로딩 지원.
로그인 및 인증 플로우
dpbr_front/app/src/lib/components/BottomSheetLogin.svelte, dpbr_front/app/src/lib/components/InputBox.svelte, dpbr_front/app/src/routes/login/+page.svelte, dpbr_front/app/src/lib/stores/auth.ts
오류 메시지 토스트를 인라인 에러 버블로 변경. 로그인 입력에 spellcheck="false"autocomplete="off" 추가. 로그인 시 "이름 저장" 기능 구현.
헤더 및 네비게이션
dpbr_front/app/src/lib/components/Header.svelte, dpbr_front/app/src/lib/components/Sidebar.svelte, dpbr_front/app/src/routes/talk/+page.svelte
아이콘 자산 경로 업데이트. 사이드바 푸터 스타일 및 텍스트 변경. 로그아웃 후 리다이렉트 경로를 /login에서 /로 변경.
멤버 저장 페이지 재설계
dpbr_front/app/src/routes/member/[id]/save/+page.svelte
공원 테마 배경, 캐릭터 이미지 오버레이, 상세 정보 섹션을 포함한 복층 레이아웃 구현. 이미지 저장 로직 및 픽셀 비율 조정.
콘텐츠 및 메시지 페이지
dpbr_front/app/src/routes/msg/[id]/+page.svelte, dpbr_front/app/src/routes/team-message/[id]/+page.svelte
이미지 소스 폴백 로직 개선. 레이아웃 간격, 레이블 정렬, 구분선 추가.
UI 컴포넌트
dpbr_front/app/src/lib/components/CharacterCard.svelte, dpbr_front/app/src/lib/components/CommentItem.svelte
Typography 스타일 업데이트 및 포매팅 조정. 댓글 작성자와 내용의 폰트 가중치 변경.
메타데이터
dpbr_front/app/src/app.html
SEO 메타 태그(Open Graph, Twitter) 추가. Favicon 경로 업데이트.

Sequence Diagram(s)

sequenceDiagram
    participant User as 사용자
    participant Client as 클라이언트
    participant Observer as IntersectionObserver
    participant API as API 서버
    participant DB as Database

    User->>Client: 페이지 접속
    Client->>API: getCharactersPaginated(page=1)
    API->>DB: 캐릭터 조회 (limit: 10)
    DB-->>API: 캐릭터 목록 반환
    API-->>Client: items[], total, page, limit
    Client->>Client: 캐릭터 렌더링
    Client->>Observer: sentinel 요소 관찰 시작

    User->>Client: 페이지 스크롤
    Observer->>Observer: sentinel 교차 감지
    Observer->>Client: 콜백 트리거
    Client->>API: getCharactersPaginated(page=2)
    API->>DB: 다음 페이지 캐릭터 조회
    DB-->>API: 캐릭터 목록 반환
    API-->>Client: 다음 아이템 반환
    Client->>Client: 기존 목록에 아이템 추가
    Client->>Client: sentinel 위치 업데이트

    alt 더 이상 데이터 없음
        Client->>Client: hasMore = false
        Client->>Observer: sentinel 관찰 중지
    else 데이터 존재
        Observer->>Observer: sentinel 재관찰
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • Hyunseong0303
  • irislarbel

Poem

🐰 페이지 넘을 때마다 데이터 흐르고,
스크롤 내릴 때마다 새로운 얼굴들이 뙇!
무한의 나락으로 폴록 떨어지는 건 아니고,
꼭 당근을 한 입씩 깨물 때처럼 자연스럽게—
유저 경험은 부드럽고, 코드는 깔끔하네! 🎉

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% 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 제목 'Dev'는 매우 모호하고 구체적이지 않아 변경사항의 실제 내용을 파악하기 어렵습니다. 제목을 'SVG 아이콘 교체 및 레이아웃 개선' 또는 '커스텀 SVG 아이콘 적용 및 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 docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dev

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.

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

이 PR은 사용자 인터페이스의 전반적인 개선과 기능 강화를 목표로 합니다. 특히, 페이지 레이아웃의 일관성을 확보하고, 로그인 경험을 개선하며, 캐릭터 및 결산 목록의 성능을 향상시키는 데 중점을 두었습니다. 또한, 시각적 요소의 통일성을 위해 아이콘 및 이미지 렌더링 방식을 최적화하고, SEO를 위한 메타데이터를 추가하여 웹 접근성을 높였습니다.

Highlights

  • UI 레이아웃 일관성 강화: 운영팀 한마디 상세 페이지의 레이아웃을 기존 결산 상세 페이지와 완벽히 동기화하고, 사이드바 푸터 디자인을 간소화했습니다.
  • 로그인 경험 및 접근성 개선: 로그인 팝업의 이름 입력 필드에 맞춤법 검사 및 자동완성 방지 기능을 적용하고, 에러 메시지 표시 UI를 개선했습니다.
  • 캐릭터 및 결산 목록 성능 최적화: 메인 페이지의 캐릭터 목록과 캐릭터별 결산 목록에 페이지네이션 기반의 무한 스크롤 기능을 도입하여 데이터 로딩 효율을 높였습니다.
  • 운영팀 캐릭터 정보 표시 로직 개선: 운영팀 캐릭터 상세 페이지에서 '레벨/서버/직업' 대신 '13기 / 가천대학교 / 운영팀' 정보가 고정적으로 표시되도록 분기 처리했습니다.
  • 시각적 요소 및 아이콘 관리 통일: 기존 Lucide 라이브러리 아이콘을 자체 SVG 에셋으로 교체하고 파일명을 통일하여 일관된 디자인을 유지했습니다. 또한, 메인 캐릭터 카드 이미지의 CSS 비율을 복구하고 픽셀화 렌더링을 적용했습니다.
  • 캐릭터 카드 저장 페이지 재설계: 캐릭터 카드 저장 페이지의 UI를 전면 개편하여 시각적 완성도를 높이고, 이미지 저장 시 픽셀 비율을 조정하여 고해상도 저장을 지원합니다.
  • SEO 메타데이터 추가: 웹 페이지의 검색 엔진 최적화 및 소셜 미디어 공유를 위해 app.html 파일에 Open Graph 및 Twitter Card 메타데이터를 추가했습니다.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • .github/PULL_REQUEST_TEMPLATE.md
    • PR 템플릿의 작업 내용, 관련 이슈, 사용한 Prompt, 체크리스트, 특이사항 섹션이 최신 변경사항에 맞춰 업데이트되었습니다.
  • dpbr_front/app/src/app.html
    • 파비콘 경로가 변경되었습니다.
    • 웹사이트의 SEO 및 소셜 미디어 공유를 위한 메타데이터(description, og:type, og:title, og:description, og:image, og:url, twitter:card, twitter:title, twitter:description, twitter:image)가 추가되었습니다.
  • dpbr_front/app/src/lib/api.ts
    • CharactersPaginationResponseSettlementsPaginationResponse 인터페이스가 추가되어 페이지네이션 응답 구조를 정의했습니다.
    • getCharactersPaginatedgetSettlementsByCharacterIdPaginated 함수가 추가되어 캐릭터 및 결산 목록을 페이지네이션으로 가져올 수 있게 되었습니다.
    • mapCharacterResponsemapSettlementResponse 헬퍼 함수가 분리되었습니다.
  • dpbr_front/app/src/lib/components/BottomSheetLogin.svelte
    • 로그인 모달에 에러 메시지 상태(errorMessage)가 추가되었고, showToastMessage 함수가 토스트 대신 이 에러 메시지를 표시하도록 변경되었습니다.
    • 모달의 높이(h-[72vh])와 너비(shrink-0)가 조정되었습니다.
    • 닫기 버튼 및 체크박스 아이콘의 경로가 새로운 자체 SVG 에셋으로 업데이트되었습니다.
    • 로그인 버튼 영역에 에러 메시지를 표시하는 UI가 추가되었습니다.
  • dpbr_front/app/src/lib/components/CharacterCard.svelte
    • 코드 포맷팅 및 주석 변경 외에 기능적인 변경은 없습니다.
  • dpbr_front/app/src/lib/components/CommentItem.svelte
    • 댓글 작성자 이름의 폰트 두께가 font-light에서 font-bold로 변경되었습니다.
    • 댓글 내용의 폰트 두께가 font-light에서 font-medium으로 변경되었습니다.
  • dpbr_front/app/src/lib/components/Header.svelte
    • 메뉴, 채팅, 뒤로가기, 닫기 아이콘의 SVG 경로가 새로운 자체 SVG 에셋으로 업데이트되었습니다.
    • detail variant 헤더의 패딩이 px-2 py-3에서 px-3 py-2로 조정되었습니다.
  • dpbr_front/app/src/lib/components/InputBox.svelte
    • input 태그에 spellcheck="false"autocomplete="off" 속성이 추가되어 맞춤법 검사 및 자동완성 기능을 비활성화했습니다.
  • dpbr_front/app/src/lib/components/Sidebar.svelte
    • 로그아웃 시 /login 대신 / (메인 페이지)로 이동하도록 변경되었습니다.
    • 사이드바 푸터에 표시되는 기록 기간 텍스트가 sidebarPeriodText에서 SIDEBAR_RECORD_PERIOD 상수로 변경되었고, 폰트 크기와 색상이 조정되었습니다.
  • dpbr_front/app/src/lib/stores/auth.ts
    • login 함수에 saveNameFlag 인자가 추가되어 이름 저장 여부에 따라 saveName 함수를 호출하도록 로직이 추가되었습니다.
  • dpbr_front/app/src/routes/+page.svelte
    • getCharacters 대신 getCharactersPaginated를 사용하여 캐릭터 목록을 가져오도록 변경되었습니다.
    • 무한 스크롤(IntersectionObserver)을 구현하여 캐릭터 목록을 동적으로 로드합니다.
  • dpbr_front/app/src/routes/login/+page.svelte
    • 로그인 페이지에 에러 메시지 상태(errorMessage)가 추가되었고, showToastMessage 함수가 토스트 대신 이 에러 메시지를 표시하도록 변경되었습니다.
    • 로그인 버튼 영역에 에러 메시지를 표시하는 UI가 추가되었고, 로그인 버튼의 너비가 w-full로 설정되었습니다.
  • dpbr_front/app/src/routes/member/[id]/+page.svelte
    • onMount 훅이 제거되고 loadCharacterData 함수가 effect 블록에서 호출되도록 변경되었습니다.
    • ADMIN_TEAM_INFO 상수가 추가되어 운영팀 캐릭터 정보를 정의했습니다.
    • 캐릭터별 결산 목록에 페이지네이션(getSettlementsByCharacterIdPaginated) 및 무한 스크롤(IntersectionObserver)이 적용되었습니다.
    • 운영팀 캐릭터의 경우 레벨/서버/직업 대신 ADMIN_TEAM_INFO에 정의된 정보가 표시되도록 분기 처리되었습니다.
    • 캐릭터 이미지에 image-rendering:pixelated 스타일이 추가되었습니다.
  • dpbr_front/app/src/routes/member/[id]/save/+page.svelte
    • html-to-image 라이브러리의 pixelRatio가 2.5로 설정되어 이미지 저장 시 해상도를 높였습니다.
    • 캐릭터 카드 저장 페이지의 UI가 전반적으로 재설계되었습니다.
    • 상단 로고, 배경 이미지(park-bg.png), 캐릭터 이미지 렌더링 방식(크기, 위치, 그림자, 픽셀화), 캐릭터 정보 표시 방식 및 '단풍바람 13기' 로고 이미지가 추가되었습니다.
  • dpbr_front/app/src/routes/msg/[id]/+page.svelte
    • 결산 상세 페이지에서 이미지가 없을 경우의 대체 이미지 로직이 개선되었습니다. 운영팀이 아니고 이미지가 없을 경우 캐릭터의 아바타 이미지를 사용하도록 변경되었습니다.
  • dpbr_front/app/src/routes/talk/+page.svelte
    • 로그아웃 버튼 클릭 시 / (메인 페이지)로 이동하도록 변경되었고, 버튼 텍스트 스타일이 text-[12px] font-light underline로 변경되었습니다.
    • 전송 버튼 아이콘의 경로가 새로운 자체 SVG 에셋으로 업데이트되었습니다.
  • dpbr_front/app/src/routes/team-message/[id]/+page.svelte
    • 팀 메시지 상세 페이지의 이미지 렌더링 스타일이 object-cover에서 object-contain으로 변경되었고, max-h-80이 제거되었습니다.
    • 직책 및 상세 내용 라벨에 고정 너비(w-[72px]), 좌측 정렬, 줄바꿈 방지(whitespace-nowrap), 축소 방지(shrink-0) 스타일이 적용되었습니다.
    • 각 섹션 사이에 hr 태그가 추가되었고, 하단에 모노톤 로고 이미지가 추가되었습니다.
  • dpbr_front/app/static/images/icons/name=Back, Color=Black.svg
    • 파일이 dpbr_front/app/static/images/icons/back-icon-black.svg로 이름이 변경되었습니다.
  • dpbr_front/app/static/images/icons/name=Back, Color=White.svg
    • 파일이 dpbr_front/app/static/images/icons/back-icon-white.svg로 이름이 변경되었습니다.
  • dpbr_front/app/static/images/icons/name=Chat, Color=White.svg
    • 파일이 dpbr_front/app/static/images/icons/chat-icon-white.svg로 이름이 변경되었습니다.
  • dpbr_front/app/static/images/icons/name=Close, Color=Black.svg
    • 파일이 dpbr_front/app/static/images/icons/close-icon-black.svg로 이름이 변경되었습니다.
  • dpbr_front/app/static/images/icons/name=Close, Color=White.svg
    • 파일이 dpbr_front/app/static/images/icons/close-icon-white.svg로 이름이 변경되었습니다.
  • dpbr_front/app/static/images/icons/name=Menu, Color=White.svg
    • 파일이 dpbr_front/app/static/images/icons/menu-icon-white.svg로 이름이 변경되었습니다.
  • dpbr_front/app/static/images/icons/name=Save, Color=White.svg
    • 파일이 삭제되었습니다.
  • dpbr_front/app/static/images/icons/name=Send, Color=White.svg
    • 파일이 삭제되었습니다.
  • dpbr_front/app/static/images/icons/name=Text Logo, color=Mono.svg
    • 파일이 삭제되었습니다.
  • dpbr_front/app/static/images/icons/name=Text Logo, color=White.svg
    • 파일이 삭제되었습니다.
  • dpbr_front/app/static/images/icons/name=check-disable, Color=White.svg
    • 파일이 dpbr_front/app/static/images/icons/check-disable-icon.svg로 이름이 변경되었습니다.
  • dpbr_front/app/static/images/icons/name=check-enable, Color=White.svg
    • 파일이 dpbr_front/app/static/images/icons/check-enable-icon.svg로 이름이 변경되었습니다.
  • dpbr_front/app/static/images/icons/send-icon-white.svg
    • 새로운 전송 아이콘 SVG 파일이 추가되었습니다.
  • dpbr_front/app/static/images/icons/symbol-logo-color.svg
    • 새로운 심볼 로고 SVG 파일이 추가되었습니다.
  • dpbr_front/app/static/images/단풍바람 13기.svg
    • 새로운 '단풍바람 13기' 로고 SVG 파일이 추가되었습니다.
  • dpbr_front/app/static/images/메생결산 아이콘 svg 파일/name=Back, Color=Black.svg
    • 파일이 삭제되었습니다.
  • dpbr_front/app/static/images/메생결산 아이콘 svg 파일/name=Back, Color=White.svg
    • 파일이 삭제되었습니다.
  • dpbr_front/app/static/images/메생결산 아이콘 svg 파일/name=Chat, Color=White.svg
    • 파일이 삭제되었습니다.
  • dpbr_front/app/static/images/메생결산 아이콘 svg 파일/name=Close, Color=Black.svg
    • 파일이 삭제되었습니다.
  • dpbr_front/app/static/images/메생결산 아이콘 svg 파일/name=Close, Color=White.svg
    • 파일이 삭제되었습니다.
  • dpbr_front/app/static/images/메생결산 아이콘 svg 파일/name=Menu, Color=White.svg
    • 파일이 삭제되었습니다.
  • dpbr_front/app/static/images/메생결산 아이콘 svg 파일/name=Save, Color=White.svg
    • 파일이 삭제되었습니다.
  • dpbr_front/app/static/images/메생결산 아이콘 svg 파일/name=Send, Color=White.svg
    • 파일이 삭제되었습니다.
  • dpbr_front/app/static/images/메생결산 아이콘 svg 파일/name=Text Logo, color=Mono.svg
    • 파일이 삭제되었습니다.
  • dpbr_front/app/static/images/메생결산 아이콘 svg 파일/name=Text Logo, color=White.svg
    • 파일이 삭제되었습니다.
  • dpbr_front/app/static/images/메생결산 아이콘 svg 파일/name=check-disable, Color=White.svg
    • 파일이 삭제되었습니다.
  • dpbr_front/app/static/images/메생결산 아이콘 svg 파일/name=check-enable, Color=White.svg
    • 파일이 삭제되었습니다.
Activity
  • 이 PR은 ark1st님이 직접 작업하고 커밋한 내용으로 구성되어 있으며, 현재까지 다른 리뷰어의 코멘트나 활동은 없습니다.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

coderabbitai[bot]
coderabbitai Bot previously requested changes Mar 2, 2026
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: 3

🧹 Nitpick comments (10)
dpbr_front/app/src/lib/components/InputBox.svelte (1)

65-66: 재사용 컴포넌트에서 자동완성/맞춤법 설정은 하드코딩보다 Props가 안전합니다.

InputBox 전체 사용처에 동일 정책이 강제되므로, 페이지별로 제어 가능하게 열어두는 편이 좋습니다.

♻️ 제안 패치
 interface Props {
 	type?: "text" | "number" | "tel";
 	placeholder: string;
 	value: string;
+	autocomplete?: string;
+	spellcheck?: boolean;
 	maxLength?: number;
 	inputState?: "default" | "focused";
 	showClearButton?: boolean;
@@
 	type = "text",
 	placeholder,
 	value: valueProp,
+	autocomplete = "off",
+	spellcheck = false,
 	maxLength,
@@
-		spellcheck="false"
-		autocomplete="off"
+		spellcheck={spellcheck}
+		autocomplete={autocomplete}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dpbr_front/app/src/lib/components/InputBox.svelte` around lines 65 - 66,
InputBox currently hardcodes spellcheck="false" and autocomplete="off", make
these configurable via props so consumers can override per-page; add two props
(e.g., export let spellcheck = false and export let autocomplete = "off") and
use them on the internal <input> (e.g., spellcheck={spellcheck}
autocomplete={autocomplete}) so default behavior stays the same but callers can
pass different values when needed; ensure prop types/defaults are defined at top
of the InputBox component and update any docs/tests that assume the hardcoded
values.
dpbr_front/app/src/routes/msg/[id]/+page.svelte (1)

67-70: 기본 폴백 정적 이미지 포맷은 가능하면 WebP로 통일해 주세요.

현재 폴백이 logo.png, default-avatar.png로 고정되어 있어 이미지 정책과 맞추기 어렵습니다. 정적 에셋만이라도 WebP 전환을 권장합니다.

Based on learnings: Images should use WebP format, have lazy loading applied, and be resized appropriately

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dpbr_front/app/src/routes/msg/`[id]/+page.svelte around lines 67 - 70, The
img src fallback chain currently uses PNGs; update the fallback static assets in
the expression that builds src (the settlement.imageUrl || (isAdminTeam ?
"/logo.png" : character?.avatarUrl || "/default-avatar.png")) to WebP versions
(e.g. "/logo.webp" and "/default-avatar.webp"), add lazy loading to the image
element (loading="lazy"), and ensure the rendered image is sized/served at an
appropriate resolution (use srcset or a resized WebP asset pipeline) so
settlement.imageUrl, isAdminTeam, and character?.avatarUrl remain unchanged but
the static fallbacks and loading/size behavior conform to the WebP + lazy +
resized policy.
dpbr_front/app/src/lib/components/BottomSheetLogin.svelte (2)

7-7: 사용되지 않는 toast import를 제거하세요.

에러 메시지가 이제 인라인 errorMessage 상태로 처리되므로 toast import가 더 이상 필요하지 않습니다.

♻️ 제안된 수정
-    import { toast } from "$lib/stores/toast";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dpbr_front/app/src/lib/components/BottomSheetLogin.svelte` at line 7, Remove
the unused import 'toast' from BottomSheetLogin.svelte: locate the import line
"import { toast } from \"$lib/stores/toast\";" and delete it so the component no
longer imports toast (error handling now uses the inline errorMessage state);
ensure no other references to the symbol 'toast' remain in functions like any
submit/login handlers or markup.

111-116: 연속 호출 시 타이머 경합 조건 가능성.

showToastMessage가 빠르게 연속 호출되면 이전 setTimeout이 새 메시지를 의도치 않게 지울 수 있습니다. 타이머 ID를 추적하고 새 호출 시 이전 타이머를 취소하는 것이 좋습니다.

♻️ 제안된 수정
+    let errorTimeoutId: ReturnType<typeof setTimeout> | null = null;
+
     function showToastMessage(message?: string) {
+        if (errorTimeoutId) {
+            clearTimeout(errorTimeoutId);
+        }
         errorMessage = message || "이름 또는 학번을 확인해 주세요.";
-        setTimeout(() => {
+        errorTimeoutId = setTimeout(() => {
             errorMessage = "";
+            errorTimeoutId = null;
         }, 3000);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dpbr_front/app/src/lib/components/BottomSheetLogin.svelte` around lines 111 -
116, showToastMessage can race when called rapidly because each call starts a
new setTimeout that may clear a later message; add a module-level timer variable
(e.g., toastTimer) and in showToastMessage call clearTimeout(toastTimer) before
creating a new timeout, then assign the returned timer id to toastTimer so the
previous timeout is cancelled and only the most recent call clears errorMessage;
reference showToastMessage and errorMessage when making this change.
dpbr_front/app/src/lib/api.ts (2)

248-266: getCharacterById에서도 mapCharacterResponse를 사용하는 것을 고려해 보세요.

일관성을 위해 getCharacterById도 새로 추출한 mapCharacterResponse 헬퍼를 사용하면 좋겠습니다. 현재 인라인 매핑과 헬퍼 함수의 로직이 동일하므로 중복을 제거할 수 있습니다.

♻️ 제안된 수정
 export async function getCharacterById(id: string): Promise<Character | null> {
 	try {
 		const data = await apiCall<CharacterResponse>(`/characters/${id}`);
-
-		return {
-			id: data.id.toString(),
-			name: data.name,
-			nickname: data.detail_txt || data.name,
-			avatarUrl: normalizeAssetUrl(data.avatar_url),
-			level: data.level,
-			job: data.job,
-			club: '단풍바람',
-			server: data.server
-		};
+		return mapCharacterResponse(data);
 	} catch (error) {
 		console.error('Failed to fetch character:', error);
 		return null;
 	}
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dpbr_front/app/src/lib/api.ts` around lines 248 - 266, Replace the inline
mapping inside getCharacterById with the existing mapCharacterResponse helper:
call apiCall<CharacterResponse>(`/characters/${id}`), then if data is truthy
return mapCharacterResponse(data) (ensuring mapCharacterResponse performs
normalizeAssetUrl for avatarUrl and sets club '단풍바람'), otherwise return null;
keep the try/catch and error logging as-is so failure still returns null.

299-315: getSettlementById에서도 mapSettlementResponse를 사용하는 것을 고려해 보세요.

동일하게 getSettlementByIdmapSettlementResponse 헬퍼를 사용하면 코드 일관성이 향상됩니다.

♻️ 제안된 수정
 export async function getSettlementById(id: string): Promise<SettlementItem | null> {
 	try {
 		const data = await apiCall<SettlementResponse>(`/settlements/${id}`);
-
-		return {
-			id: data.id.toString(),
-			characterId: data.character_id.toString(),
-			title: data.title,
-			description: data.description || '',
-			imageUrl: data.img_url ? `${getApiBaseUrl()}${data.img_url}` : '/default-avatar.png',
-			acquiredAt: data.acquired_at
-		};
+		return mapSettlementResponse(data);
 	} catch (error) {
 		console.error('Failed to fetch settlement:', error);
 		return null;
 	}
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dpbr_front/app/src/lib/api.ts` around lines 299 - 315, Replace the inline
mapping in getSettlementById with the existing mapSettlementResponse helper:
after awaiting apiCall<SettlementResponse>(`/settlements/${id}`) pass the
returned data into mapSettlementResponse and return its result (ensuring types
match), remove the duplicated field mapping (id, character_id, title,
description, img_url, acquired_at) and keep the same try/catch behavior; also
add an import for mapSettlementResponse at the top if it isn’t already imported
so getSettlementById uses mapSettlementResponse(data) instead of duplicating
mapping logic.
dpbr_front/app/src/routes/member/[id]/save/+page.svelte (2)

91-100: 외부 placeholder 서비스 대신 로컬 fallback 이미지 사용을 권장합니다.

via.placeholder.com은 외부 서비스로, 네트워크 문제나 서비스 중단 시 fallback이 실패할 수 있습니다. 프로젝트 내 로컬 fallback 이미지를 사용하는 것이 더 안정적입니다.

♻️ 제안된 수정
 						onerror={(e) =>
 							((e.currentTarget as HTMLImageElement).src =
-								"https://via.placeholder.com/300x450/EEE/999?text=Park+Background")}
+								"/images/fallback-park-bg.png")}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dpbr_front/app/src/routes/member/`[id]/save/+page.svelte around lines 91 -
100, The onerror handler for the <img> that sets a fallback to
"https://via.placeholder.com/..." should be changed to use a local project
asset; update the onerror arrow function in the <img> tag (the handler
referencing e.currentTarget as HTMLImageElement) to assign a local fallback path
(e.g. /images/local-fallback.png) instead of the external URL, and ensure the
fallback file exists in the app's static/images (or equivalent) so the fallback
will work offline and during service outages.

140-146: 파일 경로에 한글 문자 사용 시 주의가 필요합니다.

/images/단풍바람 13기.svg 경로에 한글과 공백이 포함되어 있습니다. 일부 환경에서 URL 인코딩 문제가 발생할 수 있으므로, 파일명을 영문과 하이픈으로 변경하는 것을 고려해 보세요 (예: /images/danpung-13th.svg).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dpbr_front/app/src/routes/member/`[id]/save/+page.svelte around lines 140 -
146, The img src uses a filename with Korean characters and a space
(src="/images/단풍바람 13기.svg") which can cause URL/encoding issues; rename the
file to a URL-safe name (e.g., danpung-13th.svg) in your static assets and
update the img src in +page.svelte to the new path (modify the <img src=...>
reference), and also search & update any other references (imports, CSS, tests
or build configs) that point to the old filename so all usages match the new
English/hyphenated name.
dpbr_front/app/src/routes/team-message/[id]/+page.svelte (1)

98-107: 하단 로고 높이 계산 단순화를 고려해 보세요.

h-[calc(100dvh*64/874)]는 매직 넘버로 구성되어 있어 유지보수가 어렵습니다. 의미 있는 값(예: h-16 또는 CSS 변수)으로 대체하거나, 해당 계산의 의미를 주석으로 설명하는 것이 좋습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dpbr_front/app/src/routes/team-message/`[id]/+page.svelte around lines 98 -
107, The footer container's height uses a magic calculation in the class
h-[calc(100dvh*64/874)] (the div wrapping the logo) which is hard to maintain;
replace that computed height with a meaningful Tailwind utility or CSS variable
(e.g., h-16 or var(--footer-logo-height)) and update any related styles to
preserve the visual size, or add a concise comment explaining the original
calculation if you must keep it; locate the div with class "flex justify-center
items-center h-[calc(100dvh*64/874)] bg-white shrink-0 mt-2" and change the
height part and/or introduce the CSS variable in a component-level style or
global CSS so future maintainers understand the intent.
dpbr_front/app/src/routes/member/[id]/+page.svelte (1)

113-148: 페이지네이션 fallback 로직이 잘 구현되었습니다.

404 에러 시 비페이지네이션 API로 fallback하는 로직은 백엔드 호환성을 위한 좋은 접근입니다. 다만, 에러 메시지 문자열 매칭(e.message.includes("API Error: 404"))은 취약할 수 있습니다.

♻️ 더 안정적인 에러 감지를 위한 제안

API 레이어에서 커스텀 에러 클래스를 사용하거나 에러 객체에 status code를 포함시키면 더 안정적입니다:

// api.ts에서
class ApiError extends Error {
  constructor(public status: number, message: string) {
    super(message);
  }
}

// 사용 시
if (e instanceof ApiError && e.status === 404) {
  // fallback 로직
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dpbr_front/app/src/routes/member/`[id]/+page.svelte around lines 113 - 148,
The current 404 detection in loadMoreSettlements relies on fragile string
matching (e.message.includes("API Error: 404")); update the logic to detect 404
reliably by using a structured error (e.g., ApiError) or checking a status
property on the thrown error from getSettlementsByCharacterIdPaginated: modify
the API layer to throw ApiError (class ApiError extends Error { status: number
}) or ensure the API throws an object with a numeric status, then in
loadMoreSettlements replace the string check with a robust guard such as (e
instanceof ApiError && e.status === 404) or (typeof (e as any).status ===
'number' && (e as any).status === 404) so the fallback to
getSettlementsByCharacterId runs only on real 404 responses.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@dpbr_front/app/src/app.html`:
- Line 10: Remove the zoom-locking attributes from the <meta name="viewport">
tag in app.html: replace the current content attribute ("width=device-width,
initial-scale=1, maximum-scale=1, user-scalable=no") with a permissive viewport
(e.g., "width=device-width, initial-scale=1") so users can pinch-zoom; locate
the <meta name="viewport"> element in app.html and edit its content value
accordingly to restore accessibility for low-vision users.

In `@dpbr_front/app/src/lib/components/CharacterCard.svelte`:
- Around line 40-41: When the character.server (and thus logoPath via
getWorldLogoPath) changes, reset the isWorldLogoLoadFailed flag so a prior true
value doesn't hide the new logo; add logic that watches the derived logoPath (or
character.server) and sets isWorldLogoLoadFailed(false) on change (referencing
logoPath, isWorldLogoLoadFailed, and getWorldLogoPath/character.server) so the
failure state is cleared whenever the server/logo changes.

In `@dpbr_front/app/src/routes/login/`+page.svelte:
- Around line 84-88: The showToastMessage function can leave prior timers
running so rapid consecutive calls clear a newer message early; fix by tracking
the timeout ID (e.g., toastTimeout) at module/component scope, call
clearTimeout(toastTimeout) before creating a new setTimeout, and store the
returned ID in toastTimeout so errorMessage is reliably cleared only after the
latest timer fires; update references in showToastMessage and ensure
toastTimeout is initialized (null/undefined) in the component.

---

Nitpick comments:
In `@dpbr_front/app/src/lib/api.ts`:
- Around line 248-266: Replace the inline mapping inside getCharacterById with
the existing mapCharacterResponse helper: call
apiCall<CharacterResponse>(`/characters/${id}`), then if data is truthy return
mapCharacterResponse(data) (ensuring mapCharacterResponse performs
normalizeAssetUrl for avatarUrl and sets club '단풍바람'), otherwise return null;
keep the try/catch and error logging as-is so failure still returns null.
- Around line 299-315: Replace the inline mapping in getSettlementById with the
existing mapSettlementResponse helper: after awaiting
apiCall<SettlementResponse>(`/settlements/${id}`) pass the returned data into
mapSettlementResponse and return its result (ensuring types match), remove the
duplicated field mapping (id, character_id, title, description, img_url,
acquired_at) and keep the same try/catch behavior; also add an import for
mapSettlementResponse at the top if it isn’t already imported so
getSettlementById uses mapSettlementResponse(data) instead of duplicating
mapping logic.

In `@dpbr_front/app/src/lib/components/BottomSheetLogin.svelte`:
- Line 7: Remove the unused import 'toast' from BottomSheetLogin.svelte: locate
the import line "import { toast } from \"$lib/stores/toast\";" and delete it so
the component no longer imports toast (error handling now uses the inline
errorMessage state); ensure no other references to the symbol 'toast' remain in
functions like any submit/login handlers or markup.
- Around line 111-116: showToastMessage can race when called rapidly because
each call starts a new setTimeout that may clear a later message; add a
module-level timer variable (e.g., toastTimer) and in showToastMessage call
clearTimeout(toastTimer) before creating a new timeout, then assign the returned
timer id to toastTimer so the previous timeout is cancelled and only the most
recent call clears errorMessage; reference showToastMessage and errorMessage
when making this change.

In `@dpbr_front/app/src/lib/components/InputBox.svelte`:
- Around line 65-66: InputBox currently hardcodes spellcheck="false" and
autocomplete="off", make these configurable via props so consumers can override
per-page; add two props (e.g., export let spellcheck = false and export let
autocomplete = "off") and use them on the internal <input> (e.g.,
spellcheck={spellcheck} autocomplete={autocomplete}) so default behavior stays
the same but callers can pass different values when needed; ensure prop
types/defaults are defined at top of the InputBox component and update any
docs/tests that assume the hardcoded values.

In `@dpbr_front/app/src/routes/member/`[id]/+page.svelte:
- Around line 113-148: The current 404 detection in loadMoreSettlements relies
on fragile string matching (e.message.includes("API Error: 404")); update the
logic to detect 404 reliably by using a structured error (e.g., ApiError) or
checking a status property on the thrown error from
getSettlementsByCharacterIdPaginated: modify the API layer to throw ApiError
(class ApiError extends Error { status: number }) or ensure the API throws an
object with a numeric status, then in loadMoreSettlements replace the string
check with a robust guard such as (e instanceof ApiError && e.status === 404) or
(typeof (e as any).status === 'number' && (e as any).status === 404) so the
fallback to getSettlementsByCharacterId runs only on real 404 responses.

In `@dpbr_front/app/src/routes/member/`[id]/save/+page.svelte:
- Around line 91-100: The onerror handler for the <img> that sets a fallback to
"https://via.placeholder.com/..." should be changed to use a local project
asset; update the onerror arrow function in the <img> tag (the handler
referencing e.currentTarget as HTMLImageElement) to assign a local fallback path
(e.g. /images/local-fallback.png) instead of the external URL, and ensure the
fallback file exists in the app's static/images (or equivalent) so the fallback
will work offline and during service outages.
- Around line 140-146: The img src uses a filename with Korean characters and a
space (src="/images/단풍바람 13기.svg") which can cause URL/encoding issues; rename
the file to a URL-safe name (e.g., danpung-13th.svg) in your static assets and
update the img src in +page.svelte to the new path (modify the <img src=...>
reference), and also search & update any other references (imports, CSS, tests
or build configs) that point to the old filename so all usages match the new
English/hyphenated name.

In `@dpbr_front/app/src/routes/msg/`[id]/+page.svelte:
- Around line 67-70: The img src fallback chain currently uses PNGs; update the
fallback static assets in the expression that builds src (the
settlement.imageUrl || (isAdminTeam ? "/logo.png" : character?.avatarUrl ||
"/default-avatar.png")) to WebP versions (e.g. "/logo.webp" and
"/default-avatar.webp"), add lazy loading to the image element (loading="lazy"),
and ensure the rendered image is sized/served at an appropriate resolution (use
srcset or a resized WebP asset pipeline) so settlement.imageUrl, isAdminTeam,
and character?.avatarUrl remain unchanged but the static fallbacks and
loading/size behavior conform to the WebP + lazy + resized policy.

In `@dpbr_front/app/src/routes/team-message/`[id]/+page.svelte:
- Around line 98-107: The footer container's height uses a magic calculation in
the class h-[calc(100dvh*64/874)] (the div wrapping the logo) which is hard to
maintain; replace that computed height with a meaningful Tailwind utility or CSS
variable (e.g., h-16 or var(--footer-logo-height)) and update any related styles
to preserve the visual size, or add a concise comment explaining the original
calculation if you must keep it; locate the div with class "flex justify-center
items-center h-[calc(100dvh*64/874)] bg-white shrink-0 mt-2" and change the
height part and/or introduce the CSS variable in a component-level style or
global CSS so future maintainers understand the intent.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 955b806 and d6a5869.

⛔ Files ignored due to path filters (34)
  • dpbr_front/app/static/default-avatar.png is excluded by !**/*.png
  • dpbr_front/app/static/fonts/NEXON Lv1 Gothic Low OTF Bold.otf is excluded by !**/*.otf
  • dpbr_front/app/static/fonts/NEXON Lv1 Gothic Low OTF Light.otf is excluded by !**/*.otf
  • dpbr_front/app/static/fonts/NEXON Lv1 Gothic Low OTF.otf is excluded by !**/*.otf
  • dpbr_front/app/static/images/icons/MSGS_Favicon.ico is excluded by !**/*.ico
  • dpbr_front/app/static/images/icons/back-icon-black.svg is excluded by !**/*.svg
  • dpbr_front/app/static/images/icons/back-icon-white.svg is excluded by !**/*.svg
  • dpbr_front/app/static/images/icons/chat-icon-white.svg is excluded by !**/*.svg
  • dpbr_front/app/static/images/icons/check-disable-icon.svg is excluded by !**/*.svg
  • dpbr_front/app/static/images/icons/check-enable-icon.svg is excluded by !**/*.svg
  • dpbr_front/app/static/images/icons/close-icon-black.svg is excluded by !**/*.svg
  • dpbr_front/app/static/images/icons/close-icon-white.svg is excluded by !**/*.svg
  • dpbr_front/app/static/images/icons/menu-icon-white.svg is excluded by !**/*.svg
  • dpbr_front/app/static/images/icons/name=Save, Color=White.svg is excluded by !**/*.svg
  • dpbr_front/app/static/images/icons/name=Send, Color=White.svg is excluded by !**/*.svg
  • dpbr_front/app/static/images/icons/name=Text Logo, color=Mono.svg is excluded by !**/*.svg
  • dpbr_front/app/static/images/icons/name=Text Logo, color=White.svg is excluded by !**/*.svg
  • dpbr_front/app/static/images/icons/send-icon-white.svg is excluded by !**/*.svg
  • dpbr_front/app/static/images/icons/symbol-logo-color.svg is excluded by !**/*.svg
  • dpbr_front/app/static/images/park-bg.png is excluded by !**/*.png
  • dpbr_front/app/static/images/thumbnail.png is excluded by !**/*.png
  • dpbr_front/app/static/images/단풍바람 13기.svg is excluded by !**/*.svg
  • dpbr_front/app/static/images/메생결산 아이콘 svg 파일/name=Back, Color=Black.svg is excluded by !**/*.svg
  • dpbr_front/app/static/images/메생결산 아이콘 svg 파일/name=Back, Color=White.svg is excluded by !**/*.svg
  • dpbr_front/app/static/images/메생결산 아이콘 svg 파일/name=Chat, Color=White.svg is excluded by !**/*.svg
  • dpbr_front/app/static/images/메생결산 아이콘 svg 파일/name=Close, Color=Black.svg is excluded by !**/*.svg
  • dpbr_front/app/static/images/메생결산 아이콘 svg 파일/name=Close, Color=White.svg is excluded by !**/*.svg
  • dpbr_front/app/static/images/메생결산 아이콘 svg 파일/name=Menu, Color=White.svg is excluded by !**/*.svg
  • dpbr_front/app/static/images/메생결산 아이콘 svg 파일/name=Save, Color=White.svg is excluded by !**/*.svg
  • dpbr_front/app/static/images/메생결산 아이콘 svg 파일/name=Send, Color=White.svg is excluded by !**/*.svg
  • dpbr_front/app/static/images/메생결산 아이콘 svg 파일/name=Text Logo, color=Mono.svg is excluded by !**/*.svg
  • dpbr_front/app/static/images/메생결산 아이콘 svg 파일/name=Text Logo, color=White.svg is excluded by !**/*.svg
  • dpbr_front/app/static/images/메생결산 아이콘 svg 파일/name=check-disable, Color=White.svg is excluded by !**/*.svg
  • dpbr_front/app/static/images/메생결산 아이콘 svg 파일/name=check-enable, Color=White.svg is excluded by !**/*.svg
📒 Files selected for processing (18)
  • .github/PULL_REQUEST_TEMPLATE.md
  • dpbr_front/app/check_output.txt
  • dpbr_front/app/src/app.html
  • dpbr_front/app/src/lib/api.ts
  • dpbr_front/app/src/lib/components/BottomSheetLogin.svelte
  • dpbr_front/app/src/lib/components/CharacterCard.svelte
  • dpbr_front/app/src/lib/components/CommentItem.svelte
  • dpbr_front/app/src/lib/components/Header.svelte
  • dpbr_front/app/src/lib/components/InputBox.svelte
  • dpbr_front/app/src/lib/components/Sidebar.svelte
  • dpbr_front/app/src/lib/stores/auth.ts
  • dpbr_front/app/src/routes/+page.svelte
  • dpbr_front/app/src/routes/login/+page.svelte
  • dpbr_front/app/src/routes/member/[id]/+page.svelte
  • dpbr_front/app/src/routes/member/[id]/save/+page.svelte
  • dpbr_front/app/src/routes/msg/[id]/+page.svelte
  • dpbr_front/app/src/routes/talk/+page.svelte
  • dpbr_front/app/src/routes/team-message/[id]/+page.svelte

<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin />
<link rel="stylesheet" as="style" crossorigin
href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
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 | 🟠 Major

모바일 확대를 막는 viewport 설정은 접근성 이슈입니다.

줌 비활성화는 저시력 사용자 작업을 막을 수 있어 제거가 필요합니다.

♿ 제안 패치
-	<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
+	<meta name="viewport" content="width=device-width, initial-scale=1" />
📝 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
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dpbr_front/app/src/app.html` at line 10, Remove the zoom-locking attributes
from the <meta name="viewport"> tag in app.html: replace the current content
attribute ("width=device-width, initial-scale=1, maximum-scale=1,
user-scalable=no") with a permissive viewport (e.g., "width=device-width,
initial-scale=1") so users can pinch-zoom; locate the <meta name="viewport">
element in app.html and edit its content value accordingly to restore
accessibility for low-vision users.

Comment on lines +40 to +41
let logoPath = $derived(getWorldLogoPath(character.server));
let isWorldLogoLoadFailed = $state(false);
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

월드 로고 실패 상태를 서버 변경 시 초기화해 주세요.

isWorldLogoLoadFailedtrue가 된 뒤 캐릭터가 바뀌면, 새 서버 로고도 계속 숨겨질 수 있습니다.

🔧 제안 패치
 let logoPath = $derived(getWorldLogoPath(character.server));
 let isWorldLogoLoadFailed = $state(false);
+
+$effect(() => {
+    character.server;
+    isWorldLogoLoadFailed = false;
+});

Also applies to: 50-57

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dpbr_front/app/src/lib/components/CharacterCard.svelte` around lines 40 - 41,
When the character.server (and thus logoPath via getWorldLogoPath) changes,
reset the isWorldLogoLoadFailed flag so a prior true value doesn't hide the new
logo; add logic that watches the derived logoPath (or character.server) and sets
isWorldLogoLoadFailed(false) on change (referencing logoPath,
isWorldLogoLoadFailed, and getWorldLogoPath/character.server) so the failure
state is cleared whenever the server/logo changes.

Comment on lines 84 to +88
function showToastMessage(message?: string) {
toast.show(message || "이름 또는 학번을 확인해 주세요.");
errorMessage = message || "이름 또는 학번을 확인해 주세요.";
setTimeout(() => {
errorMessage = "";
}, 3000);
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

에러 메시지 자동 제거 타이머가 중첩되면 최신 메시지가 조기 사라질 수 있습니다.

이전 타이머를 정리하지 않아 연속 실패 시 표시 시간이 불안정해집니다.

🛠️ 제안 패치
-import { onMount } from "svelte";
+import { onMount, onDestroy } from "svelte";
@@
 let errorMessage = $state("");
+let errorTimer: ReturnType<typeof setTimeout> | null = null;
@@
 function showToastMessage(message?: string) {
 	errorMessage = message || "이름 또는 학번을 확인해 주세요.";
-	setTimeout(() => {
+	if (errorTimer) clearTimeout(errorTimer);
+	errorTimer = setTimeout(() => {
 		errorMessage = "";
 	}, 3000);
 }
+
+onDestroy(() => {
+	if (errorTimer) clearTimeout(errorTimer);
+});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dpbr_front/app/src/routes/login/`+page.svelte around lines 84 - 88, The
showToastMessage function can leave prior timers running so rapid consecutive
calls clear a newer message early; fix by tracking the timeout ID (e.g.,
toastTimeout) at module/component scope, call clearTimeout(toastTimeout) before
creating a new setTimeout, and store the returned ID in toastTimeout so
errorMessage is reliably cleared only after the latest timer fires; update
references in showToastMessage and ensure toastTimeout is initialized
(null/undefined) in the component.

@ark1st ark1st merged commit 4227f5b into main Mar 2, 2026
7 checks passed
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

이번 PR은 전반적인 UI 개선과 코드 리팩토링에 중점을 둔 많은 변경 사항을 포함하고 있습니다. 특히 페이지네이션 구현, 아이콘 시스템 정리, 컴포넌트 재사용성 향상 등 코드 품질을 높이는 좋은 변화들이 많습니다. 몇 가지 추가 개선 제안 사항을 코드에 댓글로 남겼습니다. app.html의 정적 메타 태그 처리, vh 단위 사용, 그리고 이미지 카드 생성 페이지의 외부 의존성 및 스타일 관리와 관련된 내용입니다. 전반적으로 훌륭한 작업입니다.

Comment on lines +13 to +24
<!-- SEO & Metadata -->
<meta name="description" content="단풍바람 13기 메생결산 - 당신의 메이플 여정을 기록하세요." />
<meta property="og:type" content="website" />
<meta property="og:title" content="단풍바람" />
<meta property="og:description" content="단풍바람 13기 메생결산 - 당신의 메이플 여정을 기록하세요." />
<meta property="og:image" content="%sveltekit.assets%/images/thumbnail.png" />
<meta property="og:url" content="https://gc-maplewind.github.io/MSGS_13_F/" />

<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="단풍바람" />
<meta name="twitter:description" content="단풍바람 13기 메생결산 - 당신의 메이플 여정을 기록하세요." />
<meta name="twitter:image" content="%sveltekit.assets%/images/thumbnail.png" />
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

SEO 메타 태그(description, og:*, twitter:*)를 app.html에 정적으로 추가하면 모든 페이지에서 동일한 값을 사용하게 되어 SEO에 좋지 않습니다. 각 페이지의 콘텐츠에 맞는 동적인 메타 태그를 제공하기 위해 이 로직을 최상위 +layout.svelte 파일로 옮기고 <svelte:head>를 사용하여 페이지별로 적절한 값을 설정하는 것을 권장합니다. 예를 들어, og:url은 현재 페이지의 URL을 동적으로 반영해야 합니다.

>
<div
class="w-full h-[72%] bg-gradient-to-b from-[#FCDDA5] to-[#F1A470] rounded-t-3xl pt-4 pb-8 px-6 flex flex-col items-center shadow-lg transition-transform duration-300 {isVisible
class="w-full shrink-0 h-[72vh] bg-gradient-to-b from-[#FCDDA5] to-[#F1A470] rounded-t-3xl pt-4 pb-8 px-6 flex flex-col items-center shadow-lg transition-transform duration-300 {isVisible
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

모바일 브라우저에서 vh 단위는 주소 표시줄이나 가상 키보드의 등장에 따라 뷰포트 높이가 변경될 때 예기치 않은 레이아웃 변경을 유발할 수 있습니다. 이 변경이 특정 UI 문제를 해결하기 위한 것이 아니라면, vh 단위 사용의 잠재적인 부작용을 고려하여 재검토하는 것이 좋습니다.

Comment on lines +97 to +100
onerror={(e) =>
((e.currentTarget as HTMLImageElement).src =
"https://via.placeholder.com/300x450/EEE/999?text=Park+Background")}
/>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

배경 이미지 로딩 실패 시 외부 서비스인 via.placeholder.com을 사용하고 있습니다. 이는 외부 서비스에 대한 의존성을 만들며, 해당 서비스가 다운될 경우 이미지가 깨져 보일 수 있습니다. 안정성을 위해 로컬에 있는 fallback 이미지를 사용하도록 변경하는 것을 권장합니다.

alt={character.name}
onerror={handleImageError}
class="w-auto h-auto object-contain drop-shadow-lg"
style="filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.4)); transform: scale(2.2); image-rendering: pixelated; image-rendering: crisp-edges;"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

여러 인라인 스타일이 사용되어 코드가 복잡해 보입니다. 가독성과 유지보수성을 높이기 위해 이 스타일들을 별도의 CSS 클래스로 추출하는 것을 고려해 보세요. 예를 들어, character-image와 같은 클래스를 만들어 스타일을 관리할 수 있습니다.

.character-image {
  filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.4));
  transform: scale(2.2);
  image-rendering: pixelated;
  image-rendering: crisp-edges;
}

@coderabbitai coderabbitai Bot mentioned this pull request Mar 2, 2026
Merged
8 tasks
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