Conversation
📝 WalkthroughWalkthroughThis 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
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts
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. Comment |
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (1)
frontend/assets/images/binoculars.pngis excluded by!**/*.png
📒 Files selected for processing (8)
frontend/app/(app)/index.tsxfrontend/app/(app)/trips/[id]/activities/components/activity-card.tsxfrontend/app/(app)/trips/[id]/activities/components/add-activity-entry-sheet.tsxfrontend/app/(app)/trips/[id]/activities/components/add-activity-manual-sheet.tsxfrontend/app/(app)/trips/[id]/activities/components/categories-sheet.tsxfrontend/app/(app)/trips/[id]/activities/components/form-row.tsxfrontend/app/(app)/trips/[id]/activities/index.tsxfrontend/app/(app)/trips/[id]/search-location.tsx
| const handleClose = () => { | ||
| setVisible(false); | ||
| setUrl(""); | ||
| onClose(); | ||
| }; |
There was a problem hiding this comment.
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).
| 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)}`; | ||
| } |
There was a problem hiding this comment.
🧩 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 -120Repository: GenerateNU/toggo
Length of output: 5169
🏁 Script executed:
# Also check the type definitions for DateRange
rg "type DateRange|interface DateRange" -A 5Repository: GenerateNU/toggo
Length of output: 1219
🏁 Script executed:
# Check the date picker implementation
cat -n frontend/design-system/primitives/date-picker.tsx | head -100Repository: 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 2Repository: 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 3Repository: GenerateNU/toggo
Length of output: 87
🏁 Script executed:
# Search for toISOString usage in the codebase
rg "toISOString" -B 3 -A 3Repository: 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 2Repository: GenerateNU/toggo
Length of output: 1144
Fix timezone-dependent date loss and single-day selection handling.
The code has two related issues:
-
Single-day selections won't persist:
formatDateRangeaccepts a DateRange with onlystart(line 65), but the payload creation requires bothstartandend(line 152). Users who select a single date will see it displayed but it won't be saved to the API. -
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.
| 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); |
There was a problem hiding this comment.
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.
| <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> |
There was a problem hiding this comment.
🛠️ 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.
| const { data } = useGetCategoriesByTripID(tripID); | ||
| const categories = data?.categories ?? []; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "categories-sheet.tsx" | head -20Repository: GenerateNU/toggo
Length of output: 134
🏁 Script executed:
cd frontend && git ls-files | grep -E "(categories-sheet|ModelsCategoryAPIResponse|types)" | head -20Repository: 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.tsxRepository: GenerateNU/toggo
Length of output: 4636
🏁 Script executed:
# Search for the ModelsCategoryAPIResponse type definition
rg "ModelsCategoryAPIResponse" frontend/types/ -A 10Repository: GenerateNU/toggo
Length of output: 2952
🏁 Script executed:
rg "export type ModelsCategoryAPIResponse" frontend/types/types.gen.ts -A 30Repository: GenerateNU/toggo
Length of output: 577
🏁 Script executed:
rg "useGetCategoriesByTripID" frontend/api/ -A 20Repository: 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 10Repository: GenerateNU/toggo
Length of output: 569
🏁 Script executed:
# Verify the actual query response type for GetCategoriesByTripID
cat -n frontend/api/categories/useGetCategoriesByTripID.ts | head -50Repository: GenerateNU/toggo
Length of output: 1865
🏁 Script executed:
rg "GetCategoriesByTripIDQueryResponse" frontend/types/types.gen.ts -B 2 -A 2Repository: 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.
| <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> |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 | 🟠 MajorPlace-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
📒 Files selected for processing (1)
frontend/app/(app)/trips/[id]/search-location.tsx
There was a problem hiding this comment.
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
hasDatacheck misses most form fields.The discard confirmation only considers
name,description, andthumbnailUri. 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 | 🟠 MajorDate 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:
formatDateRangedisplays a single date correctly (line 63-64), but the payload is only created when bothstartandendare 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
📒 Files selected for processing (4)
frontend/app/(app)/trips/[id]/activities/components/activity-card.tsxfrontend/app/(app)/trips/[id]/activities/components/add-activity-manual-sheet.tsxfrontend/app/(app)/trips/[id]/activities/index.tsxfrontend/app/(app)/trips/[id]/search-location.tsx
| {isNew && ( | ||
| <Box style={styles.newBadge}> | ||
| <Text variant="bodyXxsMedium" color="white"> | ||
| New • 1m | ||
| </Text> | ||
| </Box> | ||
| )} | ||
| </Box> |
There was a problem hiding this comment.
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.
| // ─── Consume pending location when returning from search-location ───────── | ||
| useFocusEffect( | ||
| useCallback(() => { | ||
| const loc = getPendingLocation(); | ||
| if (loc) { | ||
| setLocationName(loc.name); | ||
| setLocationLat(loc.lat); | ||
| setLocationLng(loc.lng); | ||
| } | ||
| }, []), | ||
| ); |
There was a problem hiding this comment.
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.
| const { | ||
| id: tripID, | ||
| } = useLocalSearchParams<{ | ||
| id: string; | ||
| locationName?: string; | ||
| locationLat?: string; | ||
| locationLng?: string; | ||
| }>(); |
There was a problem hiding this comment.
🧹 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.
| 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.
| 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); | ||
| }, []); |
There was a problem hiding this comment.
🧹 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.
| <AddActivityEntrySheet | ||
| ref={entrySheetRef} | ||
| tripID={tripID ?? ""} | ||
| onManual={handleManual} | ||
| onAutofilled={handleAutofilled} | ||
| onClose={() => entrySheetRef.current?.close()} | ||
| /> | ||
|
|
||
| <AddActivityManualSheet | ||
| ref={manualSheetRef} | ||
| tripID={tripID ?? ""} | ||
| onSaved={handleSaved} | ||
| onClose={() => manualSheetRef.current?.close()} | ||
| /> |
There was a problem hiding this comment.
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
tripIDis 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.
| let _pendingLocation: { name: string; lat: number; lng: number } | null = null; | ||
|
|
||
| export function getPendingLocation() { | ||
| const loc = _pendingLocation; | ||
| _pendingLocation = null; | ||
| return loc; | ||
| } |
There was a problem hiding this comment.
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.
| const handleConfirm = () => { | ||
| if (!selectedLocation) return; | ||
| _pendingLocation = { | ||
| name: selectedLocation.name || selectedLocation.formatted_address, | ||
| lat: selectedLocation.geometry.location.lat, | ||
| lng: selectedLocation.geometry.location.lng, | ||
| }; | ||
| router.back(); | ||
| }; |
There was a problem hiding this comment.
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.
Description
How has this been tested?
Checklist
Features
Improvements
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)