Przeglądaj źródła

Allow two free Home AI job search messages before paywall.

Free users can run the Indeed-oriented AI job search on Home for up to two
user-initiated requests (typed searches, Role/Company/Skill shortcuts, and
“Show more jobs” each count). After that, the Premium sheet appears as for
other Pro-only flows. Pro subscribers remain unlimited. Usage is persisted in
UserDefaults so the cap survives app restarts. CV Maker, profiles, and filled
CV preview stay behind full Pro access unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
Uzair Tahir 2 miesięcy temu
rodzic
commit
00332297e3
1 zmienionych plików z 35 dodań i 3 usunięć
  1. 35 3
      App for Indeed/Views/DashboardView.swift

+ 35 - 3
App for Indeed/Views/DashboardView.swift

@@ -19,6 +19,26 @@ private enum PremiumSheetLayout {
     static let overscanExtraTop: CGFloat = 0.5
 }
 
+/// Free-tier cap for Home AI job search (user messages only; Pro is unlimited).
+private enum FreeTierJobSearchQuota {
+    static let maxUserMessages = 2
+    private static let userDefaultsKey = "com.appforindeed.freeJobSearchUserMessageCount"
+
+    static var userMessageCount: Int {
+        get { UserDefaults.standard.integer(forKey: userDefaultsKey) }
+        set { UserDefaults.standard.set(newValue, forKey: userDefaultsKey) }
+    }
+
+    static func canSendAnotherMessage(isProActive: Bool) -> Bool {
+        isProActive || userMessageCount < maxUserMessages
+    }
+
+    static func recordUserMessageSent(isProActive: Bool) {
+        guard !isProActive else { return }
+        userMessageCount += 1
+    }
+}
+
 private enum SettingsAppearanceID {
     static let section = "dashboard.settings.section"
     static let sectionHeader = "dashboard.settings.sectionHeader"
@@ -597,6 +617,17 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
         return true
     }
 
+    /// Home AI job search: Pro is unlimited; free users may send up to `FreeTierJobSearchQuota.maxUserMessages` user messages.
+    @discardableResult
+    private func ensureProAccessForJobSearch() -> Bool {
+        if SubscriptionStore.shared.isProActive { return true }
+        guard FreeTierJobSearchQuota.canSendAnotherMessage(isProActive: false) else {
+            presentPremiumPlansSheet()
+            return false
+        }
+        return true
+    }
+
     private func presentPremiumPlansSheet() {
         guard let hostWindow = window else { return }
 
@@ -1998,7 +2029,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
     }
 
     @objc private func didSubmitSearch() {
-        guard ensureProAccess() else { return }
+        guard ensureProAccessForJobSearch() else { return }
         let prompt = jobKeywordsField.stringValue.trimmingCharacters(in: .whitespacesAndNewlines)
         guard !prompt.isEmpty, !isAwaitingResponse else { return }
         let isContinuation = isContinuationPrompt(prompt)
@@ -2116,7 +2147,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
     }
 
     private func focusSearchField(seed: String) {
-        guard ensureProAccess() else { return }
+        guard ensureProAccessForJobSearch() else { return }
         jobKeywordsField.stringValue = seed
         window?.makeFirstResponder(jobKeywordsField)
         if let editor = jobKeywordsField.window?.fieldEditor(true, for: jobKeywordsField) as? NSTextView {
@@ -2125,7 +2156,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
     }
 
     @objc private func didTapLoadMoreJobs() {
-        guard ensureProAccess() else { return }
+        guard ensureProAccessForJobSearch() else { return }
         let prompt = "Show more jobs"
         guard !isAwaitingResponse, isContinuationPrompt(prompt) else { return }
         if anchorUserJobQuery(excludingLatestUserMessage: prompt) == nil { return }
@@ -2136,6 +2167,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
     }
 
     private func startJobSearchRequest(effectiveQuery: String, isContinuation: Bool) {
+        FreeTierJobSearchQuota.recordUserMessageSent(isProActive: SubscriptionStore.shared.isProActive)
         isAwaitingResponse = true
         addInlineChatThinkingRow()
         setInputEnabled(false)