From 9409328c70d0e3d0ad41efc49e8887f0b39b5c8c Mon Sep 17 00:00:00 2001 From: yashjagtap23 <108370176+yashjagtap23@users.noreply.github.com> Date: Tue, 18 Feb 2025 20:14:05 -0600 Subject: [PATCH 1/4] should display simple list of redeemed items - hopefully it works but can't check it - if it does work it may need to be reset everytime a new qr code is scanned so likely reset the array after success case --- HackIllinois/GoogleService-Info.plist | 2 +- .../HIScanPointsShopViewController.swift | 39 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/HackIllinois/GoogleService-Info.plist b/HackIllinois/GoogleService-Info.plist index 02d8fb8b..1ed57b64 100644 --- a/HackIllinois/GoogleService-Info.plist +++ b/HackIllinois/GoogleService-Info.plist @@ -3,7 +3,7 @@ API_KEY - placeholder + AlzaSyDbLOYdDFD5Ce8Eexl7uziX7mmRj4dYZi4 GCM_SENDER_ID 848675954156 PLIST_VERSION diff --git a/HackIllinois/ViewControllers/HIScanPointsShopViewController.swift b/HackIllinois/ViewControllers/HIScanPointsShopViewController.swift index 9e1184d8..7df764bf 100644 --- a/HackIllinois/ViewControllers/HIScanPointsShopViewController.swift +++ b/HackIllinois/ViewControllers/HIScanPointsShopViewController.swift @@ -41,6 +41,23 @@ class HIScanPointsShopViewController: HIBaseViewController { var currentUserID = "" var currentUserName = "" var dietaryString = "" + // Store redeemed items + private var redeemedItems: [String] = [] + + // UI label to display redeemed items + private let redeemedItemsLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.numberOfLines = 0 + label.textAlignment = .center + label.font = UIFont.systemFont(ofSize: 18, weight: .medium) + label.textColor = .white + label.backgroundColor = UIColor.black.withAlphaComponent(0.6) + label.layer.cornerRadius = 10 + label.clipsToBounds = true + label.isHidden = true // Initially hidden + return label + }() } // MARK: - UIViewController @@ -67,11 +84,21 @@ extension HIScanPointsShopViewController { observable.$selectedEventId.sink { eventID in self.selectedEventID = eventID }.store(in: &cancellables) + let staffButtonController = UIHostingController(rootView: HIStaffButtonView(observable: observable)) addChild(staffButtonController) staffButtonController.view.backgroundColor = .clear staffButtonController.view.frame = CGRect(x: 0, y: 100, width: Int(view.frame.maxX), height: 600) view.addSubview(staffButtonController.view) + + // 🔹 Add label to the staff UI + view.addSubview(redeemedItemsLabel) + NSLayoutConstraint.activate([ + redeemedItemsLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor), + redeemedItemsLabel.topAnchor.constraint(equalTo: staffButtonController.view.bottomAnchor, constant: 20), + redeemedItemsLabel.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.8), + redeemedItemsLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 50) + ]) } } view.addSubview(closeButton) @@ -205,6 +232,12 @@ extension HIScanPointsShopViewController: AVCaptureMetadataOutputObjectsDelegate var error = true switch status { case "Success": + + redeemedItems.append(itemName) // Store redeemed ite + // 🔹 Update UI when an item is redeemed + DispatchQueue.main.async { + self.updateRedeemedItemsUI() + } alertTitle = "\n\nPrize Obtained!" alertMessage = "\nYou have successfully redeemed \(itemName) at the Points Shop!" error = false @@ -316,4 +349,10 @@ extension HIScanPointsShopViewController: AVCaptureMetadataOutputObjectsDelegate } return values } + func updateRedeemedItemsUI() { + let redeemedList = redeemedItems.enumerated().map { "\($0 + 1). \($1)" }.joined(separator: "\n") + + redeemedItemsLabel.text = "Redeemed Items:\n" + redeemedList + redeemedItemsLabel.isHidden = redeemedItems.isEmpty // Show label only if items exist + } } From fef9e595e5a5f653749e3597987deffc323a3a97 Mon Sep 17 00:00:00 2001 From: anushkasankaran Date: Wed, 19 Feb 2025 11:30:12 -0600 Subject: [PATCH 2/4] Fixed API call to redeem cart --- HIAPI/Models/CartItem.swift | 15 ++++++++++++++- HIAPI/Models/Item.swift | 6 ------ HIAPI/Services/ShopService.swift | 14 ++------------ HackIllinois/GoogleService-Info.plist | 2 +- .../HIScanPointsShopViewController.swift | 12 +++++++++++- 5 files changed, 28 insertions(+), 21 deletions(-) diff --git a/HIAPI/Models/CartItem.swift b/HIAPI/Models/CartItem.swift index 04127e5d..1fb58635 100644 --- a/HIAPI/Models/CartItem.swift +++ b/HIAPI/Models/CartItem.swift @@ -24,9 +24,22 @@ public struct CartItemContainer: Decodable, APIReturnable { } } +public struct RedeemReturnItem: Codable, APIReturnable { + public let userId: String? + public let items: [RedeemItem]? // Return items upon success + public let error: String? + public let message: String? +} + +public struct RedeemItem: Codable, APIReturnable { + public let itemId: String + public let name: String + public let quantity: Int +} + public struct CartReturnItem: Codable, APIReturnable { - public let items: [String: Int]? // Return items upon success public let userId: String? + public let items: [String: Int]? // Return items upon success public let error: String? public let message: String? } diff --git a/HIAPI/Models/Item.swift b/HIAPI/Models/Item.swift index 6076d597..04418102 100644 --- a/HIAPI/Models/Item.swift +++ b/HIAPI/Models/Item.swift @@ -37,9 +37,3 @@ public struct Item: Codable, Hashable { public let imageURL: String } - -public struct RedeemItem: Codable, APIReturnable { - public let itemName: String? // Return itemName upon success - public let success: Bool - public let error: String? -} diff --git a/HIAPI/Services/ShopService.swift b/HIAPI/Services/ShopService.swift index 6b35abc5..ba7495db 100644 --- a/HIAPI/Services/ShopService.swift +++ b/HIAPI/Services/ShopService.swift @@ -22,23 +22,13 @@ public final class ShopService: BaseService { return APIRequest(service: self, endpoint: "shop/cart/", headers: headers, method: .GET) } - public static func redeemPrize(itemId: String, itemInstance: String, userToken: String) -> APIRequest { - let jsonBody: [String: Any] = [ - "itemId": itemId, - "instance": itemInstance - ] - let headers: HTTPParameters = ["Authorization": userToken] - - return APIRequest(service: self, endpoint: "shop/item/buy/", body: jsonBody, headers: headers, method: .POST) - } - - public static func redeemCart(qrCode: String, userToken: String) -> APIRequest { + public static func redeemCart(qrCode: String, userToken: String) -> APIRequest { let jsonBody: [String: Any] = [ "QRCode": qrCode ] let headers: HTTPParameters = ["Authorization": userToken] - return APIRequest(service: self, endpoint: "shop/cart/redeem/", body: jsonBody, headers: headers, method: .POST) + return APIRequest(service: self, endpoint: "shop/cart/redeem/", body: jsonBody, headers: headers, method: .POST) } public static func addToCart(itemId: String, userToken: String) -> APIRequest { diff --git a/HackIllinois/GoogleService-Info.plist b/HackIllinois/GoogleService-Info.plist index 1ed57b64..02d8fb8b 100644 --- a/HackIllinois/GoogleService-Info.plist +++ b/HackIllinois/GoogleService-Info.plist @@ -3,7 +3,7 @@ API_KEY - AlzaSyDbLOYdDFD5Ce8Eexl7uziX7mmRj4dYZi4 + placeholder GCM_SENDER_ID 848675954156 PLIST_VERSION diff --git a/HackIllinois/ViewControllers/HIScanPointsShopViewController.swift b/HackIllinois/ViewControllers/HIScanPointsShopViewController.swift index 7df764bf..d79dd73f 100644 --- a/HackIllinois/ViewControllers/HIScanPointsShopViewController.swift +++ b/HackIllinois/ViewControllers/HIScanPointsShopViewController.swift @@ -303,9 +303,11 @@ extension HIScanPointsShopViewController: AVCaptureMetadataOutputObjectsDelegate guard respondingToQRCodeFound else { return } let meta = metadataObjects.first as? AVMetadataMachineReadableCodeObject let code = meta?.stringValue ?? "" + let query = extractQueryValue(from: code) + print(code, query) guard let user = HIApplicationStateController.shared.user else { return } respondingToQRCodeFound = false - HIAPI.ShopService.redeemCart(qrCode: code, userToken: user.token) + HIAPI.ShopService.redeemCart(qrCode: query ?? "", userToken: user.token) .onCompletion { result in do { let (codeResult, _) = try result.get() @@ -327,6 +329,14 @@ extension HIScanPointsShopViewController: AVCaptureMetadataOutputObjectsDelegate .launch() } + func extractQueryValue(from url: String) -> String? { + guard let components = URLComponents(string: url), + let queryItem = components.queryItems?.first(where: { $0.name == "qr" }) else { + return nil + } + return queryItem.value + } + func decode(_ token: String) -> [String: AnyObject]? { let string = token.components(separatedBy: ".") if string.count == 1 { return nil } From 48a9e20b377cc37421453a10e3b4e7acd6286f21 Mon Sep 17 00:00:00 2001 From: anushkasankaran Date: Wed, 19 Feb 2025 11:56:12 -0600 Subject: [PATCH 3/4] Success/error popup on scanning --- .../HIScanPointsShopViewController.swift | 84 ++++++------------- 1 file changed, 27 insertions(+), 57 deletions(-) diff --git a/HackIllinois/ViewControllers/HIScanPointsShopViewController.swift b/HackIllinois/ViewControllers/HIScanPointsShopViewController.swift index d79dd73f..ba5e0c42 100644 --- a/HackIllinois/ViewControllers/HIScanPointsShopViewController.swift +++ b/HackIllinois/ViewControllers/HIScanPointsShopViewController.swift @@ -41,23 +41,6 @@ class HIScanPointsShopViewController: HIBaseViewController { var currentUserID = "" var currentUserName = "" var dietaryString = "" - // Store redeemed items - private var redeemedItems: [String] = [] - - // UI label to display redeemed items - private let redeemedItemsLabel: UILabel = { - let label = UILabel() - label.translatesAutoresizingMaskIntoConstraints = false - label.numberOfLines = 0 - label.textAlignment = .center - label.font = UIFont.systemFont(ofSize: 18, weight: .medium) - label.textColor = .white - label.backgroundColor = UIColor.black.withAlphaComponent(0.6) - label.layer.cornerRadius = 10 - label.clipsToBounds = true - label.isHidden = true // Initially hidden - return label - }() } // MARK: - UIViewController @@ -90,15 +73,6 @@ extension HIScanPointsShopViewController { staffButtonController.view.backgroundColor = .clear staffButtonController.view.frame = CGRect(x: 0, y: 100, width: Int(view.frame.maxX), height: 600) view.addSubview(staffButtonController.view) - - // 🔹 Add label to the staff UI - view.addSubview(redeemedItemsLabel) - NSLayoutConstraint.activate([ - redeemedItemsLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor), - redeemedItemsLabel.topAnchor.constraint(equalTo: staffButtonController.view.bottomAnchor, constant: 20), - redeemedItemsLabel.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.8), - redeemedItemsLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 50) - ]) } } view.addSubview(closeButton) @@ -225,37 +199,38 @@ extension HIScanPointsShopViewController: AVCaptureMetadataOutputObjectsDelegate } } - func handlePointsShopAlert(status: String, itemName: String) { - print(status) + func handlePointsShopAlert(code: Int, description: String, items: [RedeemItem]) { + print("Shop alert with code: \(code)") var alertTitle = "" var alertMessage = "" var error = true - switch status { - case "Success": - - redeemedItems.append(itemName) // Store redeemed ite - // 🔹 Update UI when an item is redeemed - DispatchQueue.main.async { - self.updateRedeemedItemsUI() + switch code { + case 0: + alertTitle = "\n\nSuccess!" + if items.isEmpty { + alertMessage += "\nAttendee cart is empty." + } else { + alertMessage = "\nAttendee has successfully redeemed:\n" + for item in items { + alertMessage += "\n\(item.name): \(item.quantity)" + } } - alertTitle = "\n\nPrize Obtained!" - alertMessage = "\nYou have successfully redeemed \(itemName) at the Points Shop!" error = false - case "invalidHTTPReponse(code: 404, description: \"forbidden\")": + case 404: alertTitle = "\n\nError!" - alertMessage = "\nUser has no attendee profile." + alertMessage = "\nShop item is not found." self.respondingToQRCodeFound = true - case "invalidHTTPReponse(code: 404, description: \"not found\")": - alertTitle = "\n\nError!" - alertMessage = "\nItem with itemId not found or already purchased." + case 402: + alertTitle = "\n\nInsufficient Funds!" + alertMessage = "\nAttendee does not have enough points to purchase." self.respondingToQRCodeFound = true - case "invalidHTTPReponse(code: 400, description: \"bad request\")": + case 400: alertTitle = "\n\nError!" - alertMessage = "\nYou have insufficient funds." + alertMessage = "\nInsufficient quantity in shop or QR is invalid/expired. Have attendee go back to cart and regenrate QR code." self.respondingToQRCodeFound = true default: alertTitle = "\n\nError!" - alertMessage = "\nSomething isn't quite right. Double check your coins amount and make sure you have the correct QR code." + alertMessage = "\nSomething isn't quite right. API returned: \(description)" self.respondingToQRCodeFound = true } // Create custom alert for points shop @@ -304,23 +279,24 @@ extension HIScanPointsShopViewController: AVCaptureMetadataOutputObjectsDelegate let meta = metadataObjects.first as? AVMetadataMachineReadableCodeObject let code = meta?.stringValue ?? "" let query = extractQueryValue(from: code) - print(code, query) guard let user = HIApplicationStateController.shared.user else { return } respondingToQRCodeFound = false HIAPI.ShopService.redeemCart(qrCode: query ?? "", userToken: user.token) .onCompletion { result in do { let (codeResult, _) = try result.get() - let status = codeResult.error - let itemName = "Hello" - NSLog(status ?? "Success") DispatchQueue.main.async { - self.handlePointsShopAlert(status: status ?? "Success", itemName: itemName) + self.handlePointsShopAlert(code: 0, description: "", items: codeResult.items ?? []) + } + } catch APIRequestError.invalidHTTPReponse(code: let code, description: let description) { + NSLog("Error info \(code): \(description)") + DispatchQueue.main.async { + self.handlePointsShopAlert(code: code, description: "", items: []) } } catch { NSLog("Error info: \(error)") DispatchQueue.main.async { [self] in - self.handlePointsShopAlert(status: "\(error)", itemName: "") + self.handlePointsShopAlert(code: -1, description: "unable to parse API error response", items: []) } } sleep(2) @@ -359,10 +335,4 @@ extension HIScanPointsShopViewController: AVCaptureMetadataOutputObjectsDelegate } return values } - func updateRedeemedItemsUI() { - let redeemedList = redeemedItems.enumerated().map { "\($0 + 1). \($1)" }.joined(separator: "\n") - - redeemedItemsLabel.text = "Redeemed Items:\n" + redeemedList - redeemedItemsLabel.isHidden = redeemedItems.isEmpty // Show label only if items exist - } } From d6528b9026fb75b1331c845bc30086e145a8271a Mon Sep 17 00:00:00 2001 From: anushkasankaran Date: Wed, 19 Feb 2025 20:09:35 -0600 Subject: [PATCH 4/4] Remove events from points shop scanner --- .../HIScanPointsShopViewController.swift | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/HackIllinois/ViewControllers/HIScanPointsShopViewController.swift b/HackIllinois/ViewControllers/HIScanPointsShopViewController.swift index ba5e0c42..3b58c033 100644 --- a/HackIllinois/ViewControllers/HIScanPointsShopViewController.swift +++ b/HackIllinois/ViewControllers/HIScanPointsShopViewController.swift @@ -62,18 +62,18 @@ extension HIScanPointsShopViewController { containerView.constrain(to: view, trailingInset: 0, leadingInset: 0) containerView.addSubview(previewView) setupCaptureSession() - if user.roles.contains(.STAFF) { - let observable = HIStaffButtonViewObservable() - observable.$selectedEventId.sink { eventID in - self.selectedEventID = eventID - }.store(in: &cancellables) - - let staffButtonController = UIHostingController(rootView: HIStaffButtonView(observable: observable)) - addChild(staffButtonController) - staffButtonController.view.backgroundColor = .clear - staffButtonController.view.frame = CGRect(x: 0, y: 100, width: Int(view.frame.maxX), height: 600) - view.addSubview(staffButtonController.view) - } +// if user.roles.contains(.STAFF) { +// let observable = HIStaffButtonViewObservable() +// observable.$selectedEventId.sink { eventID in +// self.selectedEventID = eventID +// }.store(in: &cancellables) +// +// let staffButtonController = UIHostingController(rootView: HIStaffButtonView(observable: observable)) +// addChild(staffButtonController) +// staffButtonController.view.backgroundColor = .clear +// staffButtonController.view.frame = CGRect(x: 0, y: 100, width: Int(view.frame.maxX), height: 600) +// view.addSubview(staffButtonController.view) +// } } view.addSubview(closeButton) closeButton.addTarget(self, action: #selector(didSelectCloseButton(_:)), for: .touchUpInside)