Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the 코드 리뷰 요약Walkthrough발주 및 주문 관리 기능을 구현합니다. 장바구니 API, 데이터 모델, 저장소, 뷰모델과 UI 컴포넌트를 추가하고, 주문 생성, 조회, 취소 기능을 제공합니다. 재고 부족 조회 기능과 홈 화면 UI를 개선하며, 새로운 색상 및 자산을 추가합니다. Changes
Sequence Diagram(s)sequenceDiagram
actor User as 사용자
participant OrderView as OrderView
participant CartVM as CartViewModel
participant InventoryVM as InventoryViewModel
participant CartAPI as CartApi
participant OrderAPI as OrderApi
participant Server as 백엔드
User->>OrderView: 화면 접속
OrderView->>CartVM: fetchCart()
CartVM->>CartAPI: GET /api/v1/order/cart
CartAPI->>Server: 장바구니 조회
Server-->>CartAPI: CartData 반환
CartAPI-->>CartVM: 업데이트
User->>OrderView: 상품 검색 및 수량 조절
OrderView->>OrderView: OrderRequestCardView 렌더링
User->>OrderView: 장바구니 추가 버튼 클릭
OrderView->>CartVM: addToCart(partId, amount)
CartVM->>CartAPI: POST /api/v1/order/cart
CartAPI->>Server: 장바구니 추가
Server-->>CartAPI: 업데이트된 CartData 반환
CartAPI-->>CartVM: 상태 갱신
CartVM-->>OrderView: UI 업데이트
User->>OrderView: 장바구니 보기 (CartSummaryBar)
OrderView->>OrderCartView: 장바구니 화면 전환
User->>OrderCartView: 결제 진행
OrderCartView->>OrderInfoView: 배송/결제 정보 입력
User->>OrderInfoView: 배송 옵션 선택 및 결제
OrderInfoView->>OrderViewModel: createOrder(request)
OrderViewModel->>OrderAPI: POST /api/v1/order
OrderAPI->>Server: 주문 생성
Server-->>OrderAPI: OrderCreateResponseData 반환
OrderAPI-->>OrderViewModel: 주문 ID 반환
OrderViewModel-->>OrderInfoView: 성공 및 네비게이션
OrderInfoView->>OrderDetailView: 주문 상세 화면
OrderDetailView->>OrderViewModel: fetchOrderDetail(orderId)
OrderViewModel->>OrderAPI: GET /api/v1/order/{orderId}
OrderAPI->>Server: 주문 상세 조회
Server-->>OrderAPI: OrderDetailResponse 반환
OrderAPI-->>OrderDetailView: 주문 상세 표시
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes 주의 사항:
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Comment |
@coderabbitai review |
|
@Yoo-Hyuna: I'll review the changes in this PR now. ✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 14
🧹 Nitpick comments (41)
StockMate/StockMate/app/feature/orders/ui/OrderResultView.swift (3)
19-63: 디자인 시스템 색상 사용을 권장합니다.현재
.blue,.white,.gray등 하드코딩된 색상을 사용하고 있습니다. Asset Catalog에 정의된 semantic 색상을 사용하면 테마 일관성과 다크모드 지원이 향상됩니다.예시:
// Asset Catalog에 정의된 색상 사용 .foregroundColor(Color("PrimaryColor")) .background(Color("BackgroundColor"))
26-71: 다국어 지원을 고려해보세요.사용자에게 표시되는 모든 문자열이 하드코딩되어 있습니다. 향후 다국어 지원이 필요할 경우를 대비해
NSLocalizedString또는 SwiftUI의LocalizedStringKey를 사용하는 것을 권장합니다.예시:
Text("order.completion.message") // LocalizedStringKey 자동 사용 // 또는 Text(NSLocalizedString("order.completion.message", comment: "Order completion message"))
19-63: 접근성 개선을 고려해보세요.아이콘과 버튼에 명시적인 접근성 레이블을 추가하면 VoiceOver 사용자 경험이 향상됩니다.
예시:
Image(systemName: "checkmark.circle.fill") .resizable() .frame(width: 80, height: 80) .foregroundColor(.blue) .padding(.bottom, 8) .accessibilityLabel("주문 완료") Button { dismiss() } label: { Text("확인") ... } .accessibilityHint("이전 화면으로 돌아갑니다")StockMate/StockMate/app/feature/cart/data/CartApi.swift (1)
37-38: 주석 처리된 코드 제거사용하지 않는 주석 코드는 제거하는 것이 좋습니다.
partId를 id로 사용하는 것으로 결정되었다면 line 37을 삭제하세요.-// var id: Int { cartItemId } var id: Int { partId }StockMate/StockMate/app/feature/inventory/domain/InventoryRepositoryProtocol.swift (1)
9-9: 불필요한 import 제거Protocol 파일에는 Alamofire import가 필요하지 않습니다.
AppResult와ApiResponse는 core 모듈에 정의되어 있어야 하며, protocol은 구현 세부사항에 의존하지 않아야 합니다.import Foundation -import AlamofireStockMate/StockMate/app/navigation/MainTabView.swift (1)
23-29: 주석 처리된 코드를 정리하세요.여러 줄의 주석 처리된 코드(lines 23, 26-27)가 남아있습니다. 코드가 안정화되면 이러한 주석들을 제거하여 가독성을 개선하는 것이 좋습니다. 또한 default 케이스에서
ContentView()로 라우팅하는 것이 의도된 동작인지 확인하세요.-// case 1: NavigationStack{ OrderView() } -// case 3: NavigationStack{ ContentView() } -// case 3: NavigationStack{ ReceiptView() }StockMate/StockMate/app/core/components/CartInfoCard.swift (1)
10-12: quantity 파라미터가 사용되지 않습니다.
CartInfoCard의 생성자에quantity: Int파라미터가 전달되지만, 실제로는item.amount를 사용하여 수량을 표시합니다(line 52).quantity파라미터가 불필요하거나, 의도적으로item.amount대신 사용해야 하는 경우라면 로직을 수정해야 합니다.struct CartInfoCard: View { let item: CartItem - let quantity: Int또는
quantity를 실제로 사용하려면:- Text("\(item.price ?? 0)원 / \(item.amount)개") + Text("\(item.price ?? 0)원 / \(quantity)개")Also applies to: 52-52
StockMate/StockMate/app/feature/cart/domain/CartRepositoryProtocol.swift (2)
9-9: 불필요한 import 정리이 파일은 Alamofire 심볼을 직접 사용하지 않습니다. import 제거로 의존성/컴파일 단위를 줄여주세요.
-import Alamofire
11-23: 레포지토리 반환 타입 일관성 정리 제안Cart 쪽은 ViewModel까지 ApiResponse를 노출하지만, Orders는 도메인 타입만 노출합니다. 레이어 경계 일관성을 위해 Cart도 AppResult/AppResult만 반환하고 ApiResponse 언래핑은 RepositoryImpl에서 처리하는 방향을 권장합니다. 변경 범위가 크면 추후 리팩토링 이슈로 분리해도 됩니다. Based on learnings
예시 시그니처:
- func fetchCart() async -> AppResult<ApiResponse<CartData>> + func fetchCart() async -> AppResult<CartData> - func addToCart(request: CartUpdateRequest) async -> AppResult<ApiResponse<CartData>> + func addToCart(request: CartUpdateRequest) async -> AppResult<CartData> - func updateCart(request: CartUpdateRequest) async -> AppResult<ApiResponse<CartData>> + func updateCart(request: CartUpdateRequest) async -> AppResult<CartData> - func clearCart() async -> AppResult<ApiResponse<VoidData>> + func clearCart() async -> AppResult<VoidData>StockMate/StockMate/app/feature/orders/domain/OrderRepositoryProtocol.swift (1)
10-29: 취소 결과 타입을 강한 타입으로 모델링 고려String 대신 의미 있는 도메인 타입(예: CancelOrderResult 또는 VoidData/EmptyResponse)을 사용하면 메시지 포맷 변경에 덜 취약합니다. 또한 각 메서드에 간단한 문서 주석(파라미터, 기본값, 오류 케이스)을 추가해 사용성을 높여주세요. Based on learnings
StockMate/StockMate/app/feature/user/ui/ProfileView.swift (3)
23-26: 자산 이미지에 색상 미적용 이슈Image("location")는 기본적으로 원본 렌더링이라 .foregroundColor가 적용되지 않습니다. SF Symbol을 쓰거나 템플릿 렌더링으로 바꿔주세요.
- Image("location") - .foregroundColor(.gray) + Image(systemName: "mappin.and.ellipse") + .foregroundColor(.gray)혹은
- Image("location") - .foregroundColor(.gray) + Image("location") + .renderingMode(.template) + .foregroundColor(.gray)
14-16: NavigationStack 컨텍스트 확인NavigationLink(주문 내역)가 동작하려면 상위에 NavigationStack이 있어야 합니다. 현재 주석 처리되어 있어 단독 사용 시 push가 되지 않습니다. 상위 컨테이너에서 보장되는지 확인하거나 이 뷰 내부에 복원하세요.
-// NavigationStack { +NavigationStack { ... -// } +}Also applies to: 79-84, 125-146
91-99: 다크 모드 가독성 개선아이콘 기본색을 .black 대신 .primary로 두면 다크/라이트 모드 모두에서 자연스럽습니다.
- var iconColor: Color = .black + var iconColor: Color = .primaryStockMate/StockMate/app/feature/orders/ui/OrderListView.swift (2)
131-147: 주문 취소 UX 안전장치(확인/중복방지)실수 취소 방지와 중복 요청 방지를 위해 확인 다이얼로그와 로딩 중 비활성화를 권장합니다.
struct OrderListCardView: View { let order: OrderResponseItem @ObservedObject var orderViewModel: OrderViewModel + @State private var showCancelConfirm = false var body: some View { ... - if order.orderStatus == "ORDER_COMPLETED" { - Button(action: { - Task { await orderViewModel.cancelOrder(orderId: order.id) } - }) { + if order.orderStatus == "ORDER_COMPLETED" { + Button(action: { showCancelConfirm = true }) { Text("주문 취소") .font(.system(size: 13, weight: .semibold)) .foregroundColor(.white) .frame(maxWidth: .infinity) .frame(height: 40) .background(Color.Primary) .cornerRadius(6) - } + } + .disabled(orderViewModel.isLoading) .padding(.top, 6) } ... } + .alert("주문을 취소하시겠어요?", isPresented: $showCancelConfirm) { + Button("취소 실행", role: .destructive) { + Task { await orderViewModel.cancelOrder(orderId: order.id) } + } + Button("돌아가기", role: .cancel) { } + }
63-68: 날짜 포맷팅 중복 제거OrderDetailView에도 유사 유틸이 있습니다. 확장(예: String→displayDate)으로 공용화하여 중복을 제거하세요. Based on learnings
StockMate/StockMate/app/feature/orders/viewmodel/OrderDetailViewModel.swift (2)
16-16: 구현체 직접 생성 대신 프로토콜 주입테스트 용이성과 결합도 감소를 위해 프로토콜 기반 DI를 사용하세요. 기본 구현은 DI 기본값으로 유지하면 사용성은 동일합니다. Based on learnings
- private let repository = OrderRepositoryImpl() + private let repository: OrderRepositoryProtocol + + init(repository: OrderRepositoryProtocol = OrderRepositoryImpl()) { + self.repository = repository + }
27-29: 에러 메시지 매핑 일관화다른 ViewModel은 error.message를 사용합니다. 동일하게 message를 노출하면 사용자 메시지가 일정해집니다. Based on learnings
- case .failure(let error): - self.errorMessage = error.localizedDescription + case .failure(let error): + self.errorMessage = error.messageStockMate/StockMate/app/core/components/OrderRequestCardView.swift (3)
45-48: 가격 표기 천단위 포맷 적용 제안정수값 그대로 출력하지 말고 통화/천단위 포맷을 사용해 가독성을 높여주세요. 아래처럼 공용 포맷터를 쓰면 일관성이 좋아집니다.
- Text("\(item.price)원") + Text("\(formatPrice(item.price))원")공용
formatPrice(_:)가 파일 간 중복이므로, Utils로 이동해 전역에서 재사용 권장.
54-61: 아이콘만 있는 버튼에 접근성 레이블 추가VoiceOver 사용자를 위해 의미 있는 레이블/힌트를 추가하세요.
- Button(action: onAddToCart) { + Button(action: onAddToCart) { Image(systemName: "cart.badge.plus") .font(.system(size: 18)) .foregroundColor(.Primary) .padding(10) .background(Color.Primary.opacity(0.1)) .clipShape(Circle()) - } + } + .accessibilityLabel("장바구니 담기") + .accessibilityHint("해당 부품을 장바구니에 1개 추가합니다") - Button(action: onRemoveFromCart) { + Button(action: onRemoveFromCart) { Image(systemName: "trash") .font(.system(size: 14, weight: .bold)) .foregroundColor(.red) - } + } + .accessibilityLabel("장바구니에서 제거") + .accessibilityHint("해당 부품을 장바구니에서 삭제합니다") - Button(action: onIncrease) { + Button(action: onIncrease) { Image(systemName: "plus") .font(.system(size: 14, weight: .bold)) .foregroundColor(.Primary) - } + } + .accessibilityLabel("수량 증가") + .accessibilityHint("해당 부품 수량을 1 증가시킵니다") - Button(action: onDecrease) { + Button(action: onDecrease) { Image(systemName: "minus") .font(.system(size: 14, weight: .bold)) .foregroundColor(.gray) - } + } + .accessibilityLabel("수량 감소") + .accessibilityHint("해당 부품 수량을 1 감소시킵니다")Also applies to: 65-79, 87-104
83-86: 다크 모드 대비 색상 하드코딩 완화
.background(Color.white)와 얕은 그림자를 다수 사용하고 있어 다크 모드에서 부자연스러울 수 있습니다. 토큰(예:Color.cardBackground)로 치환하거나Material.thin등을 고려해주세요. 최소한.environment(\.colorScheme)전환 시 대비를 확인 바랍니다.Also applies to: 105-110, 114-117
StockMate/StockMate/app/feature/inventory/ui/LackListView.swift (2)
45-51: 무한 스크롤 트리거에 로딩/hasMore 가드 추가중복 호출을 더 줄이려면
isLoading과underLimitHasMore를 함께 확인하세요.- if item.id == inventoryViewModel.underLimitItems.last?.id { + if item.id == inventoryViewModel.underLimitItems.last?.id + && !inventoryViewModel.isLoading + && inventoryViewModel.underLimitHasMore { Task { await inventoryViewModel.loadUnderLimitList() } }
16-17: 카테고리 목록 하드코딩 최소화서버/VM 제공 값과 불일치 위험이 있습니다.
InventoryViewModel의 카테고리 소스 또는 백엔드 응답으로 구동하는 방안을 권장합니다.Also applies to: 22-33
StockMate/StockMate/app/feature/cart/viewmodel/CartViewModel.swift (4)
95-118:objectWillChange.send()수동 호출 불필요/중복 알림 가능
@Published가 이미 변경 알림을 보냅니다. 수동 호출은 중복 렌더/경고를 유발할 수 있어 제거 권장. 또한cart == nil에서-1ID 더미 생성은 외부 로직을 교란할 소지가 있어 지양하세요.- if let cart = cart { - self.cart = CartData( - cartId: cart.cartId, - memberId: cart.memberId, - items: self.items, - totalPrice: total - ) - } else { - // fallback: cart가 nil일 수 있는 초기 로드 상황 대비 - self.cart = CartData( - cartId: -1, - memberId: -1, - items: self.items, - totalPrice: total - ) - } - - objectWillChange.send() // 중요! SwiftUI에게 “바뀌었어!” 알림 + guard let cart else { return } // cart 없으면 강제 생성하지 않음 + self.cart = CartData( + cartId: cart.cartId, + memberId: cart.memberId, + items: self.items, + totalPrice: total + )
121-142: 수량 변경 시 서버 호출 코얼레싱(연속 탭 최적화)빠른 탭으로
increase/decrease가 연달아 호출되면 매번updateCart/fetchCart트립이 발생합니다. 짧은 디바운스/스로틀(예:Task취소 토큰으로 마지막만 전송)로 네트워크 부하를 낮추세요. 구현은 설계에 따라 별도 PR로도 충분합니다.Also applies to: 144-157, 159-176
82-89: 에러 로깅 방식 개선
91-93: 유틸:quantity(for:)는 O(n)아이템 수가 커지면 반복 호출에서 비용이 큽니다.
Dictionary<partId, amount>캐시를 병행하거나, 호출부에서 한 번만 계산해 전달하는 구조를 고려하세요.StockMate/StockMate/app/core/components/CartCard.swift (3)
50-57: 주석 처리된 데드 코드 제거불필요한 주석 블럭은 가독성을 해칩니다. 정리 권장.
-// Text("\(item.trim) / \(item.model)") -// .font(.system(size: 13)) -// .foregroundColor(.gray) -// -// Text("\(item.price)원") -// .font(.system(size: 13, weight: .semibold)) -// .foregroundColor(.black)
46-49: 가격 포맷 일관화
formatPrice(_:)혹은 통화 포맷터로 교체하여 UI 전반의 일관성을 유지하세요.- Text("\(item.price ?? 0)원") + Text("\(formatPrice(item.price ?? 0))원")
63-72: 버튼 접근성 레이블/힌트 추가아이콘만 있는 버튼에 레이블/힌트를 추가하세요. OrderRequestCardView와 동일 패턴 권장.
- Button(action: onAddToCart) { + Button(action: onAddToCart) { Image(systemName: "cart.badge.plus") ... - } + } + .accessibilityLabel("장바구니 담기") + .accessibilityHint("해당 부품을 장바구니에 1개 추가합니다") - Button(action: onRemoveFromCart) { + Button(action: onRemoveFromCart) { Image(systemName: "trash") ... - } + } + .accessibilityLabel("장바구니에서 제거") - Button(action: onIncrease) { + Button(action: onIncrease) { Image(systemName: "plus") ... - } + } + .accessibilityLabel("수량 증가") - Button(action: onDecrease) { + Button(action: onDecrease) { Image(systemName: "minus") ... - } + } + .accessibilityLabel("수량 감소")Also applies to: 75-79, 85-89, 98-103, 109-113
StockMate/StockMate/app/feature/orders/ui/OrderRequestSearchView.swift (3)
143-147: 검색 모드 무한 스크롤에도 로딩 가드 추가검색 분기에는
!isLoading체크가 없습니다. 중복 로드 방지를 위해 동일 가드를 추가하세요.- if item.id == inventoryViewModel.filteredSearchResults.last?.id, - inventoryViewModel.searchHasMore { + if item.id == inventoryViewModel.filteredSearchResults.last?.id, + inventoryViewModel.searchHasMore, + !inventoryViewModel.isLoading { await inventoryViewModel .loadMore(searchText: searchText) }Also applies to: 149-155
58-66: 검색어 지우기 버튼 접근성 레이블 추가명확한 의미 전달을 위해 레이블/힌트를 부여하세요.
- Button(action: { + Button(action: { searchText = "" inventoryViewModel.isSearching = false }) { Image(systemName: "xmark") .foregroundColor(.gray) } + .accessibilityLabel("검색어 지우기") + .accessibilityHint("입력된 검색어를 모두 지웁니다")
121-139: 수량 콜백의 서버 왕복 비용 최소화 고려리스트 셀에서 빠르게 증감 시 서버 호출이 다수 발생합니다. CartViewModel에 디바운스/배치 업데이트가 도입되면 UX가 부드러워집니다. 우선순위 낮음.
StockMate/StockMate/app/feature/orders/ui/OrderDetailView.swift (4)
202-206: 날짜 파싱 로직 견고화(ISO8601 포맷터 사용)문자열 split 기반은 형식 변화에 취약합니다.
ISO8601DateFormatter+DateFormatter로 변환하세요.let iso = ISO8601DateFormatter() if let date = iso.date(from: isoDate) { let f = DateFormatter() f.locale = Locale(identifier: "ko_KR") f.dateFormat = "yyyy년 MM월 dd일" return f.string(from: date) }Also applies to: 273-280
79-104: ForEach ID 안정성 확인
id: \.partId는 동일 부품이 여러 라인에 존재할 경우 중복 위험이 있습니다. 고유orderItemId가 있다면 그 키로 전환하거나, 인덱스와 결합하세요.- ForEach(order.orderItems, id: \.partId) { item in + ForEach(Array(order.orderItems.enumerated()), id: \.offset) { _, item in
141-153: 영수증 확인 버튼 미구현네비게이션 또는 공유 시트 등 구체 동작을 연결해 주세요. 필요 시 라우팅/뷰 설계 지원 가능합니다.
208-245: 상태 헬퍼 함수 중복 통합 권장
OrderListView.swift(156-194줄)과OrderDetailView.swift(208-245줄)에서statusText(),statusColor(),statusBdColor()세 함수가 동일하게 구현되어 있습니다. 공유 유틸리티(예:OrderStatusFormatter.swift)로 통합하여 유지보수 일관성을 높이는 것을 권장합니다. 현재 구조에서는 한쪽을 수정할 때 다른 쪽을 빼먹을 위험이 있습니다.StockMate/StockMate/app/feature/orders/ui/OrderView.swift (1)
32-38: 검색 화면에 InventoryViewModel 주입 여부 정합성
OrderRequestSearchView는 내부에서InventoryViewModel를 자체 생성합니다. 상위에서 로드한underLimitItems와 검색 화면 상태를 공유하려면 동일 인스턴스 주입(environmentObject)을 고려하세요. 현재 구조도 동작에는 문제 없으나, 상태 일관성 측면에서 개선 여지가 있습니다.StockMate/StockMate/app/feature/orders/data/OrderApi.swift (1)
119-139: URL 생성 방식을 URLComponents로 개선하는 것을 권장합니다.현재 문자열 결합 방식도 동작하지만,
URLComponents와URLQueryItem을 사용하면 더 안전하고 유지보수가 쉬운 코드가 됩니다.다음과 같이 리팩토링할 수 있습니다:
static func getMyOrderList( status: String? = nil, startDate: String? = nil, endDate: String? = nil, page: Int = 0, size: Int = 20 ) -> DataRequest { var components = URLComponents(string: ApiClient.baseURL + "api/v1/order/list/my")! var queryItems = [ URLQueryItem(name: "page", value: "\(page)"), URLQueryItem(name: "size", value: "\(size)") ] if let status = status, !status.isEmpty { queryItems.append(URLQueryItem(name: "status", value: status)) } if let startDate = startDate, !startDate.isEmpty { queryItems.append(URLQueryItem(name: "startDate", value: startDate)) } if let endDate = endDate, !endDate.isEmpty { queryItems.append(URLQueryItem(name: "endDate", value: endDate)) } components.queryItems = queryItems return ApiClient.shared.request(components.url!.absoluteString, method: .get) }StockMate/StockMate/app/feature/auth/ui/HomeView.swift (1)
130-182: 카테고리 매핑을 중앙 집중화하는 것을 권장합니다.현재 카테고리 이름이 문자열로 하드코딩되어 있어 유지보수가 어려울 수 있습니다. 카테고리가 추가되거나 변경될 경우 여러 곳을 수정해야 합니다.
다음과 같이 enum으로 리팩토링하는 것을 고려하세요:
enum PartCategory: String { case electric = "전기/램프" case engine = "엔진/미션" case chassis = "하체/바디" case interior = "내장/외장" case consumables = "기타소모품" var color: Color { switch self { case .electric: return .Hstatus1Bg case .engine: return .Hstatus2Bg case .chassis: return .Hstatus3Bg case .interior: return .Hstatus4Bg case .consumables: return .Hstatus5Bg } } var icon: String { switch self { case .electric: return "lightbulb" case .engine: return "cog" case .chassis: return "spanner" case .interior: return "chair" case .consumables: return "package" } } }StockMate/StockMate/app/feature/orders/data/OrderRepositoryImpl.swift (2)
12-60: 저장소 구현이 올바르게 되어 있습니다.
safeApi를 사용한 네트워크 호출과 에러 처리가 적절합니다. Line 55의 하드코딩된-1에러 코드는 상수로 정의하는 것이 좋지만, 현재 구현도 허용 가능합니다.
62-77: 변수 섀도잉에 주의하세요.Line 63에서 파라미터 이름(
request)과 동일한 이름의 로컬 변수를 선언하여 변수 섀도잉이 발생합니다. 혼란을 피하기 위해 다른 이름을 사용하는 것을 권장합니다.func createOrder(request: OrderRequest) async -> AppResult<OrderCreateResponseData> { - let request = OrderApi.createOrder(request) + let apiRequest = OrderApi.createOrder(request) - let result = await safeApi(request, decodeTo: ApiResponse<OrderCreateResponseData>.self) + let result = await safeApi(apiRequest, decodeTo: ApiResponse<OrderCreateResponseData>.self)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (5)
StockMate/StockMate/resources/Assets.xcassets/chair.imageset/chair.svgis excluded by!**/*.svgStockMate/StockMate/resources/Assets.xcassets/cog.imageset/cog.svgis excluded by!**/*.svgStockMate/StockMate/resources/Assets.xcassets/lightbulb.imageset/lightbulb.svgis excluded by!**/*.svgStockMate/StockMate/resources/Assets.xcassets/package.imageset/package.svgis excluded by!**/*.svgStockMate/StockMate/resources/Assets.xcassets/spanner.imageset/spanner.svgis excluded by!**/*.svg
📒 Files selected for processing (40)
StockMate/StockMate/ContentView.swift(1 hunks)StockMate/StockMate/app/core/components/CartCard.swift(1 hunks)StockMate/StockMate/app/core/components/CartInfoCard.swift(1 hunks)StockMate/StockMate/app/core/components/CartSummaryBar.swift(1 hunks)StockMate/StockMate/app/core/components/OrderRequestCardView.swift(1 hunks)StockMate/StockMate/app/core/components/RoundedCorner.swift(1 hunks)StockMate/StockMate/app/feature/auth/ui/HomeView.swift(6 hunks)StockMate/StockMate/app/feature/auth/ui/LoginView.swift(1 hunks)StockMate/StockMate/app/feature/cart/data/CartApi.swift(1 hunks)StockMate/StockMate/app/feature/cart/data/CartRepositoryImpl.swift(1 hunks)StockMate/StockMate/app/feature/cart/domain/CartRepositoryProtocol.swift(1 hunks)StockMate/StockMate/app/feature/cart/viewmodel/CartViewModel.swift(1 hunks)StockMate/StockMate/app/feature/inventory/data/InventoryApi.swift(2 hunks)StockMate/StockMate/app/feature/inventory/data/InventoryRepositoryImpl.swift(1 hunks)StockMate/StockMate/app/feature/inventory/domain/InventoryRepositoryProtocol.swift(2 hunks)StockMate/StockMate/app/feature/inventory/ui/IncomingScanView.swift(0 hunks)StockMate/StockMate/app/feature/inventory/ui/InventoryView.swift(3 hunks)StockMate/StockMate/app/feature/inventory/ui/LackListView.swift(1 hunks)StockMate/StockMate/app/feature/inventory/viewmodel/InventoryViewModel.swift(2 hunks)StockMate/StockMate/app/feature/orders/data/OrderApi.swift(1 hunks)StockMate/StockMate/app/feature/orders/data/OrderRepositoryImpl.swift(1 hunks)StockMate/StockMate/app/feature/orders/domain/OrderRepositoryProtocol.swift(1 hunks)StockMate/StockMate/app/feature/orders/ui/OrderCartView.swift(1 hunks)StockMate/StockMate/app/feature/orders/ui/OrderDetailView.swift(1 hunks)StockMate/StockMate/app/feature/orders/ui/OrderInfoView.swift(1 hunks)StockMate/StockMate/app/feature/orders/ui/OrderListView.swift(1 hunks)StockMate/StockMate/app/feature/orders/ui/OrderRequestSearchView.swift(1 hunks)StockMate/StockMate/app/feature/orders/ui/OrderResultView.swift(1 hunks)StockMate/StockMate/app/feature/orders/ui/OrderView.swift(1 hunks)StockMate/StockMate/app/feature/orders/ui/ReceiptView.swift(1 hunks)StockMate/StockMate/app/feature/orders/viewmodel/OrderDetailViewModel.swift(1 hunks)StockMate/StockMate/app/feature/orders/viewmodel/OrderViewModel.swift(1 hunks)StockMate/StockMate/app/feature/user/ui/ProfileView.swift(1 hunks)StockMate/StockMate/app/navigation/MainTabView.swift(2 hunks)StockMate/StockMate/resources/Assets.xcassets/chair.imageset/Contents.json(1 hunks)StockMate/StockMate/resources/Assets.xcassets/cog.imageset/Contents.json(1 hunks)StockMate/StockMate/resources/Assets.xcassets/lightbulb.imageset/Contents.json(1 hunks)StockMate/StockMate/resources/Assets.xcassets/package.imageset/Contents.json(1 hunks)StockMate/StockMate/resources/Assets.xcassets/spanner.imageset/Contents.json(1 hunks)StockMate/StockMate/resources/Color.swift(2 hunks)
💤 Files with no reviewable changes (1)
- StockMate/StockMate/app/feature/inventory/ui/IncomingScanView.swift
🧰 Additional context used
🧬 Code graph analysis (20)
StockMate/StockMate/app/feature/inventory/data/InventoryRepositoryImpl.swift (2)
StockMate/StockMate/app/feature/inventory/data/InventoryApi.swift (1)
getLackCountByCategory(91-94)StockMate/StockMate/app/core/common/SafeApi.swift (1)
safeApi(31-63)
StockMate/StockMate/app/feature/inventory/data/InventoryApi.swift (1)
StockMate/StockMate/app/feature/inventory/data/InventoryRepositoryImpl.swift (1)
getLackCountByCategory(49-52)
StockMate/StockMate/app/feature/cart/domain/CartRepositoryProtocol.swift (3)
StockMate/StockMate/app/feature/cart/data/CartApi.swift (4)
fetchCart(55-58)addToCart(61-67)updateCart(70-76)clearCart(79-82)StockMate/StockMate/app/feature/cart/viewmodel/CartViewModel.swift (4)
fetchCart(26-37)addToCart(40-49)updateCart(52-68)clearCart(71-79)StockMate/StockMate/app/feature/cart/data/CartRepositoryImpl.swift (4)
fetchCart(14-17)addToCart(19-24)updateCart(26-31)clearCart(33-36)
StockMate/StockMate/app/feature/inventory/viewmodel/InventoryViewModel.swift (2)
StockMate/StockMate/app/feature/inventory/data/InventoryApi.swift (1)
getLackCountByCategory(91-94)StockMate/StockMate/app/feature/inventory/data/InventoryRepositoryImpl.swift (1)
getLackCountByCategory(49-52)
StockMate/StockMate/app/feature/user/ui/ProfileView.swift (1)
StockMate/StockMate/app/feature/user/viewmodel/UserViewModel.swift (1)
loadUserInfo(22-42)
StockMate/StockMate/app/feature/orders/viewmodel/OrderDetailViewModel.swift (1)
StockMate/StockMate/app/feature/orders/data/OrderRepositoryImpl.swift (1)
fetchOrderDetail(46-60)
StockMate/StockMate/app/feature/orders/ui/OrderListView.swift (4)
StockMate/StockMate/app/feature/orders/ui/OrderDetailView.swift (4)
formatDate(202-206)statusText(208-219)statusBdColor(234-245)statusColor(221-232)StockMate/StockMate/app/feature/orders/viewmodel/OrderViewModel.swift (2)
loadOrders(27-51)cancelOrder(99-110)StockMate/StockMate/app/feature/orders/data/OrderApi.swift (1)
cancelOrder(161-166)StockMate/StockMate/app/feature/orders/data/OrderRepositoryImpl.swift (1)
cancelOrder(80-91)
StockMate/StockMate/app/feature/auth/ui/HomeView.swift (2)
StockMate/StockMate/app/feature/inventory/viewmodel/InventoryViewModel.swift (1)
loadLackCountByCategory(280-301)StockMate/StockMate/app/feature/user/viewmodel/UserViewModel.swift (1)
loadUserInfo(22-42)
StockMate/StockMate/app/feature/orders/ui/OrderInfoView.swift (3)
StockMate/StockMate/app/feature/cart/viewmodel/CartViewModel.swift (1)
quantity(91-93)StockMate/StockMate/app/feature/orders/data/OrderApi.swift (1)
createOrder(150-158)StockMate/StockMate/app/feature/orders/viewmodel/OrderViewModel.swift (1)
createOrder(54-79)
StockMate/StockMate/app/core/components/CartInfoCard.swift (1)
StockMate/StockMate/app/feature/cart/viewmodel/CartViewModel.swift (1)
quantity(91-93)
StockMate/StockMate/app/feature/inventory/ui/LackListView.swift (1)
StockMate/StockMate/app/feature/inventory/viewmodel/InventoryViewModel.swift (1)
loadUnderLimitList(99-135)
StockMate/StockMate/app/core/components/OrderRequestCardView.swift (1)
StockMate/StockMate/app/feature/cart/viewmodel/CartViewModel.swift (1)
quantity(91-93)
StockMate/StockMate/app/core/components/CartCard.swift (1)
StockMate/StockMate/app/feature/cart/viewmodel/CartViewModel.swift (1)
quantity(91-93)
StockMate/StockMate/app/feature/orders/viewmodel/OrderViewModel.swift (2)
StockMate/StockMate/app/feature/orders/data/OrderRepositoryImpl.swift (3)
fetchMyOrders(12-43)createOrder(62-77)cancelOrder(80-91)StockMate/StockMate/app/feature/orders/data/OrderApi.swift (2)
createOrder(150-158)cancelOrder(161-166)
StockMate/StockMate/app/feature/orders/data/OrderApi.swift (2)
StockMate/StockMate/app/feature/orders/data/OrderRepositoryImpl.swift (2)
createOrder(62-77)cancelOrder(80-91)StockMate/StockMate/app/feature/orders/viewmodel/OrderViewModel.swift (2)
createOrder(54-79)cancelOrder(99-110)
StockMate/StockMate/app/feature/orders/ui/OrderCartView.swift (2)
StockMate/StockMate/app/feature/cart/viewmodel/CartViewModel.swift (4)
quantity(91-93)increaseQuantity(121-142)decreaseQuantity(144-157)fetchCart(26-37)StockMate/StockMate/app/feature/cart/data/CartApi.swift (1)
fetchCart(55-58)
StockMate/StockMate/app/feature/inventory/domain/InventoryRepositoryProtocol.swift (2)
StockMate/StockMate/app/feature/inventory/data/InventoryApi.swift (1)
getLackCountByCategory(91-94)StockMate/StockMate/app/feature/inventory/data/InventoryRepositoryImpl.swift (1)
getLackCountByCategory(49-52)
StockMate/StockMate/app/feature/orders/ui/OrderDetailView.swift (5)
StockMate/StockMate/app/feature/orders/ui/OrderListView.swift (4)
formatDate(63-68)statusText(156-167)statusBdColor(182-193)statusColor(169-180)StockMate/StockMate/app/feature/orders/data/OrderApi.swift (1)
cancelOrder(161-166)StockMate/StockMate/app/feature/orders/data/OrderRepositoryImpl.swift (2)
cancelOrder(80-91)fetchOrderDetail(46-60)StockMate/StockMate/app/feature/orders/viewmodel/OrderViewModel.swift (1)
cancelOrder(99-110)StockMate/StockMate/app/feature/orders/viewmodel/OrderDetailViewModel.swift (1)
fetchOrderDetail(18-30)
StockMate/StockMate/app/feature/cart/data/CartRepositoryImpl.swift (2)
StockMate/StockMate/app/feature/cart/data/CartApi.swift (4)
fetchCart(55-58)addToCart(61-67)updateCart(70-76)clearCart(79-82)StockMate/StockMate/app/core/common/SafeApi.swift (1)
safeApi(31-63)
StockMate/StockMate/app/feature/orders/data/OrderRepositoryImpl.swift (4)
StockMate/StockMate/app/feature/orders/data/OrderApi.swift (4)
getMyOrderList(119-139)getOrderDetail(143-146)createOrder(150-158)cancelOrder(161-166)StockMate/StockMate/app/core/common/SafeApi.swift (1)
safeApi(31-63)StockMate/StockMate/app/feature/orders/viewmodel/OrderDetailViewModel.swift (1)
fetchOrderDetail(18-30)StockMate/StockMate/app/feature/orders/viewmodel/OrderViewModel.swift (2)
createOrder(54-79)cancelOrder(99-110)
🪛 SwiftLint (0.57.0)
StockMate/StockMate/app/feature/orders/ui/ReceiptView.swift
[Warning] 15-15: TODOs should be resolved (API 연결 후 주문 상세 페이지와 연결)
(todo)
🔇 Additional comments (32)
StockMate/StockMate/resources/Assets.xcassets/cog.imageset/Contents.json (1)
8-15: 2x와 3x 스케일 이미지 누락 확인 필요2x 및 3x 스케일 항목에
filename키가 없습니다. iOS 에셋 카탈로그에서는 일반적으로 다음 두 가지 방식 중 하나를 사용합니다:
- 각 스케일별 파일 제공:
cog.svg,cog@2x.svg,cog@3x.svg- 벡터 에셋의 자동 스케일링 활용: 단일 벡터 파일로 모든 스케일 생성
현재 구성에서는 1x만 파일이 있고 2x/3x는 없어서 불완전해 보입니다.
다음을 확인해 주세요:
cog@2x.svg및cog@3x.svg파일이 실제 imageset 폴더에 존재하는지 확인- 이 에셋이 벡터 자동 스케일링을 사용하는지, 아니면 개별 스케일 파일을 제공하는지 확인
- 동일하게 추가된 다른 에셋들(lightbulb, chair, package, spanner)의 구성과 일치하는지 확인
필요한 경우 누락된 스케일 이미지를 추가하거나 에셋 카탈로그 설정을 조정해 주세요.
StockMate/StockMate/resources/Assets.xcassets/lightbulb.imageset/Contents.json (1)
1-21: SVG 파일의 경우 1x 파일만으로 모든 스케일을 지원하므로 현재 구성이 정상입니다.SVG는 스케일 독립적이므로 다양한 화면 크기를 위해 2-3개의 이미지가 필요하지 않습니다. 이는 @1x, @2x, @3x 접미사를 사용하여 각 스케일의 개별 이미지를 생성해야 할 필요성을 제거합니다. Contents.json에서 2x와 3x 항목에 "filename" 필드가 없는 것은 SVG 자산의 표준적인 구성입니다. SVG 파일의 경우 Scales 값을 "Single Scale"로 변경합니다.
StockMate/StockMate/app/feature/orders/ui/OrderResultView.swift (1)
10-72: 전체 구조가 깔끔합니다!주문 완료 화면의 구현이 잘 되어 있습니다.
dismiss()를 사용한 확인 버튼은 제대로 작동하며, "홈으로 이동" 버튼의 placeholder는 PR 설명에서 언급된 대로 추후 작업 예정인 것으로 확인됩니다.StockMate/StockMate/resources/Assets.xcassets/package.imageset/Contents.json (1)
1-21: 문제없습니다!SVG 이미지 에셋의 경우 1x만 정의하고 2x, 3x는 비워두는 것이 정상입니다. 벡터 이미지는 자동으로 스케일링되기 때문입니다.
StockMate/StockMate/app/feature/cart/data/CartRepositoryImpl.swift (1)
12-37: 깔끔한 구현입니다!Repository 패턴을 올바르게 따르고 있으며, async/await와 safeApi를 통해 일관된 에러 처리를 수행하고 있습니다.
StockMate/StockMate/app/feature/orders/ui/ReceiptView.swift (1)
15-15: TODO 추적 확인API 연동 후 리팩토링 예정이 명시되어 있습니다. 현재 하드코딩된 테스트 데이터는 임시 구현으로 이해됩니다.
StockMate/StockMate/app/feature/cart/data/CartApi.swift (1)
53-83: API 구현이 명확합니다!REST API 엔드포인트가 명확하게 정의되어 있고, HTTP 메서드와 파라미터 인코딩이 적절하게 구성되어 있습니다.
StockMate/StockMate/ContentView.swift (1)
19-27: 임시 UI 코드 확인 필요이 HStack과 아이콘들이 개발/테스트 목적의 임시 코드인지 확인이 필요합니다. 프로덕션에 포함될 코드가 아니라면 제거하는 것이 좋습니다.
StockMate/StockMate/app/feature/inventory/viewmodel/InventoryViewModel.swift (2)
41-42: 적절한 상태 추가카테고리별 부족 재고 개수를 추적하기 위한 published 속성이 적절하게 추가되었습니다.
280-301: 일관된 패턴 적용기존 ViewModel의 다른 메서드들과 동일한 패턴을 따르고 있으며, 에러 처리와 로딩 상태 관리가 적절합니다.
StockMate/StockMate/app/feature/inventory/data/InventoryRepositoryImpl.swift (1)
48-52: 일관된 구현기존 repository 메서드들과 동일한 패턴을 따르는 깔끔한 구현입니다.
StockMate/StockMate/app/feature/inventory/domain/InventoryRepositoryProtocol.swift (1)
33-33: Protocol 메서드 추가 확인카테고리별 부족 재고 조회를 위한 메서드가 적절하게 추가되었습니다.
StockMate/StockMate/app/core/components/RoundedCorner.swift (1)
10-22: 구현이 정확하지만 UIKit 의존성이 있습니다.
RoundedCornerShape 구현이 올바르게 작동합니다. 다만UIBezierPath와UIRectCorner를 사용하여 UIKit에 의존하고 있습니다. iOS 전용 프로젝트라면 문제없지만, 향후 다른 플랫폼(macOS, watchOS 등)으로 확장할 경우 제약이 될 수 있습니다.StockMate/StockMate/app/feature/inventory/ui/InventoryView.swift (1)
92-92: 일관된 색상 테마 업데이트입니다.
Color.Secondary에서Color.Primary로의 변경이 전체적으로 일관되게 적용되었습니다. 디자인 시스템 개선의 일부로 보이며 UI 통일성을 향상시킵니다.Also applies to: 149-149, 165-165, 173-173
StockMate/StockMate/resources/Color.swift (1)
31-31: 새로운 색상 상수가 추가되었습니다.
GrayStroke및 홈 상태(Hstatus) 관련 색상들이 추가되어 디자인 시스템이 확장되었습니다. 네이밍이 기존 패턴을 따르고 있습니다.Also applies to: 84-95
StockMate/StockMate/app/navigation/MainTabView.swift (1)
11-11: CartViewModel 통합이 적절합니다.
@StateObject로CartViewModel을 선언하고OrderView에 전달하는 방식이 올바릅니다. 탭 레벨에서 장바구니 상태를 관리하면 탭 전환 시에도 상태가 유지됩니다.Also applies to: 22-22
StockMate/StockMate/app/feature/inventory/data/InventoryApi.swift (2)
91-94: 새로운 부족 재고 개수 조회 API가 추가되었습니다.카테고리별 부족 재고 개수를 조회하는 API 엔드포인트가 적절하게 구현되었습니다.
44-48: Based on the shell script output and web search results, I can now verify the concern:If category names aren't unique in the ForEach, SwiftUI cannot tell which item changed when comparing arrays before and after mutations, causing incorrect animations and state management behavior.
However, from the codebase search results, I see:
LackCountItemis used inInventoryViewModelas@Published var lackCounts: [LackCountItem] = []- No UI View file is shown using
lackCountsin a ForEach loop in the provided outputThe review comment raises a valid concern about the potential for non-unique identifiers, but the actual impact depends on:
- Whether
lackCountsis used in a ForEach in any View- Whether the backend API guarantees unique category names
카테고리 이름의 고유성을 보장하세요.
LackCountItem의id가categoryName에서 파생되어 있습니다. ForEach에서 중복된 식별자는 배열 변경 시 SwiftUI가 어떤 항목이 변경되었는지 판단할 수 없어, 애니메이션과 상태 관리에 문제를 일으킵니다.이 문제가 발생하지 않으려면:
- API 응답에서
categoryName의 고유성이 백엔드에서 보장되어야 합니다.- 또는 데이터 모델에 고유한 식별자(UUID 등)를 추가하여
id를 생성해야 합니다.StockMate/StockMate/app/core/components/CartInfoCard.swift (1)
23-29: 이미지 및 가격 표시 로직이 적절합니다.
AsyncImage를 사용한 비동기 이미지 로딩과 가격 계산 로직이 올바르게 구현되었습니다. nil 처리도 적절합니다.Also applies to: 46-46
StockMate/StockMate/app/core/components/CartSummaryBar.swift (1)
11-49: 장바구니 요약 바가 잘 구현되었습니다.
CartSummaryBar가 적절하게CartViewModel을 관찰하고, 장바구니가 존재하고 비어있지 않을 때만 표시됩니다.RoundedCorner를 사용한 상단 모서리 둥글기 처리와NavigationLink를 통한 장바구니 화면 이동이 잘 구현되어 있습니다.StockMate/StockMate/app/feature/orders/ui/OrderCartView.swift (2)
11-11: 장바구니 뷰가 적절하게 구현되었습니다.
CartViewModel을 관찰하고 비동기로 장바구니를 가져오는 로직이 올바릅니다..taskmodifier를 사용하여 뷰가 나타날 때 자동으로 장바구니를 로드하는 것도 적절합니다.Also applies to: 44-59
30-34: 이 리뷰 의견은 부정확합니다.검증 결과, 현재 구현은 의도된 설계이며 올바르게 작동합니다:
CartCard.swift와OrderRequestCardView.swift의 UI 로직에서onRemoveFromCart버튼(trash icon)은 quantity가 1일 때만 표시됩니다.- quantity > 1일 때는 감소(-) 버튼이 표시됩니다.
- quantity가 1인 상태에서 trash 버튼을 클릭하면
decreaseQuantity가 호출되고, 이는amount == 1조건에서 즉시 아이템을 제거합니다.따라서 사용자 입장에서는 trash 버튼이 나타날 때 클릭하면 즉시 삭제되며, 이는 기대되는 동작입니다. 별도의
removeFromCart메서드를 추가할 필요가 없습니다.Likely an incorrect or invalid review comment.
StockMate/StockMate/app/feature/orders/ui/OrderView.swift (1)
66-99: 검증 완료 — 가드 확인됨, 승인 유지원본 리뷰 코멘트의 평가가 정확합니다.
loadUnderLimitList함수는 라인 101에서guard !isLoading, (underLimitHasMore || reset) else { return }로 동시 로드 및 중복 데이터 로드를 방지하고 있습니다. 현재 구현은 안전합니다.StockMate/StockMate/app/feature/orders/data/OrderApi.swift (2)
13-112: 데이터 모델 구조가 잘 설계되었습니다.API 응답 및 요청 모델이 명확하게 정의되어 있으며,
Decodable/Encodable프로토콜 준수가 적절합니다.
142-158: 주문 생성 및 상세 조회 API 구현이 적절합니다.
JSONParameterEncoder를 사용한 POST 요청과 쿼리 파라미터를 사용한 GET 요청이 올바르게 구현되어 있습니다.StockMate/StockMate/app/feature/auth/ui/HomeView.swift (3)
14-14: 재고 부족 데이터 로딩이 적절하게 구현되었습니다.
InventoryViewModel을@StateObject로 초기화하고.task수정자에서 데이터를 로드하는 방식이 올바릅니다.inventoryViewModel이 내부적으로 에러 상태를 관리하고 있다고 가정하지만, 필요시 UI에 에러 상태를 표시하는 것을 고려하세요.Also applies to: 113-116
46-64: 검색 UI를 NavigationLink로 변경한 것이 좋은 개선입니다.전용 검색 화면으로 이동하도록 변경하여 사용자 경험이 향상되었습니다.
185-213: StatusItem 스타일링 개선이 적절합니다.더 둥근 모서리와 개선된 타이포그래피로 UI가 향상되었습니다.
StockMate/StockMate/app/feature/orders/viewmodel/OrderViewModel.swift (2)
10-51: 뷰모델 구조가 잘 설계되었습니다.
@MainActor사용으로 스레드 안전성이 보장되고, 프로토콜 기반 의존성 주입으로 테스트 가능성이 높아졌습니다.defer를 사용한 리소스 정리도 적절합니다.
99-110: 주문 취소 후 자동 새로고침이 잘 구현되었습니다.취소 성공 후
loadOrders()를 호출하여 UI를 즉시 업데이트하는 패턴이 좋습니다.StockMate/StockMate/app/feature/orders/ui/OrderInfoView.swift (2)
9-39: 열거형과 상태 관리가 명확하게 구현되었습니다.
PaymentType과ShippingDateOption열거형으로 선택 옵션을 타입 안전하게 관리하고 있으며,destinationView에서 옵셔널을 적절히 처리하고 있습니다.
292-362: 재사용 가능한 UI 컴포넌트가 잘 추출되었습니다.
RadioButtonRow와CustomDatePickerField가 깔끔하게 분리되어 있어 재사용성과 유지보수성이 높습니다.
| //테스트 계정 사용 위해 잠시 주석처리 | ||
| private func isValidForm() -> Bool { | ||
| emailError = isValidEmail(authViewModel.email) ? nil : "이메일 형식을 확인해주세요" | ||
| pwError = authViewModel.password.count >= 8 ? nil : "8자 이상 비밀번호를 입력해주세요" | ||
| return emailError == nil && pwError == nil | ||
| // emailError = isValidEmail(authViewModel.email) ? nil : "이메일 형식을 확인해주세요" | ||
| //pwError = authViewModel.password.count >= 8 ? nil : "8자 이상 비밀번호를 입력해주세요" | ||
| // return emailError == nil //&& pwError == nil | ||
| return true | ||
| } |
There was a problem hiding this comment.
로그인 유효성 검사를 우회하는 것은 보안 위험입니다.
현재 isValidForm()이 무조건 true를 반환하여 모든 입력 검증이 비활성화되어 있습니다. 이는 다음과 같은 문제를 야기합니다:
- 보안 문제: 빈 문자열이나 잘못된 형식의 이메일/비밀번호가 로그인 API로 전송될 수 있습니다
- 사용자 경험 저하:
emailError와pwError상태 변수가 정의되어 있지만 값이 할당되지 않아 사용자에게 검증 피드백이 제공되지 않습니다 - 유지보수성: 주석 처리된 코드는 코드베이스를 어지럽히고 실수로 프로덕션에 배포될 위험이 있습니다
테스트 계정을 위한 검증 우회가 필요하다면, 다음과 같은 안전한 대안을 사용하세요:
방법 1: 컴파일 조건부 사용 (권장)
- //테스트 계정 사용 위해 잠시 주석처리
private func isValidForm() -> Bool {
- // emailError = isValidEmail(authViewModel.email) ? nil : "이메일 형식을 확인해주세요"
- //pwError = authViewModel.password.count >= 8 ? nil : "8자 이상 비밀번호를 입력해주세요"
- // return emailError == nil //&& pwError == nil
+ #if DEBUG
+ // 디버그 빌드에서는 검증 생략 (테스트용)
return true
+ #else
+ emailError = isValidEmail(authViewModel.email) ? nil : "이메일 형식을 확인해주세요"
+ pwError = authViewModel.password.count >= 8 ? nil : "8자 이상 비밀번호를 입력해주세요"
+ return emailError == nil && pwError == nil
+ #endif
}방법 2: 환경 변수 또는 피처 플래그 사용
private func isValidForm() -> Bool {
+ // 테스트 모드가 활성화된 경우 검증 생략
+ if ProcessInfo.processInfo.environment["SKIP_LOGIN_VALIDATION"] == "true" {
+ return true
+ }
+
emailError = isValidEmail(authViewModel.email) ? nil : "이메일 형식을 확인해주세요"
pwError = authViewModel.password.count >= 8 ? nil : "8자 이상 비밀번호를 입력해주세요"
return emailError == nil && pwError == nil
}또한, 104-111번 줄의 "홈화면으로 이동" 버튼도 인증 흐름을 우회하고 있어 프로덕션 배포 전에 제거해야 합니다.
🤖 Prompt for AI Agents
In StockMate/StockMate/app/feature/auth/ui/LoginView.swift around lines 121-127,
isValidForm() currently always returns true which bypasses validation; restore
the original validation logic by re-enabling the emailError and pwError
assignments (use isValidEmail(authViewModel.email) ? nil : "이메일 형식을 확인해주세요" and
authViewModel.password.count >= 8 ? nil : "8자 이상 비밀번호를 입력해주세요") and return the
combined check (emailError == nil && pwError == nil). If you need a temporary
bypass for local testing, gate it behind a compile flag or runtime feature flag
(do not leave unconditional true). Also remove or gate the "홈화면으로 이동" bypass at
lines 104-111 so the authentication flow is not circumvented in production.
| func updateCart() async { | ||
| // ✅ items가 비면 clearCart 호출하고 return | ||
| if items.isEmpty { | ||
| await clearCart() | ||
| return | ||
| } | ||
|
|
||
| let requestItems = items.map { CartUpdateItem(partId: $0.partId, amount: $0.amount) } | ||
| let req = CartUpdateRequest(items: requestItems) | ||
|
|
||
| switch await repository.updateCart(request: req) { | ||
| case .success(let response): | ||
| self.cart = response.data | ||
| case .failure(let error): | ||
| handleError(error) | ||
| } | ||
| } |
There was a problem hiding this comment.
updateCart 성공 후 로컬 상태 미동기화(서버 정합성 위험)
서버가 수량/아이템/가격을 정규화할 수 있는데, 성공 시 self.cart만 갱신하고 items는 갱신하지 않습니다. 이후 UI와 합계가 어긋날 수 있습니다. 성공 시 fetchCart() 호출로 단일 소스(서버)로 정합성을 맞추세요.
- switch await repository.updateCart(request: req) {
- case .success(let response):
- self.cart = response.data
+ switch await repository.updateCart(request: req) {
+ case .success:
+ await fetchCart()🤖 Prompt for AI Agents
In StockMate/StockMate/app/feature/cart/viewmodel/CartViewModel.swift around
lines 52 to 68, after a successful repository.updateCart response you only
update self.cart but do not resync the local items array with the server,
risking UI/total mismatches; modify the success branch to call await fetchCart()
(or otherwise fetch and apply the canonical cart from the server) so the local
items and totals come from the single server source of truth and remain
consistent with self.cart.
| @StateObject private var inventoryViewModel = InventoryViewModel() | ||
| @State private var isFirstAppear = true | ||
|
|
||
| // 전달받는 초기 카테고리 | ||
| @State var selectedCategory: String | ||
| private let categories = ["전기/램프", "엔진/미션", "하체/바디", "내장/외장", "기타소모품"] |
There was a problem hiding this comment.
🧩 Analysis chain
InventoryViewModel 주입 일관화(테스트/상태 공유 용이)
현재 @StateObject private var inventoryViewModel = InventoryViewModel()로 자체 생성합니다. 상위(HomeView 등)와 상태를 공유하려면 @EnvironmentObject로 전환해 재사용/테스트 용이성을 높이세요.
- @StateObject private var inventoryViewModel = InventoryViewModel()
+ @EnvironmentObject var inventoryViewModel: InventoryViewModel검증 스크립트(해당 뷰를 직접 생성하는 호출부 파악):
🏁 Script executed:
#!/bin/bash
rg -nP 'LackListView\\(' -C2Length of output: 72
🏁 Script executed:
#!/bin/bash
# Search for LackListView instantiation - fix regex escaping
rg -nP 'LackListView\s*\(' -C2 --type=swiftLength of output: 992
🏁 Script executed:
#!/bin/bash
# Search for InventoryViewModel creation patterns
rg -nP '@StateObject.*InventoryViewModel|@EnvironmentObject.*InventoryViewModel' -C1 --type=swiftLength of output: 2022
🏁 Script executed:
#!/bin/bash
# Find HomeView and LackListView navigation/state patterns
rg -n 'HomeView|LackListView' --type=swift | head -30Length of output: 769
🏁 Script executed:
#!/bin/bash
# Find ALL LackListView instantiation sites (production code, not preview)
rg -n 'LackListView\s*\(' --type=swift -B2 -A2 | grep -v '^#Preview' -A10Length of output: 992
🏁 Script executed:
#!/bin/bash
# Check HomeView implementation details around line 137
sed -n '130,145p' StockMate/StockMate/app/feature/auth/ui/HomeView.swiftLength of output: 734
InventoryViewModel 주입 일관화 - 불완전한 제안 (양쪽 파일 수정 필요)
현재 LackListView에서 @StateObject로 자체 생성하고, HomeView에서도 별도 인스턴스를 생성합니다. Preview는 .environmentObject() 패턴을 사용하지만 실제 네비게이션(HomeView 라인 137)에서는 전달하지 않습니다.
@EnvironmentObject로 전환하려면 두 곳 모두 수정이 필요합니다:
- LackListView.swift 라인 11:
@StateObject→@EnvironmentObject - HomeView.swift 라인 137:
LackListView(selectedCategory: item.categoryName)→.environmentObject(inventoryViewModel)추가
현재 상태에서 LackListView만 변경하면 런타임 오류가 발생합니다.
🤖 Prompt for AI Agents
In StockMate/StockMate/app/feature/inventory/ui/LackListView.swift around lines
11 to 16, change the local creation of InventoryViewModel to use dependency
injection via @EnvironmentObject (replace @StateObject private var
inventoryViewModel = InventoryViewModel() with @EnvironmentObject var
inventoryViewModel: InventoryViewModel) so the view uses a shared instance; then
in StockMate/StockMate/app/feature/home/ui/HomeView.swift at line 137, when
creating LackListView(selectedCategory: item.categoryName) pass the existing
InventoryViewModel into the view hierarchy by appending
.environmentObject(inventoryViewModel) so the same instance is provided at
runtime and avoids preview-only wiring issues.
| static func cancelOrder(orderId: Int) -> DataRequest { | ||
| let url = ApiClient.baseURL + "api/v1/order/\(orderId)/cancel" | ||
| print("🚀 CancelOrder URL:", url) | ||
| return ApiClient.shared.request(url, method: .put) | ||
| .validate() // ✅ 서버 상태코드 확인 | ||
| } |
There was a problem hiding this comment.
디버그 print문 제거 및 validate() 사용 일관성 확보 필요
- Line 163의 print문은 프로덕션 코드에 남아있으면 안 됩니다. 제거하거나 적절한 로깅 시스템으로 대체하세요.
.validate()가cancelOrder에만 사용되고 다른 API 메서드에는 없어 일관성이 부족합니다. 모든 API 호출에 일관되게 적용하거나,safeApi에서 처리하는 것을 권장합니다.
static func cancelOrder(orderId: Int) -> DataRequest {
let url = ApiClient.baseURL + "api/v1/order/\(orderId)/cancel"
- print("🚀 CancelOrder URL:", url)
return ApiClient.shared.request(url, method: .put)
- .validate() // ✅ 서버 상태코드 확인
}📝 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.
| static func cancelOrder(orderId: Int) -> DataRequest { | |
| let url = ApiClient.baseURL + "api/v1/order/\(orderId)/cancel" | |
| print("🚀 CancelOrder URL:", url) | |
| return ApiClient.shared.request(url, method: .put) | |
| .validate() // ✅ 서버 상태코드 확인 | |
| } | |
| static func cancelOrder(orderId: Int) -> DataRequest { | |
| let url = ApiClient.baseURL + "api/v1/order/\(orderId)/cancel" | |
| return ApiClient.shared.request(url, method: .put) | |
| } |
🤖 Prompt for AI Agents
In StockMate/StockMate/app/feature/orders/data/OrderApi.swift around lines 161
to 166, remove the debug print("🚀 CancelOrder URL:", url) or replace it with
the app's logging facility (e.g., use the centralized logger at appropriate
level) and ensure .validate() usage is consistent across API methods—either add
.validate() to all ApiClient.request calls or remove it here and handle response
validation centrally in safeApi; update cancelOrder accordingly so production
code has no raw prints and validation behavior is uniform.
| func cancelOrder(orderId: Int) async -> AppResult<String> { | ||
| let request = OrderApi.cancelOrder(orderId: orderId) | ||
| let result = await safeApi(request, decodeTo: ApiResponse<String>.self) | ||
|
|
||
| switch result { | ||
| case .success(let response): | ||
| print("✅ 취소 성공:", response) | ||
| return .success(response.data ?? "success") | ||
| case .failure(let error): | ||
| return .failure(error) | ||
| } | ||
| } |
There was a problem hiding this comment.
프로덕션 코드에서 print문을 제거하세요.
Line 86의 print문은 디버깅용이므로 제거하거나 적절한 로깅 시스템으로 교체해야 합니다.
switch result {
case .success(let response):
- print("✅ 취소 성공:", response)
return .success(response.data ?? "success")📝 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.
| func cancelOrder(orderId: Int) async -> AppResult<String> { | |
| let request = OrderApi.cancelOrder(orderId: orderId) | |
| let result = await safeApi(request, decodeTo: ApiResponse<String>.self) | |
| switch result { | |
| case .success(let response): | |
| print("✅ 취소 성공:", response) | |
| return .success(response.data ?? "success") | |
| case .failure(let error): | |
| return .failure(error) | |
| } | |
| } | |
| func cancelOrder(orderId: Int) async -> AppResult<String> { | |
| let request = OrderApi.cancelOrder(orderId: orderId) | |
| let result = await safeApi(request, decodeTo: ApiResponse<String>.self) | |
| switch result { | |
| case .success(let response): | |
| return .success(response.data ?? "success") | |
| case .failure(let error): | |
| return .failure(error) | |
| } | |
| } |
🤖 Prompt for AI Agents
In StockMate/StockMate/app/feature/orders/data/OrderRepositoryImpl.swift around
lines 80 to 91, remove the debugging print on line 86 (print("✅ 취소 성공:",
response)) and replace it with a production-appropriate action: either delete
the statement entirely or use the app's logging facility (e.g. logger.info or
processLogger) to record the success with sanitized details; ensure the method
still returns .success(response.data ?? "success") and that no debug-only prints
remain in production code.
| let groupedOrders = Dictionary(grouping: orderViewModel.orders) { order in | ||
| order.createdAt.split(separator: "T").first ?? "" | ||
| } | ||
| .sorted { $0.key > $1.key } |
There was a problem hiding this comment.
컴파일 오류: Substring ↔ String 타입 불일치
split().first는 Substring?이며 ""는 String입니다. ?? 연산자 양쪽 타입이 달라 컴파일 에러가 납니다. String으로 변환해 키를 생성하세요.
- let groupedOrders = Dictionary(grouping: orderViewModel.orders) { order in
- order.createdAt.split(separator: "T").first ?? ""
- }
+ let groupedOrders = Dictionary(grouping: orderViewModel.orders) { order in
+ order.createdAt.split(separator: "T").first.map(String.init) ?? ""
+ }또는
- order.createdAt.split(separator: "T").first ?? ""
+ String(order.createdAt.split(separator: "T").first ?? Substring())(후자는 가독성이 떨어져 첫 번째 방식을 권장)
📝 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.
| let groupedOrders = Dictionary(grouping: orderViewModel.orders) { order in | |
| order.createdAt.split(separator: "T").first ?? "" | |
| } | |
| .sorted { $0.key > $1.key } | |
| let groupedOrders = Dictionary(grouping: orderViewModel.orders) { order in | |
| order.createdAt.split(separator: "T").first.map(String.init) ?? "" | |
| } | |
| .sorted { $0.key > $1.key } |
🤖 Prompt for AI Agents
In StockMate/StockMate/app/feature/orders/ui/OrderListView.swift around lines 30
to 33, the expression uses split().first which returns Substring? while the
fallback "" is a String, causing a type mismatch; wrap the optional Substring in
String(...) (or call .map(String.init)) so the key is a String (e.g., convert
the optional Substring to String before the nil-coalescing), ensuring the
Dictionary(grouping:) key type is String and the compiler error is resolved.
| private func generatePDF(type: PDFType) { | ||
| let content = receiptContent | ||
| let renderer = ImageRenderer(content: content) | ||
|
|
||
| let width: CGFloat | ||
| let height: CGFloat = 2000 | ||
|
|
||
| switch type { | ||
| case .a4: width = 595.2 // A4 width in pt | ||
| case .receipt80mm: width = 226.77 // 80mm in pt | ||
| } | ||
|
|
||
| renderer.scale = UIScreen.main.scale | ||
|
|
||
| if let image = renderer.uiImage { | ||
| let pdfDoc = PDFDocument() | ||
| let pdfPage = PDFPage(image: image) | ||
| pdfDoc.insert(pdfPage!, at: 0) | ||
|
|
||
| let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent("receipt.pdf") | ||
| if pdfDoc.write(to: tempURL) { | ||
| let av = UIActivityViewController(activityItems: [tempURL], applicationActivities: nil) | ||
| UIApplication.shared.windows.first?.rootViewController?.present(av, animated: true) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
iOS 15+ 호환성 문제: deprecated API 사용
Line 167에서 UIApplication.shared.windows를 사용하고 있는데, 이는 iOS 15부터 deprecated되었습니다. 최신 iOS 버전에서 정상 작동하지 않을 수 있습니다.
다음과 같이 수정하세요:
- let av = UIActivityViewController(activityItems: [tempURL], applicationActivities: nil)
- UIApplication.shared.windows.first?.rootViewController?.present(av, animated: true)
+ let av = UIActivityViewController(activityItems: [tempURL], applicationActivities: nil)
+ if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
+ let rootVC = windowScene.windows.first?.rootViewController {
+ rootVC.present(av, animated: true)
+ }📝 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.
| private func generatePDF(type: PDFType) { | |
| let content = receiptContent | |
| let renderer = ImageRenderer(content: content) | |
| let width: CGFloat | |
| let height: CGFloat = 2000 | |
| switch type { | |
| case .a4: width = 595.2 // A4 width in pt | |
| case .receipt80mm: width = 226.77 // 80mm in pt | |
| } | |
| renderer.scale = UIScreen.main.scale | |
| if let image = renderer.uiImage { | |
| let pdfDoc = PDFDocument() | |
| let pdfPage = PDFPage(image: image) | |
| pdfDoc.insert(pdfPage!, at: 0) | |
| let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent("receipt.pdf") | |
| if pdfDoc.write(to: tempURL) { | |
| let av = UIActivityViewController(activityItems: [tempURL], applicationActivities: nil) | |
| UIApplication.shared.windows.first?.rootViewController?.present(av, animated: true) | |
| } | |
| } | |
| } | |
| private func generatePDF(type: PDFType) { | |
| let content = receiptContent | |
| let renderer = ImageRenderer(content: content) | |
| let width: CGFloat | |
| let height: CGFloat = 2000 | |
| switch type { | |
| case .a4: width = 595.2 // A4 width in pt | |
| case .receipt80mm: width = 226.77 // 80mm in pt | |
| } | |
| renderer.scale = UIScreen.main.scale | |
| if let image = renderer.uiImage { | |
| let pdfDoc = PDFDocument() | |
| let pdfPage = PDFPage(image: image) | |
| pdfDoc.insert(pdfPage!, at: 0) | |
| let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent("receipt.pdf") | |
| if pdfDoc.write(to: tempURL) { | |
| let av = UIActivityViewController(activityItems: [tempURL], applicationActivities: nil) | |
| if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, | |
| let rootVC = windowScene.windows.first?.rootViewController { | |
| rootVC.present(av, animated: true) | |
| } | |
| } | |
| } | |
| } |
🤖 Prompt for AI Agents
In StockMate/StockMate/app/feature/orders/ui/ReceiptView.swift around lines 145
to 170, replace the deprecated UIApplication.shared.windows usage with the
modern scenes API: obtain the active UIWindowScene from
UIApplication.shared.connectedScenes, find its key window (windows.first {
$0.isKeyWindow } or windows.first where appropriate), then get its
rootViewController (or traverse presentedViewController to the top-most view
controller) and call present(_:animated:). Ensure you handle casting scenes to
UIWindowScene and safely unwrap optionals before presenting the
UIActivityViewController.
| func createOrder(request: OrderRequest) async { | ||
| let result = await repository.createOrder(request: request) | ||
|
|
||
| switch result { | ||
| case .success(let response): | ||
| self.createdOrderId = response.orderId | ||
| self.isOrderSuccess = true | ||
|
|
||
| case .failure(let error): | ||
| print("❌ 주문 실패:", error.message) | ||
| self.errorMessage = error.message | ||
| } | ||
|
|
||
| // switch result { | ||
| // case .success(let data): | ||
| // let id = data.orderId | ||
| // DispatchQueue.main.async { | ||
| // self.createdOrderId = id | ||
| // self.isOrderSuccess = true | ||
| // } | ||
| // | ||
| // case .failure(let error): | ||
| // print("❌ 주문 실패:", error.message) | ||
| // self.errorMessage = error.message | ||
| // } | ||
| } | ||
|
|
||
| // func createOrder(request: OrderRequest) async -> Bool { | ||
| // isLoading = true | ||
| // defer { isLoading = false } | ||
| // | ||
| // let result = await repository.createOrder(request: request) | ||
| // | ||
| // switch result { | ||
| // case .success(_): | ||
| // isOrderSuccess = true | ||
| // return true | ||
| // case .failure(let error): | ||
| // errorMessage = error.message | ||
| // print("❌ 주문 실패:", error.message) | ||
| // return false | ||
| // } | ||
| // } |
There was a problem hiding this comment.
주석 처리된 코드 제거 및 일관성 개선 필요
-
주석 처리된 코드 블록 제거: Lines 67-78과 81-96의 주석 처리된 코드는 제거해야 합니다. 버전 관리 시스템이 코드 이력을 관리하므로 주석으로 남길 필요가 없습니다.
-
디버그 print문 제거: Line 63의 print문은 프로덕션 코드에 남아있으면 안 됩니다.
-
로딩 상태 관리 일관성:
createOrder는isLoading상태를 관리하지 않지만cancelOrder는 관리합니다. 일관성을 위해 모든 비동기 작업에서 로딩 상태를 관리하는 것을 고려하세요.
func createOrder(request: OrderRequest) async {
+ isLoading = true
+ defer { isLoading = false }
+
let result = await repository.createOrder(request: request)
switch result {
case .success(let response):
self.createdOrderId = response.orderId
self.isOrderSuccess = true
case .failure(let error):
- print("❌ 주문 실패:", error.message)
self.errorMessage = error.message
}
-
-// switch result {
-// case .success(let data):
-// let id = data.orderId
-// DispatchQueue.main.async {
-// self.createdOrderId = id
-// self.isOrderSuccess = true
-// }
-//
-// case .failure(let error):
-// print("❌ 주문 실패:", error.message)
-// self.errorMessage = error.message
-// }
}
-
-// func createOrder(request: OrderRequest) async -> Bool {
-// isLoading = true
-// defer { isLoading = false }
-//
-// let result = await repository.createOrder(request: request)
-//
-// switch result {
-// case .success(_):
-// isOrderSuccess = true
-// return true
-// case .failure(let error):
-// errorMessage = error.message
-// print("❌ 주문 실패:", error.message)
-// return false
-// }
-// }📝 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.
| func createOrder(request: OrderRequest) async { | |
| let result = await repository.createOrder(request: request) | |
| switch result { | |
| case .success(let response): | |
| self.createdOrderId = response.orderId | |
| self.isOrderSuccess = true | |
| case .failure(let error): | |
| print("❌ 주문 실패:", error.message) | |
| self.errorMessage = error.message | |
| } | |
| // switch result { | |
| // case .success(let data): | |
| // let id = data.orderId | |
| // DispatchQueue.main.async { | |
| // self.createdOrderId = id | |
| // self.isOrderSuccess = true | |
| // } | |
| // | |
| // case .failure(let error): | |
| // print("❌ 주문 실패:", error.message) | |
| // self.errorMessage = error.message | |
| // } | |
| } | |
| // func createOrder(request: OrderRequest) async -> Bool { | |
| // isLoading = true | |
| // defer { isLoading = false } | |
| // | |
| // let result = await repository.createOrder(request: request) | |
| // | |
| // switch result { | |
| // case .success(_): | |
| // isOrderSuccess = true | |
| // return true | |
| // case .failure(let error): | |
| // errorMessage = error.message | |
| // print("❌ 주문 실패:", error.message) | |
| // return false | |
| // } | |
| // } | |
| func createOrder(request: OrderRequest) async { | |
| isLoading = true | |
| defer { isLoading = false } | |
| let result = await repository.createOrder(request: request) | |
| switch result { | |
| case .success(let response): | |
| self.createdOrderId = response.orderId | |
| self.isOrderSuccess = true | |
| case .failure(let error): | |
| self.errorMessage = error.message | |
| } | |
| } |
🤖 Prompt for AI Agents
In StockMate/StockMate/app/feature/orders/viewmodel/OrderViewModel.swift around
lines 54 to 96, remove the two commented-out code blocks (approx. lines 67–78
and 81–96) and the debug print on line 63; then update createOrder to manage
loading state consistently with cancelOrder by setting isLoading = true at the
start, ensuring isLoading is set to false when the async work finishes (use
defer or finally-style handling), and dispatch any UI-mutating assignments
(createdOrderId, isOrderSuccess, errorMessage) to the main thread.
| { | ||
| "idiom" : "universal", | ||
| "scale" : "2x" | ||
| }, | ||
| { | ||
| "idiom" : "universal", | ||
| "scale" : "3x" | ||
| } |
There was a problem hiding this comment.
2x와 3x 이미지 변형에 대한 파일명이 누락되었습니다.
2x 및 3x 스케일 변형에 "filename" 키가 없습니다. Xcode 에셋 카탈로그에서 여러 스케일을 정의할 때는 모든 변형에 대응하는 파일명을 지정해야 합니다. 현재 설정으로는 2x/3x 디스플레이가 있는 기기에서 에셋이 누락되거나 빌드 경고가 발생할 수 있습니다.
다음과 같이 수정하세요 (예시):
{
"images" : [
{
"filename" : "chair.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "chair@2x.svg",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "chair@3x.svg",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}또는 벡터 이미지(SVG)를 사용하는 경우, 하나의 파일명만으로 모든 스케일을 지원하도록 설정할 수 있습니다:
{
"images" : [
{
"filename" : "chair.svg",
"idiom" : "universal",
"scale" : "1x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}실제 이미지 파일들이 imageset 폴더에 존재하는지 확인해주세요.
🤖 Prompt for AI Agents
StockMate/StockMate/resources/Assets.xcassets/chair.imageset/Contents.json
around lines 8-15: the 2x and 3x image entries are missing the "filename" key,
causing missing assets or build warnings on scaled displays; add "filename"
entries for the 2x and 3x variants (e.g., chair@2x.png, chair@3x.png) matching
the actual files in the imageset, or if using a vector SVG provide a single
filename (e.g., chair.svg) for the 1x entry only and remove extra scale entries;
ensure the referenced files exist in the folder and update the JSON accordingly.
| { | ||
| "images" : [ | ||
| { | ||
| "filename" : "spanner.svg", | ||
| "idiom" : "universal", | ||
| "scale" : "1x" | ||
| }, | ||
| { | ||
| "idiom" : "universal", | ||
| "scale" : "2x" | ||
| }, | ||
| { | ||
| "idiom" : "universal", | ||
| "scale" : "3x" | ||
| } | ||
| ], | ||
| "info" : { | ||
| "author" : "xcode", | ||
| "version" : 1 | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
2x 및 3x 스케일에 대한 이미지 파일명이 누락되었습니다.
lightbulb 애셋과 동일한 패턴으로, 현재 asset catalog에서는 1x 스케일에만 "spanner.svg" 파일명이 명시되어 있고, 2x와 3x 스케일 항목에는 파일명이 지정되지 않았습니다. 이는 다양한 디바이스 해상도에서 렌더링 문제를 야기할 수 있습니다.
다음 중 하나를 확인해주세요:
- 2x/3x 해상도에 해당하는 이미지 파일이 실제로 존재하고, 자동 감지되도록 설정된 경우
- 1x 스케일만 지원하려면 2x/3x 항목을 제거해야 합니다
🏁 Script executed:
#!/bin/bash
# spanner asset 디렉토리 내 파일 확인
find "StockMate/StockMate/resources/Assets.xcassets/spanner.imageset" -type f | sortLength of output: 151
2x와 3x 스케일 항목을 Contents.json에서 제거하거나 해당 이미지 파일을 추가하세요.
검증 결과, spanner.imageset 디렉토리에는 spanner.svg 파일만 존재하며, 2x/3x 스케일용 이미지 파일이 없습니다. Contents.json에 2x/3x 항목이 있지만 파일명이 지정되지 않은 상태이므로, 렌더링 문제가 발생할 수 있습니다.
다음 중 하나를 선택하세요:
- 2x/3x 스케일용 이미지 파일(
spanner@2x.svg,spanner@3x.svg등)을 추가하고 Contents.json을 업데이트 - 1x 스케일만 지원하려면 Contents.json에서 2x/3x 항목을 제거
🤖 Prompt for AI Agents
In StockMate/StockMate/resources/Assets.xcassets/spanner.imageset Contents.json
(lines 1-21): Contents.json lists 2x and 3x image entries but only spanner.svg
exists in the imageset, causing missing-file/render issues; either add the
corresponding 2x/3x image files (e.g., spanner@2x.svg and spanner@3x.svg) and
set their "filename" fields in Contents.json, or remove the 2x/3x entries from
Contents.json so only the existing 1x entry remains—apply whichever option
matches intended asset support and keep filenames and scale values consistent.
📣 Related Issue
📝 Summary
Summary by CodeRabbit
릴리스 노트