Skip to content

activity creation flow#262

Open
afnantuffaha wants to merge 4 commits intomainfrom
create-activity-flow
Open

activity creation flow#262
afnantuffaha wants to merge 4 commits intomainfrom
create-activity-flow

Conversation

@afnantuffaha
Copy link
Copy Markdown
Contributor

@afnantuffaha afnantuffaha commented Apr 4, 2026

Description

How has this been tested?

Checklist

  • I have self-reviewed my code for readability, maintainability, performance, and added comments/documentation where necessary.
  • New and existing tests pass locally with my changes.
  • I have followed the project's coding standards and best practices.
  • Users can add activities via a URL-autofill sheet or a manual form sheet; saved activities appear immediately in the activities list.
  • Activities list now shows compact Activity cards with thumbnail, title, going count, avatars, and per-person price; empty and loading states improved.
  • Category selection and date/location pickers integrated into the manual creation flow via bottom sheets and return-route handling.
  • Search location screen supports a select mode and a pending-location return flow for seamless location picking.

Features

  • ActivityCard component: thumbnail, “New” badge, truncated title, going count with heart, estimated price line, up to three avatars.
  • AddActivityEntrySheet: modal to paste a URL for autofill or switch to manual entry; exposes open/close ref handle.
  • AddActivityManualSheet: forward-ref bottom sheet for manual activity creation (thumbnail upload, title, description, categories, location, price, date range, link); validation, upload, and create mutation; returns created activity via onSaved.
  • CategoriesSheet: bottom sheet multi-select for trip categories; exposes done action and forwarded ref.
  • FormRow: reusable pressable row with icon, value/placeholder.
  • Activities screen refactor: flat list of ActivityCard rows, coordinator pattern to open entry/manual sheets, prepend saved activities to list.
  • SearchLocationScreen enhancements: select mode for immediate selection, pending-location export (getPendingLocation), confirm bar for map mode, and cleanup on unmount.

Improvements

  • Replaced inline comment/pending-create UI with sheet-based creation workflow.
  • Added ActivitiesSkeleton for loading and improved empty-state widget.
  • Better navigation flow: returning from location search pre-populates manual form; sheets coordinate with small delays to avoid UI overlap.
  • Reduced console noise by removing detailed place-details error logging.

Sequence diagram (activity creation flow)
User -> Activities screen: tap "Add an activity"
Activities screen -> AddActivityEntrySheet: open
AddActivityEntrySheet -> User: choose "Autofill from link" or "Add manually"
Autofill path:
AddActivityEntrySheet -> Server: parseActivityLink(url)
Server -> AddActivityEntrySheet: parsed data
AddActivityEntrySheet -> AddActivityManualSheet: open(prefill=parsed data)
Manual path:
AddActivityEntrySheet -> AddActivityManualSheet: open()
AddActivityManualSheet -> Server: createActivity(data)
Server -> AddActivityManualSheet: created activity
AddActivityManualSheet -> Activities screen: onSaved(created activity) -> prepend to list

Author contribution table (lines added/removed/net)

File Added Removed Net
frontend/app/(app)/trips/[id]/activities/components/activity-card.tsx 144 0 +144
frontend/app/(app)/trips/[id]/activities/components/add-activity-entry-sheet.tsx 190 0 +190
frontend/app/(app)/trips/[id]/activities/components/add-activity-manual-sheet.tsx 427 0 +427
frontend/app/(app)/trips/[id]/activities/components/categories-sheet.tsx 122 0 +122
frontend/app/(app)/trips/[id]/activities/components/form-row.tsx 34 0 +34
frontend/app/(app)/trips/[id]/activities/index.tsx 139 188 -49
frontend/app/(app)/trips/[id]/search-location.tsx 77 21 +56
Total 1,133 209 +924

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 4, 2026

📝 Walkthrough

Walkthrough

This PR adds activity UI and creation flows: an ActivityCard component, entry and manual bottom sheets, a categories selector, a form-row helper, refactors the Activities screen to a FlatList with sheet-driven creation, and extends search-location to support a pending selection API and select mode.

Changes

Cohort / File(s) Summary
Activity Display
frontend/app/(app)/trips/[id]/activities/components/activity-card.tsx
New ActivityCard component: thumbnail, "new" badge, truncated title, going count/heart, estimated price line, up to three avatars, optional comments placeholder, pressable card UI.
Entry & Manual Creation Sheets
frontend/app/(app)/trips/[id]/activities/components/add-activity-entry-sheet.tsx, frontend/app/(app)/trips/[id]/activities/components/add-activity-manual-sheet.tsx
Added entry sheet (autofill from link or manual) with imperative open/close ref; manual sheet is a full create form with image upload, categories, location navigation, date range, price parsing, validation, create mutation, cancel confirmation, and imperatively exposed handle.
Supporting UI
frontend/app/(app)/trips/[id]/activities/components/categories-sheet.tsx, frontend/app/(app)/trips/[id]/activities/components/form-row.tsx
New CategoriesSheet bottom-sheet with multi-select chips and Done action; FormRow helper component for icon + value/placeholder rows.
Activities Screen Refactor
frontend/app/(app)/trips/[id]/activities/index.tsx
Replaced prior Activities screen with ActivitiesScreen: renders ActivityCard list, removes in-screen create mutation, coordinates AddActivityEntrySheet and AddActivityManualSheet via refs, handles sheet callback chaining, updates loading/empty states and component export name.
Location Selection API & UI
frontend/app/(app)/trips/[id]/search-location.tsx
Added select mode and module-level pending selection (getPendingLocation() export). Confirm flow can set pending location and navigate back. Adjusted select-mode cleanup, UI tokens, and simplified error handling.

Sequence Diagram

sequenceDiagram
    participant User
    participant ActivitiesScreen as Activities
    participant EntrySheet as AddActivityEntrySheet
    participant ManualSheet as AddActivityManualSheet
    participant Categories as CategoriesSheet
    participant SearchLocation as SearchLocationScreen
    participant API

    User->>Activities: Open Activities
    Activities->>Activities: Render FlatList of ActivityCards

    User->>Activities: Tap "Add an activity"
    Activities->>EntrySheet: open()
    User->>EntrySheet: Choose "Autofill from link"
    EntrySheet->>API: parseActivityLink(tripID, url)
    API-->>EntrySheet: parsed data
    EntrySheet->>Activities: onAutofilled(parsed data)
    Activities->>ManualSheet: open(prefill)
    
    alt edit and pick categories
        User->>ManualSheet: Tap category row
        ManualSheet->>Categories: open()
        User->>Categories: Select categories -> Done
        Categories-->>ManualSheet: selected categories
    end

    alt pick location via SearchLocation
        User->>ManualSheet: Tap location row
        ManualSheet->>SearchLocation: navigate(tripID, returnTo)
        SearchLocation->>SearchLocation: select place (confirm)
        SearchLocation->>Activities: set pending location & back
        Activities->>ManualSheet: prefill location
    end

    User->>ManualSheet: Tap Save
    ManualSheet->>API: createActivity(payload)
    API-->>ManualSheet: created activity
    ManualSheet->>Activities: onSaved(activity)
    Activities->>Activities: Prepend activity to list
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • aahiltn
  • in-mai-space

A card appears on the list, concise and clear,
Sheets open for link or manual fill near,
Categories chosen, locations set true,
Save sends the payload, a new activity view,
Screens hand off data in a quiet queue.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'activity creation flow' accurately describes the main objective of the pull request, which introduces multiple components and screens to support creating activities.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch create-activity-flow
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch create-activity-flow

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/app/`(app)/index.tsx:
- Around line 37-38: The logout call from useUserStore((s) => s.logout) must be
routed through a logout helper that both signs the user out and
clears/invalidate react-query caches; create or use an existing helper (e.g.
logoutAndClearCache or handleLogout) that calls logout (from useUserStore), then
invokes the app's queryClient.clear(), queryClient.invalidateQueries(['trips']),
and queryClient.invalidateQueries(['activities']) (or queryClient.clear() if
appropriate), and replace direct calls to logout in the components (including
the occurrences around lines 103–111 and the existing const logout =
useUserStore((s) => s.logout)) to call this helper so cached trip/activity data
is removed on sign-out.

In
`@frontend/app/`(app)/trips/[id]/activities/components/add-activity-entry-sheet.tsx:
- Around line 51-55: The sheet can be dismissed while parseActivityLink is still
running, allowing its late resolution to call onAutofilled and reopen the manual
flow; update the flow around parseActivityLink/handleClose to prevent this by
adding a request token or cancel flag (e.g., a local incrementing requestId or
AbortController) that is set when starting parseActivityLink and checked before
calling onAutofilled, and ensure handleClose sets the cancel flag (or increments
a token) and clears state via setVisible, setUrl, onClose so any in-flight
response is ignored; alternatively disable backdrop/close while an autofill
promise is pending by tracking an isAutofilling boolean and preventing dismissal
in handleClose and the backdrop handler until it is false (apply same guard
where parseActivityLink resolves and where onAutofilled is invoked).

In
`@frontend/app/`(app)/trips/[id]/activities/components/add-activity-manual-sheet.tsx:
- Around line 85-92: The discard check currently only looks at name, description
and thumbnailUri; expand it to consider all editable state used in this
component—categories, locationName, locationLat, locationLng, price,
dateRange.start/dateRange.end, link (in addition to name, description,
thumbnailUri) —create a single hasData/hasUnsavedChanges predicate that inspects
categories.length, non-null locationName/Lat/Lng, non-empty price, non-null
dateRange.start or end, non-empty link, or non-empty
name/description/thumbnailUri, and use that predicate wherever the current
discard/close confirmation is performed (replace the existing
name/description/thumbnailUri-only check in the close handler and any related
functions) so the sheet prompts before losing any edited fields.
- Around line 252-321: The form blocks use raw TextInput rows and ad-hoc Box
layouts (see name/description TextInput, price/link inline TextInput, and the
category chips) which duplicates styling/behavior; refactor these to use the
app's shared field primitives (e.g., replace the plain TextInput usages in
add-activity-manual-sheet.tsx with the centralized FormField/FormRow or
ControlledInput components, wire their value/onChange to the existing state
setters like setName, setDescription, setPrice, setLink, and reuse the shared
spacing/colors instead of
styles.nameInput/styles.descriptionInput/styles.inlineInput), ensure the
location and dates keep using FormRow (keep handleLocationPress and
setIsDatePickerVisible) and remove duplicated inline Box styling by composing
primitives so validation/theming/interaction stay consistent across the app.
- Around line 60-67: formatDateRange and the payload creation are inconsistent
and use toISOString which shifts local calendar dates to UTC; ensure when
building the payload you default end to start for single-day selections (so a
DateRange with only start is persisted) and replace any toISOString() usage with
a local-date-only serialization that extracts year/month/day from the Date
(e.g., build "YYYY-MM-DD" from date.getFullYear(), date.getMonth()+1,
date.getDate()) so timezone offsets don't change the calendar day; update the
code paths that construct the API payload (the payload creation logic near where
start/end are read) and keep formatDateRange unchanged for display.

In `@frontend/app/`(app)/trips/[id]/activities/components/categories-sheet.tsx:
- Around line 88-102: The "Create amenity" chip is rendered as a Pressable with
no onPress handler (Pressable > Box ... Text "Create amenity"), producing a dead
tappable control; either remove the entire Pressable/Box block or wire it to the
real category-creation flow by adding an onPress that calls the existing
create/open handler (e.g., openCreateCategoryModal or createCategory function in
this component or parent) and keep styling via styles.chip, Plus and Text as-is
so the UI remains consistent; if the feature isn't ready, prefer removing or
rendering it disabled/non-interactive instead of leaving an empty Pressable.
- Around line 20-21: The categories list derived from
useGetCategoriesByTripID(tripID) may include items with undefined
ModelsCategoryAPIResponse.name which are currently used as React keys and passed
into toggle(), causing unstable keys and invalid selection entries; before
rendering (where categories is used and passed to toggle()), filter or map the
fetched data to only include categories with a defined non-empty name (or
normalize missing names to a stable placeholder id), e.g., produce a
sanitizedCategories variable from data?.categories that excludes entries lacking
name (or assigns a deterministic fallback like `category-<id>`), then use
sanitizedCategories everywhere React keys and toggle() are called so keys and
selection values remain stable.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 28342679-3ad7-4cf5-8313-33f595851460

📥 Commits

Reviewing files that changed from the base of the PR and between 471dff7 and acaf1b3.

⛔ Files ignored due to path filters (1)
  • frontend/assets/images/binoculars.png is excluded by !**/*.png
📒 Files selected for processing (8)
  • frontend/app/(app)/index.tsx
  • frontend/app/(app)/trips/[id]/activities/components/activity-card.tsx
  • frontend/app/(app)/trips/[id]/activities/components/add-activity-entry-sheet.tsx
  • frontend/app/(app)/trips/[id]/activities/components/add-activity-manual-sheet.tsx
  • frontend/app/(app)/trips/[id]/activities/components/categories-sheet.tsx
  • frontend/app/(app)/trips/[id]/activities/components/form-row.tsx
  • frontend/app/(app)/trips/[id]/activities/index.tsx
  • frontend/app/(app)/trips/[id]/search-location.tsx

Comment on lines +51 to +55
const handleClose = () => {
setVisible(false);
setUrl("");
onClose();
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Do not apply autofill results after the sheet is dismissed.

Backdrop close stays active while parseActivityLink is in flight. If the user dismisses the sheet, the pending request can still resolve and call onAutofilled, reopening the manual flow after the user canceled. Disable dismissal during autofill or ignore late responses with a request token/cancel flag.

Also applies to: 63-75, 85-85

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/app/`(app)/trips/[id]/activities/components/add-activity-entry-sheet.tsx
around lines 51 - 55, The sheet can be dismissed while parseActivityLink is
still running, allowing its late resolution to call onAutofilled and reopen the
manual flow; update the flow around parseActivityLink/handleClose to prevent
this by adding a request token or cancel flag (e.g., a local incrementing
requestId or AbortController) that is set when starting parseActivityLink and
checked before calling onAutofilled, and ensure handleClose sets the cancel flag
(or increments a token) and clears state via setVisible, setUrl, onClose so any
in-flight response is ignored; alternatively disable backdrop/close while an
autofill promise is pending by tracking an isAutofilling boolean and preventing
dismissal in handleClose and the backdrop handler until it is false (apply same
guard where parseActivityLink resolves and where onAutofilled is invoked).

Comment on lines +60 to +67
function formatDateRange(range: DateRange): string | null {
if (!range.start) return null;
const fmt = (d: Date) =>
d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
if (!range.end || range.start.getTime() === range.end.getTime())
return fmt(range.start);
return `${fmt(range.start)} – ${fmt(range.end)}`;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, locate and examine the target file
fd -t f "add-activity-manual-sheet.tsx"

Repository: GenerateNU/toggo

Length of output: 141


🏁 Script executed:

# Read the file with line numbers, focusing on the critical sections
cat -n frontend/app/\(app\)/trips/\[id\]/activities/components/add-activity-manual-sheet.tsx | head -170 | tail -120

Repository: GenerateNU/toggo

Length of output: 5169


🏁 Script executed:

# Also check the type definitions for DateRange
rg "type DateRange|interface DateRange" -A 5

Repository: GenerateNU/toggo

Length of output: 1219


🏁 Script executed:

# Check the date picker implementation
cat -n frontend/design-system/primitives/date-picker.tsx | head -100

Repository: GenerateNU/toggo

Length of output: 3460


🏁 Script executed:

# Look for any timezone handling utilities in the codebase
rg "timezone|getTime|UTC" --type ts --type tsx -B 2 -A 2

Repository: GenerateNU/toggo

Length of output: 87


🏁 Script executed:

# Check if there are any other date serialization patterns in the codebase
rg "toISOString" --type ts --type tsx -B 3 -A 3

Repository: GenerateNU/toggo

Length of output: 87


🏁 Script executed:

# Search for toISOString usage in the codebase
rg "toISOString" -B 3 -A 3

Repository: GenerateNU/toggo

Length of output: 2799


🏁 Script executed:

# Check the backend date expectations more clearly
cat -n backend/internal/models/activity.go | grep -A 10 "DateRange"

Repository: GenerateNU/toggo

Length of output: 5335


🏁 Script executed:

# Look for how dates are created/used elsewhere in the frontend
rg "new Date" frontend/design-system/primitives/date-picker.tsx -B 2 -A 2

Repository: GenerateNU/toggo

Length of output: 1144


Fix timezone-dependent date loss and single-day selection handling.

The code has two related issues:

  1. Single-day selections won't persist: formatDateRange accepts a DateRange with only start (line 65), but the payload creation requires both start and end (line 152). Users who select a single date will see it displayed but it won't be saved to the API.

  2. toISOString() corrupts calendar dates in non-UTC timezones: This method converts to UTC before extracting the date string. In timezones east of UTC, midnight local time becomes the previous day in UTC. For example, Jan 1 00:00:00 SGT (UTC+8) converts to Dec 31 16:00:00 UTC, losing the intended calendar day.

Use local date component extraction instead of ISO string conversion, and default end to start for single-day selections:

Suggested payload normalization
+ const formatLocalDate = (d: Date) =>
+   `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
+
  const dates =
-   dateRange.start && dateRange.end
+   dateRange.start
      ? [
          {
-           start: dateRange.start.toISOString().split("T")[0]!,
-           end: dateRange.end.toISOString().split("T")[0]!,
+           start: formatLocalDate(dateRange.start),
+           end: formatLocalDate(dateRange.end ?? dateRange.start),
          },
        ]
      : undefined;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/app/`(app)/trips/[id]/activities/components/add-activity-manual-sheet.tsx
around lines 60 - 67, formatDateRange and the payload creation are inconsistent
and use toISOString which shifts local calendar dates to UTC; ensure when
building the payload you default end to start for single-day selections (so a
DateRange with only start is persisted) and replace any toISOString() usage with
a local-date-only serialization that extracts year/month/day from the Date
(e.g., build "YYYY-MM-DD" from date.getFullYear(), date.getMonth()+1,
date.getDate()) so timezone offsets don't change the calendar day; update the
code paths that construct the API payload (the payload creation logic near where
start/end are read) and keep formatDateRange unchanged for display.

Comment on lines +85 to +92
const [categories, setCategories] = useState<string[]>([]);
const [locationName, setLocationName] = useState<string | null>(null);
const [locationLat, setLocationLat] = useState<number | null>(null);
const [locationLng, setLocationLng] = useState<number | null>(null);
const [price, setPrice] = useState("");
const [dateRange, setDateRange] = useState<DateRange>({ start: null, end: null });
const [link, setLink] = useState("");
const [isDatePickerVisible, setIsDatePickerVisible] = useState(false);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Track all edited fields before discarding the draft.

Lines 191-193 only treat name, description, and thumbnailUri as unsaved input. If the user changes categories, location, price, dates, or link, the sheet closes without confirmation and drops that work.

Suggested `hasData` check
-   const hasData = name.trim() || description.trim() || thumbnailUri;
+   const hasData =
+     name.trim() ||
+     description.trim() ||
+     thumbnailUri ||
+     categories.length > 0 ||
+     locationName ||
+     locationLat != null ||
+     locationLng != null ||
+     price.trim() ||
+     dateRange.start ||
+     dateRange.end ||
+     link.trim();

Also applies to: 191-199

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/app/`(app)/trips/[id]/activities/components/add-activity-manual-sheet.tsx
around lines 85 - 92, The discard check currently only looks at name,
description and thumbnailUri; expand it to consider all editable state used in
this component—categories, locationName, locationLat, locationLng, price,
dateRange.start/dateRange.end, link (in addition to name, description,
thumbnailUri) —create a single hasData/hasUnsavedChanges predicate that inspects
categories.length, non-null locationName/Lat/Lng, non-empty price, non-null
dateRange.start or end, non-empty link, or non-empty
name/description/thumbnailUri, and use that predicate wherever the current
discard/close confirmation is performed (replace the existing
name/description/thumbnailUri-only check in the close handler and any related
functions) so the sheet prompts before losing any edited fields.

Comment on lines +252 to +321
<Box paddingHorizontal="sm" gap="xxs">
<TextInput
value={name}
onChangeText={setName}
placeholder="New Activity"
placeholderTextColor={ColorPalette.gray400}
style={styles.nameInput}
/>
<TextInput
value={description}
onChangeText={setDescription}
placeholder="Add a description"
placeholderTextColor={ColorPalette.gray400}
multiline
style={styles.descriptionInput}
/>
</Box>

<Box paddingHorizontal="sm">
<Box flexDirection="row" flexWrap="wrap" gap="xs" alignItems="center">
{categories.map((cat) => (
<Box key={cat} paddingHorizontal="sm" paddingVertical="xxs" style={styles.categoryChip}>
<Text variant="bodyXsMedium" color="gray700">{cat}</Text>
</Box>
))}
<Pressable onPress={() => categoriesSheetRef.current?.snapToIndex(0)}>
<Box flexDirection="row" alignItems="center" gap="xxs" paddingHorizontal="sm" paddingVertical="xxs" style={styles.addCategoryChip}>
<Plus size={12} color={ColorPalette.gray500} />
<Text variant="bodyXsMedium" color="gray500">Add category</Text>
</Box>
</Pressable>
</Box>
</Box>

<Box paddingHorizontal="sm" gap="xxs" borderTopWidth={1} borderTopColor="gray100" paddingTop="sm">
<FormRow
icon={MapPin}
value={locationName ?? undefined}
placeholder="Location"
onPress={handleLocationPress}
/>
<Box flexDirection="row" alignItems="center" gap="sm" paddingVertical="xs">
<DollarSign size={16} color={price ? ColorPalette.gray700 : ColorPalette.gray400} />
<TextInput
value={price}
onChangeText={setPrice}
placeholder="Price"
placeholderTextColor={ColorPalette.gray400}
keyboardType="decimal-pad"
style={styles.inlineInput}
/>
</Box>
<FormRow
icon={Calendar}
value={formatDateRange(dateRange) ?? undefined}
placeholder="Dates"
onPress={() => setIsDatePickerVisible(true)}
/>
<Box flexDirection="row" alignItems="center" gap="sm" paddingVertical="xs">
<Link size={16} color={link ? ColorPalette.gray700 : ColorPalette.gray400} />
<TextInput
value={link}
onChangeText={setLink}
placeholder="Link"
placeholderTextColor={ColorPalette.gray400}
autoCapitalize="none"
keyboardType="url"
style={styles.inlineInput}
/>
</Box>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Build this form from shared field primitives instead of custom TextInput rows.

This new core flow duplicates field styling with raw TextInputs, inline layout objects, and custom row implementations instead of the shared form components. That will drift from shared validation, theming, and interaction behavior in a high-traffic screen.

As per coding guidelines "React/Expo UI must use a centralized design system; avoid hard-coded color values, magic spacing numbers, and duplicated style definitions" and "React/Expo components must be reusable, typed, composable, and modular; prefer primitive components (Box, Text, Stack) and build complex components from primitives (Button, Avatar, Card)".

Also applies to: 379-418

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/app/`(app)/trips/[id]/activities/components/add-activity-manual-sheet.tsx
around lines 252 - 321, The form blocks use raw TextInput rows and ad-hoc Box
layouts (see name/description TextInput, price/link inline TextInput, and the
category chips) which duplicates styling/behavior; refactor these to use the
app's shared field primitives (e.g., replace the plain TextInput usages in
add-activity-manual-sheet.tsx with the centralized FormField/FormRow or
ControlledInput components, wire their value/onChange to the existing state
setters like setName, setDescription, setPrice, setLink, and reuse the shared
spacing/colors instead of
styles.nameInput/styles.descriptionInput/styles.inlineInput), ensure the
location and dates keep using FormRow (keep handleLocationPress and
setIsDatePickerVisible) and remove duplicated inline Box styling by composing
primitives so validation/theming/interaction stay consistent across the app.

Comment on lines +20 to +21
const { data } = useGetCategoriesByTripID(tripID);
const categories = data?.categories ?? [];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "categories-sheet.tsx" | head -20

Repository: GenerateNU/toggo

Length of output: 134


🏁 Script executed:

cd frontend && git ls-files | grep -E "(categories-sheet|ModelsCategoryAPIResponse|types)" | head -20

Repository: GenerateNU/toggo

Length of output: 207


🏁 Script executed:

# Get the overall structure of the frontend directory
ls -la frontend/

Repository: GenerateNU/toggo

Length of output: 2008


🏁 Script executed:

cat -n frontend/app/\(app\)/trips/\[id\]/activities/components/categories-sheet.tsx

Repository: GenerateNU/toggo

Length of output: 4636


🏁 Script executed:

# Search for the ModelsCategoryAPIResponse type definition
rg "ModelsCategoryAPIResponse" frontend/types/ -A 10

Repository: GenerateNU/toggo

Length of output: 2952


🏁 Script executed:

rg "export type ModelsCategoryAPIResponse" frontend/types/types.gen.ts -A 30

Repository: GenerateNU/toggo

Length of output: 577


🏁 Script executed:

rg "useGetCategoriesByTripID" frontend/api/ -A 20

Repository: GenerateNU/toggo

Length of output: 12389


🏁 Script executed:

# Also check the schema for the response type
rg "ModelsCategoryListResponse" frontend/types/types.gen.ts -B 2 -A 10

Repository: GenerateNU/toggo

Length of output: 569


🏁 Script executed:

# Verify the actual query response type for GetCategoriesByTripID
cat -n frontend/api/categories/useGetCategoriesByTripID.ts | head -50

Repository: GenerateNU/toggo

Length of output: 1865


🏁 Script executed:

rg "GetCategoriesByTripIDQueryResponse" frontend/types/types.gen.ts -B 2 -A 2

Repository: GenerateNU/toggo

Length of output: 232


Ensure categories have a stable name before rendering.

ModelsCategoryAPIResponse.name is optional, but lines 64-67 use it as the React key and pass it to toggle(). A category without a name creates an unstable key and pushes undefined into the selection state, breaking toggle behavior. Filter out or normalize categories to guarantee each has a name before rendering.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/`(app)/trips/[id]/activities/components/categories-sheet.tsx
around lines 20 - 21, The categories list derived from
useGetCategoriesByTripID(tripID) may include items with undefined
ModelsCategoryAPIResponse.name which are currently used as React keys and passed
into toggle(), causing unstable keys and invalid selection entries; before
rendering (where categories is used and passed to toggle()), filter or map the
fetched data to only include categories with a defined non-empty name (or
normalize missing names to a stable placeholder id), e.g., produce a
sanitizedCategories variable from data?.categories that excludes entries lacking
name (or assigns a deterministic fallback like `category-<id>`), then use
sanitizedCategories everywhere React keys and toggle() are called so keys and
selection values remain stable.

Comment on lines +88 to +102
<Pressable>
<Box
flexDirection="row"
alignItems="center"
gap="xxs"
paddingHorizontal="sm"
paddingVertical="xxs"
style={styles.chip}
>
<Plus size={12} color={ColorPalette.gray500} />
<Text variant="bodyXsMedium" color="gray500">
Create amenity
</Text>
</Box>
</Pressable>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Remove or wire the “Create amenity” chip before shipping.

This renders as a tappable control with no handler, so the new activity flow exposes a dead-end action. Hide it, disable it, or connect it to the actual category-creation flow.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/`(app)/trips/[id]/activities/components/categories-sheet.tsx
around lines 88 - 102, The "Create amenity" chip is rendered as a Pressable with
no onPress handler (Pressable > Box ... Text "Create amenity"), producing a dead
tappable control; either remove the entire Pressable/Box block or wire it to the
real category-creation flow by adding an onPress that calls the existing
create/open handler (e.g., openCreateCategoryModal or createCategory function in
this component or parent) and keep styling via styles.chip, Plus and Text as-is
so the UI remains consistent; if the feature isn't ready, prefer removing or
rendering it disabled/non-interactive instead of leaving an empty Pressable.

@in-mai-space in-mai-space linked an issue Apr 4, 2026 that may be closed by this pull request
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/app/(app)/trips/[id]/search-location.tsx (1)

86-114: ⚠️ Potential issue | 🟠 Major

Place-details failures leave the user on a blank screen.

This flow clears the predictions before requesting details. If that request fails, the code only logs the raw error and then falls through to the empty gray box at Line 149, so the user gets no message and no retry path. Restore the prior list or render a user-friendly error state here.

As per coding guidelines, "React/Expo UI error handling must not expose raw API errors; use UI states (LoadingState, ErrorState, EmptyState); errors shown to users must be clear and user-friendly."

Also applies to: 149-149

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/`(app)/trips/[id]/search-location.tsx around lines 86 - 114, The
handler handleSelectPrediction clears predictions then fetches details; when
getPlaceDetailsCustom fails the code only logs the raw error and leaves the UI
blank. Modify handleSelectPrediction to (1) preserve the previous predictions in
a local variable before clearing, (2) on any fetch error call
setIsSelectingPlace(false) and setIsLoadingDetails(false), restore the saved
predictions via setPredictions(savedPredictions), and set a user-friendly error
state (e.g., set an ErrorState / setShowPlaceError(true) or set an error message
string) instead of logging the raw error, and (3) ensure navigation
(router.navigate/router.back) only runs on success; update any UI to render that
ErrorState with a retry action that re-invokes handleSelectPrediction or
re-queries getPlaceDetailsCustom.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/app/`(app)/trips/[id]/search-location.tsx:
- Around line 52-55: The component currently reads tripID from
useLocalSearchParams and uses it to build return destinations, which can
mismatch the route param; change it to read the route param instead (useParams
from next/navigation) and treat that as the single source of truth for the trip
id (e.g., const { id: tripID } = useParams()). Replace usages that build return
paths from the query tripID (and any logic that falls back to router.back()) to
always construct return destinations using this route-derived tripID and
preserve returnTo only for path suffixes; update the same pattern in the other
occurrence around the block that currently reads useLocalSearchParams (lines
referenced as 96-108) so all return path construction, navigation pushes, and
conditionals reference the route param variable rather than the query param.

---

Outside diff comments:
In `@frontend/app/`(app)/trips/[id]/search-location.tsx:
- Around line 86-114: The handler handleSelectPrediction clears predictions then
fetches details; when getPlaceDetailsCustom fails the code only logs the raw
error and leaves the UI blank. Modify handleSelectPrediction to (1) preserve the
previous predictions in a local variable before clearing, (2) on any fetch error
call setIsSelectingPlace(false) and setIsLoadingDetails(false), restore the
saved predictions via setPredictions(savedPredictions), and set a user-friendly
error state (e.g., set an ErrorState / setShowPlaceError(true) or set an error
message string) instead of logging the raw error, and (3) ensure navigation
(router.navigate/router.back) only runs on success; update any UI to render that
ErrorState with a retry action that re-invokes handleSelectPrediction or
re-queries getPlaceDetailsCustom.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4c608b54-9261-48cf-8a7a-a85bf39bd418

📥 Commits

Reviewing files that changed from the base of the PR and between acaf1b3 and 016d647.

📒 Files selected for processing (1)
  • frontend/app/(app)/trips/[id]/search-location.tsx

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

♻️ Duplicate comments (2)
frontend/app/(app)/trips/[id]/activities/components/add-activity-manual-sheet.tsx (2)

200-208: ⚠️ Potential issue | 🟠 Major

hasData check misses most form fields.

The discard confirmation only considers name, description, and thumbnailUri. If the user sets categories, location, price, dates, or link and then cancels, those changes are silently discarded without confirmation.

Proposed fix
  const handleCancel = () => {
-   const hasData = name.trim() || description.trim() || thumbnailUri;
+   const hasData =
+     name.trim() ||
+     description.trim() ||
+     thumbnailUri ||
+     categories.length > 0 ||
+     locationName ||
+     price.trim() ||
+     dateRange.start ||
+     link.trim();
    if (hasData) {
      setShowCancelConfirm(true);
    } else {
      resetForm();
      onClose();
    }
  };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/app/`(app)/trips/[id]/activities/components/add-activity-manual-sheet.tsx
around lines 200 - 208, The discard-confirmation logic in handleCancel only
checks name, description, and thumbnailUri (hasData) and ignores other form
fields; update the hasData calculation inside handleCancel to also consider all
other form state variables used in this component (e.g., categories, location,
price, startDate/endDate or dateRange, link, and any other form flags or arrays)
so any non-empty or modified value triggers setShowCancelConfirm(true); if none
are set, continue to call resetForm() and onClose() as before. Ensure you
reference the exact state variables used by the form (the ones declared
alongside name, description, thumbnailUri) when updating hasData so no field is
missed.

159-167: ⚠️ Potential issue | 🟠 Major

Date serialization loses local calendar day in some timezones.

toISOString() converts to UTC before extracting the date string. In timezones east of UTC, local midnight becomes the previous day in UTC. For example, selecting Jan 1 at midnight in UTC+8 produces "Dec 31" in the payload.

Additionally, single-day selections won't persist: formatDateRange displays a single date correctly (line 63-64), but the payload is only created when both start and end are non-null.

Proposed fix
+ const formatLocalDate = (d: Date) =>
+   `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
+
  const dates =
-   dateRange.start && dateRange.end
+   dateRange.start
      ? [
          {
-           start: dateRange.start.toISOString().split("T")[0]!,
-           end: dateRange.end.toISOString().split("T")[0]!,
+           start: formatLocalDate(dateRange.start),
+           end: formatLocalDate(dateRange.end ?? dateRange.start),
          },
        ]
      : undefined;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/app/`(app)/trips/[id]/activities/components/add-activity-manual-sheet.tsx
around lines 159 - 167, The payload date serialization uses toISOString() which
converts to UTC and can shift the day in timezones east of UTC, and it also only
builds dates when both dateRange.start and dateRange.end are present so
single-day selections are dropped; update the dates construction around the
dateRange variable so it formats dates in local calendar day (e.g., use a local
YYYY-MM-DD formatter such as toLocaleDateString('en-CA') or the existing
formatDate utility) and ensure a single selected day is preserved by treating
missing end as equal to start (i.e., if dateRange.start exists create an object
with start = localFormatted(dateRange.start) and end =
localFormatted(dateRange.end ?? dateRange.start)).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/app/`(app)/trips/[id]/activities/components/activity-card.tsx:
- Around line 37-44: The "New • 1m" badge and "1 new comment" text are hardcoded
placeholders; update the ActivityCard to derive these from the activity model
instead of static strings: use isNew together with activity.createdAt to render
a relative timestamp (e.g., formatDistanceToNow(activity.createdAt) or your
app's date util) for the badge text, and replace "1 new comment" with the actual
count from activity.commentCount or activity.comments?.length (render nothing or
hide the element if the value is undefined/zero). If these UI bits are
intentionally not implemented yet, remove the placeholder strings and the
conditional render blocks around them (isNew badge and comment-count span) until
real data is available. Ensure you update the JSX locations referencing isNew,
activity.createdAt, activity.commentCount, and activity.comments accordingly.

In
`@frontend/app/`(app)/trips/[id]/activities/components/add-activity-manual-sheet.tsx:
- Around line 109-119: The focus-effect currently calls getPendingLocation()
(which clears module-level state) inside useFocusEffect, risking lost/misapplied
location; change the flow so the component does not consume fragile global state
on focus: instead read the pending location from a safe source passed via
navigation params or a stable API (e.g., route.params.pendingLocation or a new
peekPendingLocation() that does not clear), then call an explicit
clearPendingLocation(id) only after applying it; update the useFocusEffect
callback that sets setLocationName, setLocationLat, setLocationLng to use the
new safe source and explicit clear function, and/or adjust getPendingLocation to
provide a non-destructive peek + separate consume function so
search-location.tsx and add-activity-manual-sheet.tsx no longer rely on a single
destructive getter.

In `@frontend/app/`(app)/trips/[id]/activities/index.tsx:
- Around line 54-62: The handlers handleAutofilled and handleManual use a
hard-coded 300ms setTimeout after calling entrySheetRef.current?.close() before
opening manualSheetRef, which couples behavior to an implicit animation
duration; replace the magic number by either using the sheet component's
close-completion callback/promise (listen for a close/animationend event or
await a returned Promise from entrySheetRef.close()) to then call
manualSheetRef.current?.open(...) OR extract the 300 into a named constant like
ANIMATION_CLOSE_DELAY_MS and document it so the delay is configurable and
obvious; update both handleAutofilled and handleManual to use the chosen
approach.
- Around line 144-157: The sheets are being rendered with tripID fallback to an
empty string which can trigger backend errors; update the rendering logic around
AddActivityEntrySheet and AddActivityManualSheet so they only mount when a valid
tripID exists (e.g., guard with if/tripID check and render an error/placeholder
otherwise) and pass the actual tripID (not "") into the components; locate the
JSX referencing AddActivityEntrySheet, AddActivityManualSheet, entrySheetRef,
manualSheetRef, and the handlers (handleManual, handleAutofilled, handleSaved)
and wrap those components in a conditional that prevents mounting when tripID is
undefined or render an explicit missing-param error state instead.
- Around line 39-46: The destructured search params include unused fields
(locationName, locationLat, locationLng) from the useLocalSearchParams call;
either remove those unused properties from the generic type so the hook is just
useLocalSearchParams<{ id: string }>() or, if they are intended to be used,
actually read and use the variables (locationName, locationLat, locationLng)
elsewhere in this component (e.g., pass them to state, props, or helper
functions); update the destructuring at the useLocalSearchParams invocation and
any related code paths (references to tripID, useLocalSearchParams) accordingly
to eliminate the unused variables or make use of them.

In `@frontend/app/`(app)/trips/[id]/search-location.tsx:
- Around line 50-56: The module-level variable _pendingLocation and its
read-and-clear accessor getPendingLocation create fragile cross-screen coupling;
replace them with an explicit state manager—e.g., create a small Zustand store
(usePendingLocationStore) that exposes setPendingLocation(location),
getPendingLocation(), and clearPendingLocation() (or use navigation params to
pass the location back to the return route) and update all references in
search-location.tsx to use the store/API instead of the module variable; remove
_pendingLocation and getPendingLocation once all callers use the new store or
navigation param flow.
- Around line 134-142: The function handleConfirm has inconsistent indentation
(its body is not indented relative to the declaration); fix by reformatting the
handleConfirm block so the body lines (the if check, _pendingLocation assignment
and router.back() call) are indented one level inside the function, ensuring
consistent spacing around the function declaration and braces and matching
surrounding file style where handleConfirm is defined.

---

Duplicate comments:
In
`@frontend/app/`(app)/trips/[id]/activities/components/add-activity-manual-sheet.tsx:
- Around line 200-208: The discard-confirmation logic in handleCancel only
checks name, description, and thumbnailUri (hasData) and ignores other form
fields; update the hasData calculation inside handleCancel to also consider all
other form state variables used in this component (e.g., categories, location,
price, startDate/endDate or dateRange, link, and any other form flags or arrays)
so any non-empty or modified value triggers setShowCancelConfirm(true); if none
are set, continue to call resetForm() and onClose() as before. Ensure you
reference the exact state variables used by the form (the ones declared
alongside name, description, thumbnailUri) when updating hasData so no field is
missed.
- Around line 159-167: The payload date serialization uses toISOString() which
converts to UTC and can shift the day in timezones east of UTC, and it also only
builds dates when both dateRange.start and dateRange.end are present so
single-day selections are dropped; update the dates construction around the
dateRange variable so it formats dates in local calendar day (e.g., use a local
YYYY-MM-DD formatter such as toLocaleDateString('en-CA') or the existing
formatDate utility) and ensure a single selected day is preserved by treating
missing end as equal to start (i.e., if dateRange.start exists create an object
with start = localFormatted(dateRange.start) and end =
localFormatted(dateRange.end ?? dateRange.start)).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: facd3a0b-0a11-4b48-8b78-ba8302e7bc28

📥 Commits

Reviewing files that changed from the base of the PR and between 016d647 and b024bb5.

📒 Files selected for processing (4)
  • frontend/app/(app)/trips/[id]/activities/components/activity-card.tsx
  • frontend/app/(app)/trips/[id]/activities/components/add-activity-manual-sheet.tsx
  • frontend/app/(app)/trips/[id]/activities/index.tsx
  • frontend/app/(app)/trips/[id]/search-location.tsx

Comment on lines +37 to +44
{isNew && (
<Box style={styles.newBadge}>
<Text variant="bodyXxsMedium" color="white">
New • 1m
</Text>
</Box>
)}
</Box>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Hardcoded placeholder text when isNew is true.

Lines 40 and 101 display static strings ("New • 1m", "1 new comment") that don't reflect actual activity data. If isNew is meant to indicate genuinely new activities, the badge timestamp and comment count should come from the activity model. If these are intentional placeholders, consider removing them until the feature is implemented.

Also applies to: 98-104

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/`(app)/trips/[id]/activities/components/activity-card.tsx around
lines 37 - 44, The "New • 1m" badge and "1 new comment" text are hardcoded
placeholders; update the ActivityCard to derive these from the activity model
instead of static strings: use isNew together with activity.createdAt to render
a relative timestamp (e.g., formatDistanceToNow(activity.createdAt) or your
app's date util) for the badge text, and replace "1 new comment" with the actual
count from activity.commentCount or activity.comments?.length (render nothing or
hide the element if the value is undefined/zero). If these UI bits are
intentionally not implemented yet, remove the placeholder strings and the
conditional render blocks around them (isNew badge and comment-count span) until
real data is available. Ensure you update the JSX locations referencing isNew,
activity.createdAt, activity.commentCount, and activity.comments accordingly.

Comment on lines +109 to +119
// ─── Consume pending location when returning from search-location ─────────
useFocusEffect(
useCallback(() => {
const loc = getPendingLocation();
if (loc) {
setLocationName(loc.name);
setLocationLat(loc.lat);
setLocationLng(loc.lng);
}
}, []),
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

useFocusEffect callback depends on fragile global state.

The callback reads from getPendingLocation() which clears the value on read. If navigation timing differs (e.g., fast back-swipe, interrupted navigation), the location data could be lost or applied to the wrong form instance. This is coupled to the module-level state issue in search-location.tsx.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/app/`(app)/trips/[id]/activities/components/add-activity-manual-sheet.tsx
around lines 109 - 119, The focus-effect currently calls getPendingLocation()
(which clears module-level state) inside useFocusEffect, risking lost/misapplied
location; change the flow so the component does not consume fragile global state
on focus: instead read the pending location from a safe source passed via
navigation params or a stable API (e.g., route.params.pendingLocation or a new
peekPendingLocation() that does not clear), then call an explicit
clearPendingLocation(id) only after applying it; update the useFocusEffect
callback that sets setLocationName, setLocationLat, setLocationLng to use the
new safe source and explicit clear function, and/or adjust getPendingLocation to
provide a non-destructive peek + separate consume function so
search-location.tsx and add-activity-manual-sheet.tsx no longer rely on a single
destructive getter.

Comment on lines +39 to +46
const {
id: tripID,
} = useLocalSearchParams<{
id: string;
locationName?: string;
locationLat?: string;
locationLng?: string;
}>();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Unused location params in destructuring.

locationName, locationLat, and locationLng are extracted from search params but never used in this component. Either remove them or document their intended purpose.

Proposed fix
   const {
     id: tripID,
-  } = useLocalSearchParams<{
-    id: string;
-    locationName?: string;
-    locationLat?: string;
-    locationLng?: string;
-  }>();
+  } = useLocalSearchParams<{ id: string }>();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const {
id: tripID,
} = useLocalSearchParams<{
id: string;
locationName?: string;
locationLat?: string;
locationLng?: string;
}>();
const {
id: tripID,
} = useLocalSearchParams<{ id: string }>();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/`(app)/trips/[id]/activities/index.tsx around lines 39 - 46, The
destructured search params include unused fields (locationName, locationLat,
locationLng) from the useLocalSearchParams call; either remove those unused
properties from the generic type so the hook is just useLocalSearchParams<{ id:
string }>() or, if they are intended to be used, actually read and use the
variables (locationName, locationLat, locationLng) elsewhere in this component
(e.g., pass them to state, props, or helper functions); update the destructuring
at the useLocalSearchParams invocation and any related code paths (references to
tripID, useLocalSearchParams) accordingly to eliminate the unused variables or
make use of them.

Comment on lines +54 to +62
const handleAutofilled = useCallback((data: ModelsParsedActivityData) => {
entrySheetRef.current?.close();
setTimeout(() => manualSheetRef.current?.open(data), 300);
}, []);

const handleCreateActivity = () => {
if (!tripID) return;
const count = activities.length + 1;
createActivity({
tripID,
data: {
name: `Test Activity ${count}`,
description: "Created for comment testing",
},
});
};
const handleManual = useCallback(() => {
entrySheetRef.current?.close();
setTimeout(() => manualSheetRef.current?.open(), 300);
}, []);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Magic 300ms delay without explanation.

The setTimeout(..., 300) calls assume the closing animation completes in 300ms. If the animation duration changes or varies by device, this could cause visual glitches. Consider either using a callback from the sheet's close completion or extracting this value to a named constant.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/`(app)/trips/[id]/activities/index.tsx around lines 54 - 62, The
handlers handleAutofilled and handleManual use a hard-coded 300ms setTimeout
after calling entrySheetRef.current?.close() before opening manualSheetRef,
which couples behavior to an implicit animation duration; replace the magic
number by either using the sheet component's close-completion callback/promise
(listen for a close/animationend event or await a returned Promise from
entrySheetRef.close()) to then call manualSheetRef.current?.open(...) OR extract
the 300 into a named constant like ANIMATION_CLOSE_DELAY_MS and document it so
the delay is configurable and obvious; update both handleAutofilled and
handleManual to use the chosen approach.

Comment on lines +144 to +157
<AddActivityEntrySheet
ref={entrySheetRef}
tripID={tripID ?? ""}
onManual={handleManual}
onAutofilled={handleAutofilled}
onClose={() => entrySheetRef.current?.close()}
/>

<AddActivityManualSheet
ref={manualSheetRef}
tripID={tripID ?? ""}
onSaved={handleSaved}
onClose={() => manualSheetRef.current?.close()}
/>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Empty string fallback for tripID may cause API errors.

Passing tripID ?? "" when tripID is undefined will likely result in 404 or validation errors from the backend. Consider either:

  • Adding a guard to prevent rendering the sheets when tripID is undefined
  • Showing an error state if the route param is missing
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/`(app)/trips/[id]/activities/index.tsx around lines 144 - 157,
The sheets are being rendered with tripID fallback to an empty string which can
trigger backend errors; update the rendering logic around AddActivityEntrySheet
and AddActivityManualSheet so they only mount when a valid tripID exists (e.g.,
guard with if/tripID check and render an error/placeholder otherwise) and pass
the actual tripID (not "") into the components; locate the JSX referencing
AddActivityEntrySheet, AddActivityManualSheet, entrySheetRef, manualSheetRef,
and the handlers (handleManual, handleAutofilled, handleSaved) and wrap those
components in a conditional that prevents mounting when tripID is undefined or
render an explicit missing-param error state instead.

Comment on lines +50 to +56
let _pendingLocation: { name: string; lat: number; lng: number } | null = null;

export function getPendingLocation() {
const loc = _pendingLocation;
_pendingLocation = null;
return loc;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Module-level mutable state creates fragile cross-screen coupling.

_pendingLocation is shared global state that persists across component lifecycles. The read-and-clear pattern in getPendingLocation() is fragile: if the consumer doesn't call it (e.g., navigation is interrupted or focus callback doesn't fire), the stale value persists for the next unrelated navigation. If called twice, the second call gets null.

Consider using a proper state management approach such as:

  • Zustand store with explicit clear semantics
  • Navigation params passed directly to the return route
  • React context scoped to the navigation stack
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/`(app)/trips/[id]/search-location.tsx around lines 50 - 56, The
module-level variable _pendingLocation and its read-and-clear accessor
getPendingLocation create fragile cross-screen coupling; replace them with an
explicit state manager—e.g., create a small Zustand store
(usePendingLocationStore) that exposes setPendingLocation(location),
getPendingLocation(), and clearPendingLocation() (or use navigation params to
pass the location back to the return route) and update all references in
search-location.tsx to use the store/API instead of the module variable; remove
_pendingLocation and getPendingLocation once all callers use the new store or
navigation param flow.

Comment on lines +134 to +142
const handleConfirm = () => {
if (!selectedLocation) return;
_pendingLocation = {
name: selectedLocation.name || selectedLocation.formatted_address,
lat: selectedLocation.geometry.location.lat,
lng: selectedLocation.geometry.location.lng,
};
router.back();
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

handleConfirm indentation is inconsistent.

The function body is not indented relative to the function declaration, which breaks code formatting conventions.

Proposed fix
 const handleConfirm = () => {
-  if (!selectedLocation) return;
-  _pendingLocation = {
-    name: selectedLocation.name || selectedLocation.formatted_address,
-    lat: selectedLocation.geometry.location.lat,
-    lng: selectedLocation.geometry.location.lng,
-  };
-  router.back();
-};
+   if (!selectedLocation) return;
+   _pendingLocation = {
+     name: selectedLocation.name || selectedLocation.formatted_address,
+     lat: selectedLocation.geometry.location.lat,
+     lng: selectedLocation.geometry.location.lng,
+   };
+   router.back();
+ };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/`(app)/trips/[id]/search-location.tsx around lines 134 - 142,
The function handleConfirm has inconsistent indentation (its body is not
indented relative to the declaration); fix by reformatting the handleConfirm
block so the body lines (the if check, _pendingLocation assignment and
router.back() call) are indented one level inside the function, ensuring
consistent spacing around the function declaration and braces and matching
surrounding file style where handleConfirm is defined.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FE]: Create Activity Flow

1 participant