Delivery tracking — ETA hero with a stage progress bar, vertical event timeline, courier card with call/message actions, pickup-to-doorstep route, and an items strip
import * as React from "react";
import { ScrollView, View } from "react-native";
import { SafeAreaView, useSafeAreaInsets } from "react-native-safe-area-context";
import { Avatar, Button, Card, Glass, Icon, Image, Separator, Steps, Text, type IconName } from "@lumioui/ui";
import { cn } from "@lumioui/core";
import { catalogImg, LumioMark, Mount, Press, StickyBottomBar, World } from "../shared";
export interface TrackingEvent {
label: string;
/** "Today, 2:41 PM" — blank for events that haven't happened yet. */
time?: string;
icon?: IconName;
}
export interface Courier {
name: string;
detail?: string;
avatarUrl?: string;
}
export interface OrderTrackingScreenProps {
orderId?: string;
/** Headline ETA — "Arriving Thursday, Sep 18". */
eta?: string;
etaDetail?: string;
/** Horizontal stage labels — Ordered → Shipped → Out for delivery → Delivered. */
stages?: string[];
/** Index into `stages` of the current stage. */
stage?: number;
/** Vertical event log under the stage bar. */
events?: TrackingEvent[];
courier?: Courier;
/** Pickup → destination rows. */
from?: string;
to?: string;
items?: { name: string; imageUrl?: string }[];
total?: number;
currency?: string;
onBack?: () => void;
onCallCourier?: () => void;
onMessageCourier?: () => void;
onHelp?: () => void;
}
const img = (p: Parameters<typeof catalogImg>[0]) => catalogImg(p, 160, 200);
const DEFAULT_STAGES = ["Ordered", "Shipped", "Out for delivery", "Delivered"];
const DEFAULT_EVENTS: TrackingEvent[] = [
{ label: "Out for delivery", time: "Today, 9:12 AM", icon: "truck" },
{ label: "Arrived at local facility", time: "Today, 6:40 AM", icon: "package" },
{ label: "Departed regional hub", time: "Yesterday, 8:15 PM", icon: "package" },
{ label: "Picked up by courier", time: "Sep 15, 4:02 PM", icon: "package" },
{ label: "Order confirmed", time: "Sep 15, 11:30 AM", icon: "check-circle" },
];
const DEFAULT_ITEMS = [
{ name: "Structured Wool Overcoat", imageUrl: img("overcoat") },
{ name: "Garment-Dyed Tee", imageUrl: img("tee") },
{ name: "Heritage Graphic Tee", imageUrl: img("graphicTee") },
];
const fmt = (n: number, currency = "$") =>
`${currency}${n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
/** Vertical timeline row — icon dot, connector line, label + time.
* The last row drops the connector. */
function TimelineRow({
event,
done,
last,
}: {
event: TrackingEvent;
done: boolean;
last: boolean;
}) {
return (
<View className="flex-row gap-3" accessibilityElementsHidden={!done}>
<View className="items-center">
<View
className={cn(
"h-7 w-7 items-center justify-center rounded-full",
done ? "bg-[#EDF8C4] dark:bg-[#1E2410]" : "bg-muted"
)}
>
<Icon
name={event.icon ?? "check"}
size="sm"
color={done ? "#65A30D" : "mutedForeground"}
/>
</View>
{!last ? <View className={cn("w-px flex-1", done ? "bg-[#BEF04C]" : "bg-border")} /> : null}
</View>
<View className="flex-1 pb-4">
<Text variant="small" className={cn(done ? "font-semibold" : "text-muted-foreground")}>
{event.label}
</Text>
{event.time ? (
<Text variant="muted" className="text-xs tabular-nums">
{event.time}
</Text>
) : null}
</View>
</View>
);
}
TimelineRow.displayName = "TimelineRow";
/** OrderTracking — ETA hero with the horizontal stage bar, vertical event
* timeline, courier card, route card, and the items strip. The delivery
* pattern every shipped-order app lands on. */
export function OrderTrackingScreen({
orderId = "HL-2094",
eta = "Arriving today",
etaDetail = "Estimated between 4–6 PM",
stages = DEFAULT_STAGES,
stage = 2,
events = DEFAULT_EVENTS,
courier = { name: "Maya Osei", detail: "Courier · Van 42", avatarUrl: "https://i.pravatar.cc/100?img=47" },
from = "NORD Standard · Secaucus, NJ",
to = "128 Linden St, Apt 4B, Brooklyn NY",
items = DEFAULT_ITEMS,
total = 628.0,
currency = "$",
onBack,
onCallCourier,
onMessageCourier,
onHelp,
}: OrderTrackingScreenProps) {
const insets = useSafeAreaInsets();
return (
<World primary="#65A30D">
<SafeAreaView className="flex-1 bg-[#F4F5F9] dark:bg-[#111114]" edges={["top"]}>
{/* Floating chrome — back disc + centered order pill. */}
<View pointerEvents="box-none" className="absolute inset-x-4 z-10" style={{ top: insets.top + 8 }}>
<View className="flex-row items-center justify-between">
<Glass interactive effect="regular" className="h-11 w-11 items-center justify-center rounded-full">
<Press
haptic="light"
onPress={() => onBack?.()}
accessibilityRole="button"
accessibilityLabel="Go back"
className="h-9 w-9 items-center justify-center"
>
<Icon name="chevron-left" size="md" color="foreground" />
</Press>
</Glass>
<View className="h-11 w-11" />
</View>
<View pointerEvents="none" className="absolute inset-0 items-center justify-center">
<View className="h-11 flex-row items-center justify-center gap-2 rounded-full pl-3.5 pr-5">
<Glass effect="regular" className="absolute inset-0 rounded-full" />
<LumioMark size={18} />
<Text accessibilityRole="header" className="text-sm font-bold tracking-tight">
Track {orderId}
</Text>
</View>
</View>
</View>
<ScrollView
className="flex-1"
contentContainerClassName="gap-5 px-6"
contentContainerStyle={{ paddingTop: insets.top + 60, paddingBottom: insets.bottom + 96 }}
showsVerticalScrollIndicator={false}
>
{/* ETA hero — the one thing the user opened the screen for. */}
<Mount>
<Card variant="elevated" className="rounded-3xl bg-white dark:bg-[#1A1A20]">
<Card.Content className="gap-4 p-5">
<View>
<Text variant="small" className="font-semibold">
{eta}
</Text>
<Text variant="muted" className="text-xs">
{etaDetail}
</Text>
</View>
<Steps
steps={stages}
current={stage}
variant="dots"
labels
interactive={false}
haptic={false}
accessibilityLabel={`Order status: ${stages[stage]}`}
/>
</Card.Content>
</Card>
</Mount>
{/* Event timeline — newest first. */}
<Mount delay={80}>
<Card variant="elevated" className="rounded-3xl bg-white dark:bg-[#1A1A20]">
<Card.Content className="p-5">
{events.map((e, i) => (
<TimelineRow
key={e.label}
event={e}
done={!!e.time}
last={i === events.length - 1}
/>
))}
</Card.Content>
</Card>
</Mount>
{/* Courier — only while the order is actually moving. */}
{courier ? (
<Mount delay={140}>
<Card variant="elevated" className="rounded-3xl bg-white dark:bg-[#1A1A20]">
<Card.Content className="flex-row items-center gap-3 p-4">
<Avatar
size="lg"
source={courier.avatarUrl ? { uri: courier.avatarUrl } : undefined}
fallback={courier.name}
status="online"
alt={`${courier.name}'s avatar`}
/>
<View className="flex-1">
<Text variant="small" className="font-semibold">
{courier.name}
</Text>
{courier.detail ? (
<Text variant="muted" className="text-xs">
{courier.detail}
</Text>
) : null}
</View>
{onMessageCourier ? (
<Button
variant="secondary"
size="icon"
haptic="light"
onPress={onMessageCourier}
accessibilityLabel={`Message ${courier.name}`}
>
<Icon name="message-circle" size="sm" color="secondaryForeground" />
</Button>
) : null}
{onCallCourier ? (
<Button
variant="secondary"
size="icon"
haptic="light"
onPress={onCallCourier}
accessibilityLabel={`Call ${courier.name}`}
>
<Icon name="phone" size="sm" color="secondaryForeground" />
</Button>
) : null}
</Card.Content>
</Card>
</Mount>
) : null}
{/* Delivery instruction — the courier-facing note every tracker has. */}
<Mount delay={160}>
<View className="flex-row items-center gap-2.5 rounded-2xl border border-dashed border-[#BEF04C] bg-[#EDF8C4] px-4 py-3 dark:border-[#BEF04C]/40 dark:bg-[#1E2410]">
<Icon name="package" size="sm" color="#65A30D" />
<Text variant="small" className="flex-1 text-xs">
Leave at door — no signature needed
</Text>
<Text variant="small" className="font-medium text-[#65A30D]">
Edit
</Text>
</View>
</Mount>
{/* Route — pickup → drop-off. */}
<Mount delay={200}>
<Card variant="elevated" className="rounded-3xl bg-white dark:bg-[#1A1A20]">
<Card.Content className="gap-3 p-4">
<View className="flex-row items-center gap-3">
<View className="h-8 w-8 items-center justify-center rounded-lg bg-muted">
<Icon name="package" size="sm" color="mutedForeground" />
</View>
<Text variant="small" className="flex-1">
{from}
</Text>
</View>
<View className="ml-4 h-4 w-px bg-border" />
<View className="flex-row items-center gap-3">
<View className="h-8 w-8 items-center justify-center rounded-lg bg-[#EDF8C4] dark:bg-[#1E2410]">
<Icon name="map-pin" size="sm" color="#65A30D" />
</View>
<Text variant="small" className="flex-1">
{to}
</Text>
</View>
</Card.Content>
</Card>
</Mount>
{/* Items strip + total. */}
<Mount delay={260}>
<Card variant="elevated" className="rounded-3xl bg-white dark:bg-[#1A1A20]">
<Card.Content className="gap-3 p-4">
<View className="flex-row items-center gap-2">
{items.map((it, i) => (
<Image
key={`${it.name}-${i}`}
source={it.imageUrl ? { uri: it.imageUrl } : undefined}
rounded="xl"
aspectRatio={4 / 5}
className="w-12"
accessibilityLabel={`Photo of ${it.name}`}
/>
))}
</View>
<Separator />
<View className="flex-row items-center justify-between">
<Text variant="muted">{items.length} items</Text>
<Text variant="small" className="font-semibold tabular-nums">
{fmt(total, currency)}
</Text>
</View>
</Card.Content>
</Card>
</Mount>
</ScrollView>
{onHelp ? (
<StickyBottomBar>
<Button
variant="secondary"
size="lg"
haptic="light"
onPress={onHelp}
accessibilityLabel="Get help with this order"
icon={<Icon name="question-circle" size="sm" color="secondaryForeground" />}
>
Need help with this order?
</Button>
</StickyBottomBar>
) : null}
</SafeAreaView>
</World>
);
}
OrderTrackingScreen.displayName = "OrderTrackingScreen";
Recreate the "Order Tracking Screen" screen in React Native using LumioUI.
Goal: Delivery tracking — ETA hero with a stage progress bar, vertical event timeline, courier card with call/message actions, pickup-to-doorstep route, and an items strip Match the reference implementation's layout, behavior, and motion exactly — this is a production screen, not a mockup.
## Setup
Fastest path — install the screen plus its component deps:
```bash
npx lumioui add ecommerce-tracking
```
Or pull just the components: `npx lumioui add avatar button card icon image separator steps text`
Native packages:
- @lumioui/ui@latest
- @lumioui/core@latest
- react-native-safe-area-context@*
- react-native-reanimated@*
- react-native-svg@*
- nativewind@*
Stack assumptions: Expo SDK 54 / RN 0.81, Reanimated 4, NativeWind v4, TypeScript strict, Expo Go compatible.
## Components — how, where, when
### `avatar` — Circular user image with fallback initials and status dot.
- `<Avatar>` ×1
### `button` — Tactile pill button with press spring, ink wash and async loading.
- `<Button>` ×3
### `card` — Compound surface — header, title, content and footer slots.
- `<Card>` ×5
### `icon` — Lucide icon set mapped to theme colors and sizes.
- `<Icon>` ×8
### `image` — Cached image with placeholder, fallback and aspect handling.
- `<Image>` ×1
### `separator` — Hairline rule with orientation and spacing control.
- `<Separator>` ×1
### `steps` — Horizontal step indicator with numbered or dot markers, check-on-complete pop, and connectors that fill as progress advances
- `<Steps>` ×1
### `text` — Themed type scale from caption to display.
- `<Text>` ×13
## Conventions (non-negotiable)
- Styling via NativeWind `className` with semantic tokens only: `bg-background`, `text-foreground`, `bg-card`, `text-card-foreground`, `bg-secondary`, `bg-muted`, `text-muted-foreground`, `bg-accent`, `bg-destructive`, `border-border`. Hard-coded colors only inside media overlays (`text-white`, `bg-black/N`).
- Motion via the token presets from `@lumioui/core` (`spring.press`, `spring.surface`, `spring.glide`, `timing.fast`, `timing.slow`, …) — no bespoke spring configs or durations.
- Gate transform motion behind `useReducedMotion()`; keep opacity/color feedback, drop travel and overshoot.
- Haptics via `useHaptic()`: `light` on press, `selection` on toggle/select, `warning` on invalid input. Never import expo-haptics directly.
- Press feedback via `usePressAnimation()` (~0.97 scale), not `activeOpacity` tricks.
- a11y: `accessibilityRole` + `accessibilityLabel` on every control, touch targets ≥44pt.
- Entrance choreography: staggered `Mount` wrappers (delay 0/60/120…) so regions land in sequence instead of popping in at once.
- Wrap the screen in `SafeAreaView`; keep dark mode working via the semantic tokens.
## Deliverable
One file: `screens/ecommerce/tracking.tsx` exporting OrderTrackingScreen. Preserve the props interface — the host app wires the callbacks (navigation, submit handlers) through them.
## Reference implementation
The LumioUI source for this screen — reproduce it faithfully:
```tsx
import * as React from "react";
import { ScrollView, View } from "react-native";
import { SafeAreaView, useSafeAreaInsets } from "react-native-safe-area-context";
import { Avatar, Button, Card, Glass, Icon, Image, Separator, Steps, Text, type IconName } from "@lumioui/ui";
import { cn } from "@lumioui/core";
import { catalogImg, LumioMark, Mount, Press, StickyBottomBar, World } from "../shared";
export interface TrackingEvent {
label: string;
/** "Today, 2:41 PM" — blank for events that haven't happened yet. */
time?: string;
icon?: IconName;
}
export interface Courier {
name: string;
detail?: string;
avatarUrl?: string;
}
export interface OrderTrackingScreenProps {
orderId?: string;
/** Headline ETA — "Arriving Thursday, Sep 18". */
eta?: string;
etaDetail?: string;
/** Horizontal stage labels — Ordered → Shipped → Out for delivery → Delivered. */
stages?: string[];
/** Index into `stages` of the current stage. */
stage?: number;
/** Vertical event log under the stage bar. */
events?: TrackingEvent[];
courier?: Courier;
/** Pickup → destination rows. */
from?: string;
to?: string;
items?: { name: string; imageUrl?: string }[];
total?: number;
currency?: string;
onBack?: () => void;
onCallCourier?: () => void;
onMessageCourier?: () => void;
onHelp?: () => void;
}
const img = (p: Parameters<typeof catalogImg>[0]) => catalogImg(p, 160, 200);
const DEFAULT_STAGES = ["Ordered", "Shipped", "Out for delivery", "Delivered"];
const DEFAULT_EVENTS: TrackingEvent[] = [
{ label: "Out for delivery", time: "Today, 9:12 AM", icon: "truck" },
{ label: "Arrived at local facility", time: "Today, 6:40 AM", icon: "package" },
{ label: "Departed regional hub", time: "Yesterday, 8:15 PM", icon: "package" },
{ label: "Picked up by courier", time: "Sep 15, 4:02 PM", icon: "package" },
{ label: "Order confirmed", time: "Sep 15, 11:30 AM", icon: "check-circle" },
];
const DEFAULT_ITEMS = [
{ name: "Structured Wool Overcoat", imageUrl: img("overcoat") },
{ name: "Garment-Dyed Tee", imageUrl: img("tee") },
{ name: "Heritage Graphic Tee", imageUrl: img("graphicTee") },
];
const fmt = (n: number, currency = "$") =>
`${currency}${n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
/** Vertical timeline row — icon dot, connector line, label + time.
* The last row drops the connector. */
function TimelineRow({
event,
done,
last,
}: {
event: TrackingEvent;
done: boolean;
last: boolean;
}) {
return (
<View className="flex-row gap-3" accessibilityElementsHidden={!done}>
<View className="items-center">
<View
className={cn(
"h-7 w-7 items-center justify-center rounded-full",
done ? "bg-[#EDF8C4] dark:bg-[#1E2410]" : "bg-muted"
)}
>
<Icon
name={event.icon ?? "check"}
size="sm"
color={done ? "#65A30D" : "mutedForeground"}
/>
</View>
{!last ? <View className={cn("w-px flex-1", done ? "bg-[#BEF04C]" : "bg-border")} /> : null}
</View>
<View className="flex-1 pb-4">
<Text variant="small" className={cn(done ? "font-semibold" : "text-muted-foreground")}>
{event.label}
</Text>
{event.time ? (
<Text variant="muted" className="text-xs tabular-nums">
{event.time}
</Text>
) : null}
</View>
</View>
);
}
TimelineRow.displayName = "TimelineRow";
/** OrderTracking — ETA hero with the horizontal stage bar, vertical event
* timeline, courier card, route card, and the items strip. The delivery
* pattern every shipped-order app lands on. */
export function OrderTrackingScreen({
orderId = "HL-2094",
eta = "Arriving today",
etaDetail = "Estimated between 4–6 PM",
stages = DEFAULT_STAGES,
stage = 2,
events = DEFAULT_EVENTS,
courier = { name: "Maya Osei", detail: "Courier · Van 42", avatarUrl: "https://i.pravatar.cc/100?img=47" },
from = "NORD Standard · Secaucus, NJ",
to = "128 Linden St, Apt 4B, Brooklyn NY",
items = DEFAULT_ITEMS,
total = 628.0,
currency = "$",
onBack,
onCallCourier,
onMessageCourier,
onHelp,
}: OrderTrackingScreenProps) {
const insets = useSafeAreaInsets();
return (
<World primary="#65A30D">
<SafeAreaView className="flex-1 bg-[#F4F5F9] dark:bg-[#111114]" edges={["top"]}>
{/* Floating chrome — back disc + centered order pill. */}
<View pointerEvents="box-none" className="absolute inset-x-4 z-10" style={{ top: insets.top + 8 }}>
<View className="flex-row items-center justify-between">
<Glass interactive effect="regular" className="h-11 w-11 items-center justify-center rounded-full">
<Press
haptic="light"
onPress={() => onBack?.()}
accessibilityRole="button"
accessibilityLabel="Go back"
className="h-9 w-9 items-center justify-center"
>
<Icon name="chevron-left" size="md" color="foreground" />
</Press>
</Glass>
<View className="h-11 w-11" />
</View>
<View pointerEvents="none" className="absolute inset-0 items-center justify-center">
<View className="h-11 flex-row items-center justify-center gap-2 rounded-full pl-3.5 pr-5">
<Glass effect="regular" className="absolute inset-0 rounded-full" />
<LumioMark size={18} />
<Text accessibilityRole="header" className="text-sm font-bold tracking-tight">
Track {orderId}
</Text>
</View>
</View>
</View>
<ScrollView
className="flex-1"
contentContainerClassName="gap-5 px-6"
contentContainerStyle={{ paddingTop: insets.top + 60, paddingBottom: insets.bottom + 96 }}
showsVerticalScrollIndicator={false}
>
{/* ETA hero — the one thing the user opened the screen for. */}
<Mount>
<Card variant="elevated" className="rounded-3xl bg-white dark:bg-[#1A1A20]">
<Card.Content className="gap-4 p-5">
<View>
<Text variant="small" className="font-semibold">
{eta}
</Text>
<Text variant="muted" className="text-xs">
{etaDetail}
</Text>
</View>
<Steps
steps={stages}
current={stage}
variant="dots"
labels
interactive={false}
haptic={false}
accessibilityLabel={`Order status: ${stages[stage]}`}
/>
</Card.Content>
</Card>
</Mount>
{/* Event timeline — newest first. */}
<Mount delay={80}>
<Card variant="elevated" className="rounded-3xl bg-white dark:bg-[#1A1A20]">
<Card.Content className="p-5">
{events.map((e, i) => (
<TimelineRow
key={e.label}
event={e}
done={!!e.time}
last={i === events.length - 1}
/>
))}
</Card.Content>
</Card>
</Mount>
{/* Courier — only while the order is actually moving. */}
{courier ? (
<Mount delay={140}>
<Card variant="elevated" className="rounded-3xl bg-white dark:bg-[#1A1A20]">
<Card.Content className="flex-row items-center gap-3 p-4">
<Avatar
size="lg"
source={courier.avatarUrl ? { uri: courier.avatarUrl } : undefined}
fallback={courier.name}
status="online"
alt={`${courier.name}'s avatar`}
/>
<View className="flex-1">
<Text variant="small" className="font-semibold">
{courier.name}
</Text>
{courier.detail ? (
<Text variant="muted" className="text-xs">
{courier.detail}
</Text>
) : null}
</View>
{onMessageCourier ? (
<Button
variant="secondary"
size="icon"
haptic="light"
onPress={onMessageCourier}
accessibilityLabel={`Message ${courier.name}`}
>
<Icon name="message-circle" size="sm" color="secondaryForeground" />
</Button>
) : null}
{onCallCourier ? (
<Button
variant="secondary"
size="icon"
haptic="light"
onPress={onCallCourier}
accessibilityLabel={`Call ${courier.name}`}
>
<Icon name="phone" size="sm" color="secondaryForeground" />
</Button>
) : null}
</Card.Content>
</Card>
</Mount>
) : null}
{/* Delivery instruction — the courier-facing note every tracker has. */}
<Mount delay={160}>
<View className="flex-row items-center gap-2.5 rounded-2xl border border-dashed border-[#BEF04C] bg-[#EDF8C4] px-4 py-3 dark:border-[#BEF04C]/40 dark:bg-[#1E2410]">
<Icon name="package" size="sm" color="#65A30D" />
<Text variant="small" className="flex-1 text-xs">
Leave at door — no signature needed
</Text>
<Text variant="small" className="font-medium text-[#65A30D]">
Edit
</Text>
</View>
</Mount>
{/* Route — pickup → drop-off. */}
<Mount delay={200}>
<Card variant="elevated" className="rounded-3xl bg-white dark:bg-[#1A1A20]">
<Card.Content className="gap-3 p-4">
<View className="flex-row items-center gap-3">
<View className="h-8 w-8 items-center justify-center rounded-lg bg-muted">
<Icon name="package" size="sm" color="mutedForeground" />
</View>
<Text variant="small" className="flex-1">
{from}
</Text>
</View>
<View className="ml-4 h-4 w-px bg-border" />
<View className="flex-row items-center gap-3">
<View className="h-8 w-8 items-center justify-center rounded-lg bg-[#EDF8C4] dark:bg-[#1E2410]">
<Icon name="map-pin" size="sm" color="#65A30D" />
</View>
<Text variant="small" className="flex-1">
{to}
</Text>
</View>
</Card.Content>
</Card>
</Mount>
{/* Items strip + total. */}
<Mount delay={260}>
<Card variant="elevated" className="rounded-3xl bg-white dark:bg-[#1A1A20]">
<Card.Content className="gap-3 p-4">
<View className="flex-row items-center gap-2">
{items.map((it, i) => (
<Image
key={`${it.name}-${i}`}
source={it.imageUrl ? { uri: it.imageUrl } : undefined}
rounded="xl"
aspectRatio={4 / 5}
className="w-12"
accessibilityLabel={`Photo of ${it.name}`}
/>
))}
</View>
<Separator />
<View className="flex-row items-center justify-between">
<Text variant="muted">{items.length} items</Text>
<Text variant="small" className="font-semibold tabular-nums">
{fmt(total, currency)}
</Text>
</View>
</Card.Content>
</Card>
</Mount>
</ScrollView>
{onHelp ? (
<StickyBottomBar>
<Button
variant="secondary"
size="lg"
haptic="light"
onPress={onHelp}
accessibilityLabel="Get help with this order"
icon={<Icon name="question-circle" size="sm" color="secondaryForeground" />}
>
Need help with this order?
</Button>
</StickyBottomBar>
) : null}
</SafeAreaView>
</World>
);
}
OrderTrackingScreen.displayName = "OrderTrackingScreen";
```
Screen: https://lumioui.dev/screens/ecommerce-tracking