feat: rewrite bootstrap, fix CloudKit sync, update canonical data, and UI fixes
- Rewrite BootstrapService: remove all legacy code paths (JSONStadium, JSONGame, bootstrapStadiumsLegacy, bootstrapGamesLegacy, venue aliases, createDefaultLeagueStructure), require canonical JSON files only - Add clearCanonicalData() to handle partial bootstrap recovery (prevents duplicate key crashes from interrupted first-launch) - Fix nullable stadium_canonical_id in games (4 MLS games have null) - Fix CKModels: logoUrl case, conference/division field keys - Fix CanonicalSyncService: sync conferenceCanonicalId/divisionCanonicalId - Add sports_canonical.json and DemoMode.swift - Delete legacy stadiums.json and games.json - Update all canonical resource JSON files with latest data - Fix TripWizardView horizontal scrolling with GeometryReader constraint - Update RegionMapSelector, TripDetailView, TripOptionsView UI improvements - Add DateRangePicker, PlanningModeStep, SportsStep enhancements - Update UI tests and marketing-videos config Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -143,6 +143,8 @@ struct TripOptionsView: View {
|
||||
@State private var citiesFilter: CitiesFilter = .noLimit
|
||||
@State private var paceFilter: TripPaceFilter = .all
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
@Environment(\.isDemoMode) private var isDemoMode
|
||||
@State private var hasAppliedDemoSelection = false
|
||||
|
||||
// MARK: - Computed Properties
|
||||
|
||||
@@ -272,7 +274,7 @@ struct TripOptionsView: View {
|
||||
}
|
||||
|
||||
// Options in this group
|
||||
ForEach(group.options) { option in
|
||||
ForEach(Array(group.options.enumerated()), id: \.element.id) { index, option in
|
||||
TripOptionCard(
|
||||
option: option,
|
||||
games: games,
|
||||
@@ -281,6 +283,7 @@ struct TripOptionsView: View {
|
||||
showTripDetail = true
|
||||
}
|
||||
)
|
||||
.accessibilityIdentifier("tripOptions.trip.\(index)")
|
||||
.padding(.horizontal, Theme.Spacing.md)
|
||||
}
|
||||
}
|
||||
@@ -300,6 +303,26 @@ struct TripOptionsView: View {
|
||||
selectedTrip = nil
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
if isDemoMode && !hasAppliedDemoSelection {
|
||||
hasAppliedDemoSelection = true
|
||||
// Auto-select "Most Games" sort after a delay
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + DemoConfig.selectionDelay) {
|
||||
withAnimation(.easeInOut(duration: 0.3)) {
|
||||
sortOption = DemoConfig.demoSortOption
|
||||
}
|
||||
}
|
||||
// Then navigate to the 4th trip (index 3)
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + DemoConfig.selectionDelay + 0.8) {
|
||||
let sortedOptions = filteredAndSortedOptions
|
||||
if sortedOptions.count > DemoConfig.demoTripIndex {
|
||||
let option = sortedOptions[DemoConfig.demoTripIndex]
|
||||
selectedTrip = convertToTrip(option)
|
||||
showTripDetail = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var sortPicker: some View {
|
||||
@@ -312,6 +335,7 @@ struct TripOptionsView: View {
|
||||
} label: {
|
||||
Label(option.rawValue, systemImage: option.icon)
|
||||
}
|
||||
.accessibilityIdentifier("tripOptions.sortOption.\(option.rawValue.lowercased().replacingOccurrences(of: " ", with: ""))")
|
||||
}
|
||||
} label: {
|
||||
HStack(spacing: 8) {
|
||||
@@ -332,6 +356,7 @@ struct TripOptionsView: View {
|
||||
.strokeBorder(Theme.textMuted(colorScheme).opacity(0.2), lineWidth: 1)
|
||||
)
|
||||
}
|
||||
.accessibilityIdentifier("tripOptions.sortDropdown")
|
||||
}
|
||||
|
||||
// MARK: - Filters Section
|
||||
|
||||
Reference in New Issue
Block a user