Add CI pipeline, pre-commit hooks, and offline caching
CI / lint-typecheck-test (push) Has been cancelled
CI / lint-typecheck-test (push) Has been cancelled
Configure GitHub Actions for linting, typechecking, and testing. Add Husky and lint-staged for pre-commit checks. Implement API response caching in AsyncStorage for offline support. Move core tests to packages/core and add vitest configuration.
This commit is contained in:
@@ -0,0 +1,604 @@
|
||||
## Phase 1 — New Settings Infrastructure (Steps 1-3)
|
||||
|
||||
Extend the `ReminderSettings` type, hook, and UI panel with three new options.
|
||||
|
||||
---
|
||||
|
||||
#### Step 1: Extend ReminderSettings Type (~5 min)
|
||||
|
||||
**File:** `packages/core/src/types.ts`
|
||||
|
||||
**Goal:** Add three new fields to `ReminderSettings` for arrival buffer, walking, and bike toggles.
|
||||
|
||||
**Change `ReminderSettings` interface:**
|
||||
|
||||
```typescript
|
||||
export interface ReminderSettings {
|
||||
bufferMinutes: number;
|
||||
enabled: boolean;
|
||||
arrivalBufferMinutes: number; // arrive X minutes before event (default 5)
|
||||
showWalkingOption: boolean; // show walk-from-station option (default true)
|
||||
showBikeOption: boolean; // show bike route section (default true)
|
||||
}
|
||||
```
|
||||
|
||||
**Actions:**
|
||||
1. Add the three new fields to the interface
|
||||
2. No changes needed to other types
|
||||
|
||||
**Acceptance criteria:**
|
||||
- [ ] `packages/core` compiles without errors
|
||||
- [ ] `npm run typecheck -w packages/core` passes
|
||||
|
||||
---
|
||||
|
||||
#### Step 2: Update useReminderSettings Hook (~10 min)
|
||||
|
||||
**File:** `apps/web/src/hooks/useReminderSettings.tsx`
|
||||
|
||||
**Goal:** Add default values and setters for the three new settings fields.
|
||||
|
||||
**Changes:**
|
||||
|
||||
Update DEFAULTS:
|
||||
```typescript
|
||||
const DEFAULTS: ReminderSettings = {
|
||||
bufferMinutes: 15,
|
||||
enabled: true,
|
||||
arrivalBufferMinutes: 5,
|
||||
showWalkingOption: true,
|
||||
showBikeOption: true,
|
||||
};
|
||||
```
|
||||
|
||||
Update context interface:
|
||||
```typescript
|
||||
interface ReminderContextType extends ReminderSettings {
|
||||
setBufferMinutes: (minutes: number) => void;
|
||||
setEnabled: (enabled: boolean) => void;
|
||||
setArrivalBufferMinutes: (minutes: number) => void;
|
||||
setShowWalkingOption: (show: boolean) => void;
|
||||
setShowBikeOption: (show: boolean) => void;
|
||||
}
|
||||
```
|
||||
|
||||
Add setter implementations:
|
||||
```typescript
|
||||
const setArrivalBufferMinutes = useCallback((minutes: number) => {
|
||||
setSettings((prev) => ({ ...prev, arrivalBufferMinutes: Math.max(0, Math.min(30, minutes)) }));
|
||||
}, []);
|
||||
|
||||
const setShowWalkingOption = useCallback((show: boolean) => {
|
||||
setSettings((prev) => ({ ...prev, showWalkingOption: show }));
|
||||
}, []);
|
||||
|
||||
const setShowBikeOption = useCallback((show: boolean) => {
|
||||
setSettings((prev) => ({ ...prev, showBikeOption: show }));
|
||||
}, []);
|
||||
```
|
||||
|
||||
Update Provider value to include new setters.
|
||||
|
||||
**Acceptance criteria:**
|
||||
- [ ] Hook exports all three new setters
|
||||
- [ ] localStorage serialization includes new fields
|
||||
- [ ] `npm run typecheck -w apps/web` passes
|
||||
|
||||
---
|
||||
|
||||
#### Step 3: Update ReminderSettingsPanel UI (~15 min)
|
||||
|
||||
**File:** `apps/web/src/app/ui/ReminderSettingsPanel.tsx`
|
||||
|
||||
**Goal:** Add UI controls for the three new settings.
|
||||
|
||||
**Add after the buffer minutes slider, before the permission status section:**
|
||||
|
||||
```tsx
|
||||
{/* Arrival buffer slider */}
|
||||
<div className="space-y-2">
|
||||
<label
|
||||
htmlFor="arrival-buffer"
|
||||
className="text-sm font-medium text-gray-700 dark:text-[#F4F1EA]/80"
|
||||
>
|
||||
Arrive early
|
||||
<span className="text-gray-500 dark:text-gray-400">
|
||||
(minutes before event)
|
||||
</span>
|
||||
</label>
|
||||
<div className="flex items-center gap-3">
|
||||
<input
|
||||
id="arrival-buffer"
|
||||
type="range"
|
||||
min={0}
|
||||
max={30}
|
||||
step={1}
|
||||
value={arrivalBufferMinutes}
|
||||
onChange={(e) => setArrivalBufferMinutes(Number(e.target.value))}
|
||||
className="flex-1 accent-[#B23CFF]"
|
||||
/>
|
||||
<output
|
||||
htmlFor="arrival-buffer"
|
||||
className="text-sm font-semibold tabular-nums min-w-[3ch] text-center text-gray-700 dark:text-[#F4F1EA]/80"
|
||||
>
|
||||
{arrivalBufferMinutes}
|
||||
</output>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Show walking option toggle */}
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm font-medium text-gray-700 dark:text-[#F4F1EA]/80">
|
||||
Show walking option
|
||||
</span>
|
||||
<button
|
||||
role="switch"
|
||||
aria-checked={showWalkingOption}
|
||||
onClick={() => setShowWalkingOption(!showWalkingOption)}
|
||||
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
|
||||
showWalkingOption ? "bg-gradient-to-r from-[#8B5CF6] to-[#FF2D8D]" : "bg-gray-300 dark:bg-gray-600"
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
|
||||
showWalkingOption ? "translate-x-6" : "translate-x-1"
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Show bike option toggle */}
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm font-medium text-gray-700 dark:text-[#F4F1EA]/80">
|
||||
Show bike route
|
||||
</span>
|
||||
<button
|
||||
role="switch"
|
||||
aria-checked={showBikeOption}
|
||||
onClick={() => setShowBikeOption(!showBikeOption)}
|
||||
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
|
||||
showBikeOption ? "bg-gradient-to-r from-[#8B5CF6] to-[#FF2D8D]" : "bg-gray-300 dark:bg-gray-600"
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
|
||||
showBikeOption ? "translate-x-6" : "translate-x-1"
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Destructure new values from the hook at the top of the component:**
|
||||
```typescript
|
||||
const {
|
||||
bufferMinutes, enabled, setBufferMinutes, setEnabled,
|
||||
arrivalBufferMinutes, showWalkingOption, showBikeOption,
|
||||
setArrivalBufferMinutes, setShowWalkingOption, setShowBikeOption,
|
||||
} = useReminderSettings();
|
||||
```
|
||||
|
||||
**Acceptance criteria:**
|
||||
- [ ] Panel renders all new controls
|
||||
- [ ] Slider range is 0-30 for arrival buffer
|
||||
- [ ] Toggles match existing visual style
|
||||
- [ ] `npm run build -w apps/web` succeeds
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 — Walk Routing Infrastructure (Steps 4-6)
|
||||
|
||||
Add the ability to calculate walking routes using OSRM's foot profile.
|
||||
|
||||
---
|
||||
|
||||
#### Step 4: Create WalkRoutingClient (~10 min)
|
||||
|
||||
**File:** `apps/web/src/lib/walk-routing-client.ts` (create new)
|
||||
|
||||
**Goal:** Clone `BikeRoutingClient` but targeting the OSRM foot profile.
|
||||
|
||||
**Actions:**
|
||||
1. Copy `apps/web/src/lib/bike-routing-client.ts` to `walk-routing-client.ts`
|
||||
2. Rename class to `WalkRoutingClient`
|
||||
3. Change OSRM path from `/route/v1/bicycle/` to `/route/v1/foot/`
|
||||
4. Change cache key prefix from `osrm:bike:` to `osrm:walk:`
|
||||
5. Rename `getBikeRoute` to `getWalkRoute`
|
||||
|
||||
**Acceptance criteria:**
|
||||
- [ ] File compiles without errors
|
||||
- [ ] Class mirrors BikeRoutingClient structure exactly
|
||||
- [ ] Cache keys are namespaced separately
|
||||
|
||||
---
|
||||
|
||||
#### Step 5: Create Walk Route API Endpoint (~10 min)
|
||||
|
||||
**File:** `apps/web/src/app/api/walk-route/route.ts` (create new)
|
||||
|
||||
**Goal:** Server-side handler that delegates to WalkRoutingClient.
|
||||
|
||||
**Actions:**
|
||||
1. Copy `apps/web/src/app/api/bike-route/route.ts` to `walk-route/route.ts`
|
||||
2. Import `WalkRoutingClient` instead of `BikeRoutingClient`
|
||||
3. Update error log messages to reference "Walk route"
|
||||
|
||||
**Acceptance criteria:**
|
||||
- [ ] Endpoint responds at `/api/walk-route`
|
||||
- [ ] Accepts same query params as bike-route
|
||||
- [ ] Returns same `BikeRoute` shape (reused type)
|
||||
|
||||
---
|
||||
|
||||
#### Step 6: Add getWalkRoute to ApiClient (~5 min)
|
||||
|
||||
**File:** `packages/api-client/src/client.ts`
|
||||
|
||||
**Goal:** Add `getWalkRoute` method mirroring `getBikeRoute`.
|
||||
|
||||
**Actions:**
|
||||
1. Copy `getBikeRoute` method
|
||||
2. Rename to `getWalkRoute`
|
||||
3. Change URL from `/api/bike-route` to `/api/walk-route`
|
||||
4. Export from `packages/api-client/src/index.ts`
|
||||
|
||||
**Acceptance criteria:**
|
||||
- [ ] Method accepts same signature as `getBikeRoute`
|
||||
- [ ] Returns `Promise<BikeRoute>`
|
||||
- [ ] `npm run typecheck -w packages/api-client` passes
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 — Departure Time Calculation (Steps 7-8)
|
||||
|
||||
Build the logic for calculating when you should leave based on transport mode.
|
||||
|
||||
---
|
||||
|
||||
#### Step 7: Create useDepartureTime Hook (~15 min)
|
||||
|
||||
**File:** `apps/web/src/hooks/useDepartureTime.ts` (create new)
|
||||
|
||||
**Goal:** Calculate the optimal departure time based on selected mode, journeys, bike route, and arrival buffer.
|
||||
|
||||
**Actions:**
|
||||
1. Create hook accepting `eventTime`, `journeys`, `bikeRoute`, `activeMode`
|
||||
2. Import `useReminderSettings` for `arrivalBufferMinutes`
|
||||
3. Calculate `targetArrivalTime = eventTime - arrivalBufferMinutes`
|
||||
4. For train mode: find journeys arriving before target, pick latest departure
|
||||
5. For bike mode: calculate departure from target minus bike duration
|
||||
6. Return `{ departureTime, arrivalTime, mode }`
|
||||
|
||||
**Key implementation notes:**
|
||||
- Bike duration from OSRM is in seconds, convert to milliseconds for Date math
|
||||
- Filter cancelled journeys
|
||||
- Return null values if no valid option exists for the mode
|
||||
- Use `useMemo` to avoid recalculating on unrelated renders
|
||||
|
||||
**Acceptance criteria:**
|
||||
- [ ] Hook returns correct departure time for train mode
|
||||
- [ ] Hook returns correct departure time for bike mode
|
||||
- [ ] Returns null when no valid journey/route exists
|
||||
- [ ] Respects arrival buffer setting
|
||||
|
||||
---
|
||||
|
||||
#### Step 8: Update useClock Hook (~10 min)
|
||||
|
||||
**File:** `apps/web/src/hooks/useClock.ts`
|
||||
|
||||
**Goal:** Accept optional departure time override so countdown reflects "time to leave" instead of "time to event".
|
||||
|
||||
**Changes:**
|
||||
|
||||
Update signature:
|
||||
```typescript
|
||||
export function useClock(targetDate: Date, departureTime?: Date | null): ClockResult {
|
||||
```
|
||||
|
||||
Update memoized logic:
|
||||
```typescript
|
||||
const effectiveTarget = departureTime ?? targetDate;
|
||||
|
||||
return useMemo(() => {
|
||||
const countdown = calculateCountdown(effectiveTarget);
|
||||
const diffMs = effectiveTarget.getTime() - now.getTime();
|
||||
// ... rest of status logic using effectiveTarget
|
||||
}, [targetDate, departureTime, now]);
|
||||
```
|
||||
|
||||
**Acceptance criteria:**
|
||||
- [ ] When departureTime is provided, countdown is to departure time
|
||||
- [ ] When departureTime is null/undefined, countdown is to event time (backward compatible)
|
||||
- [ ] `npm run typecheck -w apps/web` passes
|
||||
|
||||
---
|
||||
|
||||
## Phase 4 — Mode Selector & EventCard Updates (Steps 9-11)
|
||||
|
||||
Wire everything together in the EventCard with a mode selector and conditional rendering.
|
||||
|
||||
---
|
||||
|
||||
#### Step 9: Add Walk Route Hook (~10 min)
|
||||
|
||||
**File:** `apps/web/src/hooks/useWalkRoute.ts` (create new)
|
||||
|
||||
**Goal:** Create hook for fetching walk routes, similar to `useBikeRoute`.
|
||||
|
||||
**Actions:**
|
||||
1. Copy `apps/web/src/hooks/useBikeRoute.ts` to `useWalkRoute.ts`
|
||||
2. Rename state variables from `bikeRoute` to `walkRoute`
|
||||
3. Call `client.getWalkRoute` instead of `client.getBikeRoute`
|
||||
4. Return `{ walkRoute, loading, error }`
|
||||
|
||||
**Acceptance criteria:**
|
||||
- [ ] Hook fetches walk route via the walk-route API
|
||||
- [ ] Follows same pattern as useBikeRoute
|
||||
- [ ] Compiles without errors
|
||||
|
||||
---
|
||||
|
||||
#### Step 10: Create WalkingOption Component (~10 min)
|
||||
|
||||
**File:** `apps/web/src/app/event/WalkingOption.tsx` (create new)
|
||||
|
||||
**Goal:** Show walking duration from the arrival station to the destination.
|
||||
|
||||
**Actions:**
|
||||
1. Create component accepting `walkRoute`, `walkLoading`, `walkError` props
|
||||
2. Show loading spinner while fetching
|
||||
3. When route exists, show walk time in minutes and distance in km
|
||||
4. Use a pedestrian icon (inline SVG or emoji)
|
||||
5. Return null if no route or error
|
||||
|
||||
**Acceptance criteria:**
|
||||
- [ ] Component renders walk duration badge
|
||||
- [ ] Matches existing visual style
|
||||
- [ ] Graceful handling of loading/error states
|
||||
|
||||
---
|
||||
|
||||
#### Step 11: Update EventCard with Mode Selector (~20 min)
|
||||
|
||||
**File:** `apps/web/src/app/event/EventCard.tsx`
|
||||
|
||||
**Goal:** Add transport mode selector, wire departure time to countdown, conditionally render sections.
|
||||
|
||||
**Changes:**
|
||||
|
||||
1. Add mode state:
|
||||
```typescript
|
||||
type TransportMode = "train" | "bike";
|
||||
const [activeMode, setActiveMode] = useState<TransportMode>("train");
|
||||
```
|
||||
|
||||
2. Use settings:
|
||||
```typescript
|
||||
const { arrivalBufferMinutes, showWalkingOption, showBikeOption } = useReminderSettings();
|
||||
```
|
||||
|
||||
3. Fetch walk route from destination station to destination:
|
||||
```typescript
|
||||
const { walkRoute, loading: walkLoading, error: walkError } = useWalkRoute(
|
||||
destStation.station?.lat,
|
||||
destStation.station?.lng,
|
||||
destCoords.coords?.lat,
|
||||
destCoords.coords?.lng,
|
||||
);
|
||||
```
|
||||
|
||||
4. Use departure time hook:
|
||||
```typescript
|
||||
const { departureTime } = useDepartureTime(event.eventTime, journeys, bikeRoute, activeMode);
|
||||
```
|
||||
|
||||
5. Pass departureTime to useClock:
|
||||
```typescript
|
||||
const { countdown, status } = useClock(event.eventTime, departureTime);
|
||||
```
|
||||
|
||||
6. Add mode selector UI between the header info and the sections:
|
||||
```tsx
|
||||
<div className="mb-4 flex gap-2">
|
||||
<button
|
||||
className={`px-4 py-2 rounded text-sm font-medium transition-colors ${
|
||||
activeMode === "train"
|
||||
? "bg-[#B23CFF] text-white"
|
||||
: "bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300"
|
||||
}`}
|
||||
onClick={() => setActiveMode("train")}
|
||||
>
|
||||
Train
|
||||
</button>
|
||||
<button
|
||||
className={`px-4 py-2 rounded text-sm font-medium transition-colors ${
|
||||
activeMode === "bike"
|
||||
? "bg-[#B23CFF] text-white"
|
||||
: "bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300"
|
||||
}`}
|
||||
onClick={() => setActiveMode("bike")}
|
||||
>
|
||||
Bike
|
||||
</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
7. Conditional section rendering:
|
||||
- TrainSection renders when `activeMode === "train"` (pass arrival buffer + walk option props)
|
||||
- BikeSection renders when `showBikeOption && activeMode === "bike"`
|
||||
- WienerLinienSection stays unchanged (always visible)
|
||||
|
||||
**Acceptance criteria:**
|
||||
- [ ] Mode selector toggles between Train and Bike
|
||||
- [ ] Countdown badge updates to reflect departure time for selected mode
|
||||
- [ ] Bike section hidden when `showBikeOption` is false
|
||||
- [ ] Train section receives arrival buffer for filtering
|
||||
- [ ] Walk route fetched from station to destination
|
||||
|
||||
---
|
||||
|
||||
## Phase 5 — TrainSection & JourneyList Updates (Steps 12-13)
|
||||
|
||||
Pass arrival buffer through the component tree and use it for filtering/countdown.
|
||||
|
||||
---
|
||||
|
||||
#### Step 12: Update TrainSection Props (~10 min)
|
||||
|
||||
**File:** `apps/web/src/app/event/TrainSection.tsx`
|
||||
|
||||
**Goal:** Accept and forward new props for walking option and arrival buffer.
|
||||
|
||||
**Changes:**
|
||||
|
||||
Extend props interface:
|
||||
```typescript
|
||||
type TrainSectionProps = {
|
||||
journeys: Journey[];
|
||||
eventTime: Date;
|
||||
destName: string;
|
||||
loading: boolean;
|
||||
error?: string | null;
|
||||
onRefresh?: () => void;
|
||||
className?: string;
|
||||
arrivalBufferMinutes?: number;
|
||||
showWalkingOption?: boolean;
|
||||
walkRoute?: import("@timetoleave/core").BikeRoute | null;
|
||||
walkLoading?: boolean;
|
||||
walkError?: string | null;
|
||||
};
|
||||
```
|
||||
|
||||
Pass through to JourneyList:
|
||||
```tsx
|
||||
<JourneyList
|
||||
journeys={journeys}
|
||||
eventTime={eventTime}
|
||||
arrivalBufferMinutes={arrivalBufferMinutes ?? 0}
|
||||
/>
|
||||
```
|
||||
|
||||
Add WalkingOption at bottom when enabled:
|
||||
```tsx
|
||||
{showWalkingOption && (
|
||||
<div className="p-4 border-t border-gray-200 dark:border-white/10">
|
||||
<WalkingOption walkRoute={walkRoute} walkLoading={walkLoading} walkError={walkError} />
|
||||
</div>
|
||||
)}
|
||||
```
|
||||
|
||||
**Acceptance criteria:**
|
||||
- [ ] Props are optional with defaults
|
||||
- [ ] WalkingOption renders below journey list
|
||||
- [ ] Arrival buffer forwarded to JourneyList
|
||||
|
||||
---
|
||||
|
||||
#### Step 13: Update JourneyList with Arrival Buffer (~10 min)
|
||||
|
||||
**File:** `apps/web/src/app/event/JourneyList.tsx`
|
||||
|
||||
**Goal:** Use arrival buffer when calculating countdown and filtering invalid journeys.
|
||||
|
||||
**Changes:**
|
||||
|
||||
Update props:
|
||||
```typescript
|
||||
type JourneyListProps = {
|
||||
journeys: Journey[];
|
||||
eventTime: Date;
|
||||
arrivalBufferMinutes: number;
|
||||
className?: string;
|
||||
};
|
||||
```
|
||||
|
||||
Calculate adjusted target:
|
||||
```typescript
|
||||
const targetArrivalTime = new Date(eventTime.getTime() - arrivalBufferMinutes * 60000);
|
||||
```
|
||||
|
||||
Filter out journeys arriving after target:
|
||||
```typescript
|
||||
const validJourneys = journeys.filter(j => !j.cancelled);
|
||||
```
|
||||
|
||||
Dim journeys that arrive too late:
|
||||
```tsx
|
||||
const arrivesTooLate = j.rA.getTime() > targetArrivalTime.getTime();
|
||||
```
|
||||
|
||||
Apply dimmed styling to late journeys:
|
||||
```tsx
|
||||
<li key={j.id} className={`... ${arrivesTooLate ? "opacity-40 line-through" : ""}`}>
|
||||
```
|
||||
|
||||
**Acceptance criteria:**
|
||||
- [ ] Late journeys are visually dimmed
|
||||
- [ ] Countdown reflects arrival buffer
|
||||
- [ ] Late journeys are not removed, just dimmed (user can still see them)
|
||||
|
||||
---
|
||||
|
||||
## Phase 6 — Verification & Testing (Steps 14-16)
|
||||
|
||||
Ensure everything works together correctly.
|
||||
|
||||
---
|
||||
|
||||
#### Step 14: Integration Verification (~10 min)
|
||||
|
||||
**Goal:** Verify the full flow from settings through to the UI.
|
||||
|
||||
**Manual test steps:**
|
||||
1. Open settings, set arrival buffer to 10 minutes
|
||||
2. Verify countdown badge shows earlier departure time than event time
|
||||
3. Switch to bike mode, verify countdown updates to bike departure time
|
||||
4. Enable walking option, verify walk duration appears under train section
|
||||
5. Toggle bike option off, verify bike section disappears
|
||||
6. Toggle bike option on, verify bike section reappears
|
||||
|
||||
**Acceptance criteria:**
|
||||
- [ ] All settings persist in localStorage
|
||||
- [ ] Countdown reflects selected mode and arrival buffer
|
||||
- [ ] Walk option shows correctly when enabled
|
||||
- [ ] Bike section toggles correctly
|
||||
|
||||
---
|
||||
|
||||
#### Step 15: Run Build Verification (~5 min)
|
||||
|
||||
**Commands:**
|
||||
```bash
|
||||
npm run typecheck -w packages/core
|
||||
npm run typecheck -w packages/api-client
|
||||
npm run typecheck -w apps/web
|
||||
npm run lint -w apps/web
|
||||
npm run build -w apps/web
|
||||
npm test
|
||||
```
|
||||
|
||||
**Acceptance criteria:**
|
||||
- [ ] All typechecks pass with zero errors
|
||||
- [ ] Lint passes with zero errors
|
||||
- [ ] Build completes successfully
|
||||
- [ ] All existing tests still pass
|
||||
|
||||
---
|
||||
|
||||
#### Step 16: Update api-client exports (~5 min)
|
||||
|
||||
**File:** `packages/api-client/src/index.ts`
|
||||
|
||||
**Goal:** Ensure `getWalkRoute` is exported from the package.
|
||||
|
||||
**Actions:**
|
||||
1. Check current exports in index.ts
|
||||
2. Add `getWalkRoute` method to the exported class if not already present
|
||||
3. Verify the method is accessible from web app imports
|
||||
|
||||
**Acceptance criteria:**
|
||||
- [ ] `getWalkRoute` is importable from `@timetoleave/api-client`
|
||||
- [ ] No breaking changes to existing exports
|
||||
|
||||
---
|
||||
Reference in New Issue
Block a user