ソースを参照

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 ヶ月 前
コミット
cfb57d51e2
1 ファイル変更152 行追加28 行削除
  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) {