From 2e6554d517ac84306c72054c0447da8ad6def32b Mon Sep 17 00:00:00 2001 From: blwxnhan Date: Mon, 17 Nov 2025 19:56:16 +0900 Subject: [PATCH 1/3] =?UTF-8?q?[Fix]=20MarketInfoCell=20=EC=B0=9C=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=EB=B9=84=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=8B=9C=20=EB=B9=84=ED=99=9C=EC=84=B1=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/Components/MarketInfoCell.swift | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/MarketPlace/View/Components/MarketInfoCell.swift b/MarketPlace/View/Components/MarketInfoCell.swift index e9e6937..adbbb56 100644 --- a/MarketPlace/View/Components/MarketInfoCell.swift +++ b/MarketPlace/View/Components/MarketInfoCell.swift @@ -2,6 +2,7 @@ import SwiftUI struct MarketInfoCell: View { @ObservedObject var viewModel: MarketInfoCellViewModel + @EnvironmentObject var loginViewModel: LoginViewModel @State var isBookmarked: Bool init(isBookmarked: Bool, viewModel: MarketInfoCellViewModel) { @@ -19,19 +20,22 @@ struct MarketInfoCell: View { width: 110, height: 110 ) - .frame(width: 110, height: .infinity) .clipShape(RoundedRectangle(cornerRadius: 4)) VStack(alignment: .leading) { Text(viewModel.marketData.marketName) + .lineLimit(1) .pretendardFont(size: 16, weight: .semibold) .foregroundColor(Color(hex: "333333")) + .padding(.bottom, 3) Text(viewModel.marketData.marketDescription) + .multilineTextAlignment(.leading) + .lineLimit(2) .pretendardFont(size: 13, weight: .medium) .foregroundColor(Color(hex: "7D7D7D")) - .multilineTextAlignment(.leading) + .padding(.bottom, 10) Spacer() @@ -43,23 +47,25 @@ struct MarketInfoCell: View { Text(viewModel.marketData.address) .pretendardFont(size: 13, weight: .medium) .foregroundColor(Color(hex: "333333")) + Spacer() - Button(action: { - Task { - await viewModel.postFavoriteMarket(marketId: viewModel.marketData.id) + if loginViewModel.isLoggedIn { + Button(action: { + Task { + await viewModel.postFavoriteMarket(marketId: viewModel.marketData.id) + } + }) { + Image(systemName: viewModel.marketData.isFavorite ? "bookmark.fill" : "bookmark") + .resizable() + .frame(width: 14, height: 20) + .foregroundColor(Color(hex: "#4B4B4B")) } - }) { - Image(systemName: viewModel.marketData.isFavorite ? "bookmark.fill" : "bookmark") - .resizable() - .frame(width: 14, height: 20) - .foregroundColor(Color(hex: "#4B4B4B")) } } } - .padding(.leading, 10) + .padding([.leading, .trailing], 10) .padding(5) - .frame(maxHeight: 110) } .padding(15) } From d536e92073dcd69a325fe89a4d065b3d030bdf78 Mon Sep 17 00:00:00 2001 From: blwxnhan Date: Mon, 17 Nov 2025 19:59:17 +0900 Subject: [PATCH 2/3] =?UTF-8?q?[Fix]=20MarketDetailView=20=EC=B0=9C=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=EB=B9=84=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=8B=9C=20=EB=B9=84=ED=99=9C=EC=84=B1=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/Main/View/MarketDetailView.swift | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/MarketPlace/View/Main/View/MarketDetailView.swift b/MarketPlace/View/Main/View/MarketDetailView.swift index 0f38a48..50a4deb 100644 --- a/MarketPlace/View/Main/View/MarketDetailView.swift +++ b/MarketPlace/View/Main/View/MarketDetailView.swift @@ -39,25 +39,29 @@ struct MarketDetailView: View { .foregroundColor(.black) .frame(maxWidth: .infinity, alignment: .topLeading) Spacer() - Button(action: { - isBookmarked.toggle() - - Task { - await viewModel.postFavoriteMarket(marketId: viewModel.id) - } - }) { - if let isFavorite = shop.isFavorite { - Image(systemName: isFavorite ? "bookmark.fill" : "bookmark") - .resizable() - .scaledToFit() - .foregroundColor(.black) - .frame(width: 16) - } else { - Image(systemName: "bookmark") - .resizable() - .scaledToFit() - .foregroundColor(.black) - .frame(width: 16) + + if loginViewModel.isLoggedIn { + Button(action: { + isBookmarked.toggle() + + Task { + await viewModel.postFavoriteMarket(marketId: viewModel.id) + } + + }) { + if let isFavorite = shop.isFavorite { + Image(systemName: isFavorite ? "bookmark.fill" : "bookmark") + .resizable() + .scaledToFit() + .foregroundColor(.black) + .frame(width: 16) + } else { + Image(systemName: "bookmark") + .resizable() + .scaledToFit() + .foregroundColor(.black) + .frame(width: 16) + } } } } From 85850e7a57f080fae7202b53116c558d87a4617c Mon Sep 17 00:00:00 2001 From: blwxnhan Date: Mon, 17 Nov 2025 21:08:38 +0900 Subject: [PATCH 3/3] =?UTF-8?q?[Fix]=20=EC=8B=A0=EA=B7=9C,=20=EC=9D=B8?= =?UTF-8?q?=EA=B8=B0=20=EC=BF=A0=ED=8F=B0=20=EB=8D=94=EB=B3=B4=EA=B8=B0=20?= =?UTF-8?q?=EC=BF=A0=ED=8F=B0=20=EB=8B=A4=EC=9A=B4=EB=A1=9C=EB=93=9C=20?= =?UTF-8?q?=ED=81=B4=EB=A6=AD=20=EC=8B=9C=20=ED=8C=9D=EC=97=85=20=ED=91=9C?= =?UTF-8?q?=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MarketPlace/Model/CouponBasicModel.swift | 1 + MarketPlace/Model/CouponModel.swift | 2 +- .../View/Components/CouponInfoCell.swift | 44 +++-- .../View/Main/View/CouponGetPopupView.swift | 13 +- .../View/Main/View/MarketDetailView.swift | 4 +- .../View/Main/View/NewEventDetailView.swift | 143 ++++++++------ .../View/Main/View/Top20DetailView.swift | 182 +++++++++++------- 7 files changed, 235 insertions(+), 154 deletions(-) diff --git a/MarketPlace/Model/CouponBasicModel.swift b/MarketPlace/Model/CouponBasicModel.swift index 1d65053..85cfb8f 100644 --- a/MarketPlace/Model/CouponBasicModel.swift +++ b/MarketPlace/Model/CouponBasicModel.swift @@ -19,6 +19,7 @@ struct CouponBasicModel: Codable, Identifiable { var thumbnail: String var isAvailable: Bool var isMemberIssued: Bool + var couponType: String var id: Int { couponId } } diff --git a/MarketPlace/Model/CouponModel.swift b/MarketPlace/Model/CouponModel.swift index 0c4cb06..63c8cce 100644 --- a/MarketPlace/Model/CouponModel.swift +++ b/MarketPlace/Model/CouponModel.swift @@ -36,7 +36,7 @@ struct CouponNewModel: Codable, Identifiable { } struct CouponValidModel: Codable, Identifiable { - let couponId: Int + var couponId: Int let couponName: String let couponDescription: String let deadLine: String? diff --git a/MarketPlace/View/Components/CouponInfoCell.swift b/MarketPlace/View/Components/CouponInfoCell.swift index aece7b3..76bac3c 100644 --- a/MarketPlace/View/Components/CouponInfoCell.swift +++ b/MarketPlace/View/Components/CouponInfoCell.swift @@ -2,15 +2,26 @@ import SwiftUI struct CouponInfoCell: View { @ObservedObject var viewModel: CouponInfoCellViewModel + @EnvironmentObject var loginViewModel: LoginViewModel + @Binding var isLoginRequiredPopupVisible: Bool + @Binding var isPopupVisible: Bool + @Binding var coupon: CouponBasicModel + var isMemberIssued: Bool { return viewModel.coupon.isMemberIssued } - init(viewModel: CouponInfoCellViewModel) { + init( + viewModel: CouponInfoCellViewModel, + isLoginRequiredPopupVisible: Binding, + isPopupVisible: Binding, + coupon: Binding + ) { self.viewModel = viewModel + self._isLoginRequiredPopupVisible = isLoginRequiredPopupVisible + self._isPopupVisible = isPopupVisible + self._coupon = coupon } - @State private var showDownloadSuccess = false - var body: some View { HStack(alignment: .top) { ShimmeringAsyncImage( @@ -26,6 +37,7 @@ struct CouponInfoCell: View { Text(viewModel.coupon.marketName) .pretendardFont(size: 14, weight: .semibold) .foregroundColor(Color(hex: "333333")) + .padding(.bottom, 3) Text(viewModel.coupon.couponName) .pretendardFont(size: 18, weight: .bold) @@ -42,16 +54,17 @@ struct CouponInfoCell: View { Text(viewModel.coupon.address) .pretendardFont(size: 13, weight: .medium) .foregroundColor(Color(hex: "333333")) + Spacer() Button(action: { - if !isMemberIssued { - Task { - let result = await viewModel.downloadCoupon(couponId: viewModel.coupon.couponId) - await MainActor.run { - showDownloadSuccess = result - } - } + if !loginViewModel.isLoggedIn { + isLoginRequiredPopupVisible = true + } + + else if !isMemberIssued && loginViewModel.isLoggedIn { + coupon = viewModel.coupon + isPopupVisible = true } }, label: { Image(isMemberIssued ? "download_used" : "download") @@ -64,18 +77,7 @@ struct CouponInfoCell: View { } .padding(.leading, 10) .padding(5) - .frame(maxHeight: 110) } .padding(15) - .alert("쿠폰 다운로드 완료", isPresented: $showDownloadSuccess) { - Button("확인", role: .cancel) { } - } message: { - Text("쿠폰이 성공적으로 발급되었습니다.") - } - .alert(viewModel.errorMessage ?? "오류", isPresented: .constant(viewModel.errorMessage != nil)) { - Button("확인", role: .cancel) { - viewModel.errorMessage = nil - } - } } } diff --git a/MarketPlace/View/Main/View/CouponGetPopupView.swift b/MarketPlace/View/Main/View/CouponGetPopupView.swift index 6e768e9..d0a05b7 100644 --- a/MarketPlace/View/Main/View/CouponGetPopupView.swift +++ b/MarketPlace/View/Main/View/CouponGetPopupView.swift @@ -2,7 +2,9 @@ import SwiftUI struct CouponGetPopupView: View { @Binding var isPopupVisible: Bool - @Binding var coupon: CouponValidModel + @Binding var couponId: Int + @Binding var couponType: String + @Binding var isMemberIssued: Bool @ObservedObject var viewModel = CouponPopupViewModel() @@ -62,22 +64,21 @@ struct CouponGetPopupView: View { .padding(.vertical, 32) .background(Color.white) .cornerRadius(8) -// .shadow(radius: 10) } } private func onConfirm() { - coupon.isMemberIssued = true + isMemberIssued = true isPopupVisible = false - switch coupon.couponType { + switch couponType { case "PAYBACK": Task { - await viewModel.downloadPaybackCoupons(couponId: coupon.couponId) + await viewModel.downloadPaybackCoupons(couponId: couponId) } case "GIFT": Task { - await viewModel.downloadCoupons(couponId: coupon.couponId) + await viewModel.downloadCoupons(couponId: couponId) } default: diff --git a/MarketPlace/View/Main/View/MarketDetailView.swift b/MarketPlace/View/Main/View/MarketDetailView.swift index 50a4deb..b27b43b 100644 --- a/MarketPlace/View/Main/View/MarketDetailView.swift +++ b/MarketPlace/View/Main/View/MarketDetailView.swift @@ -161,7 +161,9 @@ struct MarketDetailView: View { ) { CouponGetPopupView( isPopupVisible: $isPopupVisible, - coupon: couponBinding + couponId: couponBinding.couponId, + couponType: couponBinding.couponType, + isMemberIssued: couponBinding.isMemberIssued ).transition(.scale) } diff --git a/MarketPlace/View/Main/View/NewEventDetailView.swift b/MarketPlace/View/Main/View/NewEventDetailView.swift index a865db4..15f80d4 100644 --- a/MarketPlace/View/Main/View/NewEventDetailView.swift +++ b/MarketPlace/View/Main/View/NewEventDetailView.swift @@ -3,75 +3,108 @@ import SwiftUI struct NewEventDetailView: View { @Environment(\.presentationMode) var presentationMode + @EnvironmentObject var loginViewModel: LoginViewModel + @ObservedObject var viewModel = NewEventViewModel() + + @State private var isLoginRequiredPopupVisible: Bool = false + @State private var isPopupVisible: Bool = false + @State private var showLoginView: Bool = false + @State private var selectedCoupon: CouponBasicModel = CouponBasicModel(couponId: 0, couponName: "", marketId: 0, marketName: "", address: "", thumbnail: "", isAvailable: false, isMemberIssued: false, couponType: "") + var currentMonth: String var body: some View { - VStack(spacing: 0) { - Divider() - .background(Color.gray.opacity(0.5)) - ScrollView { - LazyVStack(spacing: 16) { - ForEach(Array(viewModel.newCoupons.enumerated()), id: \.offset) { index, coupon in - NavigationLink(destination: - MarketDetailView(marketId: coupon.marketId) - ) { - let coupon = CouponBasicModel( - couponId: coupon.couponId, - couponName: coupon.couponName, - marketId: coupon.marketId, - marketName: coupon.marketName, - address: coupon.address, - thumbnail: coupon.thumbnail, - isAvailable: coupon.isAvailable, - isMemberIssued: coupon.isMemberIssued - ) - - VStack { - CouponInfoCell( - viewModel: CouponInfoCellViewModel(coupon: coupon) + ZStack { + VStack(spacing: 0) { + Divider() + .background(Color.gray.opacity(0.5)) + ScrollView { + LazyVStack(spacing: 16) { + ForEach(Array(viewModel.newCoupons.enumerated()), id: \.offset) { index, coupon in + NavigationLink(destination: + MarketDetailView(marketId: coupon.marketId) + ) { + let coupon = CouponBasicModel( + couponId: coupon.couponId, + couponName: coupon.couponName, + marketId: coupon.marketId, + marketName: coupon.marketName, + address: coupon.address, + thumbnail: coupon.thumbnail, + isAvailable: coupon.isAvailable, + isMemberIssued: coupon.isMemberIssued, + couponType: coupon.couponType ) + + VStack { + CouponInfoCell( + viewModel: CouponInfoCellViewModel(coupon: coupon), + isLoginRequiredPopupVisible: $isLoginRequiredPopupVisible, + isPopupVisible: $isPopupVisible, + coupon: $selectedCoupon + ) + + Divider() + .background(Color.gray.opacity(0.5)) + .padding(.horizontal, -20) + } + }.onAppear { + guard index == viewModel.newCoupons.count - 1, + let lastId = viewModel.lastCouponId, + let lastCreated = viewModel.lastCreatedAt, + let lastCouponType = viewModel.lastCouponType + else { return } - Divider() - .background(Color.gray.opacity(0.5)) - .padding(.horizontal, -20) - } - }.onAppear { - guard index == viewModel.newCoupons.count - 1, - let lastId = viewModel.lastCouponId, - let lastCreated = viewModel.lastCreatedAt, - let lastCouponType = viewModel.lastCouponType - else { return } - - Task { - await viewModel.fetchLatestCoupons( - lastCreatedAt: lastCreated, - lastCouponId: lastId, - couponType: lastCouponType - ) + Task { + await viewModel.fetchLatestCoupons( + lastCreatedAt: lastCreated, + lastCouponId: lastId, + couponType: lastCouponType + ) + } } } } } } - } - .onAppear { - Task { - await viewModel.fetchLatestCoupons() + .onAppear { + Task { + await viewModel.fetchLatestCoupons() + } } - } - .navigationTitle("\(currentMonth) 신규 | 멤버십 혜택") - .navigationBarTitleDisplayMode(.inline) - .navigationBarBackButtonHidden(true) - .toolbar { - ToolbarItem(placement: .navigationBarLeading) { - Button(action: { - presentationMode.wrappedValue.dismiss() - }) { - Image(systemName: "chevron.backward") - .foregroundColor(.black) + .navigationTitle("\(currentMonth) 신규 | 멤버십 혜택") + .navigationBarTitleDisplayMode(.inline) + .navigationBarBackButtonHidden(true) + .toolbar { + ToolbarItem(placement: .navigationBarLeading) { + Button(action: { + presentationMode.wrappedValue.dismiss() + }) { + Image(systemName: "chevron.backward") + .foregroundColor(.black) + } } } + + if !loginViewModel.isLoggedIn && isLoginRequiredPopupVisible { + LoginRequriedPopup( + isPopupVisible: $isLoginRequiredPopupVisible, + showLogin: $showLoginView + ).transition(.scale) + } + + if isPopupVisible { + CouponGetPopupView( + isPopupVisible: $isPopupVisible, + couponId: $selectedCoupon.couponId, + couponType: $selectedCoupon.couponType, + isMemberIssued: $selectedCoupon.isMemberIssued + ).transition(.scale) + } + } + .fullScreenCover(isPresented: $showLoginView) { + LoginView() } } } diff --git a/MarketPlace/View/Main/View/Top20DetailView.swift b/MarketPlace/View/Main/View/Top20DetailView.swift index 791f8d7..8d1e3f4 100644 --- a/MarketPlace/View/Main/View/Top20DetailView.swift +++ b/MarketPlace/View/Main/View/Top20DetailView.swift @@ -3,93 +3,135 @@ import SwiftUI struct Top20DetailView: View { @Environment(\.presentationMode) var presentationMode + @EnvironmentObject var loginViewModel: LoginViewModel + @StateObject var viewModel = Top20DetailViewModel() + @State private var isLoginRequiredPopupVisible: Bool = false + @State private var isPopupVisible: Bool = false + @State private var showLoginView: Bool = false + @State private var selectedCoupon: CouponBasicModel = CouponBasicModel( + couponId: 0, + couponName: "", + marketId: 0, + marketName: "", + address: "", + thumbnail: "", + isAvailable: false, + isMemberIssued: false, + couponType: "" + ) + var body: some View { - VStack(spacing: 0) { - Divider() - .background(Color.gray.opacity(0.5)) - ScrollView { - LazyVStack(spacing: 16) { - ForEach(Array(viewModel.topCoupons.enumerated()), id: \.offset) { index, coupon in - NavigationLink(destination: - MarketDetailView(marketId: coupon.marketId) - ) { - let basic = CouponBasicModel( - couponId: coupon.couponId, - couponName: coupon.couponName, - marketId: coupon.marketId, - marketName: coupon.marketName, - address: coupon.address, - thumbnail: coupon.thumbnail, - isAvailable: coupon.isAvailable, - isMemberIssued: coupon.isMemberIssued - ) - - VStack { - CouponInfoCell( - viewModel: CouponInfoCellViewModel(coupon: basic) + ZStack { + VStack(spacing: 0) { + Divider() + .background(Color.gray.opacity(0.5)) + ScrollView { + LazyVStack(spacing: 16) { + ForEach(Array(viewModel.topCoupons.enumerated()), id: \.offset) { index, coupon in + NavigationLink(destination: + MarketDetailView(marketId: coupon.marketId) + ) { + let basic = CouponBasicModel( + couponId: coupon.couponId, + couponName: coupon.couponName, + marketId: coupon.marketId, + marketName: coupon.marketName, + address: coupon.address, + thumbnail: coupon.thumbnail, + isAvailable: coupon.isAvailable, + isMemberIssued: coupon.isMemberIssued, + couponType: coupon.couponType ) - Divider() - .background(Color.gray.opacity(0.5)) - .padding(.horizontal, -20) - } - } - .onAppear { - guard index == viewModel.topCoupons.count - 1, - let lastCouponType = viewModel.couponType, - let lastId = viewModel.lastCouponId - else { return } - - switch lastCouponType { - case "PAYBACK": - if let lastOrderNo = viewModel.lastOrderNo { - Task { - await viewModel.fetchCouponPopular( - lastIssuedCount: lastOrderNo, - lastCouponId: lastId, - couponType: lastCouponType - ) - } + VStack { + CouponInfoCell( + viewModel: CouponInfoCellViewModel(coupon: basic), + isLoginRequiredPopupVisible: $isLoginRequiredPopupVisible, + isPopupVisible: $isPopupVisible, + coupon: $selectedCoupon + ) + + Divider() + .background(Color.gray.opacity(0.5)) + .padding(.horizontal, -20) } + } + .onAppear { + guard index == viewModel.topCoupons.count - 1, + let lastCouponType = viewModel.couponType, + let lastId = viewModel.lastCouponId + else { return } - case "GIFT": - if let lastIssued = viewModel.lastIssuedCount { - Task { - await viewModel.fetchCouponPopular( - lastIssuedCount: lastIssued, - lastCouponId: lastId, - couponType: lastCouponType - ) + switch lastCouponType { + case "PAYBACK": + if let lastOrderNo = viewModel.lastOrderNo { + Task { + await viewModel.fetchCouponPopular( + lastIssuedCount: lastOrderNo, + lastCouponId: lastId, + couponType: lastCouponType + ) + } } + + case "GIFT": + if let lastIssued = viewModel.lastIssuedCount { + Task { + await viewModel.fetchCouponPopular( + lastIssuedCount: lastIssued, + lastCouponId: lastId, + couponType: lastCouponType + ) + } + } + + default: print("") + } - - default: print("") - } } } } } - } - .onAppear { - Task { - await viewModel.fetchCouponPopular() + .onAppear { + Task { + await viewModel.fetchCouponPopular() + } } - } - .navigationTitle("Top 20 인기 | 멤버십 혜택") - .navigationBarTitleDisplayMode(.inline) - .navigationBarBackButtonHidden(true) - .toolbar { - ToolbarItem(placement: .navigationBarLeading) { - Button(action: { - presentationMode.wrappedValue.dismiss() - }) { - Image(systemName: "chevron.backward") - .foregroundColor(.black) + .navigationTitle("Top 20 인기 | 멤버십 혜택") + .navigationBarTitleDisplayMode(.inline) + .navigationBarBackButtonHidden(true) + .toolbar { + ToolbarItem(placement: .navigationBarLeading) { + Button(action: { + presentationMode.wrappedValue.dismiss() + }) { + Image(systemName: "chevron.backward") + .foregroundColor(.black) + } } } + + if !loginViewModel.isLoggedIn && isLoginRequiredPopupVisible { + LoginRequriedPopup( + isPopupVisible: $isLoginRequiredPopupVisible, + showLogin: $showLoginView + ).transition(.scale) + } + + if isPopupVisible { + CouponGetPopupView( + isPopupVisible: $isPopupVisible, + couponId: $selectedCoupon.couponId, + couponType: $selectedCoupon.couponType, + isMemberIssued: $selectedCoupon.isMemberIssued + ).transition(.scale) + } + } + .fullScreenCover(isPresented: $showLoginView) { + LoginView() } } }