Compare commits

..

8 commits

Author SHA1 Message Date
shafi54
9283e03ee9 basic integration testing 2026-09-17 23:13:32 +05:30
shafi54
192126cf9e enh 2026-09-16 21:48:06 +05:30
shafi54
a40725213f enh 2026-09-16 00:36:02 +05:30
shafi54
e560f51fe7 add product test 2026-09-15 23:59:59 +05:30
shafi54
839c3f8380 enh 2026-09-15 22:26:59 +05:30
shafi54
2e90c36de7 enh 2026-09-15 19:14:48 +05:30
shafi54
31085bdf32 enh 2026-09-15 18:35:54 +05:30
shafi54
b5274e0c2b enh 2026-09-14 23:34:10 +05:30
68 changed files with 14332 additions and 252 deletions

View file

@ -81,7 +81,18 @@
"Shell(cd /Users/mohammedshafiuddin/WebDev/freshyo && npx tsc --noEmit -p tests 2>&1 | tail -5; echo \"tsc_exit=$?\"; bun run test:e2e > /tmp/e2e_run9.log 2>&1; echo \"exit=$?\" >> /tmp/e2e_run9.log)",
"Shell(DEBUG=pw:browser npx playwright test tests/specs/smoke.spec.ts 2 >& 1)",
"Shell(cd /Users/mohammedshafiuddin/WebDev/freshyo && git diff product_to_sku_c1 -- apps/user-ui/hooks/cart-query-hooks.tsx apps/user-ui/hooks/useUploadToObjectStore.ts apps/user-ui/eas.json apps/user-ui/'(drawer)' 2>/dev/null; git diff product_to_sku_c1 -- \"apps/user-ui/app/(drawer)/(tabs)/me/addresses/index.tsx\" \"apps/user-ui/app/(drawer)/(tabs)/me/my-orders/index.tsx\" | head -160)",
"Shell(cd /Users/mohammedshafiuddin/WebDev/freshyo/packages/db_helper_sqlite && npx tsc --noEmit 2>&1 | tail -8; echo \"sqlite_exit=$?\"; echo \"=== user-ui ===\"; cd /Users/mohammedshafiuddin/WebDev/freshyo/apps/user-ui && npx tsc --noEmit 2>&1 | tee /tmp/uui_tsc.txt | tail -5; echo \"userui_errors=$(grep -cE 'error TS' /tmp/uui_tsc.txt)\")"
"Shell(cd /Users/mohammedshafiuddin/WebDev/freshyo/packages/db_helper_sqlite && npx tsc --noEmit 2>&1 | tail -8; echo \"sqlite_exit=$?\"; echo \"=== user-ui ===\"; cd /Users/mohammedshafiuddin/WebDev/freshyo/apps/user-ui && npx tsc --noEmit 2>&1 | tee /tmp/uui_tsc.txt | tail -5; echo \"userui_errors=$(grep -cE 'error TS' /tmp/uui_tsc.txt)\")",
"Shell(/.maestro/bin/maestro:*)",
"Shell(cd /Users/mohammedshafiuddin/WebDev/freshyo && adb shell am force-stop in.freshyo.app; adb shell monkey -p in.freshyo.app -c android.intent.category.LAUNCHER 1 >/dev/null 2>&1; sleep 8; mkdir -p \"$COMMANDCODE_SCRATCHPAD\"; adb exec-out screencap -p > \"$COMMANDCODE_SCRATCHPAD/screen1.png\"; ls -la \"$COMMANDCODE_SCRATCHPAD/screen1.png\")",
"Shell(adb:*)",
"Shell(cd /Users/mohammedshafiuddin/WebDev/freshyo && adb exec-out screencap -p > \"$COMMANDCODE_SCRATCHPAD/screen2.png\" && ls -la \"$COMMANDCODE_SCRATCHPAD/screen2.png\")",
"Shell(bash e2e/run.sh flows/smoke.yaml 2 >& 1)",
"Shell(cd /Users/mohammedshafiuddin/WebDev/freshyo && \"$HOME/.maestro/bin/maestro\" hierarchy --platform android 2>/dev/null > \"$COMMANDCODE_SCRATCHPAD/hier.json\"; wc -c \"$COMMANDCODE_SCRATCHPAD/hier.json\"; echo \"=== occurrences ===\"; grep -o \"product-card\" \"$COMMANDCODE_SCRATCHPAD/hier.json\" | head -3; echo \"--- context around testID usages ---\"; grep -oE '\"(resource-id|content-desc|text|accessibilityText)\": \"[^\"]{0,40}\"' \"$COMMANDCODE_SCRATCHPAD/hier.json\" | sort | uniq -c | sort -rn | head -20)",
"Shell(cd /Users/mohammedshafiuddin/WebDev/freshyo && adb shell uiautomator dump /sdcard/ui.xml >/dev/null 2>&1; adb shell cat /sdcard/ui.xml > \"$COMMANDCODE_SCRATCHPAD/ui.xml\" 2>/dev/null; wc -c \"$COMMANDCODE_SCRATCHPAD/ui.xml\"; echo \"=== product-card? ===\"; grep -o \"product-card\" \"$COMMANDCODE_SCRATCHPAD/ui.xml\" | head -2; echo \"=== sample attrs ===\"; grep -oE 'content-desc=\"[^\"]{1,40}\"' \"$COMMANDCODE_SCRATCHPAD/ui.xml\" | sort -u | head -15; echo \"=== text sample ===\"; grep -oE 'text=\"[^\"]{2,40}\"' \"$COMMANDCODE_SCRATCHPAD/ui.xml\" | sort -u | head -20)",
"Shell(cd /Users/mohammedshafiuddin/WebDev/freshyo && adb shell am force-stop in.freshyo.app; adb shell monkey -p in.freshyo.app -c android.intent.category.LAUNCHER 1 >/dev/null 2>&1; sleep 16; adb shell uiautomator dump /sdcard/ui2.xml >/dev/null 2>&1; adb shell cat /sdcard/ui2.xml > \"$COMMANDCODE_SCRATCHPAD/ui2.xml\" 2>/dev/null; echo \"size: $(wc -c < \"$COMMANDCODE_SCRATCHPAD/ui2.xml\")\"; echo \"=== product-card occurrences ===\"; grep -c \"product-card\" \"$COMMANDCODE_SCRATCHPAD/ui2.xml\" || true; echo \"=== resource-id sample ===\"; grep -oE 'resource-id=\"[^\"]{1,50}\"' \"$COMMANDCODE_SCRATCHPAD/ui2.xml\" | sort -u | head -15; echo \"=== content-desc sample ===\"; grep -oE 'content-desc=\"[^\"]{2,40}\"' \"$COMMANDCODE_SCRATCHPAD/ui2.xml\" | sort -u | head -15)",
"Shell(cd /Users/mohammedshafiuddin/WebDev/freshyo && cat > \"$COMMANDCODE_SCRATCHPAD/probe2.yaml\" <<'YAML' appId: in.freshyo.app --- - assertVisible: id: \"product-card\" YAML \"$HOME/.maestro/bin/maestro\" test \"$COMMANDCODE_SCRATCHPAD/probe2.yaml\" --platform android 2>&1 | tail -6)",
"Shell(bash e2e/run.sh flows/place-order.yaml 2 >& 1)",
"Shell(cd /Users/mohammedshafiuddin/WebDev/freshyo && ls ~/.maestro/tests/2026-09-14_154602/place-order/screen-hierarchy/ 2>/dev/null; F=$(ls ~/.maestro/tests/2026-09-14_154602/place-order/screen-hierarchy/*Me* 2>/dev/null | head -1); echo \"file: $F\"; grep -oE '\"(text|accessibilityText|resource-id)\": \"[^\"]{1,30}\"' \"$F\" 2>/dev/null | sort -u | head -40)"
],
"deny": [],
"defaultMode": "default"

View file

@ -124,6 +124,12 @@ er-ui/app/(drawer)/(tabs)/home/index.tsx, I want similar here") and expects the
- Prefers that disruptive user-facing dialogs be reserved for genuinely important events: the OTA update prompt should NOT be shown for every update — only for a few crucial releases ("I don't want to show this for all the updates. Only a few updates are so important"), while routine updates apply silently in the background. Confidence: 0.7
- Uses "quantity" loosely to refer to the pack size / unit notation on order items: he reported "we aren't showing quantity" about a row that already rendered the numeric count, then clarified "yeah, show the pack size" — so when he says a quantity isn't shown, check whether he actually means the unit/pack-size notation is missing. Confidence: 0.55
- For new operational switches/flags, prefers a backend constant in the admin-editable constants store (CONST_KEYS + seed + exposed via essentialConsts, editable in Customize App) over build/publish-time config (e.g. an `expo.extra` flag in app.json) or hardcoding — chosen when offered the trade-off ("let's introduce a new constant"), because it can be changed remotely with no rebuild or republish. Confidence: 0.6
- Dislikes long lists of CLI flags for supplying run-time/test values and asks for a "better way" ("isn't there a better way to supply the values"). Expects values supplied declaratively: non-secret defaults committed in a config file (e.g. Maestro `config.yaml` `env:` block), secrets in a gitignored `.env`-style file, and a thin wrapper/runner script wired to a single package-manager entry point (`bun run e2e`) so one short command runs everything — with extra flags still passable through. Secrets should never be passed inline on the command line or committed. Confidence: 0.7
- When he doubts the current approach ("isn't there a better way"), expects the agent to actually research the tool's documented capabilities (check `--help`, official docs) before proposing alternatives, rather than answering from memory. Confidence: 0.6
- For mobile app E2E testing, wants Android ONLY and never iOS — stated explicitly: "I want to test only on android. Never on ios." The test runner/harness should be wired so it can never target an iOS simulator (e.g., pin the Android package id and always pass `--platform android`), rather than auto-detecting the platform from booted devices. Confidence: 0.9
- For React Native / Expo mobile E2E automation, prefers **Maestro** (not Detox/Appium), with its flow configs in a folder at the repo ROOT named `e2e/` — "I want test end to end test automation with maestro. have a folder at the root named e2e" — mirroring his `tests/`-at-root convention for Playwright. Expects the tool's own YAML workspace layout inside it (`config.yaml`, `flows/`, reusable `subflows/`), not per-app test dirs. Confidence: 0.85
- Wants E2E flows to take the shortest realistic user path and skip unnecessary intermediate screens — e.g., add to the cart directly from the home product card's cart icon instead of opening the product-detail screen first ("don't open any product detail. Just click on the cart icon of any product, then go to the cart page and then place order"). Confidence: 0.5
- When he pastes a runtime error/log (e.g., a tRPC/D1 `Failed query: select ... from "key_val_store"` 500), he asks for an explanation first — "what does this error mean. Why am I seeing it" — expecting the agent to investigate and explain the root cause (local D1 state, migrations/config) before changing any code, not to jump straight to a fix. Confidence: 0.5
e changes. Confidence: 0.95
er-ui/app/(drawer)/(tabs)/home/index.tsx, I want similar here") and expects the agent to mirror that pattern exactly, including details like item counts and container styling. Confidence: 0.9
als) rather than introducing new brand palettes; approval of a redesign's layout/composition does not imply approval of color or theme changes. Confidence: 0.95

7
.gitignore vendored
View file

@ -133,3 +133,10 @@ dist
type-clusters/*
# ---> Maestro (root E2E suite)
tests/e2e/.env
apps/user-ui/android/
apps/admin-ui/android/
.maestro-output/*

View file

@ -2,6 +2,7 @@
## Important instructions
- Don't run any drizzle migrations. User will handle it.
- don't try to run any test unless user explicitly asks for
## Code Style Guidelines

View file

@ -29,6 +29,8 @@ const MenuItemComponent: React.FC<MenuItemComponentProps> = ({ item, router }) =
return (
<Pressable
testID={item.testID}
accessibilityLabel={item.testID}
onPress={() => item.onPress ? item.onPress() : router.push(item.route as any)}
style={({ pressed }) => [
tw`flex-row items-center p-4 bg-white border border-gray-100 rounded-xl mb-3 shadow-sm`,
@ -108,6 +110,7 @@ export default function Dashboard() {
description: 'Create a new product listing',
route: '/(drawer)/dashboard/products/add',
category: 'quick',
testID: 'add-product-menu-item',
iconColor: theme.colors.brand500,
iconBg: theme.colors.brand50,
},

View file

@ -1,10 +1,19 @@
import React from 'react'
import { Alert } from 'react-native'
import { AppContainer, ImageUploaderNeoPayload } from 'common-ui'
import { Alert, View } from 'react-native'
import { ImageUploaderNeoPayload, tw } from 'common-ui'
import ProductForm from '@/src/components/ProductForm'
import { trpc } from '@/src/trpc-client'
import { useUploadToObjectStorage } from '@/hooks/useUploadToObjectStore'
// SKUs store bucket-relative object keys, not URLs. Drop the query string and
// keep the last two path segments:
// https://<acct>.r2.cloudflarestorage.com/meatfarmer-dev/product-images/1.jpg?X-Amz-...
// -> product-images/1.jpg
const toImageKey = (url: string): string => {
const parts = url.split('?')[0].split('/').filter(Boolean)
return parts.slice(-2).join('/')
}
export default function AddProduct() {
const createProduct = trpc.admin.product.createProduct.useMutation()
const { refetch: refetchProducts } = trpc.admin.product.getProducts.useQuery(undefined, {
@ -63,7 +72,8 @@ export default function AddProduct() {
let urlCursor = 0
const skus = values.variants.map((variant: any, vIndex: number) => {
const count = imageCounts[vIndex]
const variantUrls = allUploadUrls.slice(urlCursor, urlCursor + count)
// Send keys, not the presigned URLs.
const variantUrls = allUploadUrls.slice(urlCursor, urlCursor + count).map(toImageKey)
urlCursor += count
return {
@ -129,13 +139,13 @@ export default function AddProduct() {
}
return (
<AppContainer>
<View style={tw`flex-1 bg-white`}>
<ProductForm
mode="create"
initialValues={initialValues}
onSubmit={handleSubmit}
isLoading={createProduct.isPending || isUploading}
/>
</AppContainer>
</View>
)
}

View file

@ -6,6 +6,15 @@ import ProductForm, { ProductFormRef } from '@/src/components/ProductForm';
import { trpc } from '@/src/trpc-client';
import { useUploadToObjectStorage } from '@/hooks/useUploadToObjectStore';
// SKUs store bucket-relative object keys, not URLs. Drop the query string and
// keep the last two path segments:
// https://<acct>.r2.cloudflarestorage.com/meatfarmer-dev/product-images/1.jpg?X-Amz-...
// -> product-images/1.jpg
const toImageKey = (url: string): string => {
const parts = url.split('?')[0].split('/').filter(Boolean)
return parts.slice(-2).join('/')
}
export default function EditProduct() {
const { id } = useLocalSearchParams();
const productId = Number(id);
@ -140,9 +149,10 @@ export default function EditProduct() {
// Existing images (mimeType === null) stay as-is
const existingUrls = variantImages[vIndex]
?.filter((img) => img.mimeType === null)
.map((img) => img.url) || []
.map((img) => toImageKey(img.url)) || []
const allUrls = [...existingUrls, ...newUrls]
// Send keys, not URLs — the SKU stores the bucket-relative object key.
const allUrls = [...existingUrls, ...newUrls.map(toImageKey)]
return {
id: variant.id,
@ -209,7 +219,7 @@ export default function EditProduct() {
}
return (
<AppContainer>
<View style={tw`flex-1 bg-white`}>
<ProductForm
ref={productFormRef}
mode="edit"
@ -219,6 +229,6 @@ export default function EditProduct() {
existingVariantImages={existingVariantImages}
existingVariantImageKeys={existingVariantImageKeys}
/>
</AppContainer>
</View>
);
}

View file

@ -1,9 +1,9 @@
import React, { useState, useMemo } from 'react';
import { View, TouchableOpacity, RefreshControl } from 'react-native';
import React, { useState, useMemo, useCallback } from 'react';
import { View, TouchableOpacity } from 'react-native';
import { Image } from 'expo-image';
import { useRouter } from 'expo-router';
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
import { AppContainer, MyText, tw, MyButton, useManualRefresh, SearchBar, useMarkDataFetchers, MyFlatList } from 'common-ui';
import { MyText, tw, MyButton, useManualRefresh, SearchBar, useMarkDataFetchers, MyFlatList } from 'common-ui';
import { trpc } from '@/src/trpc-client';
import { SuccessToast, ErrorToast } from '@/services/toaster';
@ -13,151 +13,59 @@ type FilterType = 'all' | 'in-stock' | 'out-of-stock';
type SerializedAdminSku = Omit<AdminSku, 'createdAt'> & { createdAt: string | Date }
function getDefaultSku(product: { skus: SerializedAdminSku[] }): SerializedAdminSku | null {
function getDefaultSku(product: { skus?: SerializedAdminSku[] }): SerializedAdminSku | null {
return product.skus?.[0] ?? null
}
export default function Products() {
const router = useRouter();
const [searchTerm, setSearchTerm] = useState('');
const [activeFilter, setActiveFilter] = useState<FilterType>('all');
const [refreshing, setRefreshing] = useState(false);
const { data: productsData, isLoading, error, refetch } = trpc.admin.product.getProducts.useQuery();
const toggleOutOfStock = trpc.admin.product.toggleOutOfStock.useMutation({
onSuccess: async (res) => {
await refetch();
SuccessToast(res.message);
},
onError: (err) => {
ErrorToast(err.message || 'Failed to update stock status');
},
});
useManualRefresh(refetch);
useMarkDataFetchers(() => {
refetch();
});
const products = productsData?.products || [];
const handleRefresh = async () => {
setRefreshing(true);
await refetch();
setRefreshing(false);
};
const filteredProducts = useMemo(() => {
return products.filter(product => {
const defaultSku = getDefaultSku(product)
const matchesSearch = product.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
(product.shortDescription?.toLowerCase().includes(searchTerm.toLowerCase()));
const matchesFilter = activeFilter === 'all' ||
(activeFilter === 'in-stock' && !defaultSku?.isOutOfStock) ||
(activeFilter === 'out-of-stock' && defaultSku?.isOutOfStock);
return matchesSearch && matchesFilter;
});
}, [products, searchTerm, activeFilter]);
const handleEdit = (productId: number) => {
router.push(`/(drawer)/dashboard/products/edit?id=${productId}` as any);
};
const handleViewDetails = (productId: number) => {
router.push(`/(drawer)/dashboard/products/detail/${productId}` as any);
};
const handleToggleStock = (product: { id: number }) => {
toggleOutOfStock.mutate({ id: product.id });
};
const FilterButton = ({ filter, label, count }: { filter: FilterType; label: string; count: number }) => (
function FilterButton({
filter,
label,
count,
activeFilter,
onSelect,
}: {
filter: FilterType
label: string
count: number
activeFilter: FilterType
onSelect: (filter: FilterType) => void
}) {
const isActive = activeFilter === filter
return (
<TouchableOpacity
onPress={() => setActiveFilter(filter)}
style={tw`px-4 py-2 rounded-lg ${activeFilter === filter ? 'bg-blue-500' : 'bg-gray-200'}`}
onPress={() => onSelect(filter)}
style={tw`px-4 py-2 rounded-lg ${isActive ? 'bg-blue-500' : 'bg-gray-200'}`}
>
<MyText style={tw`${activeFilter === filter ? 'text-white' : 'text-gray-700'} font-semibold`}>
<MyText style={tw`${isActive ? 'text-white' : 'text-gray-700'} font-semibold`}>
{label} ({count})
</MyText>
</TouchableOpacity>
);
if (isLoading) {
return (
<AppContainer>
<View style={tw`flex-1 justify-center items-center`}>
<MyText style={tw`text-gray-600`}>Loading products...</MyText>
</View>
</AppContainer>
);
)
}
if (error) {
return (
<AppContainer>
<View style={tw`flex-1 justify-center items-center`}>
<MyText style={tw`text-red-600`}>Error loading products</MyText>
<TouchableOpacity
onPress={() => refetch()}
style={tw`mt-4 bg-blue-500 px-4 py-2 rounded-lg`}
>
<MyText style={tw`text-white font-semibold`}>Retry</MyText>
</TouchableOpacity>
</View>
</AppContainer>
);
type ProductRowProps = {
product: any
isToggling: boolean
onView: (productId: number) => void
onEdit: (productId: number) => void
onToggle: (product: { id: number }) => void
}
const inStockCount = products.filter(p => getDefaultSku(p) && !getDefaultSku(p)!.isOutOfStock).length;
const outOfStockCount = products.filter(p => getDefaultSku(p)?.isOutOfStock).length;
const listHeader = (
<>
{/* Header */}
<View style={tw`flex-row justify-between items-center mb-6`}>
<MyText style={tw`text-2xl font-bold text-gray-800`}>Products</MyText>
<MyButton onPress={() => router.push('/(drawer)/dashboard/products/add' as any)}>
Add Product
</MyButton>
</View>
{/* Search Bar */}
<View style={tw`mb-4`}>
<SearchBar
value={searchTerm}
onChangeText={setSearchTerm}
placeholder="Search products..."
containerStyle={tw`mb-0`}
/>
</View>
{/* Filter Tabs */}
<View style={tw`flex-row gap-2 mb-6`}>
<FilterButton filter="all" label="All" count={products.length} />
<FilterButton filter="in-stock" label="In Stock" count={inStockCount} />
<FilterButton filter="out-of-stock" label="Out of Stock" count={outOfStockCount} />
</View>
</>
);
const renderItem = ({ item: product }: { item: any }) => {
const ProductRow = React.memo(function ProductRow({ product, isToggling, onView, onEdit, onToggle }: ProductRowProps) {
const defaultSku = getDefaultSku(product)
const skuImages = defaultSku?.images ?? null
const isOut = defaultSku?.isOutOfStock ?? false
return (
<View key={product.id} style={tw`bg-white rounded-2xl shadow-lg mb-4 overflow-hidden`}>
<View style={tw`bg-white rounded-2xl shadow-lg mb-4 overflow-hidden`}>
{/* Product Image */}
{skuImages && skuImages.length > 0 ? (
<Image
source={{ uri: skuImages[0] }}
style={tw`w-full h-48`}
resizeMode="cover"
contentFit="cover"
cachePolicy="memory-disk"
recyclingKey={String(product.id)}
/>
) : (
<View style={tw`w-full h-48 bg-gray-200 justify-center items-center`}>
@ -194,7 +102,7 @@ export default function Products() {
{/* Action Buttons */}
<View style={tw`flex-row gap-2`}>
<TouchableOpacity
onPress={() => handleViewDetails(product.id)}
onPress={() => onView(product.id)}
style={tw`flex-1 bg-gray-500 p-3 rounded-lg flex-row items-center justify-center`}
>
<MaterialIcons name="visibility" size={16} color="white" />
@ -202,7 +110,7 @@ export default function Products() {
</TouchableOpacity>
<TouchableOpacity
onPress={() => handleEdit(product.id)}
onPress={() => onEdit(product.id)}
style={tw`flex-1 bg-blue-500 p-3 rounded-lg flex-row items-center justify-center`}
>
<MaterialIcons name="edit" size={16} color="white" />
@ -210,9 +118,9 @@ export default function Products() {
</TouchableOpacity>
<TouchableOpacity
onPress={() => handleToggleStock(product)}
disabled={toggleOutOfStock.isPending && toggleOutOfStock.variables?.id === product.id}
style={tw`flex-1 ${isOut ? 'bg-green-500' : 'bg-orange-500'} p-3 rounded-lg flex-row items-center justify-center ${toggleOutOfStock.isPending && toggleOutOfStock.variables?.id === product.id ? 'opacity-50' : ''}`}
onPress={() => onToggle(product)}
disabled={isToggling}
style={tw`flex-1 ${isOut ? 'bg-green-500' : 'bg-orange-500'} p-3 rounded-lg flex-row items-center justify-center ${isToggling ? 'opacity-50' : ''}`}
>
<MaterialIcons name={isOut ? 'check-circle' : 'block'} size={16} color="white" />
<MyText style={tw`text-white font-semibold ml-1`}>
@ -223,15 +131,145 @@ export default function Products() {
</View>
</View>
)
};
})
export default function Products() {
const router = useRouter();
const [searchTerm, setSearchTerm] = useState('');
const [activeFilter, setActiveFilter] = useState<FilterType>('all');
const { data: productsData, isLoading, error, refetch } = trpc.admin.product.getProducts.useQuery();
const toggleOutOfStock = trpc.admin.product.toggleOutOfStock.useMutation({
onSuccess: async (res) => {
await refetch();
SuccessToast(res.message);
},
onError: (err) => {
ErrorToast(err.message || 'Failed to update stock status');
},
});
useManualRefresh(refetch);
useMarkDataFetchers(() => {
refetch();
});
const products = productsData?.products || [];
const handleRefresh = useCallback(async () => {
await refetch();
}, [refetch]);
const filteredProducts = useMemo(() => {
const term = searchTerm.toLowerCase();
return products.filter(product => {
const defaultSku = getDefaultSku(product)
const matchesSearch = product.name.toLowerCase().includes(term) ||
(product.shortDescription?.toLowerCase().includes(term));
const matchesFilter = activeFilter === 'all' ||
(activeFilter === 'in-stock' && !defaultSku?.isOutOfStock) ||
(activeFilter === 'out-of-stock' && defaultSku?.isOutOfStock);
return matchesSearch && matchesFilter;
});
}, [products, searchTerm, activeFilter]);
const inStockCount = useMemo(
() => products.filter(p => { const sku = getDefaultSku(p); return sku && !sku.isOutOfStock }).length,
[products]
);
const outOfStockCount = useMemo(
() => products.filter(p => getDefaultSku(p)?.isOutOfStock).length,
[products]
);
const handleEdit = useCallback((productId: number) => {
router.push(`/(drawer)/dashboard/products/edit?id=${productId}` as any);
}, [router]);
const handleViewDetails = useCallback((productId: number) => {
router.push(`/(drawer)/dashboard/products/detail/${productId}` as any);
}, [router]);
const handleToggleStock = useCallback((product: { id: number }) => {
toggleOutOfStock.mutate({ id: product.id });
}, [toggleOutOfStock]);
const handleSelectFilter = useCallback((filter: FilterType) => setActiveFilter(filter), []);
const keyExtractor = useCallback((item: { id: number }) => item.id.toString(), []);
const renderItem = useCallback(({ item: product }: { item: any }) => (
<ProductRow
product={product}
isToggling={toggleOutOfStock.isPending && toggleOutOfStock.variables?.id === product.id}
onView={handleViewDetails}
onEdit={handleEdit}
onToggle={handleToggleStock}
/>
), [toggleOutOfStock.isPending, toggleOutOfStock.variables?.id, handleViewDetails, handleEdit, handleToggleStock]);
const listHeader = useMemo(() => (
<>
{/* Header */}
<View style={tw`flex-row justify-between items-center mb-6`}>
<MyText style={tw`text-2xl font-bold text-gray-800`}>Products</MyText>
<MyButton testID="add-product-button" onPress={() => router.push('/(drawer)/dashboard/products/add' as any)}>
Add Product
</MyButton>
</View>
{/* Search Bar */}
<View style={tw`mb-4`}>
<SearchBar
value={searchTerm}
onChangeText={setSearchTerm}
placeholder="Search products..."
containerStyle={tw`mb-0`}
/>
</View>
{/* Filter Tabs */}
<View style={tw`flex-row gap-2 mb-6`}>
<FilterButton filter="all" label="All" count={products.length} activeFilter={activeFilter} onSelect={handleSelectFilter} />
<FilterButton filter="in-stock" label="In Stock" count={inStockCount} activeFilter={activeFilter} onSelect={handleSelectFilter} />
<FilterButton filter="out-of-stock" label="Out of Stock" count={outOfStockCount} activeFilter={activeFilter} onSelect={handleSelectFilter} />
</View>
</>
), [products.length, inStockCount, outOfStockCount, searchTerm, activeFilter, handleSelectFilter, router]);
if (isLoading) {
return (
<View style={tw`flex-1 bg-white justify-center items-center`}>
<MyText style={tw`text-gray-600`}>Loading products...</MyText>
</View>
);
}
if (error) {
return (
<View style={tw`flex-1 bg-white justify-center items-center`}>
<MyText style={tw`text-red-600`}>Error loading products</MyText>
<TouchableOpacity
onPress={() => refetch()}
style={tw`mt-4 bg-blue-500 px-4 py-2 rounded-lg`}
>
<MyText style={tw`text-white font-semibold`}>Retry</MyText>
</TouchableOpacity>
</View>
);
}
return (
<AppContainer>
<View style={tw`flex-1`}>
<View style={tw`flex-1 bg-white`}>
<MyFlatList
data={filteredProducts}
renderItem={renderItem}
keyExtractor={(item) => item.id.toString()}
keyExtractor={keyExtractor}
ListHeaderComponent={listHeader}
ListEmptyComponent={
<View style={tw`flex-1 justify-center items-center py-10`}>
@ -255,13 +293,14 @@ export default function Products() {
)}
</View>
}
contentContainerStyle={tw`pb-4`}
contentContainerStyle={tw`p-4`}
showsVerticalScrollIndicator={false}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={handleRefresh} />
}
onRefresh={handleRefresh}
initialNumToRender={6}
maxToRenderPerBatch={8}
windowSize={5}
removeClippedSubviews
/>
</View>
</AppContainer>
);
}

View file

@ -1,7 +1,7 @@
import React, { useState } from 'react';
import { MaterialCommunityIcons, Entypo } from '@expo/vector-icons';
import { View, TouchableOpacity, FlatList, Alert, ActivityIndicator } from 'react-native';
import { AppContainer, MyText, tw, MyFlatList , BottomDialog, MyTouchableOpacity, Checkbox } from 'common-ui';
import { MyText, tw, MyFlatList , BottomDialog, MyTouchableOpacity, Checkbox } from 'common-ui';
import { trpc } from '@/src/trpc-client';
import { useRouter } from 'expo-router';
import dayjs from 'dayjs';

View file

@ -198,6 +198,7 @@ export default function SlotDetails() {
{/* FAB for Edit Slot */}
<MyTouchableOpacity
testID="edit-slot-fab"
onPress={() => router.push(`/(drawer)/dashboard/slots/edit/${slot.id}` as any)}
activeOpacity={0.95}
style={{ position: 'absolute', bottom: 32, right: 24, zIndex: 100 }}

View file

@ -1,11 +1,19 @@
import { QueryClientProvider } from "@tanstack/react-query";
import queryClient from "../utils/queryClient";
import { Stack } from "expo-router";
import { LogBox } from "react-native";
import { StaffAuthProvider } from "@/components/context/staff-auth-context";
import { trpc, trpcClient } from "@/src/trpc-client";
import { SafeAreaView } from "react-native-safe-area-context";
import { RefreshProvider } from '../../../packages/ui/src/lib/refresh-context';
// Silence dev warnings/errors in LogBox. The "Open debugger to view warnings"
// toast overlays the bottom of the UI and intercepts taps (e.g. the Add Slot
// FAB), which breaks E2E flows.
if (__DEV__) {
LogBox.ignoreAllLogs(true);
}
export default function Layout() {
return (
<QueryClientProvider client={queryClient}>

View file

@ -28,6 +28,7 @@ interface ProductsSelectorProps {
selectedGroupIds?: number[];
onGroupChange?: (groupIds: number[]) => void;
showGroups?: boolean;
testID?: string;
}
export default function ProductsSelector({
@ -44,6 +45,7 @@ export default function ProductsSelector({
showGroups = true,
selectedGroupIds = [],
onGroupChange,
testID,
}: ProductsSelectorProps) {
const { data: skusData } = trpc.common.product.getAllSkusSummary.useQuery();
const allSkus: SkuSummary[] = skusData?.skus || [];
@ -147,6 +149,7 @@ export default function ProductsSelector({
)}
<BottomDropdown
testID={testID}
label={label}
options={productOptions}
value={multiple

View file

@ -160,6 +160,7 @@ export default function SlotForm({
selectedGroupIds={values.selectedGroupIds}
onGroupChange={(newGroupIds) => setFieldValue('selectedGroupIds', newGroupIds)}
label="Select Products"
testID="slot-products-selector"
placeholder="Select products for this slot"
/>
</View>
@ -193,6 +194,7 @@ export default function SlotForm({
selectedGroupIds={snippet.groupIds || []}
onGroupChange={(newGroupIds) => setFieldValue(`vendorSnippetList.${index}.groupIds`, newGroupIds)}
label="Select Products"
testID="snippet-products-selector"
placeholder="Select products for snippet"
isDisabled={(sku) => !values.selectedSkuIds.includes(sku.id)}
/>

View file

@ -44,6 +44,8 @@ export function useUploadToObjectStorage() {
});
if (!response.ok) {
const body = await response.text(); // <Error><Code>…</Code></Error>
console.error('upload failed', response.status, body);
throw new Error(`Upload ${index + 1} failed with status ${response.status}`);
}

View file

@ -253,8 +253,9 @@ const ProductForm = forwardRef<ProductFormRef, ProductFormProps>(({
>
{({ handleChange, handleSubmit, values, setFieldValue, validateForm }) => {
return (
<ScrollView style={tw`flex-1`} contentContainerStyle={tw`pb-10`}>
<ScrollView style={tw`flex-1`} contentContainerStyle={tw`px-4 pt-4 pb-10`} keyboardShouldPersistTaps="handled">
<MyTextInput
testID="product-name-input"
topLabel="Product Name"
placeholder="Enter product name"
value={values.name}
@ -262,6 +263,7 @@ const ProductForm = forwardRef<ProductFormRef, ProductFormProps>(({
style={{ marginBottom: 16 }}
/>
<MyTextInput
testID="product-short-description-input"
topLabel="Short Description"
placeholder="Enter short description"
multiline
@ -271,6 +273,7 @@ const ProductForm = forwardRef<ProductFormRef, ProductFormProps>(({
style={{ marginBottom: 16 }}
/>
<MyTextInput
testID="product-long-description-input"
topLabel="Long Description"
placeholder="Enter detailed description"
multiline
@ -280,6 +283,7 @@ const ProductForm = forwardRef<ProductFormRef, ProductFormProps>(({
style={{ marginBottom: 16 }}
/>
<BottomDropdown
testID="product-store-dropdown"
topLabel="Store"
label="Store"
value={values.storeId}
@ -290,6 +294,7 @@ const ProductForm = forwardRef<ProductFormRef, ProductFormProps>(({
/>
<BottomDropdown
testID="product-type-dropdown"
topLabel="Product Type"
label="Product Type"
value={values.productType}
@ -308,6 +313,7 @@ const ProductForm = forwardRef<ProductFormRef, ProductFormProps>(({
<View style={tw`flex-row justify-between items-center mb-3`}>
<MyText style={tw`text-lg font-bold text-gray-800`}>Variants</MyText>
<TouchableOpacity
testID="add-variant-button"
onPress={() => {
push(defaultVariant())
setVariantImages((prev) => [...prev, []])
@ -335,6 +341,7 @@ const ProductForm = forwardRef<ProductFormRef, ProductFormProps>(({
</View>
{(values.variants.length > 1 || isExistingSku) && (
<TouchableOpacity
testID={`variant-${vIndex}-remove`}
onPress={() => {
if (isExistingSku) {
// Mark existing SKU as deleted (sent to backend on save).
@ -355,6 +362,7 @@ const ProductForm = forwardRef<ProductFormRef, ProductFormProps>(({
</View>
<MyTextInput
testID={`variant-${vIndex}-sku-name`}
topLabel="SKU Name (optional)"
placeholder="Overrides the auto-generated name"
value={variant.name ?? ''}
@ -385,6 +393,7 @@ const ProductForm = forwardRef<ProductFormRef, ProductFormProps>(({
<View key={aIndex} style={tw`flex-row items-center mb-2 gap-2`}>
<View style={tw`flex-1`}>
<MyTextInput
testID={`variant-${vIndex}-attr-${aIndex}-name`}
placeholder="Name"
value={attr.featureName ?? ''}
onChangeText={(text) =>
@ -399,6 +408,7 @@ const ProductForm = forwardRef<ProductFormRef, ProductFormProps>(({
</View>
<View style={tw`flex-1`}>
<MyTextInput
testID={`variant-${vIndex}-attr-${aIndex}-value`}
placeholder={isQuantityFeature(attr) ? 'e.g. 0.5 kg' : 'Value'}
value={attr.featureValue}
onChangeText={handleChange(`variants.${vIndex}.features.${aIndex}.featureValue`)}
@ -432,6 +442,7 @@ const ProductForm = forwardRef<ProductFormRef, ProductFormProps>(({
<View style={tw`flex-row gap-2 mb-3`}>
<View style={tw`flex-1`}>
<MyTextInput
testID={`variant-${vIndex}-market-price`}
topLabel="Market Price"
placeholder="MRP"
keyboardType="numeric"
@ -442,6 +453,7 @@ const ProductForm = forwardRef<ProductFormRef, ProductFormProps>(({
<View style={tw`flex-1`}>
<MyTextInput
topLabel="Our Price"
testID={`variant-${vIndex}-price`}
placeholder="Selling price"
keyboardType="numeric"
value={variant.price?.toString() ?? ''}
@ -452,6 +464,7 @@ const ProductForm = forwardRef<ProductFormRef, ProductFormProps>(({
<View style={tw`flex-row items-center mb-3`}>
<Checkbox
testID={`variant-${vIndex}-flash`}
checked={variant.isFlashAvailable}
onPress={() => {
setFieldValue(`variants.${vIndex}.isFlashAvailable`, !variant.isFlashAvailable)
@ -464,6 +477,7 @@ const ProductForm = forwardRef<ProductFormRef, ProductFormProps>(({
<View style={tw`flex-row items-center mb-3`}>
<Checkbox
testID={`variant-${vIndex}-offer`}
checked={variant.isOffer}
onPress={() => setFieldValue(`variants.${vIndex}.isOffer`, !variant.isOffer)}
style={tw`mr-3`}
@ -474,6 +488,7 @@ const ProductForm = forwardRef<ProductFormRef, ProductFormProps>(({
{!(values.productType === 'combo' && (variant.comboItems?.length || 0) > 0) && (
<View style={tw`flex-row items-center mb-3`}>
<Checkbox
testID={`variant-${vIndex}-combo-only`}
checked={variant.isComboOnly}
onPress={() => setFieldValue(`variants.${vIndex}.isComboOnly`, !variant.isComboOnly)}
style={tw`mr-3`}
@ -485,6 +500,7 @@ const ProductForm = forwardRef<ProductFormRef, ProductFormProps>(({
{mode === 'edit' && (
<View style={tw`flex-row items-center mb-3`}>
<Checkbox
testID={`variant-${vIndex}-suspend`}
checked={variant.isSuspended}
onPress={() => setFieldValue(`variants.${vIndex}.isSuspended`, !variant.isSuspended)}
style={tw`mr-3`}
@ -495,6 +511,7 @@ const ProductForm = forwardRef<ProductFormRef, ProductFormProps>(({
{variant.isFlashAvailable && (
<MyTextInput
testID={`variant-${vIndex}-flash-price`}
topLabel="Flash Price"
placeholder="Enter flash price"
keyboardType="numeric"
@ -505,6 +522,7 @@ const ProductForm = forwardRef<ProductFormRef, ProductFormProps>(({
)}
<ImageUploaderNeo
testID={`variant-${vIndex}-image-add`}
images={variantImages[vIndex] || []}
onImageAdd={(payloads) =>
setVariantImages((prev) => {
@ -583,6 +601,7 @@ const ProductForm = forwardRef<ProductFormRef, ProductFormProps>(({
})}
<TouchableOpacity
testID="add-variant-button-footer"
onPress={() => {
push(defaultVariant())
setVariantImages((prev) => [...prev, []])
@ -597,6 +616,7 @@ const ProductForm = forwardRef<ProductFormRef, ProductFormProps>(({
</FieldArray>
<TouchableOpacity
testID="create-product-button"
onPress={async () => {
const validationErrors = await validateForm()
if (Object.keys(validationErrors).length > 0) {

View file

@ -118,10 +118,14 @@ export async function generateSignedUrlFromS3Url(s3UrlRaw: string|null, expiresI
try {
const client = getAwsClient()
const url = buildObjectUrl(getS3BucketName(), s3Url)
const signedRequest = await client.sign(url, {
// X-Amz-Expires must be a QUERY param, not a header: with signQuery, aws4fetch
// hoists header values into the query but still lists them in
// X-Amz-SignedHeaders, and R2 then hashes the missing header as empty — which
// makes the signature mismatch (S3 tolerates it, R2 does not).
const url = new URL(buildObjectUrl(getS3BucketName(), s3Url))
url.searchParams.set('X-Amz-Expires', String(expiresIn))
const signedRequest = await client.sign(url.toString(), {
method: 'GET',
headers: { 'X-Amz-Expires': String(expiresIn) },
aws: { signQuery: true },
})
return signedRequest.url
@ -170,13 +174,13 @@ export async function generateUploadUrl(key: string, mimeType: string, expiresIn
await createUploadUrlStatus(key)
const client = getAwsClient()
const url = buildObjectUrl(getS3BucketName(), key)
const signedRequest = await client.sign(url, {
// Same as the GET path above: keep X-Amz-Expires in the query, and do not
// sign Content-Type — leaving it unsigned means the client may send any value
// without breaking the signature.
const url = new URL(buildObjectUrl(getS3BucketName(), key))
url.searchParams.set('X-Amz-Expires', String(expiresIn))
const signedRequest = await client.sign(url.toString(), {
method: 'PUT',
headers: {
'Content-Type': mimeType,
'X-Amz-Expires': String(expiresIn),
},
aws: { signQuery: true },
})
@ -193,15 +197,22 @@ export async function generateUploadUrl(key: string, mimeType: string, expiresIn
// return decodeURIComponent(rawKey);
// }
// New function (excludes bucket name)
/**
* Normalise an image reference to the bucket-relative object key that SKUs store.
*
* Accepts either a full (presigned) URL or an already-extracted key, so callers
* can send whichever they have. Drops the query string, then keeps the last two
* path segments:
*
* https://<account>.r2.cloudflarestorage.com/meatfarmer-dev/product-images/1.jpg?X-Amz-...
* -> product-images/1.jpg
* product-images/1.jpg
* -> product-images/1.jpg
*/
export function extractKeyFromPresignedUrl(url: string): string {
const u = new URL(url)
const rawKey = u.pathname.replace(/^\/+/, '') // remove leading slash
const decodedKey = decodeURIComponent(rawKey)
// Remove bucket prefix
const parts = decodedKey.split('/')
parts.shift() // Remove bucket name
return parts.join('/')
const withoutQuery = url.split('?')[0]
const parts = withoutQuery.split('/').filter(Boolean)
return decodeURIComponent(parts.slice(-2).join('/'))
}
export async function claimUploadUrl(url: string): Promise<void> {

View file

@ -260,8 +260,6 @@ export const productRouter = router({
await deleteImageUtil({ keys: deletedImageKeys })
}
const allUploadUrls: string[] = skus.flatMap((sku) => sku.images)
const skuInputs = skus.map((sku) => ({
id: sku.id,
name: sku.name ?? null,

View file

@ -8,10 +8,10 @@ routes = [
[[d1_databases]]
binding = "DB"
database_name = "freshyo-backend-dev"
database_id = "b2528bdb-0ec0-478e-8765-b118918153c0"
#database_name = "freshyo-dev"
#database_id = "3cad440f-dc14-4baa-ab05-04eb08e206de"
#database_name = "freshyo-backend-dev"
#database_id = "b2528bdb-0ec0-478e-8765-b118918153c0"
database_name = "freshyo-dev"
database_id = "3cad440f-dc14-4baa-ab05-04eb08e206de"
migrations_dir="../../packages/db_helper_sqlite/drizzle"
migrations_pattern="migration.sql"
[durable_objects]

69
apps/user-ui/.easignore Normal file
View file

@ -0,0 +1,69 @@
# EAS CLI ignores .gitignore entirely once this file exists, so the repo's
# .gitignore patterns are reproduced here (see AGENTS.md / docs.expo.dev
# build-reference/easignore). Keep this in sync when .gitignore changes.
# --- from root .gitignore ---
node_modules/
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
pids
*.pid
*.seed
*.pid.lock
coverage
*.lcov
.nyc_output
bower_components
build/Release
jspm_packages/
web_modules/
*.tsbuildinfo
.npm
.eslintcache
.stylelintcache
.node_repl_history
*.tgz
.yarn-integrity
.cache
.parcel-cache
.next
out
.nuxt
dist
.docusaurus
.serverless/
.fusebox/
.dynamodb/
.tern-port
.vscode-test
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
type-clusters/*
.maestro-output/*
# --- from apps/user-ui/.gitignore ---
expo-env.d.ts
.expo/
# --- EAS Build does not need these ---
# Native projects are generated by Prebuild (matches git-ignored android/).
android/
ios/
.turbo/
# Tests, fixtures and mocks are never part of a build.
tests/
# Do not ship local secrets/config to the build servers.
.env
.env.*
!.env.example

View file

@ -16,6 +16,7 @@ function Login() {
const { loginWithToken } = useAuth();
const [isLoading, setIsLoading] = useState(false);
const [step, setStep] = useState<'mobile' | 'choice' | 'otp' | 'password'>('mobile');
const [passwordBackTo, setPasswordBackTo] = useState<'mobile' | 'choice' | 'otp'>('choice');
const [selectedMobile, setSelectedMobile] = useState('');
const [canResend, setCanResend] = useState(false);
const [resendCountdown, setResendCountdown] = useState(0);
@ -135,8 +136,9 @@ function Login() {
setError,
clearErrors,
setValue,
watch,
} = useForm<LoginFormInputs>({
defaultValues: { mobile: "", otp: "", password: "" },
defaultValues: { mobile: "", otp: "", identifier: "", password: "" },
});
const validateMobile = (mobile: string): boolean => {
@ -223,7 +225,17 @@ function Login() {
otp: data.otp,
});
} else if (step === 'password') {
// Login with password
// Login with username/email/mobile + password
const identifier = (data.identifier || '').trim() || selectedMobile;
if (!identifier) {
setError("identifier", {
type: "manual",
message: "Username is required",
});
return;
}
if (!data.password || data.password.length < 6) {
setError("password", {
type: "manual",
@ -234,7 +246,7 @@ function Login() {
try {
const response = await loginMutation.mutateAsync({
identifier: selectedMobile,
identifier,
password: data.password,
});
loginWithToken(response.data.token, response.data.user);
@ -278,6 +290,7 @@ function Login() {
render={({ field: { onChange, onBlur, value } }) => (
<View style={tw`mb-5`}>
<MyTextInput
testID="user-login-mobile"
topLabel="Mobile Number"
placeholder="Enter your mobile number"
value={value}
@ -313,7 +326,11 @@ function Login() {
<View style={tw`flex-row justify-between mb-6`}>
<MyButton
textContent="Use Password"
onPress={() => setStep('password')}
onPress={() => {
setPasswordBackTo('choice');
setValue('identifier', selectedMobile);
setStep('password');
}}
fillColor="gray1"
textColor="black1"
style={tw`flex-1 mr-2 border border-gray-200`}
@ -374,6 +391,8 @@ function Login() {
<MyTouchableOpacity
onPress={() => {
setPasswordBackTo('choice');
setValue('identifier', selectedMobile);
setStep('password');
setValue('otp', '');
setOtpCells(['', '', '', '']);
@ -422,6 +441,33 @@ function Login() {
{step === 'password' && (
<>
<Controller
control={control}
name="identifier"
render={({ field: { onChange, onBlur, value } }) => (
<View style={tw`mb-4`}>
<MyTextInput
testID="user-login-identifier"
topLabel="Username"
placeholder="Enter your email or mobile"
value={value}
onChangeText={onChange}
onBlur={onBlur}
autoCapitalize="none"
autoCorrect={false}
keyboardType="email-address"
style={tw`bg-gray-50`}
error={!!errors.identifier}
/>
</View>
)}
/>
{errors.identifier && (
<MyText style={tw`text-red-500 text-sm mb-4 text-center`}>
{errors.identifier.message}
</MyText>
)}
<Controller
control={control}
name="password"
@ -429,6 +475,7 @@ function Login() {
render={({ field: { onChange, onBlur, value } }) => (
<View style={tw`mb-4`}>
<MyTextInput
testID="user-login-password"
topLabel="Password"
placeholder="Enter your password"
value={value}
@ -450,7 +497,7 @@ function Login() {
<View style={tw`flex-row justify-center mb-6`}>
<MyTouchableOpacity
onPress={() => {
setStep('choice');
setStep(passwordBackTo);
setValue('password', '');
clearErrors();
}}
@ -484,6 +531,7 @@ function Login() {
<View style={tw`mt-4`}>
<MyButton
testID="user-login-submit"
onPress={handleSubmit(onSubmit)}
fillColor="brand600"
textColor="white1"
@ -502,6 +550,23 @@ function Login() {
: (step === 'otp' ? "Verify & Login" : step === 'password' ? "Login" : "Continue")}
</MyButton>
</View>
{step === 'mobile' && (
<MyTouchableOpacity
testID="user-login-other-ways"
onPress={() => {
setPasswordBackTo('mobile');
setValue('identifier', watch('mobile') || '');
setStep('password');
clearErrors();
}}
style={tw`mt-5`}
>
<MyText weight="semibold" style={tw`text-brand600 text-center`}>
other ways to login
</MyText>
</MyTouchableOpacity>
)}
</>
)}
</View>

View file

@ -231,6 +231,7 @@ const TagTabStrip = memo(({
return (
<MyTouchableOpacity
key={route.key}
testID={`tag-tab-${route.key}`}
onLayout={(e: any) => {
positions.current[i] = e.nativeEvent.layout.x;
}}
@ -295,7 +296,7 @@ const TagScene = memo(({
const hasMore = products.length > 6 && !expanded;
return (
<View style={tw`mt-3`}>
<View style={tw`mt-3`} testID={`tag-grid-${tagId}`}>
{products.length > 0 ? (
<View style={tw`flex-row flex-wrap justify-between`}>
{visible.map((product: any) => (
@ -311,6 +312,7 @@ const TagScene = memo(({
)}
{hasMore && (
<MyTouchableOpacity
testID={`tag-show-more-${tagId}`}
style={tw`self-center mt-1 mb-2 px-5 py-2.5 rounded-full bg-brand500 shadow-sm`}
activeOpacity={0.85}
onPress={() => onToggleExpand(tagId)}
@ -326,7 +328,7 @@ const ExploreProductItem = memo(({ item, onPress }: ProductItemProps) => {
const handlePress = useCallback(() => onPress(item.id), [item.id, onPress]);
return (
<View style={tw`mb-3`}>
<View style={tw`mb-3`} testID={`tag-product-${item.id}`}>
<ProductCard
item={item}
itemWidth={heroItemWidth}
@ -696,6 +698,7 @@ export default function Dashboard() {
style={searchBarContainerStyle}
>
<SearchBar
testID="home-search"
value={""}
onChangeText={() => { }}
onPress={handleSearchPress}

View file

@ -128,6 +128,7 @@ export default function SearchResults() {
<View style={tw`pt-4 pb-2 px-4`}>
<SearchBar
ref={searchInputRef}
testID="search-input"
value={inputQuery}
onChangeText={setInputQuery}
onSubmitEditing={handleSearch}

View file

@ -403,6 +403,7 @@ export default function OrderDetails() {
<MyText style={tw`text-gray-700 font-medium mb-2`}>Reason for cancellation</MyText>
<TextInput
testID="cancel-reason"
style={tw`bg-gray-50 border border-gray-200 rounded-xl p-4 min-h-24 text-base text-gray-800 mb-6`}
value={cancelReason}
onChangeText={setCancelReason}

View file

@ -8,7 +8,7 @@ import { Stack } from "expo-router";
import "react-native-reanimated";
import { useColorScheme } from "@/hooks/useColorScheme";
import { Appearance, StatusBar, View } from "react-native";
import { Appearance, StatusBar, View, LogBox } from "react-native";
import { QueryClientProvider } from "@tanstack/react-query";
import { theme , MyStatusBar } from "common-ui";
import queryClient from "@/utils/queryClient";
@ -31,6 +31,13 @@ import React from "react";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import NotifChecker from "@/services/notif-service/notif-checker";
// Silence dev warnings/errors in LogBox. The "Open debugger to view warnings"
// toast overlays the bottom of the UI and intercepts taps (e.g. tab bar /
// bottom-sheet buttons), which breaks E2E flows.
if (__DEV__) {
LogBox.ignoreAllLogs(true);
}
export default function RootLayout() {
const colorScheme = useColorScheme();
const [loaded] = useFonts({

View file

@ -160,9 +160,10 @@ const CheckoutAddressSelector: React.FC<AddressSelectorProps> = ({
decelerationRate="fast"
snapToInterval={CARD_WIDTH}
>
{sortedAddresses.map((address) => (
{sortedAddresses.map((address, index) => (
<TouchableOpacity
key={address.id}
testID={`address-option-${index}`}
onPress={() => {
onAddressSelect(address.id);
resetScrollToLeft();

View file

@ -329,7 +329,7 @@ const PaymentAndOrderComponent: React.FC<PaymentAndOrderProps> = ({
{/* Bottom Action Bar */}
<View style={tw`bg-white p-5 rounded-2xl shadow-sm mb-4 border border-gray-100`}>
{!selectedAddress && (
<View style={tw`bg-red-50 p-3 rounded-lg mb-3 flex-row items-center`}>
<View testID="select-address-error" style={tw`bg-red-50 p-3 rounded-lg mb-3 flex-row items-center`}>
<MaterialIcons name="error-outline" size={16} color="#EF4444" style={tw`mr-2`} />
<MyText style={tw`text-red-600 text-sm font-medium`}>
Please select a delivery address to place order
@ -337,6 +337,7 @@ const PaymentAndOrderComponent: React.FC<PaymentAndOrderProps> = ({
</View>
)}
<MyTouchableOpacity
testID="place-order-button"
style={tw`bg-brand500 py-4 rounded-xl items-center shadow-md ${!selectedAddress ? 'opacity-50' : ''}`}
disabled={!selectedAddress}
onPress={handlePlaceOrder}

View file

@ -156,6 +156,7 @@ const ProductCard: React.FC<ProductCardProps> = ({
return (
<ContainerComp>
<MyTouchableOpacity
testID="product-card"
style={[
tw`bg-white overflow-hidden border border-gray-200`,
isHero ? tw`rounded-xl pb-1.5` : tw`rounded-2xl pb-2`,
@ -205,6 +206,7 @@ const ProductCard: React.FC<ProductCardProps> = ({
<MiniQuantifier value={quantity} onChange={handleQuantityChange} step={item.incrementStep} />
) : (
<MyTouchableOpacity
testID="add-to-cart-icon"
style={isHero ? tw`w-7 h-7 rounded-full bg-white items-center justify-center shadow-md` : tw`w-8 h-8 rounded-full bg-white items-center justify-center shadow-md`}
onPress={() => handleQuantityChange(1)}
activeOpacity={0.8}

View file

@ -958,6 +958,7 @@ export default function CartPage({ isFlashDelivery = false }: FlashDeliveryProps
<MyText style={tw`text-gray-700 font-bold`}>Shop More</MyText>
</MyTouchableOpacity>
<MyTouchableOpacity
testID="checkout-button"
style={tw`flex-1 bg-brand500 py-3.5 rounded-xl items-center shadow-md flex-row justify-center`}
onPress={() => {
const availableItems = cartItems

View file

@ -211,6 +211,7 @@ const FloatingCartBar: React.FC<FloatingCartBarProps> = ({
</View>
<MyTouchableOpacity
testID="go-to-cart"
style={tw`bg-white px-3 py-2 rounded-2xl shadow-lg shadow-black/20`}
onPress={() => router.push(
isFlashDelivery

View file

@ -1,6 +1,8 @@
import { useCentralProductStore } from '@/src/store/centralProductStore';
import { useCentralSlotStore } from '@/src/store/centralSlotStore';
import { Alert } from 'react-native';
import { useEffect, useRef } from 'react';
import dayjs from 'dayjs';
import { useQuery, useMutation, useQueryClient, UseQueryResult, UseMutationResult } from '@tanstack/react-query';
import type { CartData as SharedCartData, ProductSummaryCore } from '@packages/shared';
import { StorageServiceCasual } from 'common-ui/src/services/StorageServiceCasual';
@ -244,6 +246,49 @@ export function useGetCart(options: UseGetCartOptions = {}, cartType: CartType =
};
}
export function useReconcileCartSlots() {
const productSlotsMap = useCentralSlotStore((state) => state.productSlotsMap);
const isSlotsLoaded = useCentralSlotStore((state) => state.isSlotsLoaded);
const queryClient = useQueryClient();
const hasRun = useRef(false);
useEffect(() => {
if (!isSlotsLoaded || hasRun.current) return;
hasRun.current = true;
(async () => {
const items = await getLocalCart("regular");
if (items.length === 0) return;
let changed = false;
const nextItems: LocalCartItem[] = [];
for (const item of items) {
const validSlots = productSlotsMap[item.skuId]?.slots ?? [];
if (validSlots.some((slot) => slot.id === item.slotId)) {
nextItems.push(item);
continue;
}
const nearestSlot = [...validSlots].sort(
(a, b) => dayjs(a.deliveryTime).valueOf() - dayjs(b.deliveryTime).valueOf()
)[0];
if (nearestSlot) {
nextItems.push({ ...item, slotId: nearestSlot.id });
}
changed = true;
}
if (changed) {
await saveLocalCart(nextItems, "regular");
queryClient.invalidateQueries({ queryKey: ["local-cart-regular"] });
}
})();
}, [isSlotsLoaded, productSlotsMap, queryClient]);
}
export function useAddToCart(options: MutationOptions<LocalCartItem[], AddToCartVariables> = {}, cartType: CartType = "regular"): UseAddToCartReturn {
const queryClient = useQueryClient();

View file

@ -17,4 +17,17 @@ config.resolver.extraNodeModules = {
'global-shared': path.resolve(__dirname, '../../packages/shared'),
};
// Never bundle the test folder. It is tooled by Jest (see tests/jest.config.js)
// and excluded from EAS uploads (.easignore); blocking it here makes an
// accidental production import fail loudly instead of silently shipping.
const escapeForRegExp = (value) =>
value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
config.resolver.blockList = [
...(config.resolver.blockList
? [].concat(config.resolver.blockList)
: []),
new RegExp(`^${escapeForRegExp(path.resolve(__dirname, 'tests'))}[/\\\\].*$`),
];
module.exports = config;

View file

@ -8,7 +8,10 @@
"android": "expo run:android",
"ios": "expo run:ios",
"web": "expo start --web",
"lint": "expo lint"
"lint": "expo lint",
"test": "jest --config tests/jest.config.js",
"test:watch": "jest --config tests/jest.config.js --watch",
"test:typecheck": "tsc --noEmit --project tsconfig.json"
},
"dependencies": {
"@expo/vector-icons": "^14.1.0",
@ -71,10 +74,16 @@
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@testing-library/react-native": "^13",
"@types/jest": "^29",
"@types/react": "~19.0.10",
"@types/react-native-razorpay": "^2.2.6",
"babel-preset-expo": "~13.0.0",
"eslint": "^9.25.0",
"eslint-config-expo": "~9.2.0",
"jest": "~29.7.0",
"jest-expo": "~53.0.0",
"react-test-renderer": "19.0.0",
"typescript": "~5.8.3"
},
"private": true

View file

@ -162,6 +162,7 @@ export default function AddToCartDialog() {
{availableSlots.map((slot: any) => (
<MyTouchableOpacity
key={slot.id}
testID={`slot-${dayjs(slot.deliveryTime).format('YYYY-MM-DD-HH-mm')}`}
style={tw`flex-row items-start mb-4 bg-gray-50 p-4 rounded-xl border border-gray-100 ${selectedSlotId === slot.id ? 'border-brand500' : 'border-gray-100'
}`}
onPress={() => {
@ -241,6 +242,7 @@ export default function AddToCartDialog() {
<View style={tw`flex-row gap-3 mt-4 pb-12`}>
<MyTouchableOpacity
testID="add-to-cart-confirm"
style={tw`flex-1 bg-brand500 py-3.5 rounded-xl items-center ${(!selectedSlotId && !selectedFlashDelivery) ? 'opacity-50' : ''}`}
onPress={handleAddToCart}
disabled={(addToCart.isLoading || updateCartItem.isLoading) || (!selectedSlotId && !selectedFlashDelivery)}

View file

@ -113,6 +113,7 @@ const AddressForm: React.FC<AddressFormProps> = ({ onSuccess, initialValues, isE
{({ handleChange, handleBlur, handleSubmit, values, errors, touched, setFieldValue }) => (
<View style={tw`flex-col gap-2`}>
<MyTextInput
testID="address-name"
placeholder="Name"
shrunkPadding={true}
onChangeText={handleChange('name')}
@ -122,6 +123,7 @@ const AddressForm: React.FC<AddressFormProps> = ({ onSuccess, initialValues, isE
{touched.name && errors.name && <MyText style={tw`text-red-500 mb-2`}>{errors.name}</MyText>}
<MyTextInput
testID="address-phone"
placeholder="Phone"
shrunkPadding={true}
keyboardType="phone-pad"
@ -132,6 +134,7 @@ const AddressForm: React.FC<AddressFormProps> = ({ onSuccess, initialValues, isE
{touched.phone && errors.phone && <MyText style={tw`text-red-500 mb-2`}>{errors.phone}</MyText>}
<MyTextInput
testID="address-line1"
placeholder="Address Line 1"
shrunkPadding={true}
onChangeText={handleChange('addressLine1')}
@ -227,6 +230,7 @@ const AddressForm: React.FC<AddressFormProps> = ({ onSuccess, initialValues, isE
</View>
<MyTouchableOpacity
testID="address-submit"
style={tw`bg-indigo-600 p-3 rounded ${isSubmitting ? 'opacity-50' : ''}`}
onPress={() => handleSubmit()}
disabled={isSubmitting}

View file

@ -1,11 +1,13 @@
import React from 'react';
import { useInitializeCentralSlotStore } from '@/src/store/centralSlotStore';
import { useInitializeCentralProductStore } from '@/src/store/centralProductStore';
import { useReconcileCartSlots } from '@/hooks/cart-query-hooks';
import type { ReactParentComponent } from '@packages/shared';
export default function CentralStoreInitializer({ children }: ReactParentComponent) {
useInitializeCentralSlotStore();
useInitializeCentralProductStore();
useReconcileCartSlots();
return <>{children}</>;
}

View file

@ -0,0 +1,27 @@
[
{
"result": {
"data": {
"freeDeliveryThreshold": 249,
"deliveryCharge": 15,
"flashFreeDeliveryThreshold": 399,
"flashDeliveryCharge": 39,
"popularItems": "40,109,9,75,98,6,24,59",
"versionNum": "1.2.0",
"playStoreUrl": "https://info.freshyo.in/qr-based-download",
"appStoreUrl": "https://info.freshyo.in/qr-based-download",
"webViewHtml": null,
"webviewHtml": null,
"isWebviewClosable": true,
"isFlashDeliveryEnabled": false,
"supportMobile": "9000885620",
"supportEmail": "qushammohd@gmail.com",
"assetsDomain": "https://assets.freshyo.in/",
"apiCacheKey": "api-cache",
"cacheVersion": 755,
"availabilityVersionNum": 72,
"slotsVersionNum": 401
}
}
}
]

View file

@ -0,0 +1,125 @@
/**
* Integration tests for app/(drawer)/(tabs)/home/index.tsx
*
* API responses are mocked at the network layer:
* - common.essentialConsts -> tests/__fixtures__/essentialConsts.json
* - assets CDN -> tests/mocks/mock-*-response.json
*
* Runner: `bun run test` (or `bun run test:watch`) from apps/user-ui.
*/
import React from 'react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import {
fireEvent,
render,
screen,
waitFor,
within,
} from '@testing-library/react-native'
import { RefreshProvider } from 'common-ui'
import Dashboard from '@/app/(drawer)/(tabs)/home/index'
import { trpc, trpcClient } from '@/src/trpc-client'
import productsResponse from './mocks/mock-products-response.json'
import { PRODUCTS_URL, setupApiMocks } from './mocks/api'
// The tag grid renders at most this many items before its "Show More" button
// expands it (see the products.slice(0, 6) in TagScene).
const TAG_GRID_PREVIEW = 6
const knownProductIds = new Set(productsResponse.products.map((p) => p.id))
const tags = productsResponse.tags
/** Products a tag points at that actually exist in the products cache. */
const productIdsForTag = (tag: (typeof tags)[number]) =>
tag.productIds.filter((id) => knownProductIds.has(id))
function renderHome() {
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false, gcTime: 0 } },
})
return render(
<trpc.Provider client={trpcClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>
<RefreshProvider queryClient={queryClient}>
<Dashboard />
</RefreshProvider>
</QueryClientProvider>
</trpc.Provider>
)
}
describe('Home page', () => {
let api: ReturnType<typeof setupApiMocks>
beforeEach(() => {
api = setupApiMocks()
})
it('requests the products cache url derived from common.essentialConsts', async () => {
renderHome()
await waitFor(() => {
expect(
api.fetchMock.mock.calls.some(([url]) =>
String(url).includes('common.essentialConsts')
)
).toBe(true)
})
await waitFor(() => {
expect(api.axiosGet).toHaveBeenCalledWith(PRODUCTS_URL)
})
})
it('shows a tab for every tag', async () => {
renderHome()
await waitFor(() => {
expect(screen.getByTestId(`tag-tab-${tags[0].id}`)).toBeTruthy()
})
for (const tag of tags) {
const tab = screen.getByTestId(`tag-tab-${tag.id}`)
expect(within(tab).getByText(tag.tagName)).toBeVisible()
}
})
it('shows every product of every tag, expanding each grid once', async () => {
renderHome()
await waitFor(() => {
expect(screen.getByTestId(`tag-grid-${tags[0].id}`)).toBeTruthy()
})
for (const tag of tags) {
fireEvent.press(screen.getByTestId(`tag-tab-${tag.id}`))
const expected = productIdsForTag(tag)
expect(expected.length).toBeGreaterThan(0)
for (const id of expected.slice(0, TAG_GRID_PREVIEW)) {
expect(screen.getByTestId(`tag-product-${id}`)).toBeVisible()
}
if (expected.length <= TAG_GRID_PREVIEW) continue
// Items past the preview are withheld until the grid is expanded.
expect(
screen.queryByTestId(`tag-product-${expected[TAG_GRID_PREVIEW]}`)
).toBeNull()
fireEvent.press(screen.getByTestId(`tag-show-more-${tag.id}`))
for (const id of expected) {
expect(screen.getByTestId(`tag-product-${id}`)).toBeVisible()
}
}
})
it.todo('shows every product returned by the products cache')
it.todo('shows the store list')
it.todo('shows the upcoming delivery slots')
})

7
apps/user-ui/tests/jest-env.d.ts vendored Normal file
View file

@ -0,0 +1,7 @@
/// <reference types="jest" />
// Declares the Jest globals (describe, it, expect, jest, ...) for the tests in
// this folder. @types/jest is a devDependency, but making the reference explicit
// means the globals are declared by a file inside the project rather than
// relying on ambient @types auto-inclusion, so editors resolve them even when a
// file falls back to an inferred project.

View file

@ -0,0 +1,56 @@
const path = require('path')
const appRoot = path.resolve(__dirname, '..')
/**
* Jest project for apps/user-ui integration tests.
*
* Kept out of the app build three ways:
* 1. .easignore (tests/ never uploaded to EAS)
* 2. metro.config.js resolver.blockList (never bundled)
* 3. tsconfig.json exclude + tests/tsconfig.json (separate typecheck scope)
*/
module.exports = {
preset: 'jest-expo',
rootDir: appRoot,
roots: ['<rootDir>/tests'],
testMatch: ['<rootDir>/tests/**/*.test.ts', '<rootDir>/tests/**/*.test.tsx'],
setupFiles: ['<rootDir>/tests/setup.ts'],
clearMocks: true,
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/$1',
'^@backend/(.*)$': '<rootDir>/../backend/src/$1',
'^shared-types$': '<rootDir>/../shared-types',
'^@packages/shared$': '<rootDir>/../../packages/shared',
'^@packages/shared/(.*)$': '<rootDir>/../../packages/shared/$1',
'^global-shared$': '<rootDir>/../../packages/shared',
'^global-shared/(.*)$': '<rootDir>/../../packages/shared/$1',
'^common-ui$': '<rootDir>/../../packages/ui',
'^common-ui/(.*)$': '<rootDir>/../../packages/ui/$1',
},
transformIgnorePatterns: [
'node_modules/(?!(?:.pnpm/)?(' +
[
'(jest-)?react-native',
'@react-native(-community)?',
'expo(nent)?',
'@expo(nent)?/.*',
'@expo-google-fonts/.*',
'react-navigation',
'@react-navigation/.*',
'@sentry/react-native',
'native-base',
'react-native-svg',
'react-native-gesture-handler',
'react-native-reanimated',
'react-native-safe-area-context',
'react-native-screens',
'@trpc/.*',
'@tanstack/.*',
'twrnc',
'zod',
'@testing-library/react-native',
].join('|') +
'))',
],
}

View file

@ -0,0 +1,90 @@
/**
* Network-layer mocks for the user-ui tests.
*
* Everything the home page fetches goes through one of two doors:
* - tRPC -> global.fetch (common.essentialConsts, ...)
* - axios -> the assets CDN (products.json, stores.json, slots.json, availability.json)
*
* Both are stubbed here and both fail loudly on an unmocked URL, so a new
* dependency introduced by app code surfaces as a test failure instead of a
* silent empty render.
*
* The tRPC URL is matched on the procedure path rather than the host, because
* BASE_API_URL (packages/ui/index.ts) is a local address during development.
* `jest.mock('axios')` lives in tests/setup.ts so it is registered before any
* app module imports axios.
*
* Fixtures are imported as JSON rather than read with `fs`: importing a Node
* builtin pulls @types/node into the typecheck program, which flips
* `setTimeout` from `number` to `NodeJS.Timeout` and breaks unrelated app types.
*
* NOTE: mock-slots-response.json is a snapshot of a real day (see the
* deliveryTime values). The home page drops past slots, so those fixtures need
* refreshing once their dates fall behind.
*/
import essentialConsts from '../__fixtures__/essentialConsts.json'
import availability from './mock-availability-response.json'
import products from './mock-products-response.json'
import slots from './mock-slots-response.json'
import stores from './mock-stores-response.json'
export const ESSENTIAL_CONSTS_URL =
'https://worker.freshyo.in/api/trpc/common.essentialConsts?batch=1&input=%7B%220%22%3A%7B%22json%22%3Anull%2C%22meta%22%3A%7B%22values%22%3A%5B%22undefined%22%5D%7D%7D%7D'
// Derived by useCacheUrl / useAvailabilityCacheUrl / useSlotsCacheUrl from the
// essentialConsts payload above (assetsDomain, apiCacheKey, cacheVersion,
// availabilityVersionNum, slotsVersionNum).
export const PRODUCTS_URL =
'https://assets.freshyo.in/api-cache/v-755/products.json'
export const AVAILABILITY_URL =
'https://assets.freshyo.in/av-72/availability.json'
export const STORES_URL = 'https://assets.freshyo.in/api-cache/v-755/stores.json'
export const SLOTS_URL = 'https://assets.freshyo.in/slots/v-401/slots.json?v=123'
/** Route a CDN file name to its mock response. */
const mockForCacheFile = (url: string): unknown => {
if (url.includes('products.json')) return products
if (url.includes('stores.json')) return stores
if (url.includes('slots.json')) return slots
if (url.includes('availability.json')) return availability
throw new Error(`[tests] unmocked assets request: ${url}`)
}
const jsonResponse = (body: unknown) => ({
ok: true,
status: 200,
statusText: 'OK',
headers: new Map(),
url: '',
json: async () => body,
text: async () => JSON.stringify(body),
})
/**
* Install the fetch + axios stubs and return the handles for assertions.
* Call from beforeEach so each test starts from a clean call history.
*/
export function setupApiMocks() {
const axios = require('axios')
axios.get.mockImplementation(async (url: string) => ({
data: mockForCacheFile(url),
status: 200,
statusText: 'OK',
headers: {},
config: {},
}))
const fetchMock = jest.fn(async (url: string) => {
if (url.includes('common.essentialConsts')) {
return jsonResponse(essentialConsts)
}
throw new Error(`[tests] unmocked fetch: ${url}`)
})
;(global as unknown as { fetch: unknown }).fetch = fetchMock
return { fetchMock, axiosGet: axios.get }
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,124 @@
{
"stores": [
{
"id": 1,
"name": "Meat Store",
"description": "\"Fresh, high-quality meat sourced from trusted suppliers and hygienically processed. Cut fresh, packed safely, and delivered to your doorstep.\"",
"signedImageUrl": "https://assets.freshyo.in/store-images/1766052073748.png",
"productCount": 39,
"sampleProducts": [
{
"id": 1,
"name": "Chicken Liver",
"signedImageUrl": "https://assets.freshyo.in/product-images/1768478127128-0"
},
{
"id": 2,
"name": "Chicken Breast 500 g",
"signedImageUrl": "https://assets.freshyo.in/product-images/1768397780093-0"
},
{
"id": 3,
"name": "Chicken Lollipops",
"signedImageUrl": "https://assets.freshyo.in/product-images/1768475599800-0"
}
]
},
{
"id": 2,
"name": "The Fruit Store",
"description": "\"Fresh, juicy fruits handpicked for quality and taste. Naturally ripened, carefully packed, and delivered fresh to your home.\"",
"signedImageUrl": "https://assets.freshyo.in/store-images/1766053828604.png",
"productCount": 35,
"sampleProducts": [
{
"id": 6,
"name": "Apple ( pack of 3) ",
"signedImageUrl": "https://assets.freshyo.in/product-images/1768640936747-0"
},
{
"id": 7,
"name": "Banana",
"signedImageUrl": "https://assets.freshyo.in/product-images/1768640009078-0"
},
{
"id": 8,
"name": "Kiwi",
"signedImageUrl": "https://assets.freshyo.in/product-images/1768647119198-0"
}
]
},
{
"id": 4,
"name": "Vegetables",
"description": "Fresh, handpicked vegetables delivered straight from trusted farms to your home. Enjoy clean, high-quality sabzi every day-healthy, natural, and full of freshness.",
"signedImageUrl": "https://assets.freshyo.in/store-images/1766051618139.png",
"productCount": 46,
"sampleProducts": [
{
"id": 5,
"name": "Cucumber(Kheera)",
"signedImageUrl": "https://assets.freshyo.in/product-images/1768710099841-0"
},
{
"id": 16,
"name": "Arvi (Arbi) Colocasia Root",
"signedImageUrl": "https://assets.freshyo.in/product-images/1769397372526-0"
},
{
"id": 17,
"name": "Tomato ",
"signedImageUrl": "https://assets.freshyo.in/product-images/1768640233068-0"
}
]
},
{
"id": 8,
"name": "Fruits Juices ",
"description": "",
"signedImageUrl": "https://assets.freshyo.in/store-images/1773121148038.png",
"productCount": 22,
"sampleProducts": [
{
"id": 104,
"name": "Green grape fresh juice",
"signedImageUrl": "https://assets.freshyo.in/product-images/1773126251435-0"
},
{
"id": 105,
"name": "Fresh fig juice (anjeer) ",
"signedImageUrl": "https://assets.freshyo.in/product-images/1773126524403-0"
},
{
"id": 106,
"name": "Chikoo (sapota)fresh juice",
"signedImageUrl": "https://assets.freshyo.in/product-images/1773127805418-0"
}
]
},
{
"id": 9,
"name": "Dry Fruits store ",
"description": "",
"signedImageUrl": "https://assets.freshyo.in/store-images/1784697413573.jpg",
"productCount": 7,
"sampleProducts": [
{
"id": 129,
"name": "Cashew (kaju) ",
"signedImageUrl": "https://assets.freshyo.in/product-images/1784220823113.jpg"
},
{
"id": 130,
"name": "Badam",
"signedImageUrl": "https://assets.freshyo.in/product-images/1784698463282.jpg"
},
{
"id": 131,
"name": "Raisins (kishmish) ",
"signedImageUrl": "https://assets.freshyo.in/product-images/1784221386651.jpg"
}
]
}
]
}

111
apps/user-ui/tests/setup.ts Normal file
View file

@ -0,0 +1,111 @@
/**
* Shared Jest setup for apps/user-ui.
*
* Only the native/context dependencies that cannot run under a plain Jest
* environment are stubbed here. Everything else (components, stores, hooks)
* stays real so tests exercise the actual wiring.
*
* Note: no `typeof import('react-native')` type expressions below. Loading the
* RN ambient globals from a type position here reverses their precedence with
* @types/node's, which flips `setTimeout` from `number` to `NodeJS.Timeout` and
* fails typecheck in unrelated app files (packages/ui/src/components/
* bottom-dropdown.tsx).
*/
import type { ReactNode } from 'react'
jest.mock('expo-router', () => ({
useRouter: () => ({
push: jest.fn(),
replace: jest.fn(),
back: jest.fn(),
navigate: jest.fn(),
}),
router: {
push: jest.fn(),
replace: jest.fn(),
back: jest.fn(),
},
useLocalSearchParams: () => ({}),
useGlobalSearchParams: () => ({}),
useFocusEffect: () => {},
Link: ({ children }: { children?: ReactNode }) => children ?? null,
}))
/**
* Refresh plumbing, irrelevant to what these tests assert.
* `useMarkDataFetchers` runs `useFocusEffect`, which throws without a
* NavigationContainer. These are mocked by their own module paths rather than
* partially mocking the `common-ui` index: several index components import
* back from `common-ui` (button.tsx -> common-ui), so spreading
* `jest.requireActual('common-ui')` walks a half-initialised module and throws.
*/
jest.mock('common-ui/hooks/useMarkDataFetchers', () => ({
__esModule: true,
useMarkDataFetchers: () => {},
}))
jest.mock('common-ui/hooks/useManualRefresh', () => ({
__esModule: true,
default: () => {},
}))
// Registered here (not in the test files) so it is in place before any app
// module imports axios. Implementations are set in tests/mocks/api.ts.
jest.mock('axios')
/**
* Auth is out of scope for these tests. `useUserDetails` throws without an
* AuthProvider (which would drag in secure-store, tRPC login and redirect
* handling), so the context is stubbed with the signed-out shape.
*/
jest.mock('@/src/contexts/AuthContext', () => ({
AuthProvider: ({ children }: { children?: ReactNode }) => children ?? null,
useAuth: () => ({
user: null,
userDetails: null,
isAuthenticated: false,
isLoading: false,
token: null,
}),
useUserDetails: () => null,
}))
/**
* Cart hooks import this deep path directly (not via the common-ui index), and
* the real service talks to expo-secure-store. In-memory map instead; the cart
* starts empty, which is the cold-start path these tests exercise.
*/
jest.mock('common-ui/src/services/StorageServiceCasual', () => {
const store = new Map<string, string>()
return {
StorageServiceCasual: {
getItem: jest.fn(async (key: string) => store.get(key) ?? null),
setItem: jest.fn(async (key: string, value: string) => {
store.set(key, value)
return true
}),
removeItem: jest.fn(async (key: string) => {
store.delete(key)
return true
}),
clearAll: jest.fn(async (keys: string[]) => {
keys.forEach((key) => store.delete(key))
return true
}),
},
}
})
/**
* Freeze the clock for every test. The home page is time-sensitive it drops
* slots whose deliveryTime is not after `dayjs()` and flags slots "closing
* soon" within 4 hours while the slot mocks carry fixed deliveryTime values.
* A drifting wall clock therefore makes those assertions flaky.
*
* Frozen at suite start rather than a hard-coded instant, so the fixed slot
* dates in the mocks stay in the future. Use `jest.advanceTimersByTime()` /
* `jest.setSystemTime()` in a test that needs to move through time.
*/
jest.useFakeTimers({ now: Date.now() })

View file

@ -47,5 +47,8 @@
".expo/types/**/*.ts",
"expo-env.d.ts",
"../../packages/shared"
],
"exclude": [
"node_modules"
]
}

406
bun.lock
View file

@ -305,10 +305,16 @@
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@testing-library/react-native": "^13",
"@types/jest": "^29",
"@types/react": "~19.0.10",
"@types/react-native-razorpay": "^2.2.6",
"babel-preset-expo": "~13.0.0",
"eslint": "^9.25.0",
"eslint-config-expo": "~9.2.0",
"jest": "~29.7.0",
"jest-expo": "~53.0.0",
"react-test-renderer": "19.0.0",
"typescript": "~5.8.3",
},
},
@ -696,6 +702,8 @@
"@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
"@bcoe/v8-coverage": ["@bcoe/v8-coverage@0.2.3", "", {}, "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw=="],
"@bramus/specificity": ["@bramus/specificity@2.4.2", "", { "dependencies": { "css-tree": "^3.0.0" }, "bin": { "specificity": "bin/cli.js" } }, "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw=="],
"@callstack/react-theme-provider": ["@callstack/react-theme-provider@3.0.9", "", { "dependencies": { "deepmerge": "^3.2.0", "hoist-non-react-statics": "^3.3.0" }, "peerDependencies": { "react": ">=16.3.0" } }, "sha512-tTQ0uDSCL0ypeMa8T/E9wAZRGKWj8kXP7+6RYgPTfOPs9N07C9xM8P02GJ3feETap4Ux5S69D9nteq9mEj86NA=="],
@ -956,14 +964,40 @@
"@istanbuljs/schema": ["@istanbuljs/schema@0.1.3", "", {}, "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA=="],
"@jest/console": ["@jest/console@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0", "slash": "^3.0.0" } }, "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg=="],
"@jest/core": ["@jest/core@29.7.0", "", { "dependencies": { "@jest/console": "^29.7.0", "@jest/reporters": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "ci-info": "^3.2.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", "jest-changed-files": "^29.7.0", "jest-config": "^29.7.0", "jest-haste-map": "^29.7.0", "jest-message-util": "^29.7.0", "jest-regex-util": "^29.6.3", "jest-resolve": "^29.7.0", "jest-resolve-dependencies": "^29.7.0", "jest-runner": "^29.7.0", "jest-runtime": "^29.7.0", "jest-snapshot": "^29.7.0", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "jest-watcher": "^29.7.0", "micromatch": "^4.0.4", "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-ansi": "^6.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"] }, "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg=="],
"@jest/create-cache-key-function": ["@jest/create-cache-key-function@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3" } }, "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA=="],
"@jest/diff-sequences": ["@jest/diff-sequences@30.5.0", "", {}, "sha512-OsqBjHXCn8cadasoAZBP6nWYvMsRhpMzGXTpxJ5aO04NlbdhIz+FVe3q49l0AwVhsz/cEmIpBes6gAFl1/dWQg=="],
"@jest/environment": ["@jest/environment@29.7.0", "", { "dependencies": { "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "jest-mock": "^29.7.0" } }, "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw=="],
"@jest/expect": ["@jest/expect@29.7.0", "", { "dependencies": { "expect": "^29.7.0", "jest-snapshot": "^29.7.0" } }, "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ=="],
"@jest/expect-utils": ["@jest/expect-utils@29.7.0", "", { "dependencies": { "jest-get-type": "^29.6.3" } }, "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA=="],
"@jest/fake-timers": ["@jest/fake-timers@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", "jest-message-util": "^29.7.0", "jest-mock": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ=="],
"@jest/get-type": ["@jest/get-type@30.5.0", "", {}, "sha512-9/2VUPitAjmBzbvDvqrxmvB7BzWsBW0WmkkojX1ODuxX1NLGxx9gfaZpHB0z8DtJ9uhGNmZG/VXBhf8uO0OV8Q=="],
"@jest/globals": ["@jest/globals@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", "@jest/types": "^29.6.3", "jest-mock": "^29.7.0" } }, "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ=="],
"@jest/react-is-18": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
"@jest/react-is-19": ["react-is@19.3.0", "", {}, "sha512-UpMYezM4v5/18F28aC66AEsjXIgE02kyEMH6yLdgLXu/UTfa1Ntwck/nNLrbqJsEXW7gPb0coNO9FQse9WTovA=="],
"@jest/reporters": ["@jest/reporters@29.7.0", "", { "dependencies": { "@bcoe/v8-coverage": "^0.2.3", "@jest/console": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@jridgewell/trace-mapping": "^0.3.18", "@types/node": "*", "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", "exit": "^0.1.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.1.3", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0", "jest-worker": "^29.7.0", "slash": "^3.0.0", "string-length": "^4.0.1", "strip-ansi": "^6.0.0", "v8-to-istanbul": "^9.0.1" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"] }, "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg=="],
"@jest/schemas": ["@jest/schemas@29.6.3", "", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="],
"@jest/source-map": ["@jest/source-map@29.6.3", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.18", "callsites": "^3.0.0", "graceful-fs": "^4.2.9" } }, "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw=="],
"@jest/test-result": ["@jest/test-result@29.7.0", "", { "dependencies": { "@jest/console": "^29.7.0", "@jest/types": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" } }, "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA=="],
"@jest/test-sequencer": ["@jest/test-sequencer@29.7.0", "", { "dependencies": { "@jest/test-result": "^29.7.0", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "slash": "^3.0.0" } }, "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw=="],
"@jest/transform": ["@jest/transform@29.7.0", "", { "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", "@jridgewell/trace-mapping": "^0.3.18", "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "jest-regex-util": "^29.6.3", "jest-util": "^29.7.0", "micromatch": "^4.0.4", "pirates": "^4.0.4", "slash": "^3.0.0", "write-file-atomic": "^4.0.2" } }, "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw=="],
"@jest/types": ["@jest/types@29.6.3", "", { "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", "@types/yargs": "^17.0.8", "chalk": "^4.0.0" } }, "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw=="],
@ -1050,9 +1084,9 @@
"@react-native/assets-registry": ["@react-native/assets-registry@0.79.6", "", {}, "sha512-UVSP1224PWg0X+mRlZNftV5xQwZGfawhivuW8fGgxNK9MS/U84xZ+16lkqcPh1ank6MOt239lIWHQ1S33CHgqA=="],
"@react-native/babel-plugin-codegen": ["@react-native/babel-plugin-codegen@0.79.6", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@react-native/codegen": "0.79.6" } }, "sha512-CS5OrgcMPixOyUJ/Sk/HSsKsKgyKT5P7y3CojimOQzWqRZBmoQfxdST4ugj7n1H+ebM2IKqbgovApFbqXsoX0g=="],
"@react-native/babel-plugin-codegen": ["@react-native/babel-plugin-codegen@0.79.0-rc.4", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@react-native/codegen": "0.79.0-rc.4" } }, "sha512-vhYfoFdzz1D/bDAuGThtNia9bUpjtCo73IWfndNoKj+84SOGTtrKaZ/jOpXs5uMskpe1D4xaHuOHHFbzp/0E8g=="],
"@react-native/babel-preset": ["@react-native/babel-preset@0.79.6", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-transform-arrow-functions": "^7.24.7", "@babel/plugin-transform-async-generator-functions": "^7.25.4", "@babel/plugin-transform-async-to-generator": "^7.24.7", "@babel/plugin-transform-block-scoping": "^7.25.0", "@babel/plugin-transform-class-properties": "^7.25.4", "@babel/plugin-transform-classes": "^7.25.4", "@babel/plugin-transform-computed-properties": "^7.24.7", "@babel/plugin-transform-destructuring": "^7.24.8", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-for-of": "^7.24.7", "@babel/plugin-transform-function-name": "^7.25.1", "@babel/plugin-transform-literals": "^7.25.2", "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", "@babel/plugin-transform-numeric-separator": "^7.24.7", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-optional-catch-binding": "^7.24.7", "@babel/plugin-transform-optional-chaining": "^7.24.8", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-react-display-name": "^7.24.7", "@babel/plugin-transform-react-jsx": "^7.25.2", "@babel/plugin-transform-react-jsx-self": "^7.24.7", "@babel/plugin-transform-react-jsx-source": "^7.24.7", "@babel/plugin-transform-regenerator": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/plugin-transform-shorthand-properties": "^7.24.7", "@babel/plugin-transform-spread": "^7.24.7", "@babel/plugin-transform-sticky-regex": "^7.24.7", "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/template": "^7.25.0", "@react-native/babel-plugin-codegen": "0.79.6", "babel-plugin-syntax-hermes-parser": "0.25.1", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" } }, "sha512-H+FRO+r2Ql6b5IwfE0E7D52JhkxjeGSBSUpCXAI5zQ60zSBJ54Hwh2bBJOohXWl4J+C7gKYSAd2JHMUETu+c/A=="],
"@react-native/babel-preset": ["@react-native/babel-preset@0.79.0-rc.4", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-transform-arrow-functions": "^7.24.7", "@babel/plugin-transform-async-generator-functions": "^7.25.4", "@babel/plugin-transform-async-to-generator": "^7.24.7", "@babel/plugin-transform-block-scoping": "^7.25.0", "@babel/plugin-transform-class-properties": "^7.25.4", "@babel/plugin-transform-classes": "^7.25.4", "@babel/plugin-transform-computed-properties": "^7.24.7", "@babel/plugin-transform-destructuring": "^7.24.8", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-for-of": "^7.24.7", "@babel/plugin-transform-function-name": "^7.25.1", "@babel/plugin-transform-literals": "^7.25.2", "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", "@babel/plugin-transform-numeric-separator": "^7.24.7", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-optional-catch-binding": "^7.24.7", "@babel/plugin-transform-optional-chaining": "^7.24.8", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-react-display-name": "^7.24.7", "@babel/plugin-transform-react-jsx": "^7.25.2", "@babel/plugin-transform-react-jsx-self": "^7.24.7", "@babel/plugin-transform-react-jsx-source": "^7.24.7", "@babel/plugin-transform-regenerator": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/plugin-transform-shorthand-properties": "^7.24.7", "@babel/plugin-transform-spread": "^7.24.7", "@babel/plugin-transform-sticky-regex": "^7.24.7", "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/template": "^7.25.0", "@react-native/babel-plugin-codegen": "0.79.0-rc.4", "babel-plugin-syntax-hermes-parser": "0.25.1", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" } }, "sha512-0vyO3Ix2N0sI43w0XNJ2zzSWzX7no/JMrXnVzJEm9IhNHsuMmKaXQdRvijDZroepDINIbb6vaIWwzeWkqhiPAA=="],
"@react-native/codegen": ["@react-native/codegen@0.79.6", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/parser": "^7.25.3", "glob": "^7.1.1", "hermes-parser": "0.25.1", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "yargs": "^17.6.2" } }, "sha512-iRBX8Lgbqypwnfba7s6opeUwVyaR23mowh9ILw7EcT2oLz3RqMmjJdrbVpWhGSMGq2qkPfqAH7bhO8C7O+xfjQ=="],
@ -1324,6 +1358,10 @@
"@tanstack/virtual-file-routes": ["@tanstack/virtual-file-routes@1.161.7", "", { "bin": { "intent": "bin/intent.js" } }, "sha512-olW33+Cn+bsCsZKPwEGhlkqS6w3M2slFv11JIobdnCFKMLG97oAI2kWKdx5/zsywTL8flpnoIgaZZPlQTFYhdQ=="],
"@testing-library/react-native": ["@testing-library/react-native@13.3.3", "", { "dependencies": { "jest-matcher-utils": "^30.0.5", "picocolors": "^1.1.1", "pretty-format": "^30.0.5", "redent": "^3.0.0" }, "peerDependencies": { "jest": ">=29.0.0", "react": ">=18.2.0", "react-native": ">=0.71", "react-test-renderer": ">=18.2.0" }, "optionalPeers": ["jest"] }, "sha512-k6Mjsd9dbZgvY4Bl7P1NIpePQNi+dfYtlJ5voi9KQlynxSyQkfOgJmYGCYmw/aSgH/rUcFvG8u5gd4npzgRDyg=="],
"@tootallnate/once": ["@tootallnate/once@2.0.1", "", {}, "sha512-HqmEUIGRJ5fSXchkVgR5F7qn48bDBzv0kWj/Kfu5e6uci4UlEeng4331LnBkWffb++Ei3FOVLxo8JJWMFBDMeQ=="],
"@trpc/client": ["@trpc/client@11.17.0", "", { "peerDependencies": { "@trpc/server": "11.17.0", "typescript": ">=5.7.2" }, "bin": { "intent": "bin/intent.js" } }, "sha512-KpJBFrbKTDeVCFv/3ckL1XBBH5Yssn8hethI/rUy7GIpTj+VzjtPjykDqJpzobuVOz+d26cXCSu1t4I6MYI5Zg=="],
"@trpc/react-query": ["@trpc/react-query@11.17.0", "", { "peerDependencies": { "@tanstack/react-query": "^5.80.3", "@trpc/client": "11.17.0", "@trpc/server": "11.17.0", "react": ">=18.2.0", "typescript": ">=5.7.2" } }, "sha512-AGcl5YAF8NnhBmyJ6PqJqKb1M5VTGSoNRNqJ3orct4o4epdcg0GWhW+qT9q6gPzs/2ImIwYCdfFpgNGdZ9yLHA=="],
@ -1386,6 +1424,10 @@
"@types/istanbul-reports": ["@types/istanbul-reports@3.0.4", "", { "dependencies": { "@types/istanbul-lib-report": "*" } }, "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ=="],
"@types/jest": ["@types/jest@29.5.14", "", { "dependencies": { "expect": "^29.0.0", "pretty-format": "^29.0.0" } }, "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ=="],
"@types/jsdom": ["@types/jsdom@20.0.1", "", { "dependencies": { "@types/node": "*", "@types/tough-cookie": "*", "parse5": "^7.0.0" } }, "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ=="],
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
"@types/json5": ["@types/json5@0.0.29", "", {}, "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="],
@ -1422,6 +1464,8 @@
"@types/three": ["@types/three@0.182.0", "", { "dependencies": { "@dimforge/rapier3d-compat": "~0.12.0", "@tweenjs/tween.js": "~23.1.3", "@types/stats.js": "*", "@types/webxr": ">=0.5.17", "@webgpu/types": "*", "fflate": "~0.8.2", "meshoptimizer": "~0.22.0" } }, "sha512-WByN9V3Sbwbe2OkWuSGyoqQO8Du6yhYaXtXLoA5FkKTUJorZ+yOHBZ35zUUPQXlAKABZmbYp5oAqpA4RBjtJ/Q=="],
"@types/tough-cookie": ["@types/tough-cookie@4.0.5", "", {}, "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA=="],
"@types/webxr": ["@types/webxr@0.5.24", "", {}, "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg=="],
"@types/yargs": ["@types/yargs@17.0.35", "", { "dependencies": { "@types/yargs-parser": "*" } }, "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg=="],
@ -1516,12 +1560,16 @@
"@xmldom/xmldom": ["@xmldom/xmldom@0.8.11", "", {}, "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw=="],
"abab": ["abab@2.0.6", "", {}, "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA=="],
"abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
"accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="],
"acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
"acorn-globals": ["acorn-globals@7.0.1", "", { "dependencies": { "acorn": "^8.1.0", "acorn-walk": "^8.0.2" } }, "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q=="],
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
"acorn-walk": ["acorn-walk@8.3.5", "", { "dependencies": { "acorn": "^8.11.0" } }, "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw=="],
@ -1618,7 +1666,7 @@
"babel-preset-current-node-syntax": ["babel-preset-current-node-syntax@1.2.0", "", { "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-import-attributes": "^7.24.7", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-numeric-separator": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0 || ^8.0.0-0" } }, "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg=="],
"babel-preset-expo": ["babel-preset-expo@13.2.5", "", { "dependencies": { "@babel/helper-module-imports": "^7.25.9", "@babel/plugin-proposal-decorators": "^7.12.9", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-transform-export-namespace-from": "^7.25.9", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/preset-react": "^7.22.15", "@babel/preset-typescript": "^7.23.0", "@react-native/babel-preset": "0.79.6", "babel-plugin-react-native-web": "~0.19.13", "babel-plugin-syntax-hermes-parser": "^0.25.1", "babel-plugin-transform-flow-enums": "^0.0.2", "debug": "^4.3.4", "react-refresh": "^0.14.2", "resolve-from": "^5.0.0" }, "peerDependencies": { "babel-plugin-react-compiler": "^19.0.0-beta-e993439-20250405" }, "optionalPeers": ["babel-plugin-react-compiler"] }, "sha512-YjVkP1bOLO2OgR2fyCedruYMPR7GFbAtCvvWITBW1UAp6e3ACYZtN6uoqkXgXP6PHQkb6M7qf2vZreBPEZK38A=="],
"babel-preset-expo": ["babel-preset-expo@13.0.0", "", { "dependencies": { "@babel/helper-module-imports": "^7.25.9", "@babel/plugin-proposal-decorators": "^7.12.9", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-transform-export-namespace-from": "^7.25.9", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/preset-react": "^7.22.15", "@babel/preset-typescript": "^7.23.0", "@react-native/babel-preset": "0.79.0-rc.4", "babel-plugin-react-native-web": "~0.19.13", "babel-plugin-syntax-hermes-parser": "^0.25.1", "babel-plugin-transform-flow-enums": "^0.0.2", "debug": "^4.3.4", "react-refresh": "^0.14.2", "resolve-from": "^5.0.0" }, "peerDependencies": { "babel-plugin-react-compiler": "^19.0.0-beta-9ee70a1-20241017", "react-compiler-runtime": "^19.0.0-beta-8a03594-20241020" }, "optionalPeers": ["babel-plugin-react-compiler", "react-compiler-runtime"] }, "sha512-4NfamKh+BKu6v0VUtZ2wFuZ9VdaDnYOC+vsAHhUF3ks1jzFLo9TwBqsrkhD129DIHfdhVJnRJah2KRCXEjcrVQ=="],
"babel-preset-jest": ["babel-preset-jest@29.6.3", "", { "dependencies": { "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA=="],
@ -1700,6 +1748,8 @@
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
"char-regex": ["char-regex@2.0.2", "", {}, "sha512-cbGOjAptfM2LVmWhwRFHEKTPkLwNddVmuqYZQt895yXwAsWsXObCG+YN4DGQ/JBtT4GP1a1lPPdio2z413LmTg=="],
"character-parser": ["character-parser@2.2.0", "", { "dependencies": { "is-regex": "^1.0.3" } }, "sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw=="],
"cheerio": ["cheerio@1.2.0", "", { "dependencies": { "cheerio-select": "^2.1.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", "encoding-sniffer": "^0.2.1", "htmlparser2": "^10.1.0", "parse5": "^7.3.0", "parse5-htmlparser2-tree-adapter": "^7.1.0", "parse5-parser-stream": "^7.1.2", "undici": "^7.19.0", "whatwg-mimetype": "^4.0.0" } }, "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg=="],
@ -1716,6 +1766,8 @@
"ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="],
"cjs-module-lexer": ["cjs-module-lexer@1.4.3", "", {}, "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q=="],
"class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="],
"cli-cursor": ["cli-cursor@2.1.0", "", { "dependencies": { "restore-cursor": "^2.0.0" } }, "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw=="],
@ -1730,6 +1782,10 @@
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
"co": ["co@4.6.0", "", {}, "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ=="],
"collect-v8-coverage": ["collect-v8-coverage@1.0.3", "", {}, "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw=="],
"color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="],
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
@ -1774,6 +1830,8 @@
"cosmiconfig": ["cosmiconfig@5.2.1", "", { "dependencies": { "import-fresh": "^2.0.0", "is-directory": "^0.3.1", "js-yaml": "^3.13.1", "parse-json": "^4.0.0" } }, "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA=="],
"create-jest": ["create-jest@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", "jest-config": "^29.7.0", "jest-util": "^29.7.0", "prompts": "^2.0.1" }, "bin": { "create-jest": "bin/create-jest.js" } }, "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q=="],
"create-require": ["create-require@1.1.1", "", {}, "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ=="],
"cross-env": ["cross-env@7.0.3", "", { "dependencies": { "cross-spawn": "^7.0.1" }, "bin": { "cross-env": "src/bin/cross-env.js", "cross-env-shell": "src/bin/cross-env-shell.js" } }, "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw=="],
@ -1796,6 +1854,8 @@
"cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
"cssom": ["cssom@0.5.0", "", {}, "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw=="],
"cssstyle": ["cssstyle@6.2.0", "", { "dependencies": { "@asamuzakjp/css-color": "^5.0.1", "@csstools/css-syntax-patches-for-csstree": "^1.0.28", "css-tree": "^3.1.0", "lru-cache": "^11.2.6" } }, "sha512-Fm5NvhYathRnXNVndkUsCCuR63DCLVVwGOOwQw782coXFi5HhkXdu289l59HlXZBawsyNccXfWRYvLzcDCdDig=="],
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
@ -1824,6 +1884,8 @@
"decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="],
"dedent": ["dedent@1.7.2", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA=="],
"deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="],
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
@ -1850,10 +1912,14 @@
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"detect-newline": ["detect-newline@3.1.0", "", {}, "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA=="],
"didyoumean": ["didyoumean@1.2.2", "", {}, "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="],
"diff": ["diff@8.0.4", "", {}, "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw=="],
"diff-sequences": ["diff-sequences@29.6.3", "", {}, "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q=="],
"dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="],
"dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="],
@ -1866,6 +1932,8 @@
"domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
"domexception": ["domexception@4.0.0", "", { "dependencies": { "webidl-conversions": "^7.0.0" } }, "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw=="],
"domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="],
"domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
@ -1892,6 +1960,8 @@
"electron-to-chromium": ["electron-to-chromium@1.5.313", "", {}, "sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA=="],
"emittery": ["emittery@0.13.1", "", {}, "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ=="],
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
@ -1944,6 +2014,8 @@
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
"escodegen": ["escodegen@2.1.0", "", { "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", "esutils": "^2.0.2" }, "optionalDependencies": { "source-map": "~0.6.1" }, "bin": { "esgenerate": "bin/esgenerate.js", "escodegen": "bin/escodegen.js" } }, "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w=="],
"eslint": ["eslint@9.39.4", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.5", "@eslint/js": "9.39.4", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ=="],
"eslint-config-expo": ["eslint-config-expo@9.2.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "^8.18.2", "@typescript-eslint/parser": "^8.18.2", "eslint-import-resolver-typescript": "^3.6.3", "eslint-plugin-expo": "^0.1.4", "eslint-plugin-import": "^2.30.0", "eslint-plugin-react": "^7.37.3", "eslint-plugin-react-hooks": "^5.1.0", "globals": "^16.0.0" }, "peerDependencies": { "eslint": ">=8.10" } }, "sha512-TQgmSx+2mRM7qUS0hB5kTDrHcSC35rA1UzOSgK5YRLmSkSMlKLmXkUrhwOpnyo9D/nHdf4ERRAySRYxgA6dlrw=="],
@ -1986,10 +2058,16 @@
"event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="],
"execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="],
"exit": ["exit@0.1.2", "", {}, "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ=="],
"exit-hook": ["exit-hook@2.2.1", "", {}, "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw=="],
"expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="],
"expect": ["expect@29.7.0", "", { "dependencies": { "@jest/expect-utils": "^29.7.0", "jest-get-type": "^29.6.3", "jest-matcher-utils": "^29.7.0", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw=="],
"expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="],
"expo": ["expo@53.0.27", "", { "dependencies": { "@babel/runtime": "^7.20.0", "@expo/cli": "0.24.24", "@expo/config": "~11.0.13", "@expo/config-plugins": "~10.1.2", "@expo/fingerprint": "0.13.4", "@expo/metro-config": "0.20.18", "@expo/vector-icons": "^14.0.0", "babel-preset-expo": "~13.2.5", "expo-asset": "~11.1.7", "expo-constants": "~17.1.8", "expo-file-system": "~18.1.11", "expo-font": "~13.3.2", "expo-keep-awake": "~14.1.4", "expo-modules-autolinking": "2.1.15", "expo-modules-core": "2.5.0", "react-native-edge-to-edge": "1.6.0", "whatwg-url-without-unicode": "8.0.0-3" }, "peerDependencies": { "@expo/dom-webview": "*", "@expo/metro-runtime": "*", "react": "*", "react-native": "*", "react-native-webview": "*" }, "optionalPeers": ["@expo/dom-webview", "@expo/metro-runtime", "react-native-webview"], "bin": { "expo": "bin/cli", "fingerprint": "bin/fingerprint", "expo-modules-autolinking": "bin/autolinking" } }, "sha512-iQwe2uWLb88opUY4vBYEW1d2GUq3lsa43gsMBEdDV+6pw0Oek93l/4nDLe0ODDdrBRjIJm/rdhKqJC/ehHCUqw=="],
@ -2238,6 +2316,8 @@
"html-encoding-sniffer": ["html-encoding-sniffer@6.0.0", "", { "dependencies": { "@exodus/bytes": "^1.6.0" } }, "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg=="],
"html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="],
"htmlparser2": ["htmlparser2@10.1.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", "entities": "^7.0.1" } }, "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ=="],
"http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
@ -2248,6 +2328,8 @@
"httpxy": ["httpxy@0.5.5", "", {}, "sha512-uDjmnPyp1q4Sgzf3w+J/Fc6UqcCEj0x4Wjp7OqK5dGhNeDgpyrAmnS6ey8QWrX3SWDon2DMKf9sBa5X9+CVyMA=="],
"human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="],
"hyphenate-style-name": ["hyphenate-style-name@1.1.0", "", {}, "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw=="],
"iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
@ -2262,8 +2344,12 @@
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
"import-local": ["import-local@3.2.0", "", { "dependencies": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" }, "bin": { "import-local-fixture": "fixtures/cli.js" } }, "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA=="],
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
"indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="],
"inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="],
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
@ -2314,6 +2400,8 @@
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
"is-generator-fn": ["is-generator-fn@2.1.0", "", {}, "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ=="],
"is-generator-function": ["is-generator-function@1.1.2", "", { "dependencies": { "call-bound": "^1.0.4", "generator-function": "^2.0.0", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA=="],
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
@ -2338,6 +2426,8 @@
"is-shared-array-buffer": ["is-shared-array-buffer@1.0.4", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A=="],
"is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
"is-string": ["is-string@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA=="],
"is-symbol": ["is-symbol@1.1.1", "", { "dependencies": { "call-bound": "^1.0.2", "has-symbols": "^1.1.0", "safe-regex-test": "^1.1.0" } }, "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w=="],
@ -2364,28 +2454,76 @@
"istanbul-lib-instrument": ["istanbul-lib-instrument@5.2.1", "", { "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", "@istanbuljs/schema": "^0.1.2", "istanbul-lib-coverage": "^3.2.0", "semver": "^6.3.0" } }, "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg=="],
"istanbul-lib-report": ["istanbul-lib-report@3.0.1", "", { "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", "supports-color": "^7.1.0" } }, "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw=="],
"istanbul-lib-source-maps": ["istanbul-lib-source-maps@4.0.1", "", { "dependencies": { "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", "source-map": "^0.6.1" } }, "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw=="],
"istanbul-reports": ["istanbul-reports@3.2.0", "", { "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" } }, "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA=="],
"iterator.prototype": ["iterator.prototype@1.1.5", "", { "dependencies": { "define-data-property": "^1.1.4", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "get-proto": "^1.0.0", "has-symbols": "^1.1.0", "set-function-name": "^2.0.2" } }, "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g=="],
"its-fine": ["its-fine@2.0.0", "", { "dependencies": { "@types/react-reconciler": "^0.28.9" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng=="],
"jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
"jest": ["jest@29.7.0", "", { "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", "import-local": "^3.0.2", "jest-cli": "^29.7.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"], "bin": { "jest": "bin/jest.js" } }, "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw=="],
"jest-changed-files": ["jest-changed-files@29.7.0", "", { "dependencies": { "execa": "^5.0.0", "jest-util": "^29.7.0", "p-limit": "^3.1.0" } }, "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w=="],
"jest-circus": ["jest-circus@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", "dedent": "^1.0.0", "is-generator-fn": "^2.0.0", "jest-each": "^29.7.0", "jest-matcher-utils": "^29.7.0", "jest-message-util": "^29.7.0", "jest-runtime": "^29.7.0", "jest-snapshot": "^29.7.0", "jest-util": "^29.7.0", "p-limit": "^3.1.0", "pretty-format": "^29.7.0", "pure-rand": "^6.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" } }, "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw=="],
"jest-cli": ["jest-cli@29.7.0", "", { "dependencies": { "@jest/core": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", "chalk": "^4.0.0", "create-jest": "^29.7.0", "exit": "^0.1.2", "import-local": "^3.0.2", "jest-config": "^29.7.0", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "yargs": "^17.3.1" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"], "bin": { "jest": "bin/jest.js" } }, "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg=="],
"jest-config": ["jest-config@29.7.0", "", { "dependencies": { "@babel/core": "^7.11.6", "@jest/test-sequencer": "^29.7.0", "@jest/types": "^29.6.3", "babel-jest": "^29.7.0", "chalk": "^4.0.0", "ci-info": "^3.2.0", "deepmerge": "^4.2.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", "jest-circus": "^29.7.0", "jest-environment-node": "^29.7.0", "jest-get-type": "^29.6.3", "jest-regex-util": "^29.6.3", "jest-resolve": "^29.7.0", "jest-runner": "^29.7.0", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "micromatch": "^4.0.4", "parse-json": "^5.2.0", "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, "peerDependencies": { "@types/node": "*", "ts-node": ">=9.0.0" }, "optionalPeers": ["@types/node", "ts-node"] }, "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ=="],
"jest-diff": ["jest-diff@30.5.1", "", { "dependencies": { "@jest/diff-sequences": "30.5.0", "@jest/get-type": "30.5.0", "chalk": "^4.1.2", "pretty-format": "30.5.1" } }, "sha512-e3cNNMpv8Kh20MjjphTXs+3Vz7DQyLM1nft7KJhnh46atFhjVJRa+0Hq0beywuwsACtMQUBihQkFl8zxb7gt1Q=="],
"jest-docblock": ["jest-docblock@29.7.0", "", { "dependencies": { "detect-newline": "^3.0.0" } }, "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g=="],
"jest-each": ["jest-each@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", "jest-get-type": "^29.6.3", "jest-util": "^29.7.0", "pretty-format": "^29.7.0" } }, "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ=="],
"jest-environment-jsdom": ["jest-environment-jsdom@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/jsdom": "^20.0.0", "@types/node": "*", "jest-mock": "^29.7.0", "jest-util": "^29.7.0", "jsdom": "^20.0.0" }, "peerDependencies": { "canvas": "^2.5.0" }, "optionalPeers": ["canvas"] }, "sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA=="],
"jest-environment-node": ["jest-environment-node@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "jest-mock": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw=="],
"jest-expo": ["jest-expo@53.0.14", "", { "dependencies": { "@expo/config": "~11.0.13", "@expo/json-file": "^9.1.5", "@jest/create-cache-key-function": "^29.2.1", "@jest/globals": "^29.2.1", "babel-jest": "^29.2.1", "find-up": "^5.0.0", "jest-environment-jsdom": "^29.2.1", "jest-snapshot": "^29.2.1", "jest-watch-select-projects": "^2.0.0", "jest-watch-typeahead": "2.2.1", "json5": "^2.2.3", "lodash": "^4.17.19", "react-test-renderer": "19.0.0", "server-only": "^0.0.1", "stacktrace-js": "^2.0.2" }, "peerDependencies": { "expo": "*", "react-native": "*", "react-server-dom-webpack": "~19.0.4 || ~19.1.5 || ~19.2.4" }, "optionalPeers": ["react-server-dom-webpack"], "bin": { "jest": "bin/jest.js" } }, "sha512-BwE5ZTjkhTvO+ejJBBJNlwar2YiahWjbcwhSPQ3oYV5UyvVdTrpKvZ+KjFv/7N5OaC3cOhv4/Ve4cTgS6m6vcw=="],
"jest-get-type": ["jest-get-type@29.6.3", "", {}, "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw=="],
"jest-haste-map": ["jest-haste-map@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "graceful-fs": "^4.2.9", "jest-regex-util": "^29.6.3", "jest-util": "^29.7.0", "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "walker": "^1.0.8" }, "optionalDependencies": { "fsevents": "^2.3.2" } }, "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA=="],
"jest-leak-detector": ["jest-leak-detector@29.7.0", "", { "dependencies": { "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw=="],
"jest-matcher-utils": ["jest-matcher-utils@30.5.1", "", { "dependencies": { "@jest/get-type": "30.5.0", "chalk": "^4.1.2", "jest-diff": "30.5.1", "pretty-format": "30.5.1" } }, "sha512-aroZVqwOz/wC2y6pC+obgFWKV9viaQWQTTSB6W5H55+egtUczIfXR/rxExTv92xD/ADYwpqaYfVWx1aqwKg7FA=="],
"jest-message-util": ["jest-message-util@29.7.0", "", { "dependencies": { "@babel/code-frame": "^7.12.13", "@jest/types": "^29.6.3", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", "pretty-format": "^29.7.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" } }, "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w=="],
"jest-mock": ["jest-mock@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "jest-util": "^29.7.0" } }, "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw=="],
"jest-pnp-resolver": ["jest-pnp-resolver@1.2.3", "", { "peerDependencies": { "jest-resolve": "*" }, "optionalPeers": ["jest-resolve"] }, "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w=="],
"jest-regex-util": ["jest-regex-util@29.6.3", "", {}, "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg=="],
"jest-resolve": ["jest-resolve@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "jest-pnp-resolver": "^1.2.2", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "resolve": "^1.20.0", "resolve.exports": "^2.0.0", "slash": "^3.0.0" } }, "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA=="],
"jest-resolve-dependencies": ["jest-resolve-dependencies@29.7.0", "", { "dependencies": { "jest-regex-util": "^29.6.3", "jest-snapshot": "^29.7.0" } }, "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA=="],
"jest-runner": ["jest-runner@29.7.0", "", { "dependencies": { "@jest/console": "^29.7.0", "@jest/environment": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "emittery": "^0.13.1", "graceful-fs": "^4.2.9", "jest-docblock": "^29.7.0", "jest-environment-node": "^29.7.0", "jest-haste-map": "^29.7.0", "jest-leak-detector": "^29.7.0", "jest-message-util": "^29.7.0", "jest-resolve": "^29.7.0", "jest-runtime": "^29.7.0", "jest-util": "^29.7.0", "jest-watcher": "^29.7.0", "jest-worker": "^29.7.0", "p-limit": "^3.1.0", "source-map-support": "0.5.13" } }, "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ=="],
"jest-runtime": ["jest-runtime@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", "@jest/globals": "^29.7.0", "@jest/source-map": "^29.6.3", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "cjs-module-lexer": "^1.0.0", "collect-v8-coverage": "^1.0.0", "glob": "^7.1.3", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "jest-message-util": "^29.7.0", "jest-mock": "^29.7.0", "jest-regex-util": "^29.6.3", "jest-resolve": "^29.7.0", "jest-snapshot": "^29.7.0", "jest-util": "^29.7.0", "slash": "^3.0.0", "strip-bom": "^4.0.0" } }, "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ=="],
"jest-snapshot": ["jest-snapshot@29.7.0", "", { "dependencies": { "@babel/core": "^7.11.6", "@babel/generator": "^7.7.2", "@babel/plugin-syntax-jsx": "^7.7.2", "@babel/plugin-syntax-typescript": "^7.7.2", "@babel/types": "^7.3.3", "@jest/expect-utils": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", "expect": "^29.7.0", "graceful-fs": "^4.2.9", "jest-diff": "^29.7.0", "jest-get-type": "^29.6.3", "jest-matcher-utils": "^29.7.0", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0", "natural-compare": "^1.4.0", "pretty-format": "^29.7.0", "semver": "^7.5.3" } }, "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw=="],
"jest-util": ["jest-util@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", "picomatch": "^2.2.3" } }, "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA=="],
"jest-validate": ["jest-validate@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "camelcase": "^6.2.0", "chalk": "^4.0.0", "jest-get-type": "^29.6.3", "leven": "^3.1.0", "pretty-format": "^29.7.0" } }, "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw=="],
"jest-watch-select-projects": ["jest-watch-select-projects@2.0.0", "", { "dependencies": { "ansi-escapes": "^4.3.0", "chalk": "^3.0.0", "prompts": "^2.2.1" } }, "sha512-j00nW4dXc2NiCW6znXgFLF9g8PJ0zP25cpQ1xRro/HU2GBfZQFZD0SoXnAlaoKkIY4MlfTMkKGbNXFpvCdjl1w=="],
"jest-watch-typeahead": ["jest-watch-typeahead@2.2.1", "", { "dependencies": { "ansi-escapes": "^6.0.0", "chalk": "^4.0.0", "jest-regex-util": "^29.0.0", "jest-watcher": "^29.0.0", "slash": "^5.0.0", "string-length": "^5.0.1", "strip-ansi": "^7.0.1" }, "peerDependencies": { "jest": "^27.0.0 || ^28.0.0 || ^29.0.0" } }, "sha512-jYpYmUnTzysmVnwq49TAxlmtOAwp8QIqvZyoofQFn8fiWhEDZj33ZXzg3JA4nGnzWFm1hbWf3ADpteUokvXgFA=="],
"jest-watcher": ["jest-watcher@29.7.0", "", { "dependencies": { "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "emittery": "^0.13.1", "jest-util": "^29.7.0", "string-length": "^4.0.1" } }, "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g=="],
"jest-worker": ["jest-worker@29.7.0", "", { "dependencies": { "@types/node": "*", "jest-util": "^29.7.0", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" } }, "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw=="],
"jimp-compact": ["jimp-compact@0.16.1", "", {}, "sha512-dZ6Ra7u1G8c4Letq/B5EzAxj4tLFHL+cGtdpR+PVm4yzPDj+lCk+AbivWt1eOM+ikzkowtyV7qSqX6qr3t71Ww=="],
@ -2410,6 +2548,8 @@
"json-parse-better-errors": ["json-parse-better-errors@1.0.2", "", {}, "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw=="],
"json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="],
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
"json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
@ -2496,6 +2636,8 @@
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
"make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="],
"make-error": ["make-error@1.3.6", "", {}, "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw=="],
"makeerror": ["makeerror@1.0.12", "", { "dependencies": { "tmpl": "1.0.5" } }, "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg=="],
@ -2560,10 +2702,12 @@
"mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"mimic-fn": ["mimic-fn@1.2.0", "", {}, "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ=="],
"mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
"mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="],
"min-indent": ["min-indent@1.0.1", "", {}, "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="],
"miniflare": ["miniflare@4.20260616.0", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "sharp": "0.34.5", "undici": "7.24.8", "workerd": "1.20260616.1", "ws": "8.20.1", "youch": "4.1.0-beta.10" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-cEpzoNgSWjedzYmhJvttUPmL4Jk6nSzzeNNi118T5zwnmYP9fnM8UXwFU/Qa/1qoQ4SzGqtM1Q7tinHvHvIGtw=="],
"minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
@ -2622,10 +2766,14 @@
"npm-package-arg": ["npm-package-arg@11.0.3", "", { "dependencies": { "hosted-git-info": "^7.0.0", "proc-log": "^4.0.0", "semver": "^7.3.5", "validate-npm-package-name": "^5.0.0" } }, "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw=="],
"npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="],
"nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
"nullthrows": ["nullthrows@1.1.1", "", {}, "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw=="],
"nwsapi": ["nwsapi@2.2.27", "", {}, "sha512-gQPNF78qebCQ6tvVFBYrvJdBNOrYZm90ZlXgpIFm06p6qHDHq/XC4TnJftN6OMbxVE0UTBAoRgcsDeJBBooITw=="],
"ob1": ["ob1@0.82.5", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-QyQQ6e66f+Ut/qUVjEce0E/wux5nAGLXYZDn1jr15JWstHsCH3l6VVrg8NKDptW9NEiBXKOJeGF/ydxeSDF3IQ=="],
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
@ -2662,7 +2810,7 @@
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
"onetime": ["onetime@2.0.1", "", { "dependencies": { "mimic-fn": "^1.0.0" } }, "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ=="],
"onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
"open": ["open@7.4.2", "", { "dependencies": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" } }, "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q=="],
@ -2682,7 +2830,7 @@
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
"parse-json": ["parse-json@4.0.0", "", { "dependencies": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" } }, "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw=="],
"parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="],
"parse-png": ["parse-png@2.1.0", "", { "dependencies": { "pngjs": "^3.3.0" } }, "sha512-Nt/a5SfCLiTnQAjx3fHlqp8hRgTL3z7kTQZzvIMS9uCAepnCyjpdEc6M/sz69WqMBdaDBw9sF1F1UaHROYzGkQ=="],
@ -2736,6 +2884,8 @@
"pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="],
"pkg-dir": ["pkg-dir@4.2.0", "", { "dependencies": { "find-up": "^4.0.0" } }, "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ=="],
"playwright": ["playwright@1.63.0", "", { "dependencies": { "playwright-core": "1.63.0" }, "bin": { "playwright": "cli.js" } }, "sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg=="],
"playwright-core": ["playwright-core@1.63.0", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg=="],
@ -2810,6 +2960,8 @@
"proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="],
"psl": ["psl@1.15.0", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w=="],
"pug": ["pug@3.0.4", "", { "dependencies": { "pug-code-gen": "^3.0.4", "pug-filters": "^4.0.0", "pug-lexer": "^5.0.1", "pug-linker": "^4.0.0", "pug-load": "^3.0.0", "pug-parser": "^6.0.0", "pug-runtime": "^3.0.1", "pug-strip-comments": "^2.0.0" } }, "sha512-kFfq5mMzrS7+wrl5pLJzZEzemx34OQ0w4SARfhy/3yxTlhbstsudDwJzhf1hP02yHzbjoVMSXUj/Sz6RNfMyXg=="],
"pug-attrs": ["pug-attrs@3.0.0", "", { "dependencies": { "constantinople": "^4.0.1", "js-stringify": "^1.0.2", "pug-runtime": "^3.0.0" } }, "sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA=="],
@ -2838,12 +2990,16 @@
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
"pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="],
"qrcode-terminal": ["qrcode-terminal@0.11.0", "", { "bin": { "qrcode-terminal": "./bin/qrcode-terminal.js" } }, "sha512-Uu7ii+FQy4Qf82G4xu7ShHhjhGahEpCWc3x8UavY3CTcWV+ufmmCtwkr7ZKsX42jdL0kr1B5FKUeqJvAn51jzQ=="],
"qs": ["qs@6.14.2", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q=="],
"query-string": ["query-string@7.1.3", "", { "dependencies": { "decode-uri-component": "^0.2.2", "filter-obj": "^1.1.0", "split-on-first": "^1.0.0", "strict-uri-encode": "^2.0.0" } }, "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg=="],
"querystringify": ["querystringify@2.2.0", "", {}, "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="],
"queue": ["queue@6.0.2", "", { "dependencies": { "inherits": "~2.0.3" } }, "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA=="],
"queue-lit": ["queue-lit@1.5.2", "", {}, "sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw=="],
@ -2870,7 +3026,7 @@
"react-hook-form": ["react-hook-form@7.71.2", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-1CHvcDYzuRUNOflt4MOq3ZM46AronNJtQ1S7tnX6YN4y72qhgiUItpacZUAQ0TyWYci3yz1X+rXaSxiuEm86PA=="],
"react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
"react-is": ["react-is@19.2.4", "", {}, "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA=="],
"react-native": ["react-native@0.79.6", "", { "dependencies": { "@jest/create-cache-key-function": "^29.7.0", "@react-native/assets-registry": "0.79.6", "@react-native/codegen": "0.79.6", "@react-native/community-cli-plugin": "0.79.6", "@react-native/gradle-plugin": "0.79.6", "@react-native/js-polyfills": "0.79.6", "@react-native/normalize-colors": "0.79.6", "@react-native/virtualized-lists": "0.79.6", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", "babel-jest": "^29.7.0", "babel-plugin-syntax-hermes-parser": "0.25.1", "base64-js": "^1.5.1", "chalk": "^4.0.0", "commander": "^12.0.0", "event-target-shim": "^5.0.1", "flow-enums-runtime": "^0.0.6", "glob": "^7.1.1", "invariant": "^2.2.4", "jest-environment-node": "^29.7.0", "memoize-one": "^5.0.0", "metro-runtime": "^0.82.0", "metro-source-map": "^0.82.0", "nullthrows": "^1.1.1", "pretty-format": "^29.7.0", "promise": "^8.3.0", "react-devtools-core": "^6.1.1", "react-refresh": "^0.14.0", "regenerator-runtime": "^0.13.2", "scheduler": "0.25.0", "semver": "^7.1.3", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0", "ws": "^6.2.3", "yargs": "^17.6.2" }, "peerDependencies": { "@types/react": "^19.0.0", "react": "^19.0.0" }, "optionalPeers": ["@types/react"], "bin": { "react-native": "cli.js" } }, "sha512-kvIWSmf4QPfY41HC25TR285N7Fv0Pyn3DAEK8qRL9dA35usSaxsJkHfw+VqnonqJjXOaoKCEanwudRAJ60TBGA=="],
@ -2916,6 +3072,8 @@
"react-refresh": ["react-refresh@0.14.2", "", {}, "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA=="],
"react-test-renderer": ["react-test-renderer@19.0.0", "", { "dependencies": { "react-is": "^19.0.0", "scheduler": "^0.25.0" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-oX5u9rOQlHzqrE/64CNr0HB0uWxkCQmZNSfozlYvwE71TLVgeZxVf0IjouGEr1v7r1kcDifdAJBeOhdhxsG/DA=="],
"react-use-measure": ["react-use-measure@2.1.7", "", { "peerDependencies": { "react": ">=16.13", "react-dom": ">=16.13" }, "optionalPeers": ["react-dom"] }, "sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg=="],
"read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="],
@ -2924,6 +3082,8 @@
"readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
"redent": ["redent@3.0.0", "", { "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" } }, "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg=="],
"reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="],
"regenerate": ["regenerate@1.4.2", "", {}, "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A=="],
@ -2946,8 +3106,12 @@
"requireg": ["requireg@0.2.2", "", { "dependencies": { "nested-error-stacks": "~2.0.1", "rc": "~1.2.7", "resolve": "~1.7.1" } }, "sha512-nYzyjnFcPNGR3lx9lwPPPnuQxv6JWEZd2Ci0u9opN7N5zUEPIhY/GbL3vMGOr2UXwEg9WwSyV9X9Y/kLFgPsOg=="],
"requires-port": ["requires-port@1.0.0", "", {}, "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="],
"resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="],
"resolve-cwd": ["resolve-cwd@3.0.0", "", { "dependencies": { "resolve-from": "^5.0.0" } }, "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg=="],
"resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="],
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
@ -3084,12 +3248,18 @@
"stable-hash": ["stable-hash@0.0.5", "", {}, "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA=="],
"stack-generator": ["stack-generator@2.0.10", "", { "dependencies": { "stackframe": "^1.3.4" } }, "sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ=="],
"stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="],
"stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
"stackframe": ["stackframe@1.3.4", "", {}, "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw=="],
"stacktrace-gps": ["stacktrace-gps@3.1.2", "", { "dependencies": { "source-map": "0.5.6", "stackframe": "^1.3.4" } }, "sha512-GcUgbO4Jsqqg6RxfyTHFiPxdPqF+3LFmQhm7MgCuYQOYuWyqxo5pwRPz5d/u6/WYJdEnWfK4r+jGbyD8TSggXQ=="],
"stacktrace-js": ["stacktrace-js@2.0.2", "", { "dependencies": { "error-stack-parser": "^2.0.6", "stack-generator": "^2.0.5", "stacktrace-gps": "^3.0.4" } }, "sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg=="],
"stacktrace-parser": ["stacktrace-parser@0.1.11", "", { "dependencies": { "type-fest": "^0.7.1" } }, "sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg=="],
"stacktracey": ["stacktracey@2.2.0", "", { "dependencies": { "as-table": "^1.0.36", "get-source": "^2.0.12" } }, "sha512-ETyQEz+CzXiLjEbyJqpbp+/T79RQD/6wqFucRBIlVNZfYq2Ay7wbretD4cxpbymZlaPWx58aIhPEY1Cr8DlVvg=="],
@ -3110,6 +3280,8 @@
"strict-uri-encode": ["strict-uri-encode@2.0.0", "", {}, "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ=="],
"string-length": ["string-length@5.0.1", "", { "dependencies": { "char-regex": "^2.0.0", "strip-ansi": "^7.0.1" } }, "sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow=="],
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
@ -3126,12 +3298,16 @@
"string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
"strip-ansi": ["strip-ansi@5.2.0", "", { "dependencies": { "ansi-regex": "^4.1.0" } }, "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA=="],
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="],
"strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="],
"strip-indent": ["strip-indent@3.0.0", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="],
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
"structured-headers": ["structured-headers@0.4.1", "", {}, "sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg=="],
@ -3310,6 +3486,8 @@
"unique-string": ["unique-string@2.0.0", "", { "dependencies": { "crypto-random-string": "^2.0.0" } }, "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg=="],
"universalify": ["universalify@0.2.0", "", {}, "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg=="],
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
"unplugin": ["unplugin@3.0.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg=="],
@ -3322,6 +3500,8 @@
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
"url-parse": ["url-parse@1.5.10", "", { "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" } }, "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ=="],
"use-latest-callback": ["use-latest-callback@0.2.6", "", { "peerDependencies": { "react": ">=16.8" } }, "sha512-FvRG9i1HSo0wagmX63Vrm8SnlUU3LMM3WyZkQ76RnslpBrX694AdG4A0zQBx2B3ZifFA0yv/BaEHGBnEax5rZg=="],
"use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="],
@ -3340,6 +3520,8 @@
"v8-compile-cache-lib": ["v8-compile-cache-lib@3.0.1", "", {}, "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg=="],
"v8-to-istanbul": ["v8-to-istanbul@9.3.0", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", "convert-source-map": "^2.0.0" } }, "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA=="],
"validate-npm-package-name": ["validate-npm-package-name@5.0.1", "", {}, "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ=="],
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
@ -3376,7 +3558,7 @@
"webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="],
"whatwg-encoding": ["whatwg-encoding@3.1.1", "", { "dependencies": { "iconv-lite": "0.6.3" } }, "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ=="],
"whatwg-encoding": ["whatwg-encoding@2.0.0", "", { "dependencies": { "iconv-lite": "0.6.3" } }, "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg=="],
"whatwg-fetch": ["whatwg-fetch@3.6.20", "", {}, "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg=="],
@ -3546,10 +3728,20 @@
"@istanbuljs/load-nyc-config/js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="],
"@jest/console/@types/node": ["@types/node@24.12.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ=="],
"@jest/core/@types/node": ["@types/node@24.12.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ=="],
"@jest/environment/@types/node": ["@types/node@24.12.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ=="],
"@jest/fake-timers/@types/node": ["@types/node@24.12.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ=="],
"@jest/reporters/@types/node": ["@types/node@24.12.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ=="],
"@jest/reporters/istanbul-lib-instrument": ["istanbul-lib-instrument@6.0.3", "", { "dependencies": { "@babel/core": "^7.23.9", "@babel/parser": "^7.23.9", "@istanbuljs/schema": "^0.1.3", "istanbul-lib-coverage": "^3.2.0", "semver": "^7.5.4" } }, "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q=="],
"@jest/reporters/string-length": ["string-length@4.0.2", "", { "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" } }, "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ=="],
"@jest/types/@types/node": ["@types/node@24.12.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ=="],
"@maplibre/maplibre-gl-style-spec/quickselect": ["quickselect@2.0.0", "", {}, "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw=="],
@ -3576,14 +3768,14 @@
"@poppinss/dumper/supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="],
"@react-native/babel-plugin-codegen/@react-native/codegen": ["@react-native/codegen@0.79.0-rc.4", "", { "dependencies": { "glob": "^7.1.1", "hermes-parser": "0.25.1", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "yargs": "^17.6.2" }, "peerDependencies": { "@babel/core": "*" } }, "sha512-62J5LVV0LBqyqSV1REin2+ciWamctlYMFyy216Gko/+aqMdoYVX/bM14pdPmqtPZRoMEgWQCgmfw+xc0yx9aPQ=="],
"@react-native/community-cli-plugin/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
"@react-native/community-cli-plugin/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
"@react-native/dev-middleware/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
"@react-navigation/core/react-is": ["react-is@19.2.4", "", {}, "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA=="],
"@tailwindcss/node/jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="],
@ -3620,10 +3812,16 @@
"@tanstack/start-plugin-core/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
"@testing-library/react-native/pretty-format": ["pretty-format@30.5.1", "", { "dependencies": { "@jest/react-is-18": "npm:react-is@^18.3.1", "@jest/react-is-19": "npm:react-is@^19.2.5", "@jest/schemas": "30.5.0", "ansi-styles": "^5.2.0" } }, "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg=="],
"@types/better-sqlite3/@types/node": ["@types/node@24.12.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ=="],
"@types/graceful-fs/@types/node": ["@types/node@24.12.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ=="],
"@types/jsdom/@types/node": ["@types/node@24.12.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ=="],
"@types/jsdom/parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
"@types/pg/@types/node": ["@types/node@24.12.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ=="],
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
@ -3690,8 +3888,6 @@
"chromium-edge-launcher/rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
"cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"common-ui/react-native-pager-view": ["react-native-pager-view@6.9.1", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-uUT0MMMbNtoSbxe9pRvdJJKEi9snjuJ3fXlZhG8F2vVMOBJVt/AFtqMPUHu9yMflmqOr08PewKzj9EPl/Yj+Gw=="],
"common-ui/zustand": ["zustand@5.0.13", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-efI2tVaVQPqtOh114loML/Z80Y4NP3yc+Ff0fYiZJPauNeWZeIp/bRFD7I9bfmCOYBh/PHxlglQ9+wvlwnPikQ=="],
@ -3710,6 +3906,8 @@
"cosmiconfig/js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="],
"cosmiconfig/parse-json": ["parse-json@4.0.0", "", { "dependencies": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" } }, "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw=="],
"css-tree/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
"cssstyle/css-tree": ["css-tree@3.2.1", "", { "dependencies": { "mdn-data": "2.27.1", "source-map-js": "^1.2.1" } }, "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="],
@ -3718,14 +3916,20 @@
"dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
"domexception/webidl-conversions": ["webidl-conversions@7.0.0", "", {}, "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="],
"dotenv-expand/dotenv": ["dotenv@16.4.7", "", {}, "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ=="],
"encoding-sniffer/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
"encoding-sniffer/whatwg-encoding": ["whatwg-encoding@3.1.1", "", { "dependencies": { "iconv-lite": "0.6.3" } }, "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ=="],
"env-runner/exsolve": ["exsolve@1.1.1", "", {}, "sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g=="],
"error-ex/is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="],
"escodegen/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
"eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
"eslint-import-resolver-typescript/tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
@ -3736,6 +3940,12 @@
"eslint-plugin-react/resolve": ["resolve@2.0.0-next.6", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "node-exports-info": "^1.6.0", "object-keys": "^1.1.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA=="],
"execa/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
"expect/jest-matcher-utils": ["jest-matcher-utils@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "jest-diff": "^29.7.0", "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g=="],
"expo/babel-preset-expo": ["babel-preset-expo@13.2.5", "", { "dependencies": { "@babel/helper-module-imports": "^7.25.9", "@babel/plugin-proposal-decorators": "^7.12.9", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-transform-export-namespace-from": "^7.25.9", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/preset-react": "^7.22.15", "@babel/preset-typescript": "^7.23.0", "@react-native/babel-preset": "0.79.6", "babel-plugin-react-native-web": "~0.19.13", "babel-plugin-syntax-hermes-parser": "^0.25.1", "babel-plugin-transform-flow-enums": "^0.0.2", "debug": "^4.3.4", "react-refresh": "^0.14.2", "resolve-from": "^5.0.0" }, "peerDependencies": { "babel-plugin-react-compiler": "^19.0.0-beta-e993439-20250405" }, "optionalPeers": ["babel-plugin-react-compiler"] }, "sha512-YjVkP1bOLO2OgR2fyCedruYMPR7GFbAtCvvWITBW1UAp6e3ACYZtN6uoqkXgXP6PHQkb6M7qf2vZreBPEZK38A=="],
"expo-dev-launcher/ajv": ["ajv@8.11.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", "uri-js": "^4.2.2" } }, "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg=="],
"expo-modules-autolinking/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="],
@ -3796,16 +4006,58 @@
"is-expression/acorn": ["acorn@7.4.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A=="],
"istanbul-lib-source-maps/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
"jest-circus/@types/node": ["@types/node@24.12.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ=="],
"jest-circus/jest-matcher-utils": ["jest-matcher-utils@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "jest-diff": "^29.7.0", "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g=="],
"jest-config/deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
"jest-diff/pretty-format": ["pretty-format@30.5.1", "", { "dependencies": { "@jest/react-is-18": "npm:react-is@^18.3.1", "@jest/react-is-19": "npm:react-is@^19.2.5", "@jest/schemas": "30.5.0", "ansi-styles": "^5.2.0" } }, "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg=="],
"jest-environment-jsdom/@types/node": ["@types/node@24.12.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ=="],
"jest-environment-jsdom/jsdom": ["jsdom@20.0.3", "", { "dependencies": { "abab": "^2.0.6", "acorn": "^8.8.1", "acorn-globals": "^7.0.0", "cssom": "^0.5.0", "cssstyle": "^2.3.0", "data-urls": "^3.0.2", "decimal.js": "^10.4.2", "domexception": "^4.0.0", "escodegen": "^2.0.0", "form-data": "^4.0.0", "html-encoding-sniffer": "^3.0.0", "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.1", "is-potential-custom-element-name": "^1.0.1", "nwsapi": "^2.2.2", "parse5": "^7.1.1", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^4.1.2", "w3c-xmlserializer": "^4.0.0", "webidl-conversions": "^7.0.0", "whatwg-encoding": "^2.0.0", "whatwg-mimetype": "^3.0.0", "whatwg-url": "^11.0.0", "ws": "^8.11.0", "xml-name-validator": "^4.0.0" }, "peerDependencies": { "canvas": "^2.5.0" }, "optionalPeers": ["canvas"] }, "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ=="],
"jest-environment-node/@types/node": ["@types/node@24.12.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ=="],
"jest-haste-map/@types/node": ["@types/node@24.12.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ=="],
"jest-matcher-utils/pretty-format": ["pretty-format@30.5.1", "", { "dependencies": { "@jest/react-is-18": "npm:react-is@^18.3.1", "@jest/react-is-19": "npm:react-is@^19.2.5", "@jest/schemas": "30.5.0", "ansi-styles": "^5.2.0" } }, "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg=="],
"jest-mock/@types/node": ["@types/node@24.12.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ=="],
"jest-runner/@types/node": ["@types/node@24.12.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ=="],
"jest-runner/source-map-support": ["source-map-support@0.5.13", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w=="],
"jest-runtime/@types/node": ["@types/node@24.12.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ=="],
"jest-runtime/strip-bom": ["strip-bom@4.0.0", "", {}, "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w=="],
"jest-snapshot/jest-diff": ["jest-diff@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.6.3", "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw=="],
"jest-snapshot/jest-matcher-utils": ["jest-matcher-utils@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "jest-diff": "^29.7.0", "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g=="],
"jest-snapshot/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
"jest-util/@types/node": ["@types/node@24.12.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ=="],
"jest-util/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"jest-watch-select-projects/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="],
"jest-watch-typeahead/ansi-escapes": ["ansi-escapes@6.2.1", "", {}, "sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig=="],
"jest-watch-typeahead/slash": ["slash@5.1.0", "", {}, "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg=="],
"jest-watch-typeahead/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
"jest-watcher/@types/node": ["@types/node@24.12.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ=="],
"jest-watcher/string-length": ["string-length@4.0.2", "", { "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" } }, "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ=="],
"jest-worker/@types/node": ["@types/node@24.12.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ=="],
"jest-worker/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
@ -3818,6 +4070,8 @@
"log-symbols/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="],
"make-dir/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
"metro/ci-info": ["ci-info@2.0.0", "", {}, "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ=="],
"metro/hermes-parser": ["hermes-parser@0.29.1", "", { "dependencies": { "hermes-estree": "0.29.1" } }, "sha512-xBHWmUtRC5e/UL0tI7Ivt2riA/YBq9+SiYFU7C1oBa/j2jYGlIF9043oak1F47ihuDIxQ5nbsKueYJDRY02UgA=="],
@ -3844,16 +4098,22 @@
"ora/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="],
"ora/strip-ansi": ["strip-ansi@5.2.0", "", { "dependencies": { "ansi-regex": "^4.1.0" } }, "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA=="],
"parse5-htmlparser2-tree-adapter/parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
"parse5-parser-stream/parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
"path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
"pkg-dir/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
"plist/xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="],
"pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
"pretty-format/react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
"prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
"rc/ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="],
@ -3878,10 +4138,14 @@
"react-native-web/memoize-one": ["memoize-one@6.0.0", "", {}, "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="],
"react-test-renderer/scheduler": ["scheduler@0.25.0", "", {}, "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA=="],
"readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"requireg/resolve": ["resolve@1.7.1", "", { "dependencies": { "path-parse": "^1.0.5" } }, "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw=="],
"restore-cursor/onetime": ["onetime@2.0.1", "", { "dependencies": { "mimic-fn": "^1.0.0" } }, "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ=="],
"restore-cursor/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
"rimraf/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
@ -3904,15 +4168,13 @@
"stack-utils/escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="],
"stacktrace-gps/source-map": ["source-map@0.5.6", "", {}, "sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA=="],
"stacktrace-parser/type-fest": ["type-fest@0.7.1", "", {}, "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg=="],
"stats-gl/three": ["three@0.170.0", "", {}, "sha512-FQK+LEpYc0fBD+J8g6oSEyyNzjp+Q7Ks1C568WWaoMRLW+TkNNWmenWeGgJjV105Gd+p/2ql1ZcjYvNiPZBhuQ=="],
"string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"strip-ansi/ansi-regex": ["ansi-regex@4.1.1", "", {}, "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g=="],
"string-length/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
"sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="],
@ -3984,10 +4246,6 @@
"wrangler/esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="],
"wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"write-file-atomic/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
"youch/cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
@ -4088,10 +4346,20 @@
"@istanbuljs/load-nyc-config/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
"@jest/console/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"@jest/core/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"@jest/environment/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"@jest/fake-timers/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"@jest/reporters/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"@jest/reporters/istanbul-lib-instrument/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
"@jest/reporters/string-length/char-regex": ["char-regex@1.0.2", "", {}, "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw=="],
"@jest/types/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"@packages/db_helper_postgres/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
@ -4106,10 +4374,18 @@
"@tanstack/router-devtools/@tanstack/react-router-devtools/@tanstack/router-devtools-core": ["@tanstack/router-devtools-core@1.166.9", "", { "dependencies": { "clsx": "^2.1.1", "goober": "^2.1.16", "tiny-invariant": "^1.3.3" }, "peerDependencies": { "@tanstack/router-core": "^1.167.2", "csstype": "^3.0.10" }, "optionalPeers": ["csstype"] }, "sha512-PNlA7GmOUX9wY7LUG709Pk3Lg33dfHBztQwzjzrOiOsuf4ggp2R6bwarF8nYGNjG79z/MaB5PN+5yvkCVk8jGw=="],
"@testing-library/react-native/pretty-format/@jest/schemas": ["@jest/schemas@30.5.0", "", { "dependencies": { "@sinclair/typebox": "^0.34.0" } }, "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg=="],
"@testing-library/react-native/pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
"@types/better-sqlite3/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"@types/graceful-fs/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"@types/jsdom/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"@types/jsdom/parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
"@types/pg/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="],
@ -4166,12 +4442,16 @@
"eslint-import-resolver-typescript/tinyglobby/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
"expect/jest-matcher-utils/jest-diff": ["jest-diff@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.6.3", "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw=="],
"expo-dev-launcher/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
"expo-modules-autolinking/glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="],
"expo-updates/glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="],
"expo/babel-preset-expo/@react-native/babel-preset": ["@react-native/babel-preset@0.79.6", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-transform-arrow-functions": "^7.24.7", "@babel/plugin-transform-async-generator-functions": "^7.25.4", "@babel/plugin-transform-async-to-generator": "^7.24.7", "@babel/plugin-transform-block-scoping": "^7.25.0", "@babel/plugin-transform-class-properties": "^7.25.4", "@babel/plugin-transform-classes": "^7.25.4", "@babel/plugin-transform-computed-properties": "^7.24.7", "@babel/plugin-transform-destructuring": "^7.24.8", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-for-of": "^7.24.7", "@babel/plugin-transform-function-name": "^7.25.1", "@babel/plugin-transform-literals": "^7.25.2", "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", "@babel/plugin-transform-numeric-separator": "^7.24.7", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-optional-catch-binding": "^7.24.7", "@babel/plugin-transform-optional-chaining": "^7.24.8", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-react-display-name": "^7.24.7", "@babel/plugin-transform-react-jsx": "^7.25.2", "@babel/plugin-transform-react-jsx-self": "^7.24.7", "@babel/plugin-transform-react-jsx-source": "^7.24.7", "@babel/plugin-transform-regenerator": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/plugin-transform-shorthand-properties": "^7.24.7", "@babel/plugin-transform-spread": "^7.24.7", "@babel/plugin-transform-sticky-regex": "^7.24.7", "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/template": "^7.25.0", "@react-native/babel-plugin-codegen": "0.79.6", "babel-plugin-syntax-hermes-parser": "0.25.1", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" } }, "sha512-H+FRO+r2Ql6b5IwfE0E7D52JhkxjeGSBSUpCXAI5zQ60zSBJ54Hwh2bBJOohXWl4J+C7gKYSAd2JHMUETu+c/A=="],
"express/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"fallback-ui/tailwindcss/arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="],
@ -4188,14 +4468,66 @@
"global-prefix/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="],
"jest-circus/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"jest-circus/jest-matcher-utils/jest-diff": ["jest-diff@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.6.3", "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw=="],
"jest-diff/pretty-format/@jest/schemas": ["@jest/schemas@30.5.0", "", { "dependencies": { "@sinclair/typebox": "^0.34.0" } }, "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg=="],
"jest-diff/pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
"jest-environment-jsdom/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"jest-environment-jsdom/jsdom/cssstyle": ["cssstyle@2.3.0", "", { "dependencies": { "cssom": "~0.3.6" } }, "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A=="],
"jest-environment-jsdom/jsdom/data-urls": ["data-urls@3.0.2", "", { "dependencies": { "abab": "^2.0.6", "whatwg-mimetype": "^3.0.0", "whatwg-url": "^11.0.0" } }, "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ=="],
"jest-environment-jsdom/jsdom/html-encoding-sniffer": ["html-encoding-sniffer@3.0.0", "", { "dependencies": { "whatwg-encoding": "^2.0.0" } }, "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA=="],
"jest-environment-jsdom/jsdom/http-proxy-agent": ["http-proxy-agent@5.0.0", "", { "dependencies": { "@tootallnate/once": "2", "agent-base": "6", "debug": "4" } }, "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w=="],
"jest-environment-jsdom/jsdom/https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="],
"jest-environment-jsdom/jsdom/parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
"jest-environment-jsdom/jsdom/tough-cookie": ["tough-cookie@4.1.4", "", { "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", "universalify": "^0.2.0", "url-parse": "^1.5.3" } }, "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag=="],
"jest-environment-jsdom/jsdom/w3c-xmlserializer": ["w3c-xmlserializer@4.0.0", "", { "dependencies": { "xml-name-validator": "^4.0.0" } }, "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw=="],
"jest-environment-jsdom/jsdom/webidl-conversions": ["webidl-conversions@7.0.0", "", {}, "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="],
"jest-environment-jsdom/jsdom/whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="],
"jest-environment-jsdom/jsdom/whatwg-url": ["whatwg-url@11.0.0", "", { "dependencies": { "tr46": "^3.0.0", "webidl-conversions": "^7.0.0" } }, "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ=="],
"jest-environment-jsdom/jsdom/ws": ["ws@8.20.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w=="],
"jest-environment-jsdom/jsdom/xml-name-validator": ["xml-name-validator@4.0.0", "", {}, "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw=="],
"jest-environment-node/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"jest-haste-map/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"jest-matcher-utils/pretty-format/@jest/schemas": ["@jest/schemas@30.5.0", "", { "dependencies": { "@sinclair/typebox": "^0.34.0" } }, "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg=="],
"jest-matcher-utils/pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
"jest-mock/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"jest-runner/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"jest-runner/source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
"jest-runtime/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"jest-util/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"jest-watch-typeahead/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
"jest-watcher/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"jest-watcher/string-length/char-regex": ["char-regex@1.0.2", "", {}, "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw=="],
"jest-worker/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"lighthouse-logger/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
@ -4292,18 +4624,26 @@
"ora/chalk/supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="],
"ora/strip-ansi/ansi-regex": ["ansi-regex@4.1.1", "", {}, "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g=="],
"parse5-htmlparser2-tree-adapter/parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
"parse5-parser-stream/parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
"pkg-dir/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
"react-native-paper/color/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
"restore-cursor/onetime/mimic-fn": ["mimic-fn@1.2.0", "", {}, "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ=="],
"rimraf/glob/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="],
"rimraf/glob/path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="],
"send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"string-length/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
"sucrase/glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="],
"tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q=="],
@ -4444,6 +4784,8 @@
"@istanbuljs/load-nyc-config/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
"@testing-library/react-native/pretty-format/@jest/schemas/@sinclair/typebox": ["@sinclair/typebox@0.34.52", "", {}, "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw=="],
"@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
"@unrs/resolver-binding-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg=="],
@ -4520,6 +4862,22 @@
"expo-updates/glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
"expo/babel-preset-expo/@react-native/babel-preset/@react-native/babel-plugin-codegen": ["@react-native/babel-plugin-codegen@0.79.6", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@react-native/codegen": "0.79.6" } }, "sha512-CS5OrgcMPixOyUJ/Sk/HSsKsKgyKT5P7y3CojimOQzWqRZBmoQfxdST4ugj7n1H+ebM2IKqbgovApFbqXsoX0g=="],
"jest-diff/pretty-format/@jest/schemas/@sinclair/typebox": ["@sinclair/typebox@0.34.52", "", {}, "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw=="],
"jest-environment-jsdom/jsdom/cssstyle/cssom": ["cssom@0.3.8", "", {}, "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg=="],
"jest-environment-jsdom/jsdom/http-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="],
"jest-environment-jsdom/jsdom/https-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="],
"jest-environment-jsdom/jsdom/parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
"jest-environment-jsdom/jsdom/whatwg-url/tr46": ["tr46@3.0.0", "", { "dependencies": { "punycode": "^2.1.1" } }, "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA=="],
"jest-matcher-utils/pretty-format/@jest/schemas/@sinclair/typebox": ["@sinclair/typebox@0.34.52", "", {}, "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw=="],
"log-symbols/chalk/ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
"log-symbols/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
@ -4528,6 +4886,8 @@
"ora/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
"pkg-dir/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
"react-native-paper/color/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
"rimraf/glob/minimatch/brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="],
@ -4584,6 +4944,8 @@
"ora/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
"pkg-dir/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
"rimraf/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
"web-ui/nitro/rolldown/@rolldown/binding-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q=="],

File diff suppressed because it is too large Load diff

194
image-upload.diff Normal file
View file

@ -0,0 +1,194 @@
diff --git a/apps/admin-ui/app/(drawer)/dashboard/products/add.tsx b/apps/admin-ui/app/(drawer)/dashboard/products/add.tsx
index e227458..4465099 100644
--- a/apps/admin-ui/app/(drawer)/dashboard/products/add.tsx
+++ b/apps/admin-ui/app/(drawer)/dashboard/products/add.tsx
@@ -5,6 +5,15 @@ import ProductForm from '@/src/components/ProductForm'
import { trpc } from '@/src/trpc-client'
import { useUploadToObjectStorage } from '@/hooks/useUploadToObjectStore'
+// SKUs store bucket-relative object keys, not URLs. Drop the query string and
+// keep the last two path segments:
+// https://<acct>.r2.cloudflarestorage.com/meatfarmer-dev/product-images/1.jpg?X-Amz-...
+// -> product-images/1.jpg
+const toImageKey = (url: string): string => {
+ const parts = url.split('?')[0].split('/').filter(Boolean)
+ return parts.slice(-2).join('/')
+}
+
export default function AddProduct() {
const createProduct = trpc.admin.product.createProduct.useMutation()
const { refetch: refetchProducts } = trpc.admin.product.getProducts.useQuery(undefined, {
@@ -63,7 +72,8 @@ export default function AddProduct() {
let urlCursor = 0
const skus = values.variants.map((variant: any, vIndex: number) => {
const count = imageCounts[vIndex]
- const variantUrls = allUploadUrls.slice(urlCursor, urlCursor + count)
+ // Send keys, not the presigned URLs.
+ const variantUrls = allUploadUrls.slice(urlCursor, urlCursor + count).map(toImageKey)
urlCursor += count
return {
diff --git a/apps/admin-ui/app/(drawer)/dashboard/products/edit.tsx b/apps/admin-ui/app/(drawer)/dashboard/products/edit.tsx
index ac72345..69f0c97 100644
--- a/apps/admin-ui/app/(drawer)/dashboard/products/edit.tsx
+++ b/apps/admin-ui/app/(drawer)/dashboard/products/edit.tsx
@@ -6,6 +6,15 @@ import ProductForm, { ProductFormRef } from '@/src/components/ProductForm';
import { trpc } from '@/src/trpc-client';
import { useUploadToObjectStorage } from '@/hooks/useUploadToObjectStore';
+// SKUs store bucket-relative object keys, not URLs. Drop the query string and
+// keep the last two path segments:
+// https://<acct>.r2.cloudflarestorage.com/meatfarmer-dev/product-images/1.jpg?X-Amz-...
+// -> product-images/1.jpg
+const toImageKey = (url: string): string => {
+ const parts = url.split('?')[0].split('/').filter(Boolean)
+ return parts.slice(-2).join('/')
+}
+
export default function EditProduct() {
const { id } = useLocalSearchParams();
const productId = Number(id);
@@ -140,9 +149,10 @@ export default function EditProduct() {
// Existing images (mimeType === null) stay as-is
const existingUrls = variantImages[vIndex]
?.filter((img) => img.mimeType === null)
- .map((img) => img.url) || []
+ .map((img) => toImageKey(img.url)) || []
- const allUrls = [...existingUrls, ...newUrls]
+ // Send keys, not URLs — the SKU stores the bucket-relative object key.
+ const allUrls = [...existingUrls, ...newUrls.map(toImageKey)]
return {
id: variant.id,
diff --git a/apps/admin-ui/hooks/useUploadToObjectStore.ts b/apps/admin-ui/hooks/useUploadToObjectStore.ts
index 8327cfa..25c8ee7 100644
--- a/apps/admin-ui/hooks/useUploadToObjectStore.ts
+++ b/apps/admin-ui/hooks/useUploadToObjectStore.ts
@@ -43,9 +43,11 @@ export function useUploadToObjectStorage() {
headers: { 'Content-Type': mimeType },
});
- if (!response.ok) {
- throw new Error(`Upload ${index + 1} failed with status ${response.status}`);
- }
+ if (!response.ok) {
+ const body = await response.text(); // <Error><Code>…</Code></Error>
+ console.error('upload failed', response.status, body);
+ throw new Error(`Upload ${index + 1} failed with status ${response.status}`);
+ }
// Update progress
setProgress(prev => prev ? { ...prev, completed: prev.completed + 1 } : null);
diff --git a/apps/backend/src/lib/s3-client.ts b/apps/backend/src/lib/s3-client.ts
index 1f8b437..47372ca 100644
--- a/apps/backend/src/lib/s3-client.ts
+++ b/apps/backend/src/lib/s3-client.ts
@@ -118,10 +118,14 @@ export async function generateSignedUrlFromS3Url(s3UrlRaw: string|null, expiresI
try {
const client = getAwsClient()
- const url = buildObjectUrl(getS3BucketName(), s3Url)
- const signedRequest = await client.sign(url, {
+ // X-Amz-Expires must be a QUERY param, not a header: with signQuery, aws4fetch
+ // hoists header values into the query but still lists them in
+ // X-Amz-SignedHeaders, and R2 then hashes the missing header as empty — which
+ // makes the signature mismatch (S3 tolerates it, R2 does not).
+ const url = new URL(buildObjectUrl(getS3BucketName(), s3Url))
+ url.searchParams.set('X-Amz-Expires', String(expiresIn))
+ const signedRequest = await client.sign(url.toString(), {
method: 'GET',
- headers: { 'X-Amz-Expires': String(expiresIn) },
aws: { signQuery: true },
})
return signedRequest.url
@@ -170,13 +174,13 @@ export async function generateUploadUrl(key: string, mimeType: string, expiresIn
await createUploadUrlStatus(key)
const client = getAwsClient()
- const url = buildObjectUrl(getS3BucketName(), key)
- const signedRequest = await client.sign(url, {
+ // Same as the GET path above: keep X-Amz-Expires in the query, and do not
+ // sign Content-Type — leaving it unsigned means the client may send any value
+ // without breaking the signature.
+ const url = new URL(buildObjectUrl(getS3BucketName(), key))
+ url.searchParams.set('X-Amz-Expires', String(expiresIn))
+ const signedRequest = await client.sign(url.toString(), {
method: 'PUT',
- headers: {
- 'Content-Type': mimeType,
- 'X-Amz-Expires': String(expiresIn),
- },
aws: { signQuery: true },
})
@@ -193,15 +197,22 @@ export async function generateUploadUrl(key: string, mimeType: string, expiresIn
// return decodeURIComponent(rawKey);
// }
-// New function (excludes bucket name)
+/**
+ * Normalise an image reference to the bucket-relative object key that SKUs store.
+ *
+ * Accepts either a full (presigned) URL or an already-extracted key, so callers
+ * can send whichever they have. Drops the query string, then keeps the last two
+ * path segments:
+ *
+ * https://<account>.r2.cloudflarestorage.com/meatfarmer-dev/product-images/1.jpg?X-Amz-...
+ * -> product-images/1.jpg
+ * product-images/1.jpg
+ * -> product-images/1.jpg
+ */
export function extractKeyFromPresignedUrl(url: string): string {
- const u = new URL(url)
- const rawKey = u.pathname.replace(/^\/+/, '') // remove leading slash
- const decodedKey = decodeURIComponent(rawKey)
- // Remove bucket prefix
- const parts = decodedKey.split('/')
- parts.shift() // Remove bucket name
- return parts.join('/')
+ const withoutQuery = url.split('?')[0]
+ const parts = withoutQuery.split('/').filter(Boolean)
+ return decodeURIComponent(parts.slice(-2).join('/'))
}
export async function claimUploadUrl(url: string): Promise<void> {
diff --git a/apps/backend/src/trpc/apis/admin-apis/apis/product.ts b/apps/backend/src/trpc/apis/admin-apis/apis/product.ts
index 7018961..57b51a7 100644
--- a/apps/backend/src/trpc/apis/admin-apis/apis/product.ts
+++ b/apps/backend/src/trpc/apis/admin-apis/apis/product.ts
@@ -260,8 +260,6 @@ export const productRouter = router({
await deleteImageUtil({ keys: deletedImageKeys })
}
- const allUploadUrls: string[] = skus.flatMap((sku) => sku.images)
-
const skuInputs = skus.map((sku) => ({
id: sku.id,
name: sku.name ?? null,
diff --git a/packages/ui/src/components/ImageUploaderNeo.tsx b/packages/ui/src/components/ImageUploaderNeo.tsx
index eb5e5d7..37201f9 100644
--- a/packages/ui/src/components/ImageUploaderNeo.tsx
+++ b/packages/ui/src/components/ImageUploaderNeo.tsx
@@ -11,11 +11,12 @@ import usePickImage from './use-pick-image'
export type { ImageUploaderNeoItem, ImageUploaderNeoPayload } from '@packages/shared'
-const ImageUploaderNeo: React.FC<ImageUploaderNeoProps> = ({
+const ImageUploaderNeo: React.FC<ImageUploaderNeoProps & { testID?: string }> = ({
images,
onImageAdd,
onImageRemove,
allowMultiple = true,
+ testID,
}) => {
const totalImageCount = images.length
@@ -60,6 +61,7 @@ const ImageUploaderNeo: React.FC<ImageUploaderNeoProps> = ({
</View>
))}
<MyTouchableOpacity
+ testID={testID}
disabled={!allowMultiple && totalImageCount >= 1}
onPress={handlePickImage}
style={tw`w-1/3 px-1 mb-2`}

4
opencode.jsonc Normal file
View file

@ -0,0 +1,4 @@
// {
// "$schema": "https://opencode.ai/config.json",
// "permission": "allow"
// }

View file

@ -29,6 +29,7 @@ export interface OrderCardActionProps<T> {
export interface LoginFormInputs {
mobile: string
otp?: string
identifier?: string
password?: string
}

View file

@ -11,11 +11,12 @@ import usePickImage from './use-pick-image'
export type { ImageUploaderNeoItem, ImageUploaderNeoPayload } from '@packages/shared'
const ImageUploaderNeo: React.FC<ImageUploaderNeoProps> = ({
const ImageUploaderNeo: React.FC<ImageUploaderNeoProps & { testID?: string }> = ({
images,
onImageAdd,
onImageRemove,
allowMultiple = true,
testID,
}) => {
const totalImageCount = images.length
@ -47,6 +48,7 @@ const ImageUploaderNeo: React.FC<ImageUploaderNeoProps> = ({
style={tw`w-full aspect-square rounded`}
/>
<MyTouchableOpacity
testID={testID ? `${testID}-remove` : undefined}
onPress={() =>
onImageRemove({
url: image.imgUrl,
@ -60,6 +62,7 @@ const ImageUploaderNeo: React.FC<ImageUploaderNeoProps> = ({
</View>
))}
<MyTouchableOpacity
testID={testID}
disabled={!allowMultiple && totalImageCount >= 1}
onPress={handlePickImage}
style={tw`w-1/3 px-1 mb-2`}

View file

@ -212,6 +212,8 @@ const BottomDropdown: React.FC<BottomDropdownProps> = ({
return (
<TouchableOpacity
key={option.value}
testID={`${testID}-option-${option.value}`}
accessibilityLabel={`${testID}-option-${option.value}`}
style={[
tw`px-4 py-3 rounded-md my-1 mx-2 flex-row items-center justify-between`,
selected ? { backgroundColor: colors.brand50 } : tw`bg-transparent`,

View file

@ -14,6 +14,7 @@ interface CheckboxProps {
checkedColor?: string; // deprecated, use checkColor
fillColor?: string; // new prop for box/circle fill
checkColor?: string; // new prop for check/tick color
testID?: string;
}
const Checkbox: React.FC<CheckboxProps> = ({
@ -25,6 +26,7 @@ const Checkbox: React.FC<CheckboxProps> = ({
checkedColor, // deprecated
fillColor,
checkColor,
testID,
}) => {
const { colors } = useTheme().theme;
const fill = fillColor || colors.brand500;
@ -39,6 +41,7 @@ const Checkbox: React.FC<CheckboxProps> = ({
style={style}
activeOpacity={0.7}
disabled={disabled}
testID={testID}
accessibilityRole="checkbox"
accessibilityState={{ checked, disabled }}
>

View file

@ -16,6 +16,7 @@ interface SearchBarProps {
containerStyle?: ViewStyle;
onPress?: () => void;
activeOpacity?: number;
testID?: string;
}
const SearchBar = forwardRef<any, SearchBarProps>(({
@ -28,6 +29,7 @@ const SearchBar = forwardRef<any, SearchBarProps>(({
containerStyle,
onPress,
activeOpacity = 0.8,
testID,
}, ref) => {
const Wrapper = onPress ? MyTouchableOpacity : View;
@ -35,8 +37,10 @@ const SearchBar = forwardRef<any, SearchBarProps>(({
style: [tw`flex-row items-center bg-white rounded-2xl px-4 h-12 mb-6 shadow-md`, { overflow: 'visible' }, containerStyle],
onPress,
activeOpacity,
testID,
} : {
style: [tw`flex-row items-center bg-white rounded-2xl px-4 h-12 mb-6 shadow-md`, { overflow: 'visible' }, containerStyle],
testID,
};
return (
@ -44,6 +48,7 @@ const SearchBar = forwardRef<any, SearchBarProps>(({
<MaterialIcons name="search" size={24} color={theme.colors.brand500} />
<MyTextInput
ref={ref}
testID={testID ? `${testID}-input` : undefined}
style={[tw`flex-1 text-base text-gray-800 bg-transparent h-8`, { borderWidth: 0 }]}
placeholder={placeholder}
placeholderTextColor="#9CA3AF"

15
tests/e2e/.env.example Normal file
View file

@ -0,0 +1,15 @@
APP_ID=in.freshyo.adminui
STAFF_NAME=
STAFF_PASSWORD=
PRODUCT_NAME="E2E Test Product"
STORE_ID=1
SKU1_QTY="500 g"
SKU1_PRICE=199
SKU1_MRP=249
SKU2_QTY="1 kg"
SKU2_PRICE=349
SKU2_MRP=399
SKU2_FLASH_PRICE=299
SLOT_DATE=
SLOT_HOUR=
SLOT_MIN=

81
tests/e2e/README.md Normal file
View file

@ -0,0 +1,81 @@
# Admin E2E tests (Maestro)
Maestro flows that drive the **admin-ui** Expo app on an Android emulator.
## Layout
```
tests/e2e/
config.yaml # workspace config (entry flows + execution order)
add-product.yaml # create a product with 2 SKUs (one flash)
add-slot-with-product.yaml # create a slot (SLOT_DATE + SLOT_HOUR:SLOT_MIN) holding that product
subflows/
login.yaml # reusable login (callers invoke it conditionally)
pick-image.yaml # OS photo-picker adapter (Android)
set-slot-times.yaml # absolute delivery time + freeze (-10m) via native pickers
dev/
add-empty-slot.yaml # standalone: create a slot with no products (validate pickers)
assets/test-product.png # seeded into the emulator gallery via `addMedia`
run.sh # loads .env and runs `maestro test`
.env.example # copy to .env and fill in
```
## Prerequisites
- Maestro CLI installed (`curl -Ls "https://get.maestro.mobile.dev" | bash`).
- A booted Android emulator (`maestro start-device --platform android`, or Android Studio).
- The admin-ui **dev build** installed on the emulator (`bun run --filter admin-ui android`, appId `in.freshyo.adminui`).
- The build must be able to reach the backend (login + image upload hit the API/R2).
## Setup
```bash
cp tests/e2e/.env.example tests/e2e/.env
# edit tests/e2e/.env — at minimum STAFF_NAME, STAFF_PASSWORD, STORE_ID
```
## Run
```bash
bash tests/e2e/run.sh # every entry flow (ordered: add-product, then add-slot-with-product)
bash tests/e2e/run.sh add-product.yaml
bash tests/e2e/run.sh dev/add-empty-slot.yaml # standalone: slot only, no product
```
`PRODUCT_NAME` is used **verbatim**`add-product.yaml` creates it with that exact name
and `add-slot-with-product.yaml` searches for it. The backend rejects duplicate names, so
change `PRODUCT_NAME` (or delete the product) between suite runs.
## What `add-product.yaml` does
1. `addMedia` seeds `assets/test-product.png` into the emulator gallery.
2. `launchApp` with `clearState: true` (forces login) and gallery permission.
3. Logs in (subflow) and taps the `add-product-menu-item` quick action.
4. Fills the product name and selects Store + Product Type.
5. Variant 1: quantity, price, MRP, attaches an image.
6. Adds Variant 2, fills quantity/prices, toggles **Flash Available**, sets flash price, attaches an image.
7. Submits and asserts `Product created successfully!`.
## What `add-slot-with-product.yaml` does
1. `evalScript` computes delivery = now + 60 min and freeze = now + 50 min.
2. Logs in and taps the `add-slot-fab`.
3. Sets the delivery/freeze date (today) and time via the native Android pickers
(`android:id/button1`, `toggle_mode`, `input_hour`, `input_minute`).
4. Opens `slot-products-selector`, searches the exact `PRODUCT_NAME`, selects the
two SKU options and taps Done.
5. Submits and asserts `Slot created successfully!`.
All in-app interactions use `testID` selectors (`id:`). The exceptions are system UI —
the OS photo picker (`subflows/pick-image.yaml`) and the native date/time pickers.
## Notes / gotchas
- The image picker flow is the most version-sensitive part; thumbs are targeted by
the Android 13/14 Photo Picker id with a DocumentsUI fallback, and the confirm
button by its `button_add` id.
- Native time-picker ids (`android:id/toggle_mode`, `input_hour`, `input_minute`,
`button1`) depend on the OS version. The date dialog defaults to today, so a run
within ~60 min of midnight would put delivery on the next day.
- On a reused emulator, `addMedia` images accumulate in the gallery (harmless).
- Tests create real data on the connected backend.

167
tests/e2e/add-product.yaml Normal file
View file

@ -0,0 +1,167 @@
appId: ${APP_ID || "in.freshyo.adminui"}
env:
APP_ID: ${APP_ID || "in.freshyo.adminui"}
STAFF_NAME: ${STAFF_NAME || ""}
STAFF_PASSWORD: ${STAFF_PASSWORD || ""}
PRODUCT_NAME: ${PRODUCT_NAME || "E2E Test Product"}
STORE_ID: ${STORE_ID || 1}
SKU1_QTY: ${SKU1_QTY || "500 g"}
SKU1_PRICE: ${SKU1_PRICE || "199"}
SKU1_MRP: ${SKU1_MRP || "249"}
SKU2_QTY: ${SKU2_QTY || "1 kg"}
SKU2_PRICE: ${SKU2_PRICE || "349"}
SKU2_MRP: ${SKU2_MRP || "399"}
SKU2_FLASH_PRICE: ${SKU2_FLASH_PRICE || "299"}
tags:
- products
- smoke
---
- addMedia:
- './assets/test-product.png'
- launchApp:
clearState: true
permissions:
storage: allow
- extendedWaitUntil:
visible:
id: login-name-input
timeout: 180000
- runFlow:
when:
visible:
id: login-name-input
commands:
- runFlow: subflows/login.yaml
- tapOn:
id: add-product-menu-item
- assertVisible:
id: product-name-input
- tapOn:
id: product-name-input
- inputText: ${PRODUCT_NAME}
- hideKeyboard
- scrollUntilVisible:
element:
id: product-store-dropdown
direction: DOWN
- tapOn:
id: product-store-dropdown
- tapOn:
id: product-store-dropdown-option-${STORE_ID}
- scrollUntilVisible:
element:
id: product-type-dropdown
direction: DOWN
- tapOn:
id: product-type-dropdown
- tapOn:
id: product-type-dropdown-option-item
- scrollUntilVisible:
element:
id: variant-0-attr-0-value
direction: DOWN
- tapOn:
id: variant-0-attr-0-value
- inputText: ${SKU1_QTY}
- hideKeyboard
- scrollUntilVisible:
element:
id: variant-0-price
direction: DOWN
- tapOn:
id: variant-0-price
- inputText: ${SKU1_PRICE}
- hideKeyboard
- scrollUntilVisible:
element:
id: variant-0-market-price
direction: DOWN
- tapOn:
id: variant-0-market-price
- inputText: ${SKU1_MRP}
- hideKeyboard
- scrollUntilVisible:
element:
id: variant-0-image-add
direction: DOWN
- tapOn:
id: variant-0-image-add
- runFlow: subflows/pick-image.yaml
- scrollUntilVisible:
element:
id: add-variant-button
direction: UP
- tapOn:
id: add-variant-button
- scrollUntilVisible:
element:
id: variant-1-attr-0-value
direction: DOWN
- tapOn:
id: variant-1-attr-0-value
- inputText: ${SKU2_QTY}
- hideKeyboard
- scrollUntilVisible:
element:
id: variant-1-price
direction: DOWN
- tapOn:
id: variant-1-price
- inputText: ${SKU2_PRICE}
- hideKeyboard
- scrollUntilVisible:
element:
id: variant-1-market-price
direction: DOWN
- tapOn:
id: variant-1-market-price
- inputText: ${SKU2_MRP}
- hideKeyboard
- scrollUntilVisible:
element:
id: variant-1-flash
direction: DOWN
- tapOn:
id: variant-1-flash
- scrollUntilVisible:
element:
id: variant-1-flash-price
direction: DOWN
- tapOn:
id: variant-1-flash-price
- inputText: ${SKU2_FLASH_PRICE}
- hideKeyboard
- scrollUntilVisible:
element:
id: variant-1-image-add
direction: DOWN
- tapOn:
id: variant-1-image-add
- runFlow: subflows/pick-image.yaml
- scrollUntilVisible:
element:
id: create-product-button
direction: DOWN
- tapOn:
id: create-product-button
- assertVisible: 'Product created successfully!'
- tapOn: 'OK'

View file

@ -0,0 +1,61 @@
appId: ${APP_ID || "in.freshyo.adminui"}
env:
APP_ID: ${APP_ID || "in.freshyo.adminui"}
STAFF_NAME: ${STAFF_NAME || ""}
STAFF_PASSWORD: ${STAFF_PASSWORD || ""}
PRODUCT_NAME: ${PRODUCT_NAME || "E2E Test Product"}
SLOT_DATE: ${SLOT_DATE || ""}
SLOT_HOUR: ${SLOT_HOUR || ""}
SLOT_MIN: ${SLOT_MIN || ""}
tags:
- slots
- smoke
---
- launchApp
- extendedWaitUntil:
visible:
id: 'login-name-input|add-product-menu-item'
timeout: 180000
- runFlow:
when:
visible:
id: login-name-input
commands:
- runFlow: subflows/login.yaml
- openLink: freshyoadmin://dashboard/slots/add
- extendedWaitUntil:
visible:
id: delivery-date-picker
timeout: 30000
- runFlow: subflows/set-slot-times.yaml
- scrollUntilVisible:
element:
id: slot-products-selector
direction: DOWN
- tapOn:
id: slot-products-selector
- tapOn:
id: slot-products-selector-search
- inputText: ${PRODUCT_NAME}
- hideKeyboard
- tapOn:
id: 'slot-products-selector-option-.*'
index: 0
- tapOn:
id: 'slot-products-selector-option-.*'
index: 1
- tapOn:
id: slot-products-selector-done
- scrollUntilVisible:
element:
id: create-slot-button
direction: DOWN
- tapOn:
id: create-slot-button
- assertVisible: 'Slot created successfully!'
- tapOn: 'OK'

BIN
tests/e2e/assets/test-product.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5 KiB

10
tests/e2e/config.yaml Normal file
View file

@ -0,0 +1,10 @@
flows:
- '*.yaml'
- '!config.yaml'
executionOrder:
continueOnFailure: false
flowsOrder:
- add-product
- add-slot-with-product
- user-order

View file

@ -0,0 +1,47 @@
appId: ${APP_ID || "in.freshyo.adminui"}
env:
APP_ID: ${APP_ID || "in.freshyo.adminui"}
STAFF_NAME: ${STAFF_NAME || ""}
STAFF_PASSWORD: ${STAFF_PASSWORD || ""}
SLOT_DATE: ${SLOT_DATE || ""}
SLOT_HOUR: ${SLOT_HOUR || ""}
SLOT_MIN: ${SLOT_MIN || ""}
tags:
- dev
---
- launchApp
- extendedWaitUntil:
visible:
id: 'login-name-input|add-product-menu-item'
timeout: 180000
- runFlow:
when:
visible:
id: login-name-input
commands:
- runFlow: ../subflows/login.yaml
- scrollUntilVisible:
element:
id: delivery-slots-menu-item
direction: DOWN
- tapOn:
id: delivery-slots-menu-item
- assertVisible:
id: add-slot-fab
- tapOn:
id: add-slot-fab
- assertVisible:
id: delivery-date-picker
- runFlow: ../subflows/set-slot-times.yaml
- scrollUntilVisible:
element:
id: create-slot-button
direction: DOWN
- tapOn:
id: create-slot-button
- assertVisible: 'Slot created successfully!'
- tapOn: 'OK'

59
tests/e2e/run.sh Executable file
View file

@ -0,0 +1,59 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
if [ ! -f .env ]; then
echo "Missing tests/e2e/.env — copy .env.example to .env and fill it in." >&2
exit 1
fi
while IFS= read -r line || [ -n "$line" ]; do
line="${line%$'\r'}"
case "$line" in
'' | '#'*) continue ;;
esac
export "$line"
done < ./.env
: "${APP_ID:?APP_ID is required}"
: "${STAFF_NAME:?STAFF_NAME is required}"
: "${STAFF_PASSWORD:?STAFF_PASSWORD is required}"
: "${PRODUCT_NAME:?PRODUCT_NAME is required}"
: "${STORE_ID:?STORE_ID is required}"
: "${SLOT_DATE:?SLOT_DATE is required (YYYY-MM-DD, must be today)}"
: "${SLOT_HOUR:?SLOT_HOUR is required (delivery hour, 24h zero-padded)}"
: "${SLOT_MIN:?SLOT_MIN is required (delivery minute, zero-padded)}"
export PRODUCT_NAME
if [ "$#" -eq 0 ]; then
set -- .
fi
freeze_total=$((10#$SLOT_HOUR * 60 + 10#$SLOT_MIN - 10))
SLOT_FREEZE_HOUR=$(printf '%02d' $((freeze_total / 60)))
SLOT_FREEZE_MIN=$(printf '%02d' $((freeze_total % 60)))
export SLOT_FREEZE_HOUR SLOT_FREEZE_MIN
maestro test \
-e APP_ID="$APP_ID" \
-e STAFF_NAME="$STAFF_NAME" \
-e STAFF_PASSWORD="$STAFF_PASSWORD" \
-e PRODUCT_NAME="$PRODUCT_NAME" \
-e STORE_ID="$STORE_ID" \
-e SKU1_QTY="${SKU1_QTY:-}" \
-e SKU1_PRICE="${SKU1_PRICE:-}" \
-e SKU1_MRP="${SKU1_MRP:-}" \
-e SKU2_QTY="${SKU2_QTY:-}" \
-e SKU2_PRICE="${SKU2_PRICE:-}" \
-e SKU2_MRP="${SKU2_MRP:-}" \
-e SKU2_FLASH_PRICE="${SKU2_FLASH_PRICE:-}" \
-e SLOT_DATE="$SLOT_DATE" \
-e SLOT_HOUR="$SLOT_HOUR" \
-e SLOT_MIN="$SLOT_MIN" \
-e SLOT_FREEZE_HOUR="$SLOT_FREEZE_HOUR" \
-e SLOT_FREEZE_MIN="$SLOT_FREEZE_MIN" \
-e USER_APP_ID="${USER_APP_ID:-in.freshyo.app}" \
-e USER_MOBILE="${USER_MOBILE:-}" \
-e USER_PASSWORD="${USER_PASSWORD:-}" \
"$@"

View file

@ -0,0 +1,17 @@
appId: ${USER_APP_ID}
env:
USER_MOBILE: ${USER_MOBILE || ""}
USER_PASSWORD: ${USER_PASSWORD || ""}
---
- tapOn:
id: user-login-mobile
- inputText: ${USER_MOBILE}
- pressKey: enter
- tapOn:
id: user-login-other-ways
- tapOn:
id: user-login-password
- inputText: ${USER_PASSWORD}
- pressKey: enter
- tapOn:
id: user-login-submit

View file

@ -0,0 +1,16 @@
appId: ${APP_ID || "in.freshyo.adminui"}
env:
STAFF_NAME: ${STAFF_NAME || ""}
STAFF_PASSWORD: ${STAFF_PASSWORD || ""}
---
- tapOn:
id: login-name-input
- inputText: ${STAFF_NAME}
- tapOn:
id: login-password-input
- inputText: ${STAFF_PASSWORD}
- hideKeyboard
- tapOn:
id: login-button
- assertVisible:
id: add-product-menu-item

View file

@ -0,0 +1,26 @@
appId: ${APP_ID || "in.freshyo.adminui"}
---
- runFlow:
when:
platform: Android
commands:
- extendedWaitUntil:
visible:
id: 'com\.(google\.)?android\.providers\.media\.module:id/icon_thumbnail'
timeout: 15000
- tapOn:
id: 'com\.(google\.)?android\.providers\.media\.module:id/icon_thumbnail'
index: 0
- extendedWaitUntil:
visible:
id: 'com\.(google\.)?android\.providers\.media\.module:id/button_add'
timeout: 10000
- tapOn:
id: 'com\.(google\.)?android\.providers\.media\.module:id/button_add'
- extendedWaitUntil:
visible:
id: app-root
timeout: 15000

View file

@ -0,0 +1,43 @@
appId: ${APP_ID || "in.freshyo.adminui"}
---
- tapOn:
id: delivery-date-picker
- tapOn:
id: 'android:id/button1'
- tapOn:
id: delivery-time-picker
- tapOn:
id: 'android:id/toggle_mode'
optional: true
- tapOn:
id: 'android:id/input_hour'
- eraseText
- inputText: ${SLOT_HOUR}
- tapOn:
id: 'android:id/input_minute'
- eraseText
- inputText: ${SLOT_MIN}
- tapOn:
id: 'android:id/button1'
- tapOn:
id: freeze-date-picker
- tapOn:
id: 'android:id/button1'
- tapOn:
id: freeze-time-picker
- tapOn:
id: 'android:id/toggle_mode'
optional: true
- tapOn:
id: 'android:id/input_hour'
- eraseText
- inputText: ${SLOT_FREEZE_HOUR}
- tapOn:
id: 'android:id/input_minute'
- eraseText
- inputText: ${SLOT_FREEZE_MIN}
- tapOn:
id: 'android:id/button1'

119
tests/e2e/user-order.yaml Normal file
View file

@ -0,0 +1,119 @@
appId: ${USER_APP_ID}
env:
USER_APP_ID: ${USER_APP_ID || "in.freshyo.app"}
USER_MOBILE: ${USER_MOBILE || ""}
USER_PASSWORD: ${USER_PASSWORD || ""}
PRODUCT_NAME: ${PRODUCT_NAME || "E2E Test Product"}
SLOT_DATE: ${SLOT_DATE || ""}
SLOT_HOUR: ${SLOT_HOUR || ""}
SLOT_MIN: ${SLOT_MIN || ""}
tags:
- user
- smoke
---
- launchApp:
clearState: true
- extendedWaitUntil:
visible:
text: 'Next|Home'
timeout: 60000
- tapOn:
text: 'Next'
optional: true
- waitForAnimationToEnd
- extendedWaitUntil:
visible:
id: home-search
timeout: 30000
- tapOn:
id: home-search
- tapOn:
id: search-input-input
- inputText: ${PRODUCT_NAME}
- pressKey: enter
- tapOn: 'Add to Cart'
- extendedWaitUntil:
visible:
id: 'slot-${SLOT_DATE}-${SLOT_HOUR}-${SLOT_MIN}'
timeout: 20000
- tapOn:
id: 'slot-${SLOT_DATE}-${SLOT_HOUR}-${SLOT_MIN}'
- tapOn:
id: add-to-cart-confirm
- extendedWaitUntil:
visible:
id: go-to-cart
timeout: 20000
- tapOn:
id: go-to-cart
- scrollUntilVisible:
element:
id: checkout-button
direction: DOWN
- tapOn:
id: checkout-button
- extendedWaitUntil:
visible:
id: 'user-login-mobile|place-order-button'
timeout: 30000
- runFlow:
when:
visible:
id: user-login-mobile
commands:
- runFlow: subflows/login-user.yaml
- extendedWaitUntil:
visible:
id: home-search
timeout: 60000
- extendedWaitUntil:
visible:
id: go-to-cart
timeout: 20000
- tapOn:
id: go-to-cart
- scrollUntilVisible:
element:
id: checkout-button
direction: DOWN
- tapOn:
id: checkout-button
- extendedWaitUntil:
visible:
id: address-option-0
timeout: 30000
- scrollUntilVisible:
element:
id: place-order-button
direction: DOWN
- runFlow:
when:
visible:
id: select-address-error
commands:
- scrollUntilVisible:
element:
id: address-option-0
direction: UP
- tapOn:
id: address-option-0
- scrollUntilVisible:
element:
id: place-order-button
direction: DOWN
- extendedWaitUntil:
notVisible:
id: select-address-error
timeout: 15000
- tapOn:
id: place-order-button
- assertVisible: 'Order Placed Successfully!'