Sfoglia il codice sorgente

Add trust footer strip and links to the paywall.

Moves the trust row above the CTA and ensures the footer content compresses to avoid widening the window.

Co-authored-by: Cursor <cursoragent@cursor.com>
AhtashamShahzad1 1 mese fa
parent
commit
cfb57d51e2
1 ha cambiato i file con 152 aggiunte e 28 eliminazioni
  1. 152 28
      smart_printer/PaywallView.swift

+ 152 - 28
smart_printer/PaywallView.swift

@@ -335,6 +335,76 @@ private final class PaywallFooterLink: NSButton {
     }
 }
 
+// MARK: - Footer Trust Item
+
+private final class PaywallTrustItemView: NSView {
+    init(iconName: String, title: String, subtitle: String) {
+        super.init(frame: .zero)
+        translatesAutoresizingMaskIntoConstraints = false
+
+        let iconContainer = NSView()
+        iconContainer.translatesAutoresizingMaskIntoConstraints = false
+        iconContainer.wantsLayer = true
+        iconContainer.layer?.backgroundColor = AppTheme.blueLight.cgColor
+        iconContainer.layer?.cornerRadius = 10
+        iconContainer.layer?.masksToBounds = true
+
+        let icon = NSImageView()
+        icon.translatesAutoresizingMaskIntoConstraints = false
+        if let image = NSImage(systemSymbolName: iconName, accessibilityDescription: nil) {
+            let config = NSImage.SymbolConfiguration(pointSize: 11, weight: .semibold)
+            icon.image = image.withSymbolConfiguration(config)
+        }
+        icon.contentTintColor = AppTheme.navy
+
+        let titleLabel = NSTextField(labelWithString: title)
+        titleLabel.font = AppTheme.semiboldFont(size: 13)
+        titleLabel.textColor = AppTheme.navy
+        titleLabel.lineBreakMode = .byTruncatingTail
+        titleLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
+        titleLabel.translatesAutoresizingMaskIntoConstraints = false
+
+        let subtitleLabel = NSTextField(labelWithString: subtitle)
+        subtitleLabel.font = AppTheme.regularFont(size: 12)
+        subtitleLabel.textColor = AppTheme.textSecondary
+        subtitleLabel.lineBreakMode = .byTruncatingTail
+        subtitleLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
+        subtitleLabel.translatesAutoresizingMaskIntoConstraints = false
+
+        addSubview(iconContainer)
+        iconContainer.addSubview(icon)
+        addSubview(titleLabel)
+        addSubview(subtitleLabel)
+
+        NSLayoutConstraint.activate([
+            iconContainer.leadingAnchor.constraint(equalTo: leadingAnchor),
+            iconContainer.topAnchor.constraint(equalTo: topAnchor),
+            iconContainer.widthAnchor.constraint(equalToConstant: 20),
+            iconContainer.heightAnchor.constraint(equalToConstant: 20),
+
+            icon.centerXAnchor.constraint(equalTo: iconContainer.centerXAnchor),
+            icon.centerYAnchor.constraint(equalTo: iconContainer.centerYAnchor),
+            icon.widthAnchor.constraint(equalToConstant: 12),
+            icon.heightAnchor.constraint(equalToConstant: 12),
+
+            titleLabel.leadingAnchor.constraint(equalTo: iconContainer.trailingAnchor, constant: 8),
+            titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: 1),
+            titleLabel.trailingAnchor.constraint(equalTo: trailingAnchor),
+
+            subtitleLabel.leadingAnchor.constraint(equalTo: titleLabel.leadingAnchor),
+            subtitleLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 2),
+            subtitleLabel.trailingAnchor.constraint(equalTo: trailingAnchor),
+            subtitleLabel.bottomAnchor.constraint(equalTo: bottomAnchor),
+        ])
+
+        setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
+        setContentHuggingPriority(.defaultLow, for: .horizontal)
+    }
+
+    @available(*, unavailable)
+    required init?(coder: NSCoder) { nil }
+}
+
 // MARK: - Main Paywall Card
 
 final class PaywallView: NSView {
@@ -464,14 +534,16 @@ final class PaywallView: NSView {
         ctaButton.action = #selector(purchaseTapped)
         ctaButton.translatesAutoresizingMaskIntoConstraints = false
 
-        let footer = makeFooter()
+        let trustRow = makeTrustRow()
+        let footerLinks = makeFooterLinks()
 
         panel.addSubview(closeButton)
         panel.addSubview(title)
         panel.addSubview(subtitle)
         panel.addSubview(plansStack)
+        panel.addSubview(trustRow)
         panel.addSubview(ctaButton)
-        panel.addSubview(footer)
+        panel.addSubview(footerLinks)
 
         NSLayoutConstraint.activate([
             closeButton.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -20),
@@ -491,60 +563,112 @@ final class PaywallView: NSView {
             plansStack.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -28),
             plansStack.topAnchor.constraint(equalTo: subtitle.bottomAnchor, constant: 24),
 
+            trustRow.leadingAnchor.constraint(equalTo: panel.leadingAnchor, constant: 22),
+            trustRow.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -22),
+            trustRow.topAnchor.constraint(equalTo: plansStack.bottomAnchor, constant: 18),
+
             ctaButton.leadingAnchor.constraint(equalTo: title.leadingAnchor),
             ctaButton.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -28),
-            ctaButton.topAnchor.constraint(equalTo: plansStack.bottomAnchor, constant: 20),
+            ctaButton.topAnchor.constraint(equalTo: trustRow.bottomAnchor, constant: 16),
             ctaButton.heightAnchor.constraint(equalToConstant: 48),
+            ctaButton.bottomAnchor.constraint(lessThanOrEqualTo: footerLinks.topAnchor, constant: -14),
 
-            footer.centerXAnchor.constraint(equalTo: panel.centerXAnchor),
-            footer.bottomAnchor.constraint(equalTo: panel.bottomAnchor, constant: -20),
+            footerLinks.centerXAnchor.constraint(equalTo: panel.centerXAnchor),
+            footerLinks.bottomAnchor.constraint(equalTo: panel.bottomAnchor, constant: -20),
         ])
 
         return panel
     }
 
-    private func makeFooter() -> NSView {
+    private func makeTrustRow() -> NSView {
+        let securePayments = PaywallTrustItemView(
+            iconName: "shield.fill",
+            title: "Secure Payments",
+            subtitle: "Your payment is 100% secure"
+        )
+        let cancelAnytime = PaywallTrustItemView(
+            iconName: "arrow.counterclockwise",
+            title: "Cancel Anytime",
+            subtitle: "No commitment, cancel anytime."
+        )
+        let support = PaywallTrustItemView(
+            iconName: "headphones",
+            title: "24/7 Support",
+            subtitle: "We're here to help you anytime."
+        )
+        let privacyFirst = PaywallTrustItemView(
+            iconName: "lock.fill",
+            title: "Privacy First",
+            subtitle: "Your data is safe with us."
+        )
+
+        let trustStack = NSStackView(views: [securePayments, cancelAnytime, support, privacyFirst])
+        trustStack.orientation = .horizontal
+        trustStack.distribution = .fillEqually
+        trustStack.spacing = 16
+        trustStack.alignment = .top
+        trustStack.translatesAutoresizingMaskIntoConstraints = false
+        trustStack.wantsLayer = true
+        trustStack.layer?.backgroundColor = NSColor.white.cgColor
+        trustStack.layer?.cornerRadius = 12
+        trustStack.layer?.borderWidth = 1
+        trustStack.layer?.borderColor = AppTheme.paywallBorder.cgColor
+        trustStack.layer?.masksToBounds = true
+        trustStack.edgeInsets = NSEdgeInsets(top: 12, left: 14, bottom: 12, right: 14)
+        trustStack.translatesAutoresizingMaskIntoConstraints = false
+
+        return trustStack
+    }
+
+    private func makeFooterLinks() -> NSView {
         let container = NSView()
         container.translatesAutoresizingMaskIntoConstraints = false
 
-        let restoreLink = PaywallFooterLink(title: "Restore Purchases")
+        let manageSubscriptionLink = PaywallFooterLink(title: "Manage Subscription")
+        let restoreLink = PaywallFooterLink(title: "Restore Purchase")
         restoreLink.target = self
         restoreLink.action = #selector(restoreTapped)
 
         let privacyLink = PaywallFooterLink(title: "Privacy Policy")
         let termsLink = PaywallFooterLink(title: "Terms of Service")
+        let supportLink = PaywallFooterLink(title: "Support")
 
-        let dot1 = makeFooterDot()
-        let dot2 = makeFooterDot()
+        let separator1 = makeFooterSeparator()
+        let separator2 = makeFooterSeparator()
+        let separator3 = makeFooterSeparator()
+        let separator4 = makeFooterSeparator()
 
-        let stack = NSStackView(views: [restoreLink, dot1, privacyLink, dot2, termsLink])
-        stack.orientation = .horizontal
-        stack.spacing = 6
-        stack.alignment = .centerY
-        stack.translatesAutoresizingMaskIntoConstraints = false
+        let linksStack = NSStackView(views: [
+            manageSubscriptionLink, separator1, restoreLink, separator2, privacyLink, separator3, termsLink, separator4, supportLink,
+        ])
+        linksStack.orientation = .horizontal
+        linksStack.spacing = 8
+        linksStack.alignment = .centerY
+        linksStack.distribution = .fillProportionally
+        linksStack.translatesAutoresizingMaskIntoConstraints = false
 
-        container.addSubview(stack)
+        container.addSubview(linksStack)
         NSLayoutConstraint.activate([
-            stack.leadingAnchor.constraint(equalTo: container.leadingAnchor),
-            stack.trailingAnchor.constraint(equalTo: container.trailingAnchor),
-            stack.topAnchor.constraint(equalTo: container.topAnchor),
-            stack.bottomAnchor.constraint(equalTo: container.bottomAnchor),
+            linksStack.centerXAnchor.constraint(equalTo: container.centerXAnchor),
+            linksStack.leadingAnchor.constraint(greaterThanOrEqualTo: container.leadingAnchor),
+            linksStack.trailingAnchor.constraint(lessThanOrEqualTo: container.trailingAnchor),
+            linksStack.topAnchor.constraint(equalTo: container.topAnchor),
+            linksStack.bottomAnchor.constraint(equalTo: container.bottomAnchor)
         ])
 
         return container
     }
 
-    private func makeFooterDot() -> NSView {
-        let dot = NSView()
-        dot.translatesAutoresizingMaskIntoConstraints = false
-        dot.wantsLayer = true
-        dot.layer?.backgroundColor = AppTheme.textSecondary.cgColor
-        dot.layer?.cornerRadius = 1.5
+    private func makeFooterSeparator() -> NSView {
+        let separator = NSView()
+        separator.translatesAutoresizingMaskIntoConstraints = false
+        separator.wantsLayer = true
+        separator.layer?.backgroundColor = AppTheme.paywallBorder.cgColor
         NSLayoutConstraint.activate([
-            dot.widthAnchor.constraint(equalToConstant: 3),
-            dot.heightAnchor.constraint(equalToConstant: 3),
+            separator.widthAnchor.constraint(equalToConstant: 1),
+            separator.heightAnchor.constraint(equalToConstant: 12),
         ])
-        return dot
+        return separator
     }
 
     private func selectPlan(_ plan: PaywallPlan) {