feat(store): add In-App Purchase system with Pro subscription

Implement freemium model with StoreKit 2:
- StoreManager singleton for purchase/restore/entitlements
- ProFeature enum defining gated features
- PaywallView and OnboardingPaywallView for upsell UI
- ProGate view modifier and ProBadge component

Feature gating:
- Trip saving: 1 free trip, then requires Pro
- PDF export: Pro only with badge indicator
- Progress tab: Shows ProLockedView for free users
- Settings: Subscription management section

Also fixes pre-existing test issues with StadiumVisit
and ItineraryOption model signature changes.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-01-13 11:41:40 -06:00
parent e4204175ea
commit 22772fa57f
19 changed files with 1293 additions and 34 deletions

View File

@@ -4,29 +4,36 @@
//
import Testing
import Foundation
import CoreLocation
@testable import SportsTime
struct TripOptionsGroupingTests {
// Helper to create mock ItineraryStop
private func makeStop(city: String, games: [String] = []) -> ItineraryStop {
ItineraryStop(
city: city,
state: "XX",
coordinate: CLLocationCoordinate2D(latitude: 0, longitude: 0),
games: games,
arrivalDate: Date(),
departureDate: Date(),
location: LocationInput(name: city, coordinate: nil),
firstGameStart: nil
)
}
// Helper to create mock ItineraryOption
private func makeOption(stops: [(city: String, games: [String])], totalMiles: Double = 500) -> ItineraryOption {
let tripStops = stops.map { stopData in
TripStop(
city: stopData.city,
state: "XX",
coordinate: .init(latitude: 0, longitude: 0),
games: stopData.games,
arrivalDate: Date(),
departureDate: Date(),
travelFromPrevious: nil
)
}
let itineraryStops = stops.map { makeStop(city: $0.city, games: $0.games) }
return ItineraryOption(
id: UUID().uuidString,
stops: tripStops,
totalDistanceMiles: totalMiles,
rank: 1,
stops: itineraryStops,
travelSegments: [],
totalDrivingHours: totalMiles / 60,
score: 1.0
totalDistanceMiles: totalMiles,
geographicRationale: "Test"
)
}