Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/expo/config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export const featureFlags = {
enableOAuth: true,
enableTrips: true,
enableTrips: false,
enablePackInsights: false,
enableShoppingList: false,
enableSharedPacks: false,
Expand Down
13 changes: 13 additions & 0 deletions apps/expo/features/trips/components/TripCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Alert, type AlertMethods, Button } from '@packrat/ui/nativewindui';
import { Icon } from '@roninoss/icons';
import { useColorScheme } from 'expo-app/lib/hooks/useColorScheme';
import { useTranslation } from 'expo-app/lib/hooks/useTranslation';
import { formatLocalDate } from 'expo-app/lib/utils/dateUtils';
import { useRouter } from 'expo-router';
import { useRef } from 'react';
import { Pressable, Text, View } from 'react-native';
Expand Down Expand Up @@ -117,6 +118,18 @@ export function TripCard({ trip, onPress }: TripCardProps) {
</Button>
</View>

{/* Dates */}
{(trip.startDate != null || trip.endDate != null) && (
<View className="flex-row items-center mt-1">
<Icon name="calendar-month" size={14} color={colors.primary} />
<Text className="ml-1 text-sm text-muted-foreground">
{trip.startDate != null && trip.endDate != null
? `${formatLocalDate(trip.startDate)} → ${formatLocalDate(trip.endDate)}`
: formatLocalDate(trip.startDate ?? trip.endDate)}
</Text>
</View>
)}

{/* Description */}
{trip.description && (
<Text className="text-sm text-muted-foreground mt-2" numberOfLines={2}>
Expand Down
14 changes: 12 additions & 2 deletions apps/expo/features/trips/components/UpcomingTripsTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Icon } from '@roninoss/icons';
import { featureFlags } from 'expo-app/config';
import { useTrips } from 'expo-app/features/trips/hooks';
import { useTranslation } from 'expo-app/lib/hooks/useTranslation';
import { parseLocalDate } from 'expo-app/lib/utils/dateUtils';
import { useRouter } from 'expo-router';
import { useMemo, useRef, useState } from 'react';
import { View } from 'react-native';
Expand All @@ -17,9 +18,18 @@ export function UpcomingTripsTile() {
// ✅ get all trips
const trips = useTrips();

// ✅ derive upcoming trips (in future)
// ✅ derive upcoming trips (today or in future)
const upcomingTrips = useMemo(
() => trips.filter((t) => t.startDate && new Date(t.startDate) > new Date()),
() =>
trips.filter((t) => {
if (!t.startDate) return false;
const parsed = parseLocalDate(t.startDate);
if (parsed == null) return false;
// Compare against start-of-today so same-day trips are included
const startOfToday = new Date();
startOfToday.setHours(0, 0, 0, 0);
return parsed >= startOfToday;
}),
Comment thread
coderabbitai[bot] marked this conversation as resolved.
[trips],
);

Expand Down
2 changes: 2 additions & 0 deletions apps/expo/features/trips/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './hooks';
export * from './types';
26 changes: 23 additions & 3 deletions apps/expo/features/trips/screens/TripListScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useTranslation } from 'expo-app/lib/hooks/useTranslation';
import { TestIds } from 'expo-app/lib/testIds';
import { asNonNullableRef } from 'expo-app/lib/utils/asNonNullableRef';
import { Link, useRouter } from 'expo-router';
import { useCallback, useRef } from 'react';
import { useCallback, useMemo, useRef, useState } from 'react';
import { FlatList, Pressable, Text, TouchableOpacity, View } from 'react-native';
import { TripCard } from '../components/TripCard';
import { useTrips } from '../hooks';
Expand All @@ -29,6 +29,19 @@ export function TripsListScreen() {
const { t } = useTranslation();
const trips = useTrips();
const searchBarRef = useRef<LargeTitleSearchBarMethods>(null);
const [searchValue, setSearchValue] = useState('');

const filteredTrips = useMemo(() => {
const trimmed = searchValue.trim();
if (!trimmed) return trips;
const lower = trimmed.toLowerCase();
return trips.filter(
(trip) =>
trip.name.toLowerCase().includes(lower) ||
(trip.description ?? '').toLowerCase().includes(lower) ||
(trip.location?.name ?? '').toLowerCase().includes(lower),
);
}, [trips, searchValue]);
Comment thread
coderabbitai[bot] marked this conversation as resolved.

const handleTripPress = useCallback(
(trip: Trip) => {
Expand All @@ -42,6 +55,13 @@ export function TripsListScreen() {
};

const renderEmptyState = () => {
if (searchValue.trim() && trips.length > 0) {
return (
<View className="flex-1 items-center justify-center p-8">
<Text className="text-center text-muted-foreground">{t('trips.noSearchResults')}</Text>
</View>
);
}
return (
<View className="flex-1 items-center justify-center p-8">
<View className="mb-4 rounded-full bg-muted p-4">
Expand All @@ -64,7 +84,7 @@ export function TripsListScreen() {
searchBar={{
iosHideWhenScrolling: true,
ref: asNonNullableRef(searchBarRef),
onChangeText() {}, // no search filtering
onChangeText: setSearchValue,
content: (
<View className="flex-1 items-center justify-center">
<Text>{t('trips.searchTrips')}</Text>
Expand All @@ -79,7 +99,7 @@ export function TripsListScreen() {
/>

<FlatList
data={trips || []}
data={filteredTrips}
keyExtractor={(trip) => trip.id}
renderItem={({ item: trip }) => (
<View className="px-4 pt-4">
Expand Down
2 changes: 1 addition & 1 deletion apps/expo/features/trips/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export interface Trip {
localUpdatedAt?: string;
}

export type TripInStore = Omit<Trip, 'trips'>;
export type TripInStore = Trip;

export type TripInput = Omit<
TripInStore,
Expand Down
1 change: 1 addition & 0 deletions apps/expo/lib/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,7 @@
"tripLocation": "Trip Location",
"noTrips": "No trips available",
"noTripsFound": "No trips found",
"noSearchResults": "No trips match your search.",
"noTripsYet": "You haven't created any trips yet.",
"createNewTrip": "Create New Trip",
"upcomingTrips": "Upcoming Trips",
Expand Down
32 changes: 32 additions & 0 deletions apps/expo/lib/utils/dateUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Parse a date string, handling YYYY-MM-DD strings as local dates
* instead of UTC (which is the default `new Date('YYYY-MM-DD')` behavior).
*
* Returns `null` for missing or invalid input.
*/
export function parseLocalDate(dateString?: string): Date | null {
if (!dateString) return null;
const dateOnlyMatch = /^(\d{4})-(\d{2})-(\d{2})$/.exec(dateString);
if (dateOnlyMatch) {
const year = Number(dateOnlyMatch[1]);
const month = Number(dateOnlyMatch[2]);
const day = Number(dateOnlyMatch[3]);
const date = new Date(year, month - 1, day);
if (Number.isNaN(date.getTime())) return null;
if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day) {
return null;
}
return date;
}
const date = new Date(dateString);
return Number.isNaN(date.getTime()) ? null : date;
}

/**
* Format a date string for display, returning an em-dash for missing or
* invalid values. Uses the user's locale via `toLocaleDateString()`.
*/
export function formatLocalDate(dateString?: string): string {
const date = parseLocalDate(dateString);
return date ? date.toLocaleDateString() : '\u2014';
}
Loading