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:
@@ -11,9 +11,11 @@ struct DateRangePicker: View {
|
||||
@Binding var startDate: Date
|
||||
@Binding var endDate: Date
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
@Environment(\.isDemoMode) private var isDemoMode
|
||||
|
||||
@State private var displayedMonth: Date = Date()
|
||||
@State private var selectionState: SelectionState = .none
|
||||
@State private var hasAppliedDemoSelection = false
|
||||
|
||||
enum SelectionState {
|
||||
case none
|
||||
@@ -89,6 +91,24 @@ struct DateRangePicker: View {
|
||||
if endDate > startDate {
|
||||
selectionState = .complete
|
||||
}
|
||||
|
||||
// Demo mode: auto-select dates
|
||||
if isDemoMode && !hasAppliedDemoSelection {
|
||||
hasAppliedDemoSelection = true
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + DemoConfig.selectionDelay) {
|
||||
withAnimation(.easeInOut(duration: 0.3)) {
|
||||
// Navigate to demo month
|
||||
displayedMonth = DemoConfig.demoStartDate
|
||||
}
|
||||
}
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + DemoConfig.selectionDelay + 0.5) {
|
||||
withAnimation(.easeInOut(duration: 0.3)) {
|
||||
startDate = DemoConfig.demoStartDate
|
||||
endDate = DemoConfig.demoEndDate
|
||||
selectionState = .complete
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: startDate) { oldValue, newValue in
|
||||
// Navigate calendar to show the new month when startDate changes externally
|
||||
@@ -159,12 +179,14 @@ struct DateRangePicker: View {
|
||||
.background(Theme.warmOrange.opacity(0.15))
|
||||
.clipShape(Circle())
|
||||
}
|
||||
.accessibilityIdentifier("wizard.dates.previousMonth")
|
||||
|
||||
Spacer()
|
||||
|
||||
Text(monthYearString)
|
||||
.font(.headline)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
.accessibilityIdentifier("wizard.dates.monthLabel")
|
||||
|
||||
Spacer()
|
||||
|
||||
@@ -180,6 +202,7 @@ struct DateRangePicker: View {
|
||||
.background(Theme.warmOrange.opacity(0.15))
|
||||
.clipShape(Circle())
|
||||
}
|
||||
.accessibilityIdentifier("wizard.dates.nextMonth")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,6 +310,12 @@ struct DayCell: View {
|
||||
calendar.startOfDay(for: date) < calendar.startOfDay(for: Date())
|
||||
}
|
||||
|
||||
private var accessibilityId: String {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "yyyy-MM-dd"
|
||||
return "wizard.dates.day.\(formatter.string(from: date))"
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Button(action: onTap) {
|
||||
ZStack {
|
||||
@@ -330,6 +359,7 @@ struct DayCell: View {
|
||||
.frame(width: 36, height: 36)
|
||||
}
|
||||
}
|
||||
.accessibilityIdentifier(accessibilityId)
|
||||
.buttonStyle(.plain)
|
||||
.disabled(isPast)
|
||||
.frame(height: 40)
|
||||
|
||||
Reference in New Issue
Block a user