diff --git a/momogo-api/src/main/java/com/momogo/api/auth/config/SecurityConfig.java b/momogo-api/src/main/java/com/momogo/api/auth/config/SecurityConfig.java index 10a4139..d8380b6 100644 --- a/momogo-api/src/main/java/com/momogo/api/auth/config/SecurityConfig.java +++ b/momogo-api/src/main/java/com/momogo/api/auth/config/SecurityConfig.java @@ -101,6 +101,9 @@ public SecurityFilterChain filterChain( // 소셜 로그인 관련 .requestMatchers("/oauth2/**", "/login/oauth2/**").permitAll() + // 프로필 등 업로드 이미지 조회 (민감정보 아니므로 인증 없이 공개) + .requestMatchers(HttpMethod.GET, "/uploads/**").permitAll() + .requestMatchers("/api/**").authenticated() .anyRequest().authenticated() ) diff --git a/momogo-api/src/main/java/com/momogo/api/common/exception/GlobalExceptionHandler.java b/momogo-api/src/main/java/com/momogo/api/common/exception/GlobalExceptionHandler.java index 3b6ef4a..26a5eaf 100644 --- a/momogo-api/src/main/java/com/momogo/api/common/exception/GlobalExceptionHandler.java +++ b/momogo-api/src/main/java/com/momogo/api/common/exception/GlobalExceptionHandler.java @@ -20,6 +20,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import org.springframework.web.multipart.MaxUploadSizeExceededException; @Slf4j @RestControllerAdvice @@ -63,6 +64,15 @@ public ResponseEntity handleDataIntegrityViolation(DataIntegrityV .body(ErrorResponse.of(code, code.getMessage(), request.getRequestURI())); } + @ExceptionHandler(MaxUploadSizeExceededException.class) + public ResponseEntity handleMaxUploadSizeExceeded(MaxUploadSizeExceededException e, HttpServletRequest request) { + ErrorCode code = GlobalErrorCode.FILE_UPLOAD_FAILED; + log.warn("업로드 파일 용량 초과 : path={}", request.getRequestURI()); + + ErrorResponse response = ErrorResponse.of(code, "업로드 가능한 파일 용량(10MB)을 초과했습니다.", request.getRequestURI()); + return ResponseEntity.status(code.getHttpStatus()).body(response); + } + @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity handleValidationException(MethodArgumentNotValidException e, HttpServletRequest request) { ErrorCode code = GlobalErrorCode.INVALID_INPUT; diff --git a/momogo-api/src/main/resources/application.yaml b/momogo-api/src/main/resources/application.yaml index b3f0a2e..aea3cc5 100644 --- a/momogo-api/src/main/resources/application.yaml +++ b/momogo-api/src/main/resources/application.yaml @@ -4,6 +4,10 @@ spring: enabled: true application: name: momogo-api + servlet: + multipart: + max-file-size: 10MB + max-request-size: 10MB profiles: active: dev # 기본 활성화 프로필 group: diff --git a/momogo-frontend/src/pages/DashboardPage.tsx b/momogo-frontend/src/pages/DashboardPage.tsx index 99bba89..97aab50 100644 --- a/momogo-frontend/src/pages/DashboardPage.tsx +++ b/momogo-frontend/src/pages/DashboardPage.tsx @@ -1806,6 +1806,9 @@ const styles: Record = { marginTop: '0.25rem', }, headerActions: { + display: 'flex', + alignItems: 'center', + gap: '1rem', position: 'relative', }, notiTriggerBtn: { @@ -1828,6 +1831,66 @@ const styles: Record = { alignItems: 'center', justifyContent: 'center', }, + userHeaderWidget: { + display: 'flex', + alignItems: 'center', + gap: '0.625rem', + padding: '0.4rem 0.75rem', + borderRadius: '24px', + backgroundColor: '#ffffff', + border: '1px solid #eaecf0', + cursor: 'pointer', + boxShadow: '0 1px 3px rgba(16, 24, 40, 0.05)', + }, + headerAvatar: { + width: '32px', + height: '32px', + borderRadius: '50%', + objectFit: 'cover', + border: '1px solid #eaecf0', + }, + headerUserName: { + fontWeight: 600, + fontSize: '0.875rem', + color: '#1d2939', + }, + userMenuPopover: { + position: 'absolute', + top: 'calc(100% + 8px)', + right: 0, + width: '240px', + padding: '12px', + borderRadius: '12px', + backgroundColor: '#ffffff', + boxShadow: '0 10px 25px rgba(0, 0, 0, 0.1)', + zIndex: 1000, + border: '1px solid #eaecf0', + }, + userMenuHeader: { + display: 'flex', + alignItems: 'center', + gap: '10px', + paddingBottom: '8px', + }, + menuAvatar: { + width: '40px', + height: '40px', + borderRadius: '50%', + objectFit: 'cover', + border: '1px solid #eaecf0', + }, + popoverItemBtn: { + width: '100%', + padding: '8px 10px', + borderRadius: '6px', + border: 'none', + backgroundColor: 'transparent', + textAlign: 'left', + fontSize: '0.85rem', + fontWeight: 600, + color: '#344054', + cursor: 'pointer', + }, notiPopover: { position: 'absolute', top: '5rem',