feat: Trips home screen — inspired by Apple's Wishlist sample
All checks were successful
Security Checks / dockerfile-lint (push) Successful in 3s
Security Checks / dependency-audit (push) Successful in 14s
Security Checks / secret-scanning (push) Successful in 4s

Mapped Wishlist → Trips:
- WishlistView → TripsHomeView (NavigationStack + ScrollView)
- RecentTripsPageView → UpcomingTripsPageView (paged TabView hero)
- TripCollectionView → PastTripsSection (horizontal scroll compact)
- TripCard → TripCard (.compact/.expanded sizes)
- TripImageView → TripImageView (Rectangle overlay + AsyncImage)
- ExpandedNavigationTitle → same pattern for "Trips" title
- AddTripView → Plan Trip button (.glassProminent, Phase 2)

Structure (9 files):
- TripModels.swift: Trip model with date helpers, image URL builder
- TripsAPI.swift: getTrips() via gateway /api/trips/trips
- TripsViewModel.swift: upcoming/past sorting, @Observable
- TripsHomeView.swift: main screen, Wishlist layout pattern
- UpcomingTripsPageView.swift: full-width paged hero cards
- PastTripsSection.swift: horizontal compact card scroll
- TripCard.swift: reusable card with compact/expanded sizes
- TripImageView.swift: AsyncImage with gradient placeholder
- TripPlaceholderView.swift: simple detail for Phase 1

New tab: Trips (airplane icon) in tab bar for all users.
Images served via gateway: /api/trips/images/{filename}

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Yusuf Suleman
2026-04-04 14:16:15 -05:00
parent 1cfb729cae
commit 4d6960c508
11 changed files with 586 additions and 0 deletions

View File

@@ -14,6 +14,15 @@
A10005 /* AuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10005 /* AuthManager.swift */; };
A10050 /* AppearanceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10050 /* AppearanceManager.swift */; };
A10051 /* EditableDraftCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10051 /* EditableDraftCard.swift */; };
A10060 /* TripModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10060 /* TripModels.swift */; };
A10061 /* TripsAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10061 /* TripsAPI.swift */; };
A10062 /* TripsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10062 /* TripsViewModel.swift */; };
A10063 /* TripsHomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10063 /* TripsHomeView.swift */; };
A10064 /* UpcomingTripsPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10064 /* UpcomingTripsPageView.swift */; };
A10065 /* PastTripsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10065 /* PastTripsSection.swift */; };
A10066 /* TripCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10066 /* TripCard.swift */; };
A10067 /* TripImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10067 /* TripImageView.swift */; };
A10068 /* TripPlaceholderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10068 /* TripPlaceholderView.swift */; };
A10006 /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10006 /* LoginView.swift */; };
A10007 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10007 /* HomeView.swift */; };
A10008 /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10008 /* HomeViewModel.swift */; };
@@ -90,6 +99,15 @@
B10005 /* AuthManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthManager.swift; sourceTree = "<group>"; };
B10050 /* AppearanceManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearanceManager.swift; sourceTree = "<group>"; };
B10051 /* EditableDraftCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditableDraftCard.swift; sourceTree = "<group>"; };
B10060 /* TripModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TripModels.swift; sourceTree = "<group>"; };
B10061 /* TripsAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TripsAPI.swift; sourceTree = "<group>"; };
B10062 /* TripsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TripsViewModel.swift; sourceTree = "<group>"; };
B10063 /* TripsHomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TripsHomeView.swift; sourceTree = "<group>"; };
B10064 /* UpcomingTripsPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpcomingTripsPageView.swift; sourceTree = "<group>"; };
B10065 /* PastTripsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PastTripsSection.swift; sourceTree = "<group>"; };
B10066 /* TripCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TripCard.swift; sourceTree = "<group>"; };
B10067 /* TripImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TripImageView.swift; sourceTree = "<group>"; };
B10068 /* TripPlaceholderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TripPlaceholderView.swift; sourceTree = "<group>"; };
B10006 /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = "<group>"; };
B10007 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = "<group>"; };
B10008 /* HomeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewModel.swift; sourceTree = "<group>"; };
@@ -201,6 +219,7 @@
F10014 /* Assistant */,
F10021 /* Feedback */,
F10030 /* Reader */,
F10050 /* Trips */,
);
path = Features;
sourceTree = "<group>";
@@ -400,6 +419,54 @@
path = Views;
sourceTree = "<group>";
};
F10050 /* Trips */ = {
isa = PBXGroup;
children = (
F10051 /* Models */,
F10052 /* API */,
F10053 /* ViewModels */,
F10054 /* Views */,
);
path = Trips;
sourceTree = "<group>";
};
F10051 /* Models */ = {
isa = PBXGroup;
children = (
B10060 /* TripModels.swift */,
);
path = Models;
sourceTree = "<group>";
};
F10052 /* API */ = {
isa = PBXGroup;
children = (
B10061 /* TripsAPI.swift */,
);
path = API;
sourceTree = "<group>";
};
F10053 /* ViewModels */ = {
isa = PBXGroup;
children = (
B10062 /* TripsViewModel.swift */,
);
path = ViewModels;
sourceTree = "<group>";
};
F10054 /* Views */ = {
isa = PBXGroup;
children = (
B10063 /* TripsHomeView.swift */,
B10064 /* UpcomingTripsPageView.swift */,
B10065 /* PastTripsSection.swift */,
B10066 /* TripCard.swift */,
B10067 /* TripImageView.swift */,
B10068 /* TripPlaceholderView.swift */,
);
path = Views;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -518,6 +585,15 @@
A10005 /* AuthManager.swift in Sources */,
A10050 /* AppearanceManager.swift in Sources */,
A10051 /* EditableDraftCard.swift in Sources */,
A10060 /* TripModels.swift in Sources */,
A10061 /* TripsAPI.swift in Sources */,
A10062 /* TripsViewModel.swift in Sources */,
A10063 /* TripsHomeView.swift in Sources */,
A10064 /* UpcomingTripsPageView.swift in Sources */,
A10065 /* PastTripsSection.swift in Sources */,
A10066 /* TripCard.swift in Sources */,
A10067 /* TripImageView.swift in Sources */,
A10068 /* TripPlaceholderView.swift in Sources */,
A10006 /* LoginView.swift in Sources */,
A10007 /* HomeView.swift in Sources */,
A10008 /* HomeViewModel.swift in Sources */,