The frontend communicates with the Django REST backend exclusively through a thin set of typed functions in theDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/viet2811/uk-travel-recommendation/llms.txt
Use this file to discover all available pages before exploring further.
api/ directory and a custom React hook in hooks/. All calls go through a shared axiosInstance that is pre-configured with the backend base URL, a JSON content-type header, and two interceptors — one that attaches the JWT access token to every outgoing request, and one that silently refreshes the token and retries the original call on any 401 Unauthorized response. This architecture means that feature-level code never deals with token management or retry logic directly.
Configuration
The Axios instance is created once inapi/axios.ts and imported by every API module:
Base URL
Set via the
EXPO_PUBLIC_BACKEND_URL environment variable. Prefix the variable name with EXPO_PUBLIC_ to make it available in the JavaScript bundle at build time via Expo’s env-var injection.Auth Interceptor
A request interceptor reads
accessToken from expo-secure-store before every call and injects Authorization: Bearer <token>. A response interceptor handles 401 errors by refreshing the token and retrying — see the Authentication page for full details.Never hard-code a backend URL. Use
.env (development) and Expo’s EAS Secrets (production) to keep the base URL configurable across environments.Attraction Functions
All attraction-related API calls are defined inapi/attraction.ts and imported by screens and hooks as needed.
- getRecommendations
- searchAttractions
- likeAttraction
- dislikeAttraction
- addVisitedAttractions
- getLikedAttraction
Fetches the next batch of personalised attraction recommendations for the current user.
The geo filter value is a pre-formed query string (e.g.
| Detail | Value |
|---|---|
| Method | GET |
| Path | /recommendations (optionally with a geo query string, e.g. /recommendations?area=Scotland) |
| Auth required | Yes |
| Geo filter | Read from AsyncStorage key geoFilter and appended verbatim to the URL path |
| Returns | response.data — the raw backend payload |
?area=London) written by the UpdateGeoFilter screen and read here without further processing.User Functions
User account and preference management functions are defined inapi/user.ts.
- registerUser
- setUserPreferences
Creates a new user account on the backend.
| Detail | Value |
|---|---|
| Method | POST |
| Path | user/register/ |
| Auth required | No |
| Body | { username: string, password: string } |
After a successful registration, the
RegisterScreen immediately calls login(username, password) from AuthContext to obtain tokens and transition the user into the onboarding flow without a separate sign-in step.useRecommendations Hook
TheuseRecommendations hook in hooks/useRecommendation.tsx is the primary data layer for the DiscoveryScreen. It wraps a TanStack Query useInfiniteQuery and co-locates all swipe interaction logic, so the screen component itself stays thin.
Parameters
The hook takes no parameters. It reads the geo filter fromAsyncStorage internally via getRecommendations().
Return Values
| Return value | Type | Description |
|---|---|---|
allRecommendations | Attraction[] | Flat, deduplicated array of all recommendations fetched so far across all pages. Safe to index directly with currentIndex. |
currentIndex | number | Zero-based index of the card currently displayed on top of the swipe deck. |
setCurrentIndex | (index: number) => void | Setter for currentIndex. Increment this after each swipe to advance the deck. |
isLoading | boolean | true during the initial fetch. Show a loading indicator while this is true. |
onSwipeLeft | (id: string) => void | Call with the swiped attraction’s ID. Calls dislikeAttraction(id) directly (not via a mutation). |
onSwipeRight | (id: string) => void | Call with the swiped attraction’s ID. Calls likeMutation.mutate(id), which invokes likeAttraction(id) and on success invalidates the likedHistory query. |
Auto-Pagination
The hook monitorscurrentIndex against the length of allRecommendations. When five or fewer cards remain (i.e. allRecommendations.length - currentIndex <= 5), the TanStack Query infinite query automatically fetches the next page from getRecommendations() and appends the new items. Duplicates are removed via a Set-based dedup step before the combined array is returned, ensuring the deck never shows the same attraction twice.
Swipe Behaviour
The two swipe handlers have different internal implementations:onSwipeLeft(id)— callsdislikeAttraction(id)directly. The call is fire-and-forget; there is no mutation wrapper and no cache invalidation.onSwipeRight(id)— callslikeMutation.mutate(id). The underlyinguseMutationcallslikeAttraction(id)and, on success, runsqueryClient.invalidateQueries({ queryKey: ['likedHistory'] })so the Liked tab updates immediately.