Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
7ae36c4
✨ feat: 알람 컴포넌트에서 라우팅 경로 수정
Whatdoyumin Aug 18, 2025
8e4d21c
✨ feat: 쿼리 라우팅 추가
Whatdoyumin Aug 18, 2025
e3697ef
✨ feat: 버튼 수정
Whatdoyumin Aug 18, 2025
b577c2c
💄 style: 높이 뷰포트 기준으로 수정
Whatdoyumin Aug 18, 2025
e058281
✨ feat: 본인 인증 alert 수정
Whatdoyumin Aug 18, 2025
6e8f109
✨ feat: 이미지 슬라이드 추가
Whatdoyumin Aug 18, 2025
1222702
✨ feat: 상세 정보 월세일 경우 표시 수정
Whatdoyumin Aug 18, 2025
763b107
✨ feat: homeId 쿼리 API 요청으로 추가
Whatdoyumin Aug 18, 2025
f467320
✨ feat: 매물 등록 alert 추가
Whatdoyumin Aug 18, 2025
3dd6beb
✨ feat: 주석 삭제
Whatdoyumin Aug 18, 2025
97b965d
♻ refactor: 계약서 채팅 오류 수정
Whatdoyumin Aug 18, 2025
056b796
♻ refactor: 로딩 처리
Whatdoyumin Aug 18, 2025
42b52d7
♻ refactor: 기본값 수정
Whatdoyumin Aug 18, 2025
8d6122b
Merge branch 'develop' of https://github.com/ITZEEP/frontend into ref…
Whatdoyumin Aug 18, 2025
3fc0cb6
✨ feat: 웹 소켓 수정
Whatdoyumin Aug 19, 2025
ecea430
✨ feat: 4단계 최종 계약서 확정 후 내보내기 페이지로 라우팅
Whatdoyumin Aug 19, 2025
29f27c7
🌱 chore: Merge develop
Whatdoyumin Aug 19, 2025
c0eb97e
✨ feat: 메인 페이지 캐릭터 애니메이션 추가
Whatdoyumin Aug 19, 2025
2107d89
🐛 fix: 웹 소켓 수정
Whatdoyumin Aug 19, 2025
dc666f1
✨ feat: 내가 보낸 버튼 비활성화
Whatdoyumin Aug 19, 2025
d712614
:bug: fix: solve conflict
MeongW Aug 19, 2025
685b3bc
:bug: fix: 다 수정 해버릴거야
MeongW Aug 19, 2025
efe799f
🐛 fix: 임대인 로딩 처리 수정
Whatdoyumin Aug 19, 2025
22fc56e
:bug: fix: 죄송한커밋
MeongW Aug 19, 2025
aa4404b
Merge branch 'refactor/all-ui' of https://github.com/ITZEEP/frontend …
MeongW Aug 19, 2025
86cda14
✨ feat: 최종 계약서 다운로드 1회 제한
Whatdoyumin Aug 19, 2025
37f032d
💄 style: div 색상 수정
Whatdoyumin Aug 19, 2025
8a5b4f4
✨ feat: username과 인사말 표시
Whatdoyumin Aug 19, 2025
6080a10
✨ feat: 실명인증 확인 표시 추가
Whatdoyumin Aug 19, 2025
7df0d1f
✨ feat: 마이페이지 계약서 내역 확인 추가
Whatdoyumin Aug 19, 2025
225aa64
✨ feat: 계약 채팅방 바로가기 버튼은 클릭 가능하게 수정
Whatdoyumin Aug 19, 2025
cf46c1a
✨ feat: 일반 채팅 뷰포트 수정
Whatdoyumin Aug 19, 2025
de17476
🐛 fix: 버튼 오류 삭제
Whatdoyumin Aug 19, 2025
a9fb14d
✨ feat: 안내 멘트 추가
Whatdoyumin Aug 19, 2025
be9b6a4
✨ feat: 수락/거절 시 alert 추가
Whatdoyumin Aug 19, 2025
abbd48c
✨ feat: 기본 특약 검토 버튼 삭제
Whatdoyumin Aug 19, 2025
6de88e9
:bug: fix: UI 개선
MeongW Aug 19, 2025
27562a6
:bug: fix: 일부 UI 수정
MeongW Aug 19, 2025
d3b770b
Merge branch 'refactor/all-ui' of https://github.com/ITZEEP/frontend …
MeongW Aug 19, 2025
7601267
✨ feat: 주소 추가
Whatdoyumin Aug 19, 2025
489c35f
✨ feat: 로그인 버튼으로 변경
Whatdoyumin Aug 19, 2025
6d6279d
💄 style: 마지막 디자인 수정
MeongW Aug 19, 2025
2cc1068
♻ refactor: 특약 검토 버튼 추가
Whatdoyumin Aug 20, 2025
ee310a0
✨ feat: 참여자 명단 추가
Whatdoyumin Aug 20, 2025
9c8c1d1
Merge branch 'refactor/all-ui' of https://github.com/ITZEEP/frontend …
Whatdoyumin Aug 20, 2025
6c56879
:bug: fix: 일부 수정
MeongW Aug 20, 2025
2d112f2
Merge branch 'refactor/all-ui' of https://github.com/ITZEEP/frontend …
MeongW Aug 20, 2025
271d601
:bug: fix: 일부 수정
MeongW Aug 20, 2025
978889b
💄 style: 색상 변경
Whatdoyumin Aug 20, 2025
d027be2
✨ feat: 문구 변경
Whatdoyumin Aug 20, 2025
d8da1d3
✨ feat: 적법성 확인 메시지 디자인 추가
Whatdoyumin Aug 20, 2025
b42c6ff
:sparkles: feat: 모바일 지원
MeongW Aug 20, 2025
b67c641
Merge branch 'refactor/all-ui' of https://github.com/ITZEEP/frontend …
MeongW Aug 20, 2025
6d402a4
:bug: fix: 일부 수정
MeongW Aug 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# API 설정
VITE_API_BASE_URL=http://localhost:8080
VITE_WS_URL=ws://localhost:8080

# Kakao OAuth
VITE_KAKAO_JAVASCRIPT_KEY=your_kakao_key_here

# Firebase FCM
VITE_FIREBASE_API_KEY=AIzaSyCoYr1H2VwJaLpHA0krtZCOX-dTDGlXZYM
VITE_FIREBASE_AUTH_DOMAIN=itzeep-de0ca.firebaseapp.com
VITE_FIREBASE_PROJECT_ID=itzeep-de0ca
VITE_FIREBASE_STORAGE_BUCKET=itzeep-de0ca.firebasestorage.app
VITE_FIREBASE_MESSAGING_SENDER_ID=966020195845
VITE_FIREBASE_APP_ID=1:966020195845:web:9532cf29ef4f7108d55e6c
VITE_FIREBASE_MEASUREMENT_ID=G-4MWDC6FYCE
VITE_FIREBASE_VAPID_KEY=BBwhqrm3fd9077YciPjcCv1H7E1rrEbfIko3CwjtE4PlpkY-3PGnV0V1TBUAU_epvIP9ug_ktwaDvxQsYAQobk0
16 changes: 16 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,27 @@ COPY . .
ARG VITE_API_BASE_URL
ARG VITE_WS_URL
ARG VITE_KAKAO_JAVASCRIPT_KEY
ARG VITE_FIREBASE_API_KEY
ARG VITE_FIREBASE_AUTH_DOMAIN
ARG VITE_FIREBASE_PROJECT_ID
ARG VITE_FIREBASE_STORAGE_BUCKET
ARG VITE_FIREBASE_MESSAGING_SENDER_ID
ARG VITE_FIREBASE_APP_ID
ARG VITE_FIREBASE_MEASUREMENT_ID
ARG VITE_FIREBASE_VAPID_KEY

# 환경 변수 설정
ENV VITE_API_BASE_URL=$VITE_API_BASE_URL
ENV VITE_WS_URL=$VITE_WS_URL
ENV VITE_KAKAO_JAVASCRIPT_KEY=$VITE_KAKAO_JAVASCRIPT_KEY
ENV VITE_FIREBASE_API_KEY=$VITE_FIREBASE_API_KEY
ENV VITE_FIREBASE_AUTH_DOMAIN=$VITE_FIREBASE_AUTH_DOMAIN
ENV VITE_FIREBASE_PROJECT_ID=$VITE_FIREBASE_PROJECT_ID
ENV VITE_FIREBASE_STORAGE_BUCKET=$VITE_FIREBASE_STORAGE_BUCKET
ENV VITE_FIREBASE_MESSAGING_SENDER_ID=$VITE_FIREBASE_MESSAGING_SENDER_ID
ENV VITE_FIREBASE_APP_ID=$VITE_FIREBASE_APP_ID
ENV VITE_FIREBASE_MEASUREMENT_ID=$VITE_FIREBASE_MEASUREMENT_ID
ENV VITE_FIREBASE_VAPID_KEY=$VITE_FIREBASE_VAPID_KEY

# 애플리케이션 빌드
RUN npm run build
Expand Down
6 changes: 6 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@
</head>
<body>
<div id="app"></div>
<script>
// SockJS를 위한 global 폴리필
if (typeof global === 'undefined') {
window.global = window;
}
</script>
<script type="module" src="/src/main.js"></script>
</body>
</html>
Binary file added public/400.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/404.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/contract_complete.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion public/firebase-messaging-sw.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,9 @@ if (typeof firebase !== 'undefined') {
const messaging = firebase.messaging()

messaging.onBackgroundMessage(async function (payload) {
console.log('[SW] 백그라운드 메시지 수신:', payload)
console.log('[SW] 🔔🔔🔔 백그라운드 메시지 수신:', payload)
console.log('[SW] 🔔 메시지 타입:', payload.data?.type)
console.log('[SW] 🔔 채팅방 ID:', payload.data?.chatRoomId)

try {
// 백그라운드 알림을 DB에 저장하지 않음 (백엔드에서 이미 저장됨)
Expand Down
Binary file added public/no-mobile.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/panda&lion.mp4
Binary file not shown.
24 changes: 24 additions & 0 deletions src/App.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,36 @@
<script setup>
import { RouterView } from 'vue-router'
import DefaultLayout from './components/layouts/DefaultLayout.vue'
import LoginModal from './components/common/LoginModal.vue'
import ConfirmModal from './components/common/ConfirmModal.vue'
import { useLoginModal } from '@/composables/useLoginModal'
import { useConfirmModal } from '@/composables/useConfirmModal'

const { isLoginModalOpen, redirectPath, closeLoginModal } = useLoginModal()
const { isConfirmModalOpen, confirmTitle, confirmMessage, handleConfirm, handleCancel } = useConfirmModal()
</script>

<template>
<DefaultLayout>
<RouterView />
</DefaultLayout>

<!-- 전역 로그인 모달 -->
<LoginModal
v-model="isLoginModalOpen"
:redirect-path="redirectPath"
@cancel="closeLoginModal"
@confirm="closeLoginModal"
/>

<!-- 전역 확인 모달 -->
<ConfirmModal
:is-open="isConfirmModalOpen"
:title="confirmTitle"
:message="confirmMessage"
@confirm="handleConfirm"
@cancel="handleCancel"
/>
</template>

<style scoped></style>
1 change: 1 addition & 0 deletions src/apis/chatApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ export async function refreshToken() {

if (res.data.success && res.data.data.accessToken) {
localStorage.setItem('accessToken', res.data.data.accessToken)
localStorage.setItem('access-token', res.data.data.accessToken) // 호환성 유지
return true
}

Expand Down
12 changes: 12 additions & 0 deletions src/apis/contractApi.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
import api from './index'

const CONTRACT_BASE_URL = '/api/contract'
const CONTRACT_CHAT_BASE_URL = '/api/chat/contract'

// 전체 계약서 조회 (오른쪽)
export const contractApi = {
// 계약 채팅방 URL로 이동
moveContractChat: async (chatRoomId) => {
try {
const response = await api.get(`${CONTRACT_CHAT_BASE_URL}/${chatRoomId}/moveContractChat`)
return response.data
} catch (error) {
console.error('계약 채팅방 이동 실패: ', error)
throw error
}
},

getContractBasic: async (contractChatId) => {
try {
const response = await api.post(`${CONTRACT_BASE_URL}/${contractChatId}/getContract`)
Expand Down
8 changes: 6 additions & 2 deletions src/apis/contractChatApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -461,10 +461,14 @@ export const updateSignatureStatus = async (contractChatId, signatureData) => {
export const getExportStatus = async (contractChatId) => {
try {
const response = await api.get(`/api/contract/${contractChatId}/export/status`)
return response.data.data
if (response.data && response.data.success) {
return response.data.data
} else {
return null
}
} catch (error) {
console.error('내보내기 상태 조회 실패:', error)
throw error
return null
}
Comment on lines +464 to 472
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

getExportStatus: 에러 시 null 반환으로 변경 — 호출부 영향 점검 및 반환 형태 문서화/표준화 권장

이번 변경으로 성공이 아니면 null을 반환하게 되었습니다. 폴링 기반 처리에는 합리적이지만, 기존 호출부가 객체 형태(예: { success, data })를 기대했다면 런타임 오류를 유발할 수 있습니다. 최소한 호출부에서 null 체크가 모두 되어있는지 확인 부탁드립니다. 또한 이 파일 내 다른 API들이 반환하는 형태가 제각각이라(일부는 result, 일부는 result.data, 일부는 null), 일관된 Result 형태로 표준화하는 것을 권장합니다.

아래 스크립트로 getExportStatus 호출부에서 null 체크가 누락된 곳을 빠르게 살펴볼 수 있습니다.

선택지:

  • 유지(현재 설계): null 반환 유지 + JSDoc로 계약을 명확히
  • 표준화(권장): Result 형태로 통일

옵션 B(표준화) 최소 패치 예시:

-    if (response.data && response.data.success) {
-      return response.data.data
-    } else {
-      return null
-    }
+    if (response.data?.success) {
+      return { success: true, data: response.data.data }
+    }
+    return { success: false, data: null }
@@
-    return null
+    return { success: false, data: null }

🏁 Script executed:

#!/bin/bash
# getExportStatus 사용처와 주변 null 체크 여부 확인
rg -nP -C3 --glob '!node_modules/**' '\bgetExportStatus\s*\('

echo
echo "잠재적 null 미체크 패턴 (단순 휴리스틱):"
rg -nP -C2 --glob '!node_modules/**' '\bgetExportStatus\s*\([^)]+\)\s*(;|\))\s*$' | sed 's/^/  /'

Length of output: 1254


🏁 Script executed:

#!/bin/bash
echo "=== Context around initial getExportStatus call ==="
sed -n '525,545p' src/pages/contract/ContractCompletePage.vue
echo
echo "=== Context around polling getExportStatus call ==="
sed -n '930,960p' src/pages/contract/ContractCompletePage.vue

Length of output: 1686


getExportStatus 폴링 로직에 null 처리 누락으로 인한 런타임 오류 가능 – 즉시 수정 필요

폴링 루프에서 getExportStatusnull을 반환할 경우, 아래 코드에서 status.isCompleted 등을 읽으면서 예외가 발생합니다. 초기 호출부에는 null 체크(if (initialStatus))가 되어 있으나, 폴링 구간에는 빠져 있습니다.

  • 위치: src/pages/contract/ContractCompletePage.vue (약 937–940행)
  • 현상:
    const status = await getExportStatus(contractId.value)
    // → status가 null이면 다음 줄에서 Cannot read properties of null 에러
    exportStatus.value = status
    if ((status.isCompleted || status.completed) && status.finalPdfUrl) {  }

수정 방안 (택1):

  1. null 반환 시 건너뛰기
    const status = await getExportStatus(contractId.value)
  • if (!status) {
  • console.warn('getExportStatus returned null, skip polling iteration')
  • return
  • }
    exportStatus.value = status
    if ((status.isCompleted || status.completed) && status.finalPdfUrl) { … }
    
    
  1. 반환 타입을 항상 객체 형태로 표준화 (예: { success, data })
    - if (response.data?.success) {
    -   return { ... }
    - }
    + return { success: !!response.data?.success, data: response.data?.data ?? null }
    이후 호출부에서도 status.success로 검사하도록 일관화.

또한, getExportStatus 함수에 JSDoc/타입 정의로 null 반환 가능 여부를 명확히 문서화해 주세요.

🤖 Prompt for AI Agents
In src/apis/contractChatApi.js around lines 464–472 the getExportStatus function
can return null but callers in src/pages/contract/ContractCompletePage.vue
(~lines 937–940) assume an object, causing runtime errors; fix by either (A)
making the polling loop defensively skip when status is null (e.g., if (!status)
continue/return) before reading status.isCompleted/status.finalPdfUrl, or (B)
change getExportStatus to always return a standardized object shape (never null)
and update all callers to check a success flag (e.g., status.success) instead;
additionally add JSDoc/type annotation to getExportStatus documenting nullable
return or the new standardized shape so callers know which to handle.

}

Expand Down
86 changes: 76 additions & 10 deletions src/apis/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import axios from 'axios'
import { useLoginModal } from '@/composables/useLoginModal'

// axios 인스턴스 생성
const api = axios.create({
Expand All @@ -11,8 +12,8 @@ const api = axios.create({
// 요청 인터셉터 - 모든 요청에 토큰 추가
api.interceptors.request.use(
(config) => {
// localStorage에서 토큰 가져오기
const token = localStorage.getItem('access-token')
// localStorage에서 토큰 가져오기 (두 가지 키 모두 확인)
const token = localStorage.getItem('accessToken') || localStorage.getItem('access-token')

if (token) {
// Authorization 헤더에 Bearer 토큰 추가
Expand All @@ -33,14 +34,33 @@ api.interceptors.response.use(
},
async (error) => {
const originalRequest = error.config
const router = window.$router // 라우터 인스턴스는 main.js에서 설정

// 에러 응답이 없는 경우 (네트워크 에러 등)
if (!error.response) {
console.error('네트워크 에러:', error.message)
return Promise.reject(error)
}

const status = error.response.status

// 401 Unauthorized 에러 처리
if (error.response && error.response.status === 401 && !originalRequest._retry) {
if (status === 401 && !originalRequest._retry) {
originalRequest._retry = true

// 토큰이 없는 경우 바로 로그인 모달 표시
const currentToken = localStorage.getItem('accessToken') || localStorage.getItem('access-token')
if (!currentToken) {
console.error('인증 토큰이 없습니다.')
const { openLoginModal } = useLoginModal()
const currentPath = router?.currentRoute.value.fullPath || window.location.pathname
openLoginModal(currentPath)
return Promise.reject(error)
}

try {
// 리프레시 토큰으로 새 액세스 토큰 요청
const refreshToken = localStorage.getItem('refresh-token')
const refreshToken = localStorage.getItem('refreshToken') || localStorage.getItem('refresh-token')
if (refreshToken) {
const refreshResponse = await axios.post(
`${import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080'}/api/auth/refresh`,
Expand All @@ -49,7 +69,8 @@ api.interceptors.response.use(

if (refreshResponse.data.success) {
const newAccessToken = refreshResponse.data.data.accessToken
localStorage.setItem('access-token', newAccessToken)
localStorage.setItem('accessToken', newAccessToken)
localStorage.setItem('access-token', newAccessToken) // 호환성 유지

// 실패한 요청을 새 토큰으로 재시도
originalRequest.headers.Authorization = `Bearer ${newAccessToken}`
Expand All @@ -58,11 +79,56 @@ api.interceptors.response.use(
}
} catch (refreshError) {
console.error('토큰 갱신 실패:', refreshError)
// 로그인 페이지로 리다이렉트
localStorage.removeItem('access-token')
localStorage.removeItem('refresh-token')
localStorage.removeItem('user')
window.location.href = '/auth/signin'
}

// 토큰 갱신 실패 또는 리프레시 토큰이 없는 경우
localStorage.removeItem('accessToken')
localStorage.removeItem('access-token')
localStorage.removeItem('refreshToken')
localStorage.removeItem('refresh-token')
localStorage.removeItem('userInfo')
localStorage.removeItem('user')

// 로그인 모달 표시
const { openLoginModal } = useLoginModal()
const currentPath = router?.currentRoute.value.fullPath || window.location.pathname
openLoginModal(currentPath)
}

// 현재 경로 확인 (ContractCompletePage에서는 페이지 이동 대신 에러 전달)
const currentPath = router?.currentRoute.value.path || window.location.pathname
const isContractCompletePage = currentPath.includes('/contract/') && currentPath.includes('/complete')

// 404 Not Found
if (status === 404) {
console.error('404 에러:', error.response.data)
if (!isContractCompletePage && router && router.currentRoute.value.name !== 'not-found') {
router.push({ name: 'not-found' })
}
if (isContractCompletePage) {
return Promise.reject(error)
}
}

// 403 Forbidden
if (status === 403) {
console.error('403 권한 없음:', error.response.data)
if (!isContractCompletePage && router) {
router.push({ name: 'unauthorized' })
}
if (isContractCompletePage) {
return Promise.reject(error)
}
}

// 500 Server Error
if (status >= 500) {
console.error('서버 에러:', error.response.data)
if (!isContractCompletePage && router && router.currentRoute.value.name !== 'server-error') {
router.push({ name: 'server-error' })
}
if (isContractCompletePage) {
return Promise.reject(error)
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/apis/mypage.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,9 @@ export const mypageAPI = {
},

// 매물 삭제
deleteProperty: async (propertyId) => {
deleteProperty: async (homeId) => {
try {
const response = await api.delete(`/api/mypage/properties/${propertyId}`)
const response = await api.delete(`/api/homes/${homeId}`)
return response.data
} catch (error) {
console.error('매물 삭제 실패:', error)
Expand Down
Loading