Manos Chorianopoulos

add retrieveCoupon functionality

......@@ -7,7 +7,7 @@
<key>Pods-SwiftWarplyFramework.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>1</integer>
<integer>0</integer>
</dict>
</dict>
</dict>
......
......@@ -7,7 +7,7 @@
<key>SwiftWarplyFramework.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
<integer>1</integer>
</dict>
</dict>
</dict>
......
......@@ -1675,6 +1675,77 @@ public final class WarplySDK {
}
}
/// Retrieve a coupon from a coupon set (offer)
/// - Parameters:
/// - couponSetUuid: The UUID of the coupon set (offer) to retrieve a coupon from
/// - completion: Completion handler with response dictionary containing coupon code and expiration
/// - failureCallback: Failure callback with error code
///
/// Response structure on success:
/// ```json
/// {
/// "msg": "Retrieved",
/// "result": {
/// "coupon": "FNMRE2E5FXHH",
/// "expiration": "2026-10-31 23:59:00"
/// },
/// "status": 1
/// }
/// ```
public func retrieveCoupon(couponSetUuid: String, completion: @escaping ([String: Any]?) -> Void, failureCallback: @escaping (Int) -> Void) {
Task {
do {
let response = try await networkService.retrieveCoupon(couponSetUuid: couponSetUuid)
await MainActor.run {
if response["status"] as? Int == 1 {
let dynatraceEvent = LoyaltySDKDynatraceEventModel()
dynatraceEvent._eventName = "custom_success_retrieve_coupon_loyalty"
dynatraceEvent._parameters = nil
self.postFrameworkEvent("dynatrace", sender: dynatraceEvent)
print("✅ [WarplySDK] Coupon retrieved successfully")
if let result = response["result"] as? [String: Any] {
print(" Coupon: \(result["coupon"] as? String ?? "unknown")")
print(" Expiration: \(result["expiration"] as? String ?? "unknown")")
}
completion(response)
} else {
let dynatraceEvent = LoyaltySDKDynatraceEventModel()
dynatraceEvent._eventName = "custom_error_retrieve_coupon_loyalty"
dynatraceEvent._parameters = nil
self.postFrameworkEvent("dynatrace", sender: dynatraceEvent)
failureCallback(-1)
}
}
} catch {
await MainActor.run {
self.handleError(error, context: "retrieveCoupon", endpoint: "retrieveCoupon", failureCallback: failureCallback)
}
}
}
}
/// Retrieve a coupon from a coupon set (async/await variant)
/// - Parameter couponSetUuid: The UUID of the coupon set (offer) to retrieve a coupon from
/// - Returns: Response dictionary containing coupon code and expiration
/// - Throws: WarplyError if the request fails
public func retrieveCoupon(couponSetUuid: String) async throws -> [String: Any] {
return try await withCheckedThrowingContinuation { continuation in
retrieveCoupon(couponSetUuid: couponSetUuid, completion: { response in
if let response = response {
continuation.resume(returning: response)
} else {
continuation.resume(throwing: WarplyError.networkError)
}
}, failureCallback: { errorCode in
continuation.resume(throwing: WarplyError.unknownError(errorCode))
})
}
}
/// Verify ticket for user authentication
public func verifyTicket(guid: String, ticket: String, completion: @escaping (VerifyTicketResponseModel?) -> Void) {
// Clear previous state
......
......@@ -89,6 +89,7 @@ public enum Endpoint {
// Coupon Operations
case validateCoupon(coupon: [String: Any])
case redeemCoupon(productId: String, productUuid: String, merchantId: String)
case retrieveCoupon(couponSetUuid: String)
// Profile
case getProfile
......@@ -137,7 +138,7 @@ public enum Endpoint {
return "/api/mobile/v2/{appUUID}/context/"
// Authenticated Context endpoints - /oauth/{appUUID}/context
case .getCampaignsPersonalized, .getCoupons, .getMarketPassDetails, .getProfile, .addCard, .getCards, .deleteCard, .getTransactionHistory, .getPointsHistory, .validateCoupon, .redeemCoupon:
case .getCampaignsPersonalized, .getCoupons, .getMarketPassDetails, .getProfile, .addCard, .getCards, .deleteCard, .getTransactionHistory, .getPointsHistory, .validateCoupon, .redeemCoupon, .retrieveCoupon:
return "/oauth/{appUUID}/context"
// Session endpoints - /api/session/{sessionUuid}
......@@ -166,7 +167,7 @@ public enum Endpoint {
switch self {
case .register, .changePassword, .resetPassword, .requestOtp, .verifyTicket, .refreshToken, .logout, .getCampaigns, .getCampaignsPersonalized,
.getCoupons, .getCouponSets, .getAvailableCoupons,
.getMarketPassDetails, .getProfile, .addCard, .getCards, .deleteCard, .getTransactionHistory, .getPointsHistory, .validateCoupon, .redeemCoupon, .getMerchants, .getMerchantCategories, .getArticles, .sendEvent, .sendDeviceInfo, .getCosmoteUser, .deiLogin:
.getMarketPassDetails, .getProfile, .addCard, .getCards, .deleteCard, .getTransactionHistory, .getPointsHistory, .validateCoupon, .redeemCoupon, .retrieveCoupon, .getMerchants, .getMerchantCategories, .getArticles, .sendEvent, .sendDeviceInfo, .getCosmoteUser, .deiLogin:
return .POST
case .getSingleCampaign, .getNetworkStatus:
return .GET
......@@ -383,6 +384,14 @@ public enum Endpoint {
]
]
case .retrieveCoupon(let couponSetUuid):
return [
"coupon": [
"action": "retrieve_coupon",
"coupon_set": couponSetUuid
]
]
// Merchants - using correct shops structure for DEI API
case .getMerchants(let language, let categories, let defaultShown, let center, let tags, let uuid, let distance, let parentUuids):
return [
......@@ -473,7 +482,7 @@ public enum Endpoint {
return .standardContext
// Authenticated Context - /oauth/{appUUID}/context
case .getCampaignsPersonalized, .getCoupons, .getMarketPassDetails, .getProfile, .addCard, .getCards, .deleteCard, .getTransactionHistory, .getPointsHistory, .validateCoupon, .redeemCoupon:
case .getCampaignsPersonalized, .getCoupons, .getMarketPassDetails, .getProfile, .addCard, .getCards, .deleteCard, .getTransactionHistory, .getPointsHistory, .validateCoupon, .redeemCoupon, .retrieveCoupon:
return .authenticatedContext
// Authentication - /oauth/{appUUID}/login, /oauth/{appUUID}/token
......@@ -515,7 +524,7 @@ public enum Endpoint {
return .standard
// Bearer Token Authentication (loyalty headers + Authorization: Bearer)
case .changePassword, .getCampaignsPersonalized, .getCoupons, .getMarketPassDetails, .getProfile, .addCard, .getCards, .deleteCard, .getTransactionHistory, .getPointsHistory, .validateCoupon, .redeemCoupon:
case .changePassword, .getCampaignsPersonalized, .getCoupons, .getMarketPassDetails, .getProfile, .addCard, .getCards, .deleteCard, .getTransactionHistory, .getPointsHistory, .validateCoupon, .redeemCoupon, .retrieveCoupon:
return .bearerToken
// Basic Authentication (loyalty headers + Authorization: Basic)
......
......@@ -1084,6 +1084,20 @@ extension NetworkService {
return response
}
/// Retrieve a coupon from a coupon set
/// - Parameter couponSetUuid: The UUID of the coupon set (offer) to retrieve a coupon from
/// - Returns: Response dictionary containing the retrieved coupon code and expiration
/// - Throws: NetworkError if request fails
public func retrieveCoupon(couponSetUuid: String) async throws -> [String: Any] {
print("🔄 [NetworkService] Retrieving coupon for coupon set: \(couponSetUuid)")
let endpoint = Endpoint.retrieveCoupon(couponSetUuid: couponSetUuid)
let response = try await requestRaw(endpoint)
print("✅ [NetworkService] Coupon retrieval request completed")
return response
}
// MARK: - Card Security Utilities
/// Mask card number for secure logging
......
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="23094" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="24506" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23084"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="24504"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
......@@ -55,10 +55,10 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="XaT-fU-eNh" userLabel="Main View">
<rect key="frame" x="0.0" y="59" width="393" height="793"/>
<rect key="frame" x="0.0" y="118" width="393" height="734"/>
<subviews>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" translatesAutoresizingMaskIntoConstraints="NO" id="YpC-uS-mhw">
<rect key="frame" x="0.0" y="0.0" width="393" height="793"/>
<rect key="frame" x="0.0" y="0.0" width="393" height="734"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="qrm-40-JLT" userLabel="Scroll Content View">
<rect key="frame" x="0.0" y="0.0" width="393" height="1177"/>
......@@ -74,14 +74,14 @@
<rect key="frame" x="239" y="17" width="138" height="20.666666666666671"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="tFA-1H-T5S" userLabel="Info Image View">
<rect key="frame" x="10" y="3.6666666666666723" width="13.666666666666664" height="13.333333333333336"/>
<rect key="frame" x="10" y="3.6666666666666581" width="13.666666666666664" height="13.333333333333336"/>
<constraints>
<constraint firstAttribute="height" constant="13.5" id="uQt-vY-kbk"/>
<constraint firstAttribute="width" constant="13.5" id="yzR-99-6Vp"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Περισσότερα" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="K34-LM-bC1" userLabel="Info Label">
<rect key="frame" x="28" y="-0.3333333333333286" width="100" height="21"/>
<rect key="frame" x="28" y="-0.33333333333334281" width="100" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
......@@ -144,7 +144,7 @@
<rect key="frame" x="0.0" y="0.0" width="345" height="54.333333333333336"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lEF-bh-hOi" userLabel="CouponCodeTitleLabel">
<rect key="frame" x="17" y="17" width="277" height="20.333333333333329"/>
<rect key="frame" x="17" y="17.000000000000057" width="277" height="20.333333333333329"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
......@@ -181,16 +181,16 @@
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="wT1-HY-mg9" userLabel="CouponCodeContentView">
<rect key="frame" x="0.0" y="54.333333333333371" width="345" height="80"/>
<rect key="frame" x="0.0" y="54.333333333333314" width="345" height="80"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="2Fn-5d-j8v" userLabel="CouponCodeValueLabel">
<rect key="frame" x="16.999999999999996" y="25.999999999999943" width="56.666666666666657" height="28"/>
<rect key="frame" x="16.999999999999996" y="26" width="56.666666666666657" height="28"/>
<fontDescription key="fontDescription" type="system" pointSize="24"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="AXc-Yh-5Ek" userLabel="CopyButtonImage">
<rect key="frame" x="84.666666666666671" y="23.666666666666686" width="33" height="33"/>
<rect key="frame" x="84.666666666666671" y="23.666666666666742" width="33" height="33"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="33" id="7ds-Ao-fBn"/>
......@@ -318,7 +318,7 @@
<nil key="highlightedColor"/>
</label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="C8k-TU-LiL" userLabel="TermsButtonArrowImage">
<rect key="frame" x="109.33333333333334" y="15" width="9" height="5"/>
<rect key="frame" x="109.33333333333334" y="15.000000000000114" width="9" height="5"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="5" id="ERb-FD-j0Y"/>
......@@ -349,7 +349,7 @@
</constraints>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ak8-Tc-k8X" userLabel="TermsLabel">
<rect key="frame" x="24" y="940.66666666666663" width="345" height="20.333333333333371"/>
<rect key="frame" x="24" y="940.66666666666674" width="345" height="20.333333333333371"/>
<constraints>
<constraint firstAttribute="height" constant="20.329999999999998" id="ZA1-TX-m9Y"/>
</constraints>
......
......@@ -42,9 +42,7 @@ import UIKit
@IBOutlet weak var termsLabel: UILabel!
@IBOutlet weak var termsLabelHeight: NSLayoutConstraint!
@IBOutlet weak var mapButton: UIButton!
@IBOutlet weak var websiteButton: UIButton!
@IBOutlet weak var redeemButton: UIButton!
var couponset: CouponSetItemModel?
private var isDetailsExpanded = false
......@@ -52,6 +50,9 @@ import UIKit
private var isCouponQRExpanded = false
private var isTermsExpanded = false
// Loader overlay
private var loaderOverlay: UIView?
var postImageURL: String? {
didSet {
if let url = postImageURL {
......@@ -97,21 +98,13 @@ import UIKit
termsButton.addTarget(self, action: #selector(toggleTerms), for: .touchUpInside)
termsLabelHeight.constant = 0
mapButton.titleLabel?.font = UIFont(name: "PingLCG-Bold", size: 16)
mapButton.setTitle("Καταστήματα κοντά μου", for: .normal)
mapButton.setTitleColor(UIColor(rgb: 0xFFFFFF), for: .normal)
mapButton.setTitleColor(UIColor(rgb: 0xFFFFFF), for: .highlighted)
mapButton.layer.cornerRadius = 4.0
mapButton.backgroundColor = UIColor(rgb: 0x000F1E)
websiteButton.titleLabel?.font = UIFont(name: "PingLCG-Bold", size: 16)
websiteButton.setTitle("Δες το website", for: .normal)
websiteButton.setTitleColor(UIColor(rgb: 0x000F1E), for: .normal)
websiteButton.setTitleColor(UIColor(rgb: 0x000F1E), for: .highlighted)
websiteButton.backgroundColor = .clear
websiteButton.layer.borderWidth = 1
websiteButton.layer.borderColor = UIColor(rgb: 0x000F1E).cgColor
websiteButton.layer.cornerRadius = 4.0
redeemButton.titleLabel?.font = UIFont(name: "PingLCG-Bold", size: 16)
redeemButton.setTitle("Απόκτησε το κουπόνι", for: .normal)
redeemButton.setTitleColor(UIColor(rgb: 0xFFFFFF), for: .normal)
redeemButton.setTitleColor(UIColor(rgb: 0xFFFFFF), for: .highlighted)
redeemButton.layer.cornerRadius = 4.0
redeemButton.backgroundColor = UIColor(rgb: 0x000F1E)
redeemButton.addTarget(self, action: #selector(redeemButtonTapped), for: .touchUpInside)
// Configure the view with offer data
if let offer = couponset {
......@@ -119,6 +112,79 @@ import UIKit
}
}
@objc private func redeemButtonTapped() {
guard let couponSetUuid = couponset?._uuid, !couponSetUuid.isEmpty else {
showErrorAlert(message: "Δεν βρέθηκε το αναγνωριστικό της προσφοράς.")
return
}
// Disable button to prevent double taps
redeemButton.isEnabled = false
showLoader()
WarplySDK.shared.retrieveCoupon(couponSetUuid: couponSetUuid, completion: { [weak self] response in
guard let self = self else { return }
self.hideLoader()
self.redeemButton.isEnabled = true
if let response = response,
let result = response["result"] as? [String: Any],
let couponCode = result["coupon"] as? String {
let expiration = result["expiration"] as? String ?? ""
self.showSuccessAlert(couponCode: couponCode, expiration: expiration)
} else {
self.showErrorAlert(message: "Η ενεργοποίηση του κουπονιού απέτυχε. Παρακαλώ δοκίμασε ξανά.")
}
}, failureCallback: { [weak self] errorCode in
guard let self = self else { return }
self.hideLoader()
self.redeemButton.isEnabled = true
self.showErrorAlert(message: "Η ενεργοποίηση του κουπονιού απέτυχε. Παρακαλώ δοκίμασε ξανά.")
})
}
// MARK: - Loader
private func showLoader() {
let overlay = UIView(frame: self.view.bounds)
overlay.backgroundColor = UIColor.black.withAlphaComponent(0.3)
overlay.autoresizingMask = [.flexibleWidth, .flexibleHeight]
let spinner = UIActivityIndicatorView(style: .large)
spinner.color = .white
spinner.center = overlay.center
spinner.autoresizingMask = [.flexibleTopMargin, .flexibleBottomMargin, .flexibleLeftMargin, .flexibleRightMargin]
spinner.startAnimating()
overlay.addSubview(spinner)
self.view.addSubview(overlay)
self.loaderOverlay = overlay
}
private func hideLoader() {
loaderOverlay?.removeFromSuperview()
loaderOverlay = nil
}
// MARK: - Alerts
private func showSuccessAlert(couponCode: String, expiration: String) {
var message = "Ο κωδικός κουπονιού σου:\n\(couponCode)"
if !expiration.isEmpty {
message += "\n\nΛήξη: \(expiration)"
}
let alert = UIAlertController(title: "Επιτυχία", message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
present(alert, animated: true, completion: nil)
}
private func showErrorAlert(message: String) {
let alert = UIAlertController(title: "Σφάλμα", message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
present(alert, animated: true, completion: nil)
}
private func setupUI(with couponset: CouponSetItemModel) {
// couponImage.image = UIImage(named: couponset._img_preview, in: Bundle.frameworkResourceBundle, compatibleWith: nil)
self.postImageURL = couponset._img_preview
......
......@@ -17,7 +17,7 @@
<outlet property="infoImage" destination="RK8-R9-SZK" id="ZjM-PX-DgT"/>
<outlet property="infoLabel" destination="oln-Zl-Mu1" id="OZb-dV-dlF"/>
<outlet property="infoView" destination="raJ-sJ-NGX" id="fol-KV-D65"/>
<outlet property="mapButton" destination="bTa-jT-Ufb" id="gH0-yo-in7"/>
<outlet property="redeemButton" destination="bTa-jT-Ufb" id="zz9-GB-5CD"/>
<outlet property="shareImage" destination="a1t-wH-dMg" id="Viw-KD-1JB"/>
<outlet property="subtitleLabel" destination="iWC-mi-WKw" id="peM-oa-eSS"/>
<outlet property="termsButton" destination="eGV-3B-aQo" id="Y0X-hP-SDU"/>
......@@ -28,7 +28,6 @@
<outlet property="termsLabelHeight" destination="9VB-9j-Q79" id="G0C-tp-L7M"/>
<outlet property="titleLabel" destination="aSO-pm-a0W" id="uyw-AX-ElA"/>
<outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
<outlet property="websiteButton" destination="lU8-hR-MF0" id="cj6-X8-D8Z"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
......@@ -102,7 +101,7 @@
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="iWC-mi-WKw" userLabel="Subtitle Label">
<rect key="frame" x="24" y="254.33333333333337" width="345" height="20.333333333333258"/>
<rect key="frame" x="24" y="254.33333333333334" width="345" height="20.333333333333286"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
......@@ -120,7 +119,7 @@
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="12M-YC-Cox" userLabel="TermsButtonView">
<rect key="frame" x="23.999999999999993" y="452.66666666666663" width="123.33333333333331" height="35"/>
<rect key="frame" x="23.999999999999993" y="527.66666666666663" width="123.33333333333331" height="35"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Όροι Χρήσης" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="uSf-AS-iFa" userLabel="TermsButtonTitleLabel">
<rect key="frame" x="0.0" y="5" width="101.33333333333333" height="25"/>
......@@ -160,7 +159,7 @@
</constraints>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="jug-xV-lmv" userLabel="TermsLabel">
<rect key="frame" x="24" y="497.66666666666663" width="345" height="20.333333333333371"/>
<rect key="frame" x="24" y="572.66666666666663" width="345" height="20.333333333333371"/>
<constraints>
<constraint firstAttribute="height" constant="20.329999999999998" id="9VB-9j-Q79"/>
</constraints>
......@@ -168,18 +167,10 @@
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="bTa-jT-Ufb" userLabel="MapButton">
<rect key="frame" x="24" y="558" width="345" height="55"/>
<constraints>
<constraint firstAttribute="height" constant="55" id="5AV-zY-O6v"/>
</constraints>
<state key="normal" title="Button"/>
<buttonConfiguration key="configuration" style="plain" title="Button"/>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="lU8-hR-MF0" userLabel="WebsiteButton">
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="bTa-jT-Ufb" userLabel="RedeemButton">
<rect key="frame" x="24" y="633" width="345" height="55"/>
<constraints>
<constraint firstAttribute="height" constant="55" id="aeV-F1-tWk"/>
<constraint firstAttribute="height" constant="55" id="5AV-zY-O6v"/>
</constraints>
<state key="normal" title="Button"/>
<buttonConfiguration key="configuration" style="plain" title="Button"/>
......@@ -192,7 +183,6 @@
<constraint firstAttribute="trailing" secondItem="50f-Uw-WmD" secondAttribute="trailing" id="5vF-D2-mBO"/>
<constraint firstItem="12M-YC-Cox" firstAttribute="top" secondItem="tOt-gP-Et5" secondAttribute="bottom" constant="50" id="6cC-FH-1Xw"/>
<constraint firstItem="50f-Uw-WmD" firstAttribute="leading" secondItem="mci-HQ-9iu" secondAttribute="leading" id="7Rd-U6-d9j"/>
<constraint firstItem="lU8-hR-MF0" firstAttribute="leading" secondItem="mci-HQ-9iu" secondAttribute="leading" constant="24" id="9R6-Lj-xSo"/>
<constraint firstItem="aSO-pm-a0W" firstAttribute="leading" secondItem="mci-HQ-9iu" secondAttribute="leading" constant="24" id="BIn-gA-oSw"/>
<constraint firstItem="a1t-wH-dMg" firstAttribute="leading" secondItem="kQb-LM-Zaa" secondAttribute="trailing" constant="11" id="DlZ-NN-vE2"/>
<constraint firstItem="12M-YC-Cox" firstAttribute="leading" secondItem="mci-HQ-9iu" secondAttribute="leading" constant="24" id="Eal-pm-W9t"/>
......@@ -211,12 +201,10 @@
<constraint firstItem="a1t-wH-dMg" firstAttribute="top" secondItem="50f-Uw-WmD" secondAttribute="bottom" constant="17" id="bSw-JI-jDV"/>
<constraint firstItem="iWC-mi-WKw" firstAttribute="leading" secondItem="mci-HQ-9iu" secondAttribute="leading" constant="24" id="dMq-cH-vOX"/>
<constraint firstItem="raJ-sJ-NGX" firstAttribute="top" secondItem="mci-HQ-9iu" secondAttribute="top" constant="17" id="g7b-Ql-ifv"/>
<constraint firstItem="lU8-hR-MF0" firstAttribute="top" secondItem="bTa-jT-Ufb" secondAttribute="bottom" constant="20" id="hGL-sc-uEw"/>
<constraint firstItem="aSO-pm-a0W" firstAttribute="top" secondItem="50f-Uw-WmD" secondAttribute="bottom" constant="23" id="jvc-rb-E6b"/>
<constraint firstItem="tOt-gP-Et5" firstAttribute="top" secondItem="PZ8-Go-A85" secondAttribute="bottom" constant="20" id="kQ3-4v-3Jp"/>
<constraint firstItem="jug-xV-lmv" firstAttribute="top" secondItem="12M-YC-Cox" secondAttribute="bottom" constant="9.9999999999998863" id="kSx-qT-pPa"/>
<constraint firstAttribute="bottom" secondItem="lU8-hR-MF0" secondAttribute="bottom" constant="46" id="l1j-ca-Uq9"/>
<constraint firstAttribute="trailing" secondItem="lU8-hR-MF0" secondAttribute="trailing" constant="24" id="lEm-My-Oi9"/>
<constraint firstAttribute="bottom" secondItem="bTa-jT-Ufb" secondAttribute="bottom" constant="46" id="l42-O3-RQt"/>
<constraint firstItem="bTa-jT-Ufb" firstAttribute="leading" secondItem="mci-HQ-9iu" secondAttribute="leading" constant="24" id="nR2-Py-LTi"/>
<constraint firstItem="jug-xV-lmv" firstAttribute="leading" secondItem="mci-HQ-9iu" secondAttribute="leading" constant="24" id="oUa-cz-J7N"/>
<constraint firstItem="tOt-gP-Et5" firstAttribute="leading" secondItem="mci-HQ-9iu" secondAttribute="leading" constant="24" id="xz5-C4-0GD"/>
......