refactor(native): overhaul auth screens, add ModalStackHeader, and bump Expo patch releases

- Replace `Alert` with `toast` for sign-in/register errors and add a `getFormErrors` utility to extract the first Zod validation message and field set
- Display the configured server host inline on the login screen and hide the navigation header in the auth layout (`headerShown: false`, `navigationBarHidden: true`)
- Delete `AuthStackHeader` and `DetailStackHeader`; introduce `ModalStackHeader` for screens that are pushed as modals
- Remove unused `StatusBadge` component and delete unused shadcn web components (`calendar`, `drawer`)
- Bump all Expo 55 SDK packages to their latest patch versions and add `expo-web-browser` plugin to `app.json` + `@react-navigation/native` as an explicit dependency
This commit is contained in:
2026-03-17 18:33:11 -04:00
parent daf90a4f39
commit c6ae58f41d
31 changed files with 941 additions and 1347 deletions
+1
View File
@@ -102,6 +102,7 @@
}
],
["expo-system-ui"],
["expo-web-browser"],
[
"expo-image-picker",
{
+19 -18
View File
@@ -16,12 +16,13 @@
"dependencies": {
"@better-auth/expo": "catalog:",
"@expo/metro-runtime": "55.0.6",
"@expo/ui": "55.0.2",
"@expo/ui": "55.0.3",
"@orpc/client": "catalog:",
"@orpc/contract": "catalog:",
"@orpc/tanstack-query": "catalog:",
"@react-native-menu/menu": "2.0.0",
"@react-navigation/elements": "2.9.10",
"@react-navigation/native": "7.1.33",
"@shopify/flash-list": "2.0.2",
"@sofa/api": "workspace:*",
"@tabler/icons-react-native": "3.40.0",
@@ -32,28 +33,28 @@
"better-auth": "catalog:",
"burnt": "0.13.0",
"date-fns": "catalog:",
"expo": "55.0.6",
"expo-application": "55.0.9",
"expo-clipboard": "55.0.8",
"expo-constants": "55.0.7",
"expo-dev-client": "55.0.16",
"expo-device": "55.0.9",
"expo": "55.0.7",
"expo-application": "55.0.10",
"expo-clipboard": "55.0.9",
"expo-constants": "55.0.8",
"expo-dev-client": "55.0.17",
"expo-device": "55.0.10",
"expo-font": "55.0.4",
"expo-glass-effect": "55.0.8",
"expo-haptics": "55.0.8",
"expo-haptics": "55.0.9",
"expo-image": "55.0.6",
"expo-image-picker": "55.0.12",
"expo-linear-gradient": "55.0.8",
"expo-image-picker": "55.0.13",
"expo-linear-gradient": "55.0.9",
"expo-linking": "55.0.7",
"expo-localization": "55.0.8",
"expo-network": "55.0.8",
"expo-router": "55.0.5",
"expo-secure-store": "55.0.8",
"expo-splash-screen": "55.0.10",
"expo-localization": "55.0.9",
"expo-network": "55.0.9",
"expo-router": "55.0.6",
"expo-secure-store": "55.0.9",
"expo-splash-screen": "55.0.11",
"expo-status-bar": "55.0.4",
"expo-system-ui": "55.0.9",
"expo-tracking-transparency": "55.0.8",
"expo-web-browser": "55.0.9",
"expo-system-ui": "55.0.10",
"expo-tracking-transparency": "55.0.9",
"expo-web-browser": "55.0.10",
"posthog-react-native": "4.37.3",
"react": "19.2.0",
"react-dom": "19.2.0",
+2
View File
@@ -16,6 +16,8 @@ export default function AuthLayout() {
<Stack
screenOptions={{
contentStyle,
headerShown: false,
navigationBarHidden: true,
animation: "fade",
}}
/>
+147 -123
View File
@@ -1,24 +1,24 @@
import { useForm } from "@tanstack/react-form";
import { IconServer2 } from "@tabler/icons-react-native";
import { useForm, useStore } from "@tanstack/react-form";
import { useQuery } from "@tanstack/react-query";
import { Link } from "expo-router";
import { useRef } from "react";
import { Alert, Pressable, type TextInput, View } from "react-native";
import { useRef, useState } from "react";
import { Pressable, type TextInput, View } from "react-native";
import Animated, { FadeIn, FadeInDown } from "react-native-reanimated";
import { useCSSVariable } from "uniwind";
import { z } from "zod";
import { AuthScreen } from "@/components/auth-screen";
import { AuthStackHeader } from "@/components/navigation/auth-stack-header";
import { Button, ButtonLabel } from "@/components/ui/button";
import { ScaledIcon } from "@/components/ui/scaled-icon";
import { Spinner } from "@/components/ui/spinner";
import { Text } from "@/components/ui/text";
import {
FieldError,
Input,
Label,
TextField,
} from "@/components/ui/text-field";
import { Input, Label, TextField } from "@/components/ui/text-field";
import { authClient } from "@/lib/auth-client";
import { orpc } from "@/lib/orpc";
import { queryClient } from "@/lib/query-client";
import { getServerUrl, splitUrl } from "@/lib/server-url";
import { toast } from "@/lib/toast";
import { getFormErrors } from "@/utils/form-errors";
import * as Haptics from "@/utils/haptics";
const signInSchema = z.object({
@@ -30,37 +30,33 @@ const signInSchema = z.object({
password: z.string().min(1, "Password is required"),
});
function formatFormErrors(errors: unknown): string | null {
if (!errors) return null;
if (typeof errors === "string") return errors;
if (typeof errors === "object") {
const first = Object.values(errors as Record<string, { message: string }[]>)
.flat()
.find((e) => e.message);
if (first) return first.message;
}
return null;
}
export default function LoginScreen() {
const passwordRef = useRef<TextInput>(null);
const [errorFields, setErrorFields] = useState<Set<string>>(new Set());
const [isSignedIn, setIsSignedIn] = useState(false);
const authConfig = useQuery(orpc.system.authConfig.queryOptions());
const form = useForm({
defaultValues: { email: "", password: "" },
validators: { onSubmit: signInSchema },
onSubmit: async ({ value, formApi }) => {
onSubmit: async ({ value }) => {
const result = signInSchema.safeParse(value);
if (!result.success) {
const { message, fields } = getFormErrors(result.error);
setErrorFields(fields);
toast.error(message);
return;
}
await authClient.signIn.email(
{ email: value.email.trim(), password: value.password },
{ email: result.data.email, password: result.data.password },
{
onError(error) {
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error);
Alert.alert("Error", error.error?.message || "Failed to sign in");
toast.error(error.error?.message || "Failed to sign in");
},
onSuccess() {
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
formApi.reset();
setIsSignedIn(true);
queryClient.invalidateQueries();
},
},
@@ -68,13 +64,30 @@ export default function LoginScreen() {
},
});
const isSubmitting = useStore(form.store, (s) => s.isSubmitting);
const busy = isSubmitting || isSignedIn;
const statusCompletedColor = useCSSVariable(
"--color-status-completed",
) as string;
const serverHost = splitUrl(getServerUrl()).host;
const showPasswordLogin = !authConfig.data?.passwordLoginDisabled;
const showOidc = authConfig.data?.oidcEnabled;
const showRegister = authConfig.data?.registrationOpen;
const clearFieldError = (name: string) => {
if (errorFields.has(name)) {
setErrorFields((prev) => {
const next = new Set(prev);
next.delete(name);
return next;
});
}
};
return (
<AuthScreen title="Sofa" subtitle="Sign in to continue">
<AuthStackHeader title="Sign In" />
{showOidc && (
<Animated.View
entering={FadeInDown.duration(300).delay(100)}
@@ -106,108 +119,119 @@ export default function LoginScreen() {
)}
{showPasswordLogin && (
<form.Subscribe
selector={(state) => ({
isSubmitting: state.isSubmitting,
validationError: formatFormErrors(state.errorMap.onSubmit),
})}
>
{({ isSubmitting, validationError }) => (
<View className="gap-3">
{validationError && (
<FieldError isInvalid className="mb-1">
{validationError}
</FieldError>
<View className="gap-3">
<Animated.View entering={FadeInDown.duration(300).delay(200)}>
<form.Field name="email">
{(field) => (
<TextField>
<Label>Email</Label>
<Input
value={field.state.value}
accessibilityLabel="Email"
onBlur={field.handleBlur}
onChangeText={(text) => {
field.handleChange(text);
clearFieldError("email");
}}
placeholder="wwhite@graymatter.biz"
keyboardType="email-address"
autoCapitalize="none"
autoComplete="email"
textContentType="emailAddress"
returnKeyType="next"
blurOnSubmit={false}
onSubmitEditing={() => passwordRef.current?.focus()}
className={
errorFields.has("email")
? "border-destructive"
: undefined
}
/>
</TextField>
)}
</form.Field>
</Animated.View>
<Animated.View entering={FadeInDown.duration(300).delay(200)}>
<form.Field name="email">
{(field) => (
<TextField>
<Label>Email</Label>
<Input
value={field.state.value}
accessibilityLabel="Email"
onBlur={field.handleBlur}
onChangeText={field.handleChange}
placeholder="email@example.com"
keyboardType="email-address"
autoCapitalize="none"
autoComplete="email"
textContentType="emailAddress"
returnKeyType="next"
blurOnSubmit={false}
onSubmitEditing={() => passwordRef.current?.focus()}
/>
</TextField>
)}
</form.Field>
</Animated.View>
<Animated.View entering={FadeInDown.duration(300).delay(300)}>
<form.Field name="password">
{(field) => (
<TextField>
<Label>Password</Label>
<Input
ref={passwordRef}
value={field.state.value}
accessibilityLabel="Password"
onBlur={field.handleBlur}
onChangeText={(text) => {
field.handleChange(text);
clearFieldError("password");
}}
placeholder="••••••••"
secureTextEntry
autoComplete="password"
textContentType="password"
returnKeyType="go"
onSubmitEditing={form.handleSubmit}
className={
errorFields.has("password")
? "border-destructive"
: undefined
}
/>
</TextField>
)}
</form.Field>
</Animated.View>
<Animated.View entering={FadeInDown.duration(300).delay(300)}>
<form.Field name="password">
{(field) => (
<TextField>
<Label>Password</Label>
<Input
ref={passwordRef}
value={field.state.value}
accessibilityLabel="Password"
onBlur={field.handleBlur}
onChangeText={field.handleChange}
placeholder="••••••••"
secureTextEntry
autoComplete="password"
textContentType="password"
returnKeyType="go"
onSubmitEditing={form.handleSubmit}
/>
</TextField>
)}
</form.Field>
</Animated.View>
<Animated.View entering={FadeInDown.duration(300).delay(400)}>
<Button
onPress={form.handleSubmit}
disabled={busy}
className="mt-2"
>
{busy ? (
<Spinner size="sm" />
) : (
<ButtonLabel>Sign In</ButtonLabel>
)}
</Button>
</Animated.View>
<Animated.View entering={FadeInDown.duration(300).delay(400)}>
<Button
onPress={form.handleSubmit}
disabled={isSubmitting}
className="mt-1 bg-primary"
>
{isSubmitting ? (
<Spinner size="sm" />
) : (
<ButtonLabel>Sign In</ButtonLabel>
)}
{showRegister && (
<Animated.View entering={FadeIn.duration(300).delay(500)}>
<Link href="/(auth)/register" asChild>
<Button disabled={busy} variant="secondary">
<ButtonLabel>Create an account</ButtonLabel>
</Button>
</Animated.View>
</View>
</Link>
</Animated.View>
)}
</form.Subscribe>
)}
{showRegister && (
<Animated.View
entering={FadeIn.duration(300).delay(500)}
className="mt-6 items-center"
>
<Link href="/(auth)/register" asChild>
<Pressable>
<Text className="text-primary text-sm">Create an account</Text>
</Pressable>
</Link>
</Animated.View>
<Animated.View
entering={FadeIn.duration(300).delay(500)}
className="mt-8 items-center"
>
<Link href="/(auth)/server-url" replace asChild>
<Pressable
disabled={busy}
accessibilityRole="button"
accessibilityState={{ disabled: busy }}
className="flex-row items-center gap-1.5"
>
<ScaledIcon
icon={IconServer2}
size={14}
color={statusCompletedColor}
/>
<Text className="font-sans text-muted-foreground text-xs">
Connected to <Text className="font-medium">{serverHost}</Text>
. Tap to change.
</Text>
</Pressable>
</Link>
</Animated.View>
</View>
)}
<Animated.View
entering={FadeIn.duration(300).delay(500)}
className="mt-4 items-center"
>
<Link href="/(auth)/server-url" asChild>
<Pressable>
<Text className="text-muted-foreground text-xs">Change server</Text>
</Pressable>
</Link>
</Animated.View>
</AuthScreen>
);
}
+144 -133
View File
@@ -1,24 +1,21 @@
import { useForm } from "@tanstack/react-form";
import { useForm, useStore } from "@tanstack/react-form";
import { useQuery } from "@tanstack/react-query";
import { Link } from "expo-router";
import { useRef } from "react";
import { Alert, Pressable, type TextInput, View } from "react-native";
import { useRef, useState } from "react";
import { Pressable, type TextInput, View } from "react-native";
import Animated, { FadeIn, FadeInDown } from "react-native-reanimated";
import { z } from "zod";
import { AuthScreen } from "@/components/auth-screen";
import { AuthStackHeader } from "@/components/navigation/auth-stack-header";
import { Button, ButtonLabel } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner";
import { Text } from "@/components/ui/text";
import {
FieldError,
Input,
Label,
TextField,
} from "@/components/ui/text-field";
import { Input, Label, TextField } from "@/components/ui/text-field";
import { authClient } from "@/lib/auth-client";
import { orpc } from "@/lib/orpc";
import { queryClient } from "@/lib/query-client";
import { getServerUrl, splitUrl } from "@/lib/server-url";
import { toast } from "@/lib/toast";
import { getFormErrors } from "@/utils/form-errors";
import * as Haptics from "@/utils/haptics";
const signUpSchema = z.object({
@@ -38,46 +35,39 @@ const signUpSchema = z.object({
.min(8, "Use at least 8 characters"),
});
function formatFormErrors(errors: unknown): string | null {
if (!errors) return null;
if (typeof errors === "string") return errors;
if (typeof errors === "object") {
const first = Object.values(errors as Record<string, { message: string }[]>)
.flat()
.find((e) => e.message);
if (first) return first.message;
}
return null;
}
export default function RegisterScreen() {
const emailRef = useRef<TextInput>(null);
const passwordRef = useRef<TextInput>(null);
const [errorFields, setErrorFields] = useState<Set<string>>(new Set());
const [isSignedUp, setIsSignedUp] = useState(false);
const publicInfo = useQuery(orpc.system.publicInfo.queryOptions());
const registrationOpen = publicInfo.data?.registrationOpen ?? false;
const form = useForm({
defaultValues: { name: "", email: "", password: "" },
validators: { onSubmit: signUpSchema },
onSubmit: async ({ value, formApi }) => {
onSubmit: async ({ value }) => {
const result = signUpSchema.safeParse(value);
if (!result.success) {
const { message, fields } = getFormErrors(result.error);
setErrorFields(fields);
toast.error(message);
return;
}
await authClient.signUp.email(
{
name: value.name.trim(),
email: value.email.trim(),
password: value.password,
name: result.data.name,
email: result.data.email,
password: result.data.password,
},
{
onError(error) {
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error);
Alert.alert(
"Error",
error.error?.message || "Failed to create account",
);
toast.error(error.error?.message || "Failed to create account");
},
onSuccess() {
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
formApi.reset();
setIsSignedUp(true);
queryClient.invalidateQueries();
},
},
@@ -85,13 +75,27 @@ export default function RegisterScreen() {
},
});
const isSubmitting = useStore(form.store, (s) => s.isSubmitting);
const busy = isSubmitting || isSignedUp;
const serverHost = splitUrl(getServerUrl()).host;
const clearFieldError = (name: string) => {
if (errorFields.has(name)) {
setErrorFields((prev) => {
const next = new Set(prev);
next.delete(name);
return next;
});
}
};
if (!registrationOpen && !publicInfo.isPending) {
return (
<AuthScreen
title="Registration Closed"
subtitle="New account creation is currently disabled."
>
<AuthStackHeader title="Registration Closed" />
<Animated.View entering={FadeInDown.duration(300).delay(200)}>
<Link href="/(auth)/login" asChild>
<Button className="mt-6 bg-primary">
@@ -106,115 +110,122 @@ export default function RegisterScreen() {
}
return (
<AuthScreen title="Create Account">
<AuthStackHeader title="Create Account" />
<form.Subscribe
selector={(state) => ({
isSubmitting: state.isSubmitting,
validationError: formatFormErrors(state.errorMap.onSubmit),
})}
>
{({ isSubmitting, validationError }) => (
<View className="gap-3">
{validationError && (
<FieldError isInvalid className="mb-1">
{validationError}
</FieldError>
<AuthScreen
title="Create Account"
subtitle={`Registering on ${serverHost}`}
>
<View className="gap-3">
<Animated.View entering={FadeInDown.duration(300).delay(100)}>
<form.Field name="name">
{(field) => (
<TextField>
<Label>Name</Label>
<Input
value={field.state.value}
accessibilityLabel="Name"
onBlur={field.handleBlur}
onChangeText={(text) => {
field.handleChange(text);
clearFieldError("name");
}}
placeholder="Your name"
autoComplete="name"
textContentType="name"
returnKeyType="next"
blurOnSubmit={false}
onSubmitEditing={() => emailRef.current?.focus()}
className={
errorFields.has("name") ? "border-destructive" : undefined
}
/>
</TextField>
)}
</form.Field>
</Animated.View>
<Animated.View entering={FadeInDown.duration(300).delay(100)}>
<form.Field name="name">
{(field) => (
<TextField>
<Label>Name</Label>
<Input
value={field.state.value}
accessibilityLabel="Name"
onBlur={field.handleBlur}
onChangeText={field.handleChange}
placeholder="Your name"
autoComplete="name"
textContentType="name"
returnKeyType="next"
blurOnSubmit={false}
onSubmitEditing={() => emailRef.current?.focus()}
/>
</TextField>
)}
</form.Field>
</Animated.View>
<Animated.View entering={FadeInDown.duration(300).delay(200)}>
<form.Field name="email">
{(field) => (
<TextField>
<Label>Email</Label>
<Input
ref={emailRef}
value={field.state.value}
accessibilityLabel="Email"
onBlur={field.handleBlur}
onChangeText={(text) => {
field.handleChange(text);
clearFieldError("email");
}}
placeholder="email@example.com"
keyboardType="email-address"
autoCapitalize="none"
autoComplete="email"
textContentType="emailAddress"
returnKeyType="next"
blurOnSubmit={false}
onSubmitEditing={() => passwordRef.current?.focus()}
className={
errorFields.has("email") ? "border-destructive" : undefined
}
/>
</TextField>
)}
</form.Field>
</Animated.View>
<Animated.View entering={FadeInDown.duration(300).delay(200)}>
<form.Field name="email">
{(field) => (
<TextField>
<Label>Email</Label>
<Input
ref={emailRef}
value={field.state.value}
accessibilityLabel="Email"
onBlur={field.handleBlur}
onChangeText={field.handleChange}
placeholder="email@example.com"
keyboardType="email-address"
autoCapitalize="none"
autoComplete="email"
textContentType="emailAddress"
returnKeyType="next"
blurOnSubmit={false}
onSubmitEditing={() => passwordRef.current?.focus()}
/>
</TextField>
)}
</form.Field>
</Animated.View>
<Animated.View entering={FadeInDown.duration(300).delay(300)}>
<form.Field name="password">
{(field) => (
<TextField>
<Label>Password</Label>
<Input
ref={passwordRef}
value={field.state.value}
accessibilityLabel="Password"
onBlur={field.handleBlur}
onChangeText={(text) => {
field.handleChange(text);
clearFieldError("password");
}}
placeholder="••••••••"
secureTextEntry
autoComplete="new-password"
textContentType="newPassword"
returnKeyType="go"
onSubmitEditing={form.handleSubmit}
className={
errorFields.has("password")
? "border-destructive"
: undefined
}
/>
</TextField>
)}
</form.Field>
</Animated.View>
<Animated.View entering={FadeInDown.duration(300).delay(300)}>
<form.Field name="password">
{(field) => (
<TextField>
<Label>Password</Label>
<Input
ref={passwordRef}
value={field.state.value}
accessibilityLabel="Password"
onBlur={field.handleBlur}
onChangeText={field.handleChange}
placeholder="••••••••"
secureTextEntry
autoComplete="new-password"
textContentType="newPassword"
returnKeyType="go"
onSubmitEditing={form.handleSubmit}
/>
</TextField>
)}
</form.Field>
</Animated.View>
<Animated.View entering={FadeInDown.duration(300).delay(400)}>
<Button
onPress={form.handleSubmit}
disabled={isSubmitting}
className="mt-1 bg-primary"
>
{isSubmitting ? (
<Spinner size="sm" />
) : (
<ButtonLabel>Create Account</ButtonLabel>
)}
</Button>
</Animated.View>
</View>
)}
</form.Subscribe>
<Animated.View entering={FadeInDown.duration(300).delay(400)}>
<Button
onPress={form.handleSubmit}
disabled={busy}
className="mt-1 bg-primary"
>
{busy ? (
<Spinner size="sm" />
) : (
<ButtonLabel>Create Account</ButtonLabel>
)}
</Button>
</Animated.View>
</View>
<Animated.View
entering={FadeIn.duration(300).delay(500)}
className="mt-6 items-center"
>
<Link href="/(auth)/login" asChild>
<Pressable>
<Pressable disabled={busy}>
<Text className="text-primary text-sm">
Already have an account? Sign in
</Text>
+19 -25
View File
@@ -5,7 +5,7 @@ import {
} from "@tabler/icons-react-native";
import { useRouter } from "expo-router";
import { useEffect, useRef, useState } from "react";
import { Linking, Pressable, TextInput, View } from "react-native";
import { Linking, Pressable, type TextInput, View } from "react-native";
import Animated, {
FadeIn,
FadeInDown,
@@ -16,10 +16,10 @@ import Animated, {
} from "react-native-reanimated";
import { useCSSVariable } from "uniwind";
import { AuthScreen } from "@/components/auth-screen";
import { AuthStackHeader } from "@/components/navigation/auth-stack-header";
import { Button, ButtonLabel } from "@/components/ui/button";
import { ScaledIcon } from "@/components/ui/scaled-icon";
import { Text } from "@/components/ui/text";
import { Input } from "@/components/ui/text-field";
import {
getServerUrl,
hasStoredServerUrl,
@@ -153,30 +153,24 @@ export default function ServerUrlScreen() {
subtitle="Enter your Sofa server URL to get started"
logoStyle={iconAnimatedStyle}
>
<AuthStackHeader title="Server" />
<Animated.View entering={FadeInDown.duration(300).delay(200)}>
<View
className="min-h-12 flex-row items-center rounded-[12px] border border-border bg-input px-3.5"
style={{ borderCurve: "continuous" }}
>
<TextInput
ref={inputRef}
value={url}
accessibilityLabel="Server URL"
accessibilityHint="Enter the full URL for your Sofa server"
onChangeText={handleChangeText}
placeholder="https://sofa.example.com"
placeholderTextColorClassName="accent-muted-foreground/50"
keyboardType="url"
autoCapitalize="none"
autoCorrect={false}
textContentType="URL"
returnKeyType="go"
editable={!isDisabled}
onSubmitEditing={handleConnect}
className="flex-1 py-3 font-mono text-base text-foreground"
/>
</View>
<Input
ref={inputRef}
value={url}
accessibilityLabel="Server URL"
accessibilityHint="Enter the full URL for your Sofa server"
onChangeText={handleChangeText}
placeholder="https://sofa.example.com"
placeholderTextColorClassName="accent-muted-foreground/50"
keyboardType="url"
autoCapitalize="none"
autoCorrect={false}
textContentType="URL"
returnKeyType="go"
editable={!isDisabled}
onSubmitEditing={handleConnect}
className="font-mono"
/>
</Animated.View>
{/* Connect Button / Status */}
@@ -74,12 +74,9 @@ export default function ExploreScreen() {
className="bg-background"
contentContainerStyle={exploreContentContainerStyle}
contentInsetAdjustmentBehavior="automatic"
scrollToOverflowEnabled
refreshControl={
<RefreshControl
refreshing={isRefreshing}
onRefresh={onRefresh}
tintColorClassName="accent-primary"
/>
<RefreshControl refreshing={isRefreshing} onRefresh={onRefresh} />
}
>
<View className="gap-8">
+2 -5
View File
@@ -83,12 +83,9 @@ export default function DashboardScreen() {
className="bg-background"
contentContainerStyle={dashboardContentContainerStyle}
contentInsetAdjustmentBehavior="automatic"
scrollToOverflowEnabled
refreshControl={
<RefreshControl
refreshing={isRefreshing}
onRefresh={onRefresh}
tintColorClassName="accent-primary"
/>
<RefreshControl refreshing={isRefreshing} onRefresh={onRefresh} />
}
>
<View className="gap-8">
@@ -210,12 +210,9 @@ export default function SettingsScreen() {
className="bg-background"
contentContainerStyle={settingsContentContainerStyle}
contentInsetAdjustmentBehavior="automatic"
scrollToOverflowEnabled
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={onRefresh}
tintColorClassName="accent-primary"
/>
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}
>
{/* Account */}
@@ -286,7 +283,7 @@ export default function SettingsScreen() {
</View>
) : (
<View className="flex-1 items-center justify-center bg-primary/[0.08]">
<Text className="font-medium font-sans text-lg text-primary">
<Text className="font-display font-medium text-lg text-primary">
{session?.user?.name?.charAt(0)?.toUpperCase() ?? "?"}
</Text>
</View>
+15 -1
View File
@@ -1,5 +1,19 @@
import { useQuery } from "@tanstack/react-query";
import { NativeTabBar } from "@/components/navigation/native-tab-bar";
import { authClient } from "@/lib/auth-client";
import { orpc } from "@/lib/orpc";
export default function TabLayout() {
return <NativeTabBar />;
const { data: session } = authClient.useSession();
const isAdmin = session?.user?.role === "admin";
const updateCheck = useQuery({
...orpc.admin.updateCheck.queryOptions(),
enabled: isAdmin,
staleTime: 10 * 60 * 1000,
});
const showSettingsBadge = !!updateCheck.data?.updateCheck?.updateAvailable;
return <NativeTabBar showSettingsBadge={showSettingsBadge} />;
}
+34 -12
View File
@@ -5,7 +5,12 @@ import {
persistQueryClientRestore,
persistQueryClientSubscribe,
} from "@tanstack/react-query-persist-client";
import { Stack, useGlobalSearchParams, usePathname } from "expo-router";
import {
Stack,
useGlobalSearchParams,
usePathname,
useRouter,
} from "expo-router";
import * as SplashScreen from "expo-splash-screen";
import { StatusBar } from "expo-status-bar";
import {
@@ -17,6 +22,7 @@ import { PostHogErrorBoundary, PostHogProvider } from "posthog-react-native";
import { useEffect, useRef, useState } from "react";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { KeyboardProvider } from "react-native-keyboard-controller";
import { enableFreeze } from "react-native-screens";
import { Uniwind, useResolveClassNames } from "uniwind";
import { OfflineBanner } from "@/components/ui/offline-banner";
import { ServerUnreachableBanner } from "@/components/ui/server-unreachable-banner";
@@ -45,6 +51,7 @@ import { sofaTheme } from "@/lib/theme";
import { toast } from "@/lib/toast";
SplashScreen.preventAutoHideAsync();
enableFreeze(true);
// Seed the session atom with cached data from SecureStore before React renders.
// This allows the app to show cached data immediately when the server is
@@ -75,7 +82,7 @@ const changePasswordOptions =
sheetAllowedDetents: "fitToContents" as const,
sheetGrabberVisible: true,
headerLargeTitle: false,
headerTransparent: false,
headerTransparent: true,
headerBlurEffect: "none" as const,
}
: {
@@ -188,9 +195,18 @@ function AppContent() {
}, [hasServerUrl]);
// --- Session reconciliation: re-validate when server comes back ---
// Tracks whether the current session was seeded from cache and has NOT
// yet been confirmed by the server. Once confirmed (isRefetching becomes
// false while session still exists), this flips to false so that an
// explicit sign-out doesn't show a misleading "session expired" toast.
const hadOptimisticSession = useRef(!!cachedSession);
const prevSession = useRef(session);
const { isRefetching } = authClient.useSession();
if (hadOptimisticSession.current && session && !isRefetching) {
hadOptimisticSession.current = false;
}
useEffect(() => {
return onServerReachabilityChange((reachable) => {
if (reachable) {
@@ -201,17 +217,24 @@ function AppContent() {
});
}, []);
// If session was seeded from cache and then invalidated by the server,
// show a toast so the user knows why they were signed out.
const { replace } = useRouter();
// When session is lost (sign-out or server invalidation), explicitly
// navigate to auth. Stack.Protected handles screen availability, but
// enableFreeze can prevent the navigator from transitioning on its own.
useEffect(() => {
if (prevSession.current && !session && hadOptimisticSession.current) {
toast.info("Session expired", {
description: "Please sign in again.",
});
hadOptimisticSession.current = false;
if (prevSession.current && !session) {
replace("/(auth)/login");
if (hadOptimisticSession.current) {
toast.info("Session expired", {
description: "Please sign in again.",
});
hadOptimisticSession.current = false;
}
}
prevSession.current = session;
}, [session]);
}, [session, replace]);
return (
<ThemeProvider value={sofaTheme}>
@@ -239,8 +262,7 @@ function AppContent() {
name="title/[id]"
dangerouslySingular
options={{
headerShown: true,
animation: "slide_from_right",
presentation: "modal",
}}
/>
<Stack.Screen
+2 -2
View File
@@ -42,7 +42,7 @@ function formatFormErrors(errors: unknown): string | null {
}
export default function ChangePasswordScreen() {
const router = useRouter();
const { back } = useRouter();
const newPasswordRef = useRef<TextInput>(null);
const confirmPasswordRef = useRef<TextInput>(null);
const [revokeOtherSessions, setRevokeOtherSessions] = useState(false);
@@ -73,7 +73,7 @@ export default function ChangePasswordScreen() {
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
toast.success("Password updated");
formApi.reset();
router.back();
back();
} catch {
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error);
Alert.alert("Error", "Something went wrong");
+32 -33
View File
@@ -21,7 +21,7 @@ import {
import Animated, { FadeIn, FadeInDown } from "react-native-reanimated";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { useCSSVariable } from "uniwind";
import { DetailStackHeader } from "@/components/navigation/detail-stack-header";
import { DetailStackHeader } from "@/components/navigation/modal-stack-header";
import { ExpandableText } from "@/components/ui/expandable-text";
import { Image } from "@/components/ui/image";
import { PosterCard } from "@/components/ui/poster-card";
@@ -249,6 +249,8 @@ export default function PersonDetailScreen() {
const listHeader = (
<>
<DetailStackHeader />
{/* Profile hero */}
<Animated.View
entering={FadeIn.duration(400)}
@@ -336,38 +338,35 @@ export default function PersonDetailScreen() {
);
return (
<>
<DetailStackHeader title={person.name} />
<View className="flex-1 bg-background">
<FlashList
data={filmography}
keyExtractor={(item) => item.titleId}
renderItem={renderFilmographyItem}
numColumns={filmographyColumns}
showsVerticalScrollIndicator={false}
contentInsetAdjustmentBehavior={
useAutomaticInsets ? "automatic" : "never"
<View className="flex-1 bg-background" collapsable={false}>
<FlashList
data={filmography}
keyExtractor={(item) => item.titleId}
renderItem={renderFilmographyItem}
numColumns={filmographyColumns}
showsVerticalScrollIndicator={false}
contentInsetAdjustmentBehavior={
useAutomaticInsets ? "automatic" : "never"
}
contentContainerStyle={{
paddingBottom: useAutomaticInsets ? 32 : insets.bottom + 32,
paddingHorizontal: FILMOGRAPHY_PADDING - FILMOGRAPHY_GUTTER,
}}
ListHeaderComponent={listHeader}
onEndReached={() => {
if (hasNextPage && !isFetchingNextPage) {
fetchNextPage();
}
contentContainerStyle={{
paddingBottom: useAutomaticInsets ? 32 : insets.bottom + 32,
paddingHorizontal: FILMOGRAPHY_PADDING - FILMOGRAPHY_GUTTER,
}}
ListHeaderComponent={listHeader}
onEndReached={() => {
if (hasNextPage && !isFetchingNextPage) {
fetchNextPage();
}
}}
onEndReachedThreshold={0.5}
ListFooterComponent={
isFetchingNextPage ? (
<View className="items-center py-4">
<ActivityIndicator />
</View>
) : null
}
/>
</View>
</>
}}
onEndReachedThreshold={0.5}
ListFooterComponent={
isFetchingNextPage ? (
<View className="items-center py-4">
<ActivityIndicator />
</View>
) : null
}
/>
</View>
);
}
+307 -308
View File
@@ -25,7 +25,7 @@ import {
HorizontalPosterRow,
type PosterRowItem,
} from "@/components/dashboard/horizontal-poster-row";
import { DetailStackHeader } from "@/components/navigation/detail-stack-header";
import { DetailStackHeader } from "@/components/navigation/modal-stack-header";
import { CastCard } from "@/components/titles/cast-card";
import { ContinueWatchingBanner } from "@/components/titles/continue-watching-banner";
import { SeasonAccordion } from "@/components/titles/season-accordion";
@@ -306,334 +306,333 @@ export default function TitleDetailScreen() {
const year = (title.releaseDate ?? title.firstAirDate)?.slice(0, 4);
return (
<>
<DetailStackHeader title={title.title} />
<ScrollView
className="bg-background"
contentInsetAdjustmentBehavior={
useAutomaticInsets ? "automatic" : "never"
}
contentContainerStyle={titleScrollContentStyle}
<ScrollView
className="bg-background"
contentInsetAdjustmentBehavior={
useAutomaticInsets ? "automatic" : "never"
}
contentContainerStyle={titleScrollContentStyle}
>
<DetailStackHeader />
{/* Hero */}
<View className="h-[300px]" style={heroMarginStyle}>
{title.backdropPath && (
<Image
source={{ uri: title.backdropPath }}
thumbHash={title.backdropThumbHash}
style={titleDetailStyles.heroFill}
contentFit="cover"
/>
)}
{/* Base darkening overlay */}
<View style={titleDetailStyles.heroBaseOverlay} />
{/* Colored tint from palette */}
{palette?.darkMuted && (
<View style={[titleDetailStyles.heroFill, darkMutedOverlayStyle]} />
)}
{palette?.vibrant && (
<View style={[titleDetailStyles.heroFill, vibrantOverlayStyle]} />
)}
{/* Bottom fade to background */}
<LinearGradient
colors={["transparent", "rgba(0,0,0,0.6)", "rgba(0,0,0,0.95)"]}
locations={[0, 0.5, 1]}
style={titleDetailStyles.heroGradient}
/>
{title.trailerVideoKey && (
<Pressable
onPress={() =>
WebBrowser.openBrowserAsync(
`https://www.youtube.com/watch?v=${title.trailerVideoKey}`,
)
}
accessibilityRole="button"
accessibilityLabel={`Play trailer for ${title.title}`}
accessibilityHint="Opens the trailer in YouTube"
className="absolute inset-0 items-center justify-center"
>
{isLiquidGlassAvailable() ? (
<GlassView
glassEffectStyle="clear"
colorScheme="dark"
isInteractive={true}
style={titleDetailStyles.trailerGlass}
>
<IconPlayerPlay size={28} color="white" fill="white" />
</GlassView>
) : (
<View
className="h-14 w-14 items-center justify-center rounded-full"
style={titleDetailStyles.trailerFallback}
>
<IconPlayerPlay size={28} color="white" fill="white" />
</View>
)}
</Pressable>
)}
<View className="absolute right-0 bottom-0 left-0 flex-row items-end p-4">
{title.posterPath && (
<Link.AppleZoomTarget>
<View
className="mr-3 h-[150px] w-[100px] overflow-hidden rounded-lg"
style={[{ borderCurve: "continuous" }, posterShadowStyle]}
>
<Image
source={{ uri: title.posterPath }}
thumbHash={title.posterThumbHash}
style={titleDetailStyles.posterImage}
contentFit="cover"
/>
</View>
</Link.AppleZoomTarget>
)}
<View className="flex-1 pb-1">
<Text
className="font-display text-2xl text-white"
numberOfLines={2}
>
{title.title}
</Text>
<View className="mt-1.5 flex-row flex-wrap items-center gap-2">
<View className="rounded-full bg-title-accent px-2 py-0.5">
<Text
maxFontSizeMultiplier={1.0}
className="font-medium font-sans text-title-accent-foreground text-xs"
>
{title.type === "movie" ? "Movie" : "TV"}
</Text>
</View>
{year ? (
<Text className="text-sm text-white/70">{year}</Text>
) : null}
{title.contentRating ? (
<Text className="text-white/50 text-xs">
{title.contentRating}
</Text>
) : null}
{title.voteAverage != null && title.voteAverage > 0 && (
<View className="flex-row items-center gap-0.5">
<ScaledIcon
icon={IconStarFilled}
size={12}
color={titleAccent}
/>
<Text className="text-title-accent text-xs">
{title.voteAverage.toFixed(1)}
</Text>
</View>
)}
</View>
</View>
</View>
</View>
{/* Genres */}
{title.genres && title.genres.length > 0 && (
<Animated.View entering={FadeInDown.duration(300).delay(100)}>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
className="mt-3"
contentContainerStyle={titleGenresContentStyle}
>
{title.genres.map((genre: string) => (
<View
key={genre}
className="mr-2 rounded-full bg-secondary px-2.5 py-1"
>
<Text className="text-muted-foreground text-xs">{genre}</Text>
</View>
))}
</ScrollView>
</Animated.View>
)}
{/* Actions */}
<Animated.View
entering={FadeInDown.duration(300).delay(200)}
className="mt-4 px-4"
>
{/* Hero */}
<View className="h-[300px]" style={heroMarginStyle}>
{title.backdropPath && (
<Image
source={{ uri: title.backdropPath }}
thumbHash={title.backdropThumbHash}
style={titleDetailStyles.heroFill}
contentFit="cover"
/>
)}
{/* Base darkening overlay */}
<View style={titleDetailStyles.heroBaseOverlay} />
{/* Colored tint from palette */}
{palette?.darkMuted && (
<View style={[titleDetailStyles.heroFill, darkMutedOverlayStyle]} />
)}
{palette?.vibrant && (
<View style={[titleDetailStyles.heroFill, vibrantOverlayStyle]} />
)}
{/* Bottom fade to background */}
<LinearGradient
colors={["transparent", "rgba(0,0,0,0.6)", "rgba(0,0,0,0.95)"]}
locations={[0, 0.5, 1]}
style={titleDetailStyles.heroGradient}
<View className="flex-row flex-wrap items-center gap-3">
<StatusActionButton
currentStatus={userInfo.data?.status ?? null}
onStatusChange={(status) => {
if (status === "watchlist") {
quickAddMutation.mutate({ id });
} else {
updateStatus.mutate({ id, status: null });
}
}}
isPending={
updateStatus.isPending ||
quickAddMutation.isPending ||
watchMovie.isPending
}
/>
{title.trailerVideoKey && (
{title.type === "movie" && (
<Pressable
onPress={() =>
WebBrowser.openBrowserAsync(
`https://www.youtube.com/watch?v=${title.trailerVideoKey}`,
)
}
accessibilityRole="button"
accessibilityLabel={`Play trailer for ${title.title}`}
accessibilityHint="Opens the trailer in YouTube"
className="absolute inset-0 items-center justify-center"
onPress={() => watchMovie.mutate({ id })}
disabled={watchMovie.isPending}
className="flex-row items-center gap-1.5 rounded-lg bg-title-accent px-4 py-2"
>
{isLiquidGlassAvailable() ? (
<GlassView
glassEffectStyle="clear"
colorScheme="dark"
isInteractive={true}
style={titleDetailStyles.trailerGlass}
>
<IconPlayerPlay size={28} color="white" fill="white" />
</GlassView>
{watchMovie.isPending ? (
<Spinner size="sm" />
) : (
<View
className="h-14 w-14 items-center justify-center rounded-full"
style={titleDetailStyles.trailerFallback}
>
<IconPlayerPlay size={28} color="white" fill="white" />
</View>
<>
<ScaledIcon
icon={IconCheck}
size={16}
color={titleAccentForeground}
/>
<Text className="font-medium font-sans text-sm text-title-accent-foreground">
Mark Watched
</Text>
</>
)}
</Pressable>
)}
<View className="absolute right-0 bottom-0 left-0 flex-row items-end p-4">
{title.posterPath && (
<Link.AppleZoomTarget>
<View
className="mr-3 h-[150px] w-[100px] overflow-hidden rounded-lg"
style={[{ borderCurve: "continuous" }, posterShadowStyle]}
>
<Image
source={{ uri: title.posterPath }}
thumbHash={title.posterThumbHash}
style={titleDetailStyles.posterImage}
contentFit="cover"
/>
</View>
</Link.AppleZoomTarget>
)}
<View className="flex-1 pb-1">
<Text
className="font-display text-2xl text-white"
numberOfLines={2}
>
{title.title}
</Text>
<View className="mt-1.5 flex-row flex-wrap items-center gap-2">
<View className="rounded-full bg-title-accent px-2 py-0.5">
<Text
maxFontSizeMultiplier={1.0}
className="font-medium font-sans text-title-accent-foreground text-xs"
>
{title.type === "movie" ? "Movie" : "TV"}
</Text>
</View>
{year ? (
<Text className="text-sm text-white/70">{year}</Text>
) : null}
{title.contentRating ? (
<Text className="text-white/50 text-xs">
{title.contentRating}
</Text>
) : null}
{title.voteAverage != null && title.voteAverage > 0 && (
<View className="flex-row items-center gap-0.5">
<ScaledIcon
icon={IconStarFilled}
size={12}
color={titleAccent}
/>
<Text className="text-title-accent text-xs">
{title.voteAverage.toFixed(1)}
</Text>
</View>
)}
</View>
</View>
</View>
<View className="h-6 w-px bg-border/50" />
<StarRating
rating={userInfo.data?.rating ?? 0}
onRate={(stars) => updateRating.mutate({ id, stars })}
accentColor={titleAccent}
/>
</View>
</Animated.View>
{/* Genres */}
{title.genres && title.genres.length > 0 && (
<Animated.View entering={FadeInDown.duration(300).delay(100)}>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
className="mt-3"
contentContainerStyle={titleGenresContentStyle}
>
{title.genres.map((genre: string) => (
<View
key={genre}
className="mr-2 rounded-full bg-secondary px-2.5 py-1"
>
<Text className="text-muted-foreground text-xs">{genre}</Text>
</View>
))}
</ScrollView>
</Animated.View>
)}
{/* Actions */}
<Animated.View
entering={FadeInDown.duration(300).delay(200)}
className="mt-4 px-4"
>
<View className="flex-row flex-wrap items-center gap-3">
<StatusActionButton
currentStatus={userInfo.data?.status ?? null}
onStatusChange={(status) => {
if (status === "watchlist") {
quickAddMutation.mutate({ id });
} else {
updateStatus.mutate({ id, status: null });
}
}}
isPending={
updateStatus.isPending ||
quickAddMutation.isPending ||
watchMovie.isPending
}
/>
{title.type === "movie" && (
<Pressable
onPress={() => watchMovie.mutate({ id })}
disabled={watchMovie.isPending}
className="flex-row items-center gap-1.5 rounded-lg bg-title-accent px-4 py-2"
>
{watchMovie.isPending ? (
<Spinner size="sm" />
) : (
<>
<ScaledIcon
icon={IconCheck}
size={16}
color={titleAccentForeground}
/>
<Text className="font-medium font-sans text-sm text-title-accent-foreground">
Mark Watched
</Text>
</>
)}
</Pressable>
)}
<View className="h-6 w-px bg-border/50" />
<StarRating
rating={userInfo.data?.rating ?? 0}
onRate={(stars) => updateRating.mutate({ id, stars })}
accentColor={titleAccent}
/>
{/* Overview */}
{title.overview ? (
<Animated.View entering={FadeIn.duration(300).delay(300)}>
<View className="mt-5 px-4">
<ExpandableText text={title.overview} actionColor={titleAccent} />
</View>
</Animated.View>
) : null}
{/* Overview */}
{title.overview ? (
<Animated.View entering={FadeIn.duration(300).delay(300)}>
<View className="mt-5 px-4">
<ExpandableText text={title.overview} actionColor={titleAccent} />
</View>
</Animated.View>
) : null}
{/* Availability */}
{availability.length > 0 && (
<Animated.View
entering={FadeInDown.duration(300).delay(400)}
className="mt-6"
>
<View className="px-4">
<SectionHeader
title="Where to Watch"
icon={providerIcon}
iconColor={titleAccent}
/>
</View>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={titleAvailabilityContentStyle}
>
{availability.map((offer) => (
<View
key={`${offer.providerId}-${offer.offerType}`}
className="items-center"
>
{offer.logoPath && (
<Image
source={{ uri: offer.logoPath }}
style={titleDetailStyles.providerLogo}
contentFit="cover"
/>
)}
<Text
maxFontSizeMultiplier={1.0}
className="mt-1 max-w-[60px] text-center text-muted-foreground text-xs"
numberOfLines={1}
>
{offer.providerName}
</Text>
</View>
))}
</ScrollView>
</Animated.View>
)}
{/* Continue Watching */}
{title.type === "tv" && (
<ContinueWatchingBanner
seasons={seasons}
watchedEpisodeIds={watchedEpisodeIds}
userStatus={userInfo.data?.status ?? null}
backdropPath={title.backdropPath}
backdropThumbHash={title.backdropThumbHash}
/>
)}
{/* Seasons & Episodes */}
{title.type === "tv" && seasons.length > 0 && (
<Animated.View
entering={FadeInDown.duration(300).delay(400)}
className="mt-6 px-4"
>
{/* Availability */}
{availability.length > 0 && (
<Animated.View
entering={FadeInDown.duration(300).delay(400)}
className="mt-6"
>
<View className="px-4">
<SectionHeader
title="Seasons"
icon={IconList}
title="Where to Watch"
icon={providerIcon}
iconColor={titleAccent}
/>
{seasons.map((season) => (
<SeasonAccordion
key={season.id}
season={season}
episodes={season.episodes ?? []}
watchedEpisodeIds={watchedEpisodeIds}
/>
</View>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={titleAvailabilityContentStyle}
>
{availability.map((offer) => (
<View
key={`${offer.providerId}-${offer.offerType}`}
className="items-center"
>
{offer.logoPath && (
<Image
source={{ uri: offer.logoPath }}
style={titleDetailStyles.providerLogo}
contentFit="cover"
/>
)}
<Text
maxFontSizeMultiplier={1.0}
className="mt-1 max-w-[60px] text-center text-muted-foreground text-xs"
numberOfLines={1}
>
{offer.providerName}
</Text>
</View>
))}
</Animated.View>
)}
</ScrollView>
</Animated.View>
)}
{/* Cast */}
{cast.length > 0 && (
<Animated.View
entering={FadeInDown.duration(300).delay(500)}
className="mt-6"
>
<View className="px-4">
<SectionHeader
title="Cast"
icon={IconUsers}
iconColor={titleAccent}
/>
</View>
<FlashList
horizontal
showsHorizontalScrollIndicator={false}
data={cast}
keyExtractor={(item, index) => `${item.id}-${index}`}
renderItem={renderCastItem}
ItemSeparatorComponent={HorizontalListSeparator}
contentContainerStyle={horizontalListContentStyle}
style={horizontalListStyle}
{/* Continue Watching */}
{title.type === "tv" && (
<ContinueWatchingBanner
seasons={seasons}
watchedEpisodeIds={watchedEpisodeIds}
userStatus={userInfo.data?.status ?? null}
backdropPath={title.backdropPath}
backdropThumbHash={title.backdropThumbHash}
/>
)}
{/* Seasons & Episodes */}
{title.type === "tv" && seasons.length > 0 && (
<Animated.View
entering={FadeInDown.duration(300).delay(400)}
className="mt-6 px-4"
>
<SectionHeader
title="Seasons"
icon={IconList}
iconColor={titleAccent}
/>
{seasons.map((season) => (
<SeasonAccordion
key={season.id}
season={season}
episodes={season.episodes ?? []}
watchedEpisodeIds={watchedEpisodeIds}
/>
</Animated.View>
)}
))}
</Animated.View>
)}
{/* Recommendations */}
{recItems.length > 0 && (
<Animated.View
entering={FadeInDown.duration(300).delay(600)}
className="mt-6"
>
<View className="px-4">
<SectionHeader
title="More Like This"
icon={IconThumbUp}
iconColor={titleAccent}
/>
</View>
<HorizontalPosterRow items={recItems} />
</Animated.View>
)}
</ScrollView>
</>
{/* Cast */}
{cast.length > 0 && (
<Animated.View
entering={FadeInDown.duration(300).delay(500)}
className="mt-6"
>
<View className="px-4">
<SectionHeader
title="Cast"
icon={IconUsers}
iconColor={titleAccent}
/>
</View>
<FlashList
horizontal
showsHorizontalScrollIndicator={false}
data={cast}
keyExtractor={(item, index) => `${item.id}-${index}`}
renderItem={renderCastItem}
ItemSeparatorComponent={HorizontalListSeparator}
contentContainerStyle={horizontalListContentStyle}
style={horizontalListStyle}
/>
</Animated.View>
)}
{/* Recommendations */}
{recItems.length > 0 && (
<Animated.View
entering={FadeInDown.duration(300).delay(600)}
className="mt-6"
>
<View className="px-4">
<SectionHeader
title="More Like This"
icon={IconThumbUp}
iconColor={titleAccent}
/>
</View>
<HorizontalPosterRow items={recItems} />
</Animated.View>
)}
</ScrollView>
);
}
+2 -6
View File
@@ -19,19 +19,15 @@ export function AuthScreen({
logoStyle,
children,
}: AuthScreenProps) {
const useAutomaticInsets = process.env.EXPO_OS === "ios";
return (
<KeyboardAvoidingView behavior="padding" style={{ flex: 1 }}>
<ScrollView
contentInsetAdjustmentBehavior={
useAutomaticInsets ? "automatic" : "never"
}
contentInsetAdjustmentBehavior="never"
contentContainerStyle={{
flexGrow: 1,
justifyContent: "center",
paddingHorizontal: 24,
paddingTop: 24,
paddingTop: 42,
paddingBottom: 24,
}}
keyboardShouldPersistTaps="handled"
+3 -3
View File
@@ -9,7 +9,7 @@ import * as Haptics from "@/utils/haptics";
export function HeaderAvatar() {
const { data: session } = authClient.useSession();
const router = useRouter();
const { navigate } = useRouter();
if (!session?.user) return null;
@@ -39,7 +39,7 @@ export function HeaderAvatar() {
<View className="flex-1 items-center justify-center bg-primary/[0.08]">
<Text
maxFontSizeMultiplier={1.0}
className="font-medium font-sans text-primary text-sm"
className="font-display font-medium text-primary text-sm"
>
{user.name?.charAt(0)?.toUpperCase() ?? "?"}
</Text>
@@ -52,7 +52,7 @@ export function HeaderAvatar() {
<DropdownMenu.Content>
<DropdownMenu.Item
key="settings"
onSelect={() => router.navigate("/(tabs)/(settings)")}
onSelect={() => navigate("/(tabs)/(settings)")}
>
<DropdownMenu.ItemIcon
ios={{ name: "gear" }}
@@ -1,22 +0,0 @@
import { Stack } from "expo-router";
import { useCSSVariable, useResolveClassNames } from "uniwind";
export function AuthStackHeader({ title }: { title: string }) {
const headerTitleStyle = useResolveClassNames(
"font-display text-base text-foreground",
);
const tintColor = useCSSVariable("--color-primary") as string;
return (
<>
<Stack.Header
transparent
style={{ color: tintColor, shadowColor: "transparent" }}
/>
<Stack.Screen.BackButton displayMode="minimal" />
<Stack.Screen.Title style={headerTitleStyle as Record<string, unknown>}>
{title}
</Stack.Screen.Title>
</>
);
}
@@ -1,14 +0,0 @@
import { Stack } from "expo-router";
export function DetailStackHeader({ title }: { title?: string }) {
return (
<>
<Stack.Header transparent blurEffect="none" style={{ color: "white" }} />
<Stack.Screen.BackButton
displayMode="minimal"
withMenu={process.env.EXPO_OS === "ios"}
/>
{title ? <Stack.Screen.Title>{title}</Stack.Screen.Title> : null}
</>
);
}
@@ -0,0 +1,20 @@
import { Stack, useRouter } from "expo-router";
import { View } from "react-native";
export function DetailStackHeader() {
const { dismissAll } = useRouter();
return (
<>
<Stack.Header transparent blurEffect="none" />
<Stack.Toolbar placement="right">
<Stack.Toolbar.Button onPress={() => dismissAll()}>
<Stack.Toolbar.Icon sf="xmark" />
<Stack.Toolbar.Label>Close</Stack.Toolbar.Label>
</Stack.Toolbar.Button>
</Stack.Toolbar>
<Stack.Screen.Title asChild>
<View />
</Stack.Screen.Title>
</>
);
}
@@ -1,27 +1,21 @@
import { useQuery } from "@tanstack/react-query";
import {
NativeTabs,
type NativeTabsProps,
} from "expo-router/unstable-native-tabs";
import { useMemo } from "react";
import { useCSSVariable, useResolveClassNames } from "uniwind";
import { authClient } from "@/lib/auth-client";
import { orpc } from "@/lib/orpc";
import * as Haptics from "@/utils/haptics";
export function NativeTabBar() {
export function NativeTabBar({
showSettingsBadge,
}: {
showSettingsBadge: boolean;
}) {
const primaryColor = useCSSVariable("--color-primary") as string;
const mutedFgColor = useCSSVariable("--color-muted-foreground") as string;
const surfaceColor = useCSSVariable("--color-card") as string;
const rippleColor = useCSSVariable("--color-secondary") as string;
const { data: session } = authClient.useSession();
const isAdmin = session?.user?.role === "admin";
const updateCheck = useQuery({
...orpc.admin.updateCheck.queryOptions(),
enabled: isAdmin,
staleTime: 10 * 60 * 1000,
});
const showSettingsBadge = !!updateCheck.data?.updateCheck?.updateAvailable;
const labelTextStyle = useResolveClassNames("font-medium font-sans text-xs");
const screenListeners = useMemo<NativeTabsProps["screenListeners"]>(
() => ({
@@ -31,7 +25,6 @@ export function NativeTabBar() {
}),
[],
);
const labelTextStyle = useResolveClassNames("font-medium font-sans text-xs");
return (
<NativeTabs
@@ -43,14 +36,8 @@ export function NativeTabBar() {
}}
indicatorColor="transparent"
labelStyle={{
default: [
labelTextStyle as Record<string, unknown>,
{ color: mutedFgColor },
],
selected: [
labelTextStyle as Record<string, unknown>,
{ color: primaryColor },
],
default: [labelTextStyle, { color: mutedFgColor }],
selected: [labelTextStyle, { color: primaryColor }],
}}
labelVisibilityMode="labeled"
rippleColor={rippleColor}
@@ -1,77 +1,54 @@
import { useQuery } from "@tanstack/react-query";
import * as Haptics from "expo-haptics";
import { Stack } from "expo-router";
import { NativeTabs } from "expo-router/unstable-native-tabs";
import { useState } from "react";
import { useCSSVariable } from "uniwind";
import { authClient } from "@/lib/auth-client";
import { orpc } from "@/lib/orpc";
const tabTitles: Record<string, string> = {
"(home)": "Home",
"(explore)": "Explore",
"(search)": "Search",
"(settings)": "Settings",
};
export function NativeTabBar() {
export function NativeTabBar({
showSettingsBadge,
}: {
showSettingsBadge: boolean;
}) {
const mutedFgColor = useCSSVariable("--color-muted-foreground") as string;
const primaryColor = useCSSVariable("--color-primary") as string;
const [activeTitle, setActiveTitle] = useState("Home");
const { data: session } = authClient.useSession();
const isAdmin = session?.user?.role === "admin";
const updateCheck = useQuery({
...orpc.admin.updateCheck.queryOptions(),
enabled: isAdmin,
staleTime: 10 * 60 * 1000,
});
const showSettingsBadge = !!updateCheck.data?.updateCheck?.updateAvailable;
return (
<>
<Stack.Screen options={{ title: activeTitle }} />
<NativeTabs
iconColor={{
default: mutedFgColor,
selected: primaryColor,
}}
labelStyle={{
default: { color: mutedFgColor },
selected: { color: primaryColor },
}}
screenListeners={({ route }) => ({
tabPress: () => {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
},
focus: () => {
setActiveTitle(tabTitles[route.name] ?? "Home");
},
})}
<NativeTabs
iconColor={{
default: mutedFgColor,
selected: primaryColor,
}}
labelStyle={{
default: { color: mutedFgColor },
selected: { color: primaryColor },
}}
screenListeners={() => ({
tabPress: () => {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
},
})}
>
<NativeTabs.Trigger name="(home)" disableTransparentOnScrollEdge>
<NativeTabs.Trigger.Label>Home</NativeTabs.Trigger.Label>
<NativeTabs.Trigger.Icon sf="house.fill" md="home" />
</NativeTabs.Trigger>
<NativeTabs.Trigger name="(explore)" disableTransparentOnScrollEdge>
<NativeTabs.Trigger.Label>Explore</NativeTabs.Trigger.Label>
<NativeTabs.Trigger.Icon sf="safari" md="explore" />
</NativeTabs.Trigger>
<NativeTabs.Trigger name="(settings)" disableTransparentOnScrollEdge>
<NativeTabs.Trigger.Label>Settings</NativeTabs.Trigger.Label>
<NativeTabs.Trigger.Icon sf="gear" md="settings" />
{showSettingsBadge ? (
<NativeTabs.Trigger.Badge>!</NativeTabs.Trigger.Badge>
) : null}
</NativeTabs.Trigger>
<NativeTabs.Trigger
name="(search)"
role="search"
disableTransparentOnScrollEdge
>
<NativeTabs.Trigger name="(home)" disableTransparentOnScrollEdge>
<NativeTabs.Trigger.Label>Home</NativeTabs.Trigger.Label>
<NativeTabs.Trigger.Icon sf="house.fill" md="home" />
</NativeTabs.Trigger>
<NativeTabs.Trigger name="(explore)" disableTransparentOnScrollEdge>
<NativeTabs.Trigger.Label>Explore</NativeTabs.Trigger.Label>
<NativeTabs.Trigger.Icon sf="safari" md="explore" />
</NativeTabs.Trigger>
<NativeTabs.Trigger name="(settings)" disableTransparentOnScrollEdge>
<NativeTabs.Trigger.Label>Settings</NativeTabs.Trigger.Label>
<NativeTabs.Trigger.Icon sf="gear" md="settings" />
{showSettingsBadge ? (
<NativeTabs.Trigger.Badge>!</NativeTabs.Trigger.Badge>
) : null}
</NativeTabs.Trigger>
<NativeTabs.Trigger
name="(search)"
role="search"
disableTransparentOnScrollEdge
>
<NativeTabs.Trigger.Label>Search</NativeTabs.Trigger.Label>
<NativeTabs.Trigger.Icon sf="magnifyingglass" md="search" />
</NativeTabs.Trigger>
</NativeTabs>
</>
<NativeTabs.Trigger.Label>Search</NativeTabs.Trigger.Label>
<NativeTabs.Trigger.Icon sf="magnifyingglass" md="search" />
</NativeTabs.Trigger>
</NativeTabs>
);
}
@@ -3,13 +3,6 @@ import type { ReactNode } from "react";
import { useCSSVariable, useResolveClassNames } from "uniwind";
import { HeaderAvatar } from "@/components/header-avatar";
const hiddenScrollEdgeEffects = {
top: "hidden" as const,
bottom: "hidden" as const,
left: "hidden" as const,
right: "hidden" as const,
};
export function TabStack({
title,
children,
@@ -35,7 +28,6 @@ export function TabStack({
<Stack
screenOptions={{
contentStyle,
scrollEdgeEffects: hiddenScrollEdgeEffects,
unstable_headerRightItems: () => [
{
type: "custom" as const,
@@ -49,7 +41,7 @@ export function TabStack({
<Stack.Screen name="index">
<Stack.Header
transparent
blurEffect="dark"
blurEffect="systemChromeMaterialDark"
style={{ color: tintColor, shadowColor: "transparent" }}
largeStyle={{
backgroundColor: "transparent",
@@ -1,57 +0,0 @@
import type { Icon } from "@tabler/icons-react-native";
import {
IconBookmarkFilled,
IconCircleCheckFilled,
IconPlayerPlayFilled,
} from "@tabler/icons-react-native";
import { View } from "react-native";
import { useCSSVariable } from "uniwind";
import { ScaledIcon } from "@/components/ui/scaled-icon";
import { Text } from "@/components/ui/text";
type TitleStatus = "watchlist" | "in_progress" | "completed";
const bgClasses: Record<TitleStatus, string> = {
watchlist: "bg-status-watchlist/10",
in_progress: "bg-status-watching/10",
completed: "bg-status-completed/10",
};
const textClasses: Record<TitleStatus, string> = {
watchlist: "text-status-watchlist",
in_progress: "text-status-watching",
completed: "text-status-completed",
};
const icons: Record<TitleStatus, { label: string; Icon: Icon }> = {
watchlist: { label: "Watchlist", Icon: IconBookmarkFilled },
in_progress: { label: "Watching", Icon: IconPlayerPlayFilled },
completed: { label: "Completed", Icon: IconCircleCheckFilled },
};
export function StatusBadge({ status }: { status: TitleStatus }) {
const watchlistColor = useCSSVariable("--color-status-watchlist") as string;
const watchingColor = useCSSVariable("--color-status-watching") as string;
const completedColor = useCSSVariable("--color-status-completed") as string;
const colorMap: Record<TitleStatus, string> = {
watchlist: watchlistColor,
in_progress: watchingColor,
completed: completedColor,
};
const { label, Icon: StatusIcon } = icons[status];
return (
<View
className={`flex-row items-center gap-1.5 rounded-full px-2.5 py-1 ${bgClasses[status]}`}
>
<ScaledIcon icon={StatusIcon} size={12} color={colorMap[status]} />
<Text
maxFontSizeMultiplier={1.0}
className={`font-medium font-sans text-xs ${textClasses[status]}`}
>
{label}
</Text>
</View>
);
}
+1 -1
View File
@@ -52,7 +52,7 @@ export const Input = forwardRef<
accessibilityLabelledBy={ctxId}
placeholderTextColorClassName="accent-muted-foreground/70"
className={cn(
"min-h-12 rounded-[12px] border border-border bg-input px-3.5 py-3 font-sans text-base text-foreground",
"min-h-12 rounded-[12px] border border-border bg-input px-3.5 py-3 font-sans text-foreground text-sm",
className,
)}
style={[{ borderCurve: "continuous" }, style]}
+28
View File
@@ -0,0 +1,28 @@
import type { ZodError } from "zod";
/**
* Extracts the first error message and the set of field names with errors
* from a Zod validation result.
*/
export function getFormErrors(error: ZodError): {
message: string;
fields: Set<string>;
} {
const fieldErrors = error.flatten().fieldErrors as Record<
string,
string[] | undefined
>;
const fields = new Set<string>();
let message = "";
for (const [key, messages] of Object.entries(fieldErrors)) {
if (messages && messages.length > 0) {
fields.add(key);
if (!message) {
message = messages[0];
}
}
}
return { message, fields };
}
-1
View File
@@ -11,7 +11,6 @@
"check-types": "tsc --noEmit"
},
"dependencies": {
"@orpc/client": "catalog:",
"@orpc/contract": "catalog:",
"@orpc/json-schema": "1.13.7",
"@orpc/openapi": "1.13.7",
+7 -10
View File
@@ -25,17 +25,16 @@
"@tabler/icons-react": "3.40.0",
"@tanstack/react-hotkeys": "0.4.1",
"@tanstack/react-query": "catalog:",
"@tanstack/react-router": "1.167.1",
"@tanstack/react-router": "1.167.4",
"better-auth": "catalog:",
"class-variance-authority": "0.7.1",
"clsx": "2.1.1",
"cmdk": "1.1.1",
"date-fns": "catalog:",
"jotai": "2.18.1",
"media-chrome": "4.18.0",
"motion": "12.36.0",
"media-chrome": "4.18.1",
"motion": "12.38.0",
"react": "catalog:",
"react-day-picker": "9.14.0",
"react-dom": "catalog:",
"recharts": "3.8.0",
"shadcn": "4.0.8",
@@ -43,17 +42,15 @@
"tailwind-merge": "catalog:",
"thumbhash": "catalog:",
"tw-animate-css": "1.4.0",
"vaul": "1.1.2",
"youtube-video-element": "1.9.0",
"zod": "catalog:"
"youtube-video-element": "1.9.0"
},
"devDependencies": {
"@tailwindcss/vite": "0.0.0-insiders.d24b112",
"@tailwindcss/vite": "0.0.0-insiders.f302fce",
"@tanstack/devtools-vite": "0.6.0",
"@tanstack/react-devtools": "0.10.0",
"@tanstack/react-query-devtools": "5.91.3",
"@tanstack/react-router-devtools": "1.166.8",
"@tanstack/router-plugin": "1.166.10",
"@tanstack/react-router-devtools": "1.166.9",
"@tanstack/router-plugin": "1.166.13",
"@types/bun": "catalog:",
"@types/node": "catalog:",
"@types/react": "catalog:",
-225
View File
@@ -1,225 +0,0 @@
import {
IconChevronDown,
IconChevronLeft,
IconChevronRight,
} from "@tabler/icons-react";
import * as React from "react";
import {
type DayButton,
DayPicker,
getDefaultClassNames,
type Locale,
} from "react-day-picker";
import { Button, buttonVariants } from "@/components/ui/button";
import { cn } from "@/lib/utils";
function Calendar({
className,
classNames,
showOutsideDays = true,
captionLayout = "label",
buttonVariant = "ghost",
locale,
formatters,
components,
...props
}: React.ComponentProps<typeof DayPicker> & {
buttonVariant?: React.ComponentProps<typeof Button>["variant"];
}) {
const defaultClassNames = getDefaultClassNames();
return (
<DayPicker
showOutsideDays={showOutsideDays}
className={cn(
"group/calendar bg-background in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent p-3 [--cell-radius:var(--radius-md)] [--cell-size:--spacing(6)]",
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
className,
)}
captionLayout={captionLayout}
locale={locale}
formatters={{
formatMonthDropdown: (date) =>
date.toLocaleString(locale?.code, { month: "short" }),
...formatters,
}}
classNames={{
root: cn("w-fit", defaultClassNames.root),
months: cn(
"relative flex flex-col gap-4 md:flex-row",
defaultClassNames.months,
),
month: cn("flex w-full flex-col gap-4", defaultClassNames.month),
nav: cn(
"absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1",
defaultClassNames.nav,
),
button_previous: cn(
buttonVariants({ variant: buttonVariant }),
"size-(--cell-size) select-none p-0 aria-disabled:opacity-50",
defaultClassNames.button_previous,
),
button_next: cn(
buttonVariants({ variant: buttonVariant }),
"size-(--cell-size) select-none p-0 aria-disabled:opacity-50",
defaultClassNames.button_next,
),
month_caption: cn(
"flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)",
defaultClassNames.month_caption,
),
dropdowns: cn(
"flex h-(--cell-size) w-full items-center justify-center gap-1.5 font-medium text-sm",
defaultClassNames.dropdowns,
),
dropdown_root: cn(
"relative rounded-(--cell-radius)",
defaultClassNames.dropdown_root,
),
dropdown: cn(
"absolute inset-0 bg-popover opacity-0",
defaultClassNames.dropdown,
),
caption_label: cn(
"select-none font-medium",
captionLayout === "label"
? "text-sm"
: "flex items-center gap-1 rounded-(--cell-radius) text-sm [&>svg]:size-3.5 [&>svg]:text-muted-foreground",
defaultClassNames.caption_label,
),
table: "w-full border-collapse",
weekdays: cn("flex", defaultClassNames.weekdays),
weekday: cn(
"flex-1 select-none rounded-(--cell-radius) font-normal text-[0.8rem] text-muted-foreground",
defaultClassNames.weekday,
),
week: cn("mt-2 flex w-full", defaultClassNames.week),
week_number_header: cn(
"w-(--cell-size) select-none",
defaultClassNames.week_number_header,
),
week_number: cn(
"select-none text-[0.8rem] text-muted-foreground",
defaultClassNames.week_number,
),
day: cn(
"group/day relative aspect-square h-full w-full select-none rounded-(--cell-radius) p-0 text-center [&:last-child[data-selected=true]_button]:rounded-r-(--cell-radius)",
props.showWeekNumber
? "[&:nth-child(2)[data-selected=true]_button]:rounded-l-(--cell-radius)"
: "[&:first-child[data-selected=true]_button]:rounded-l-(--cell-radius)",
defaultClassNames.day,
),
range_start: cn(
"relative isolate z-0 rounded-l-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:right-0 after:w-4 after:bg-muted",
defaultClassNames.range_start,
),
range_middle: cn("rounded-none", defaultClassNames.range_middle),
range_end: cn(
"relative isolate z-0 rounded-r-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:left-0 after:w-4 after:bg-muted",
defaultClassNames.range_end,
),
today: cn(
"rounded-(--cell-radius) bg-muted text-foreground data-[selected=true]:rounded-none",
defaultClassNames.today,
),
outside: cn(
"text-muted-foreground aria-selected:text-muted-foreground",
defaultClassNames.outside,
),
disabled: cn(
"text-muted-foreground opacity-50",
defaultClassNames.disabled,
),
hidden: cn("invisible", defaultClassNames.hidden),
...classNames,
}}
components={{
Root: ({ className, rootRef, ...props }) => {
return (
<div
data-slot="calendar"
ref={rootRef}
className={cn(className)}
{...props}
/>
);
},
Chevron: ({ className, orientation, ...props }) => {
if (orientation === "left") {
return (
<IconChevronLeft className={cn("size-4", className)} {...props} />
);
}
if (orientation === "right") {
return (
<IconChevronRight
className={cn("size-4", className)}
{...props}
/>
);
}
return (
<IconChevronDown className={cn("size-4", className)} {...props} />
);
},
DayButton: ({ ...props }) => (
<CalendarDayButton locale={locale} {...props} />
),
WeekNumber: ({ children, ...props }) => {
return (
<td {...props}>
<div className="flex size-(--cell-size) items-center justify-center text-center">
{children}
</div>
</td>
);
},
...components,
}}
{...props}
/>
);
}
function CalendarDayButton({
className,
day,
modifiers,
locale,
...props
}: React.ComponentProps<typeof DayButton> & { locale?: Partial<Locale> }) {
const defaultClassNames = getDefaultClassNames();
const ref = React.useRef<HTMLButtonElement>(null);
React.useEffect(() => {
if (modifiers.focused) ref.current?.focus();
}, [modifiers.focused]);
return (
<Button
variant="ghost"
size="icon"
data-day={day.date.toLocaleDateString(locale?.code)}
data-selected-single={
modifiers.selected &&
!modifiers.range_start &&
!modifiers.range_end &&
!modifiers.range_middle
}
data-range-start={modifiers.range_start}
data-range-end={modifiers.range_end}
data-range-middle={modifiers.range_middle}
className={cn(
"relative isolate z-10 flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 border-0 font-normal leading-none data-[range-end=true]:rounded-(--cell-radius) data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-(--cell-radius) data-[range-end=true]:rounded-r-(--cell-radius) data-[range-start=true]:rounded-l-(--cell-radius) data-[range-end=true]:bg-primary data-[range-middle=true]:bg-muted data-[range-start=true]:bg-primary data-[selected-single=true]:bg-primary data-[range-end=true]:text-primary-foreground data-[range-middle=true]:text-foreground data-[range-start=true]:text-primary-foreground data-[selected-single=true]:text-primary-foreground group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-[3px] group-data-[focused=true]/day:ring-ring/50 dark:hover:text-foreground [&>span]:text-xs [&>span]:opacity-70",
defaultClassNames.day,
className,
)}
{...props}
/>
);
}
export { Calendar, CalendarDayButton };
-129
View File
@@ -1,129 +0,0 @@
import type * as React from "react";
import { Drawer as DrawerPrimitive } from "vaul";
import { cn } from "@/lib/utils";
function Drawer({
...props
}: React.ComponentProps<typeof DrawerPrimitive.Root>) {
return <DrawerPrimitive.Root data-slot="drawer" {...props} />;
}
function DrawerTrigger({
...props
}: React.ComponentProps<typeof DrawerPrimitive.Trigger>) {
return <DrawerPrimitive.Trigger data-slot="drawer-trigger" {...props} />;
}
function DrawerPortal({
...props
}: React.ComponentProps<typeof DrawerPrimitive.Portal>) {
return <DrawerPrimitive.Portal data-slot="drawer-portal" {...props} />;
}
function DrawerClose({
...props
}: React.ComponentProps<typeof DrawerPrimitive.Close>) {
return <DrawerPrimitive.Close data-slot="drawer-close" {...props} />;
}
function DrawerOverlay({
className,
...props
}: React.ComponentProps<typeof DrawerPrimitive.Overlay>) {
return (
<DrawerPrimitive.Overlay
data-slot="drawer-overlay"
className={cn(
"data-closed:fade-out-0 data-open:fade-in-0 fixed inset-0 z-50 bg-black/80 data-closed:animate-out data-open:animate-in supports-backdrop-filter:backdrop-blur-xs",
className,
)}
{...props}
/>
);
}
function DrawerContent({
className,
children,
...props
}: React.ComponentProps<typeof DrawerPrimitive.Content>) {
return (
<DrawerPortal data-slot="drawer-portal">
<DrawerOverlay />
<DrawerPrimitive.Content
data-slot="drawer-content"
className={cn(
"group/drawer-content fixed z-50 flex h-auto flex-col overscroll-contain bg-transparent p-2 text-xs/relaxed before:absolute before:inset-2 before:-z-10 before:rounded-xl before:border before:border-border before:bg-background data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=left]:sm:max-w-sm data-[vaul-drawer-direction=right]:sm:max-w-sm",
className,
)}
{...props}
>
<div className="mx-auto mt-4 hidden h-1.5 w-[100px] shrink-0 rounded-full bg-muted group-data-[vaul-drawer-direction=bottom]/drawer-content:block" />
{children}
</DrawerPrimitive.Content>
</DrawerPortal>
);
}
function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="drawer-header"
className={cn(
"flex flex-col gap-1 p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:text-left",
className,
)}
{...props}
/>
);
}
function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="drawer-footer"
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
{...props}
/>
);
}
function DrawerTitle({
className,
...props
}: React.ComponentProps<typeof DrawerPrimitive.Title>) {
return (
<DrawerPrimitive.Title
data-slot="drawer-title"
className={cn("font-medium text-foreground text-sm", className)}
{...props}
/>
);
}
function DrawerDescription({
className,
...props
}: React.ComponentProps<typeof DrawerPrimitive.Description>) {
return (
<DrawerPrimitive.Description
data-slot="drawer-description"
className={cn("text-muted-foreground text-xs/relaxed", className)}
{...props}
/>
);
}
export {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerOverlay,
DrawerPortal,
DrawerTitle,
DrawerTrigger,
};
+99 -111
View File
@@ -16,12 +16,13 @@
"dependencies": {
"@better-auth/expo": "catalog:",
"@expo/metro-runtime": "55.0.6",
"@expo/ui": "55.0.2",
"@expo/ui": "55.0.3",
"@orpc/client": "catalog:",
"@orpc/contract": "catalog:",
"@orpc/tanstack-query": "catalog:",
"@react-native-menu/menu": "2.0.0",
"@react-navigation/elements": "2.9.10",
"@react-navigation/native": "7.1.33",
"@shopify/flash-list": "2.0.2",
"@sofa/api": "workspace:*",
"@tabler/icons-react-native": "3.40.0",
@@ -32,28 +33,28 @@
"better-auth": "catalog:",
"burnt": "0.13.0",
"date-fns": "catalog:",
"expo": "55.0.6",
"expo-application": "55.0.9",
"expo-clipboard": "55.0.8",
"expo-constants": "55.0.7",
"expo-dev-client": "55.0.16",
"expo-device": "55.0.9",
"expo": "55.0.7",
"expo-application": "55.0.10",
"expo-clipboard": "55.0.9",
"expo-constants": "55.0.8",
"expo-dev-client": "55.0.17",
"expo-device": "55.0.10",
"expo-font": "55.0.4",
"expo-glass-effect": "55.0.8",
"expo-haptics": "55.0.8",
"expo-haptics": "55.0.9",
"expo-image": "55.0.6",
"expo-image-picker": "55.0.12",
"expo-linear-gradient": "55.0.8",
"expo-image-picker": "55.0.13",
"expo-linear-gradient": "55.0.9",
"expo-linking": "55.0.7",
"expo-localization": "55.0.8",
"expo-network": "55.0.8",
"expo-router": "55.0.5",
"expo-secure-store": "55.0.8",
"expo-splash-screen": "55.0.10",
"expo-localization": "55.0.9",
"expo-network": "55.0.9",
"expo-router": "55.0.6",
"expo-secure-store": "55.0.9",
"expo-splash-screen": "55.0.11",
"expo-status-bar": "55.0.4",
"expo-system-ui": "55.0.9",
"expo-tracking-transparency": "55.0.8",
"expo-web-browser": "55.0.9",
"expo-system-ui": "55.0.10",
"expo-tracking-transparency": "55.0.9",
"expo-web-browser": "55.0.10",
"posthog-react-native": "4.37.3",
"react": "19.2.0",
"react-dom": "19.2.0",
@@ -101,7 +102,6 @@
"name": "@sofa/server",
"version": "0.1.0",
"dependencies": {
"@orpc/client": "catalog:",
"@orpc/contract": "catalog:",
"@orpc/json-schema": "1.13.7",
"@orpc/openapi": "1.13.7",
@@ -140,17 +140,16 @@
"@tabler/icons-react": "3.40.0",
"@tanstack/react-hotkeys": "0.4.1",
"@tanstack/react-query": "catalog:",
"@tanstack/react-router": "1.167.1",
"@tanstack/react-router": "1.167.4",
"better-auth": "catalog:",
"class-variance-authority": "0.7.1",
"clsx": "2.1.1",
"cmdk": "1.1.1",
"date-fns": "catalog:",
"jotai": "2.18.1",
"media-chrome": "4.18.0",
"motion": "12.36.0",
"media-chrome": "4.18.1",
"motion": "12.38.0",
"react": "catalog:",
"react-day-picker": "9.14.0",
"react-dom": "catalog:",
"recharts": "3.8.0",
"shadcn": "4.0.8",
@@ -158,17 +157,15 @@
"tailwind-merge": "catalog:",
"thumbhash": "catalog:",
"tw-animate-css": "1.4.0",
"vaul": "1.1.2",
"youtube-video-element": "1.9.0",
"zod": "catalog:",
},
"devDependencies": {
"@tailwindcss/vite": "0.0.0-insiders.d24b112",
"@tailwindcss/vite": "0.0.0-insiders.f302fce",
"@tanstack/devtools-vite": "0.6.0",
"@tanstack/react-devtools": "0.10.0",
"@tanstack/react-query-devtools": "5.91.3",
"@tanstack/react-router-devtools": "1.166.8",
"@tanstack/router-plugin": "1.166.10",
"@tanstack/react-router-devtools": "1.166.9",
"@tanstack/router-plugin": "1.166.13",
"@types/bun": "catalog:",
"@types/node": "catalog:",
"@types/react": "catalog:",
@@ -267,7 +264,6 @@
"name": "@sofa/tmdb",
"version": "0.1.0",
"dependencies": {
"@sofa/db": "workspace:*",
"@sofa/logger": "workspace:*",
"openapi-fetch": "0.17.0",
},
@@ -347,7 +343,7 @@
"@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="],
"@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.7", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "debug": "^4.4.3", "lodash.debounce": "^4.0.8", "resolve": "^1.22.11" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-6Fqi8MtQ/PweQ9xvux65emkLQ83uB+qAVtfHkC9UodyHMIZdxNI01HjLCLUtybElp2KY2XNE0nOgyP1E1vXw9w=="],
"@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.8", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "debug": "^4.4.3", "lodash.debounce": "^4.0.8", "resolve": "^1.22.11" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA=="],
"@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="],
@@ -375,9 +371,9 @@
"@babel/helper-wrap-function": ["@babel/helper-wrap-function@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ=="],
"@babel/helpers": ["@babel/helpers@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw=="],
"@babel/helpers": ["@babel/helpers@7.29.2", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="],
"@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
"@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="],
"@babel/plugin-proposal-decorators": ["@babel/plugin-proposal-decorators@7.29.0", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/plugin-syntax-decorators": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-CVBVv3VY/XRMxRYq5dwr2DS7/MvqPm23cOCjbwNnVrfOqcWlnefua1uUs0sjdKOGjvPUG633o07uWzJq4oI6dA=="],
@@ -507,7 +503,7 @@
"@babel/preset-typescript": ["@babel/preset-typescript@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", "@babel/plugin-transform-typescript": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ=="],
"@babel/runtime": ["@babel/runtime@7.28.6", "", {}, "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA=="],
"@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="],
"@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="],
@@ -559,8 +555,6 @@
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.7", "", { "os": "win32", "cpu": "x64" }, "sha512-qEpGjSkPC3qX4ycbMUthXvi9CkRq7kZpkqMY1OyhmYlYLnANnooDQ7hDerM8+0NJ+DZKVnsIc07h30XOpt7LtQ=="],
"@date-fns/tz": ["@date-fns/tz@1.4.1", "", {}, "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA=="],
"@dominicstop/ts-event-emitter": ["@dominicstop/ts-event-emitter@1.1.0", "", {}, "sha512-CcxmJIvUb1vsFheuGGVSQf4KdPZC44XolpUT34+vlal+LyQoBUOn31pjFET5M9ctOxEpt8xa0M3/2M7uUiAoJw=="],
"@dotenvx/dotenvx": ["@dotenvx/dotenvx@1.55.1", "", { "dependencies": { "commander": "^11.1.0", "dotenv": "^17.2.1", "eciesjs": "^0.4.10", "execa": "^5.1.1", "fdir": "^6.2.0", "ignore": "^5.3.0", "object-treeify": "1.1.33", "picomatch": "^4.0.2", "which": "^4.0.0" }, "bin": { "dotenvx": "src/cli/dotenvx.js" } }, "sha512-WEuKyoe9CA7dfcFBnNbL0ndbCNcptaEYBygfFo9X1qEG+HD7xku4CYIplw6sbAHJavesZWbVBHeRSpvri0eKqw=="],
@@ -629,13 +623,13 @@
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
"@expo-google-fonts/material-symbols": ["@expo-google-fonts/material-symbols@0.4.26", "", {}, "sha512-1RBDVbRQDIAldwf0oV9QjEallOJ+FkkuYxgKFc345Vzi0dK/ipVITWTUTJHr49Jh1XQuEyVKdHH+mo3DXqZU7g=="],
"@expo-google-fonts/material-symbols": ["@expo-google-fonts/material-symbols@0.4.27", "", {}, "sha512-cnb3DZnWUWpezGFkJ8y4MT5f/lw6FcgDzeJzic+T+vpQHLHG1cg3SC3i1w1i8Bk4xKR4HPY3t9iIRNvtr5ml8A=="],
"@expo/cli": ["@expo/cli@55.0.16", "", { "dependencies": { "@expo/code-signing-certificates": "^0.0.6", "@expo/config": "~55.0.8", "@expo/config-plugins": "~55.0.6", "@expo/devcert": "^1.2.1", "@expo/env": "~2.1.1", "@expo/image-utils": "^0.8.12", "@expo/json-file": "^10.0.12", "@expo/log-box": "55.0.7", "@expo/metro": "~54.2.0", "@expo/metro-config": "~55.0.9", "@expo/osascript": "^2.4.2", "@expo/package-manager": "^1.10.3", "@expo/plist": "^0.5.2", "@expo/prebuild-config": "^55.0.8", "@expo/require-utils": "^55.0.2", "@expo/router-server": "^55.0.10", "@expo/schema-utils": "^55.0.2", "@expo/spawn-async": "^1.7.2", "@expo/ws-tunnel": "^1.0.1", "@expo/xcpretty": "^4.4.0", "@react-native/dev-middleware": "0.83.2", "accepts": "^1.3.8", "arg": "^5.0.2", "better-opn": "~3.0.2", "bplist-creator": "0.1.0", "bplist-parser": "^0.3.1", "chalk": "^4.0.0", "ci-info": "^3.3.0", "compression": "^1.7.4", "connect": "^3.7.0", "debug": "^4.3.4", "dnssd-advertise": "^1.1.3", "expo-server": "^55.0.6", "fetch-nodeshim": "^0.4.6", "getenv": "^2.0.0", "glob": "^13.0.0", "lan-network": "^0.2.0", "multitars": "^0.2.3", "node-forge": "^1.3.3", "npm-package-arg": "^11.0.0", "ora": "^3.4.0", "picomatch": "^4.0.3", "pretty-format": "^29.7.0", "progress": "^2.0.3", "prompts": "^2.3.2", "resolve-from": "^5.0.0", "semver": "^7.6.0", "send": "^0.19.0", "slugify": "^1.3.4", "source-map-support": "~0.5.21", "stacktrace-parser": "^0.1.10", "structured-headers": "^0.4.1", "terminal-link": "^2.1.1", "toqr": "^0.1.1", "wrap-ansi": "^7.0.0", "ws": "^8.12.1", "zod": "^3.25.76" }, "peerDependencies": { "expo": "*", "expo-router": "*", "react-native": "*" }, "optionalPeers": ["expo-router", "react-native"], "bin": { "expo-internal": "build/bin/cli" } }, "sha512-rp1mBnA5msGDPTfFuqVl+9RsJOtuA0cXsWSJpHdvsIxcSVg0oJyF/rgvrwsFrNQCLXzkMXm+o3CsY9iL1D/CDA=="],
"@expo/cli": ["@expo/cli@55.0.17", "", { "dependencies": { "@expo/code-signing-certificates": "^0.0.6", "@expo/config": "~55.0.9", "@expo/config-plugins": "~55.0.6", "@expo/devcert": "^1.2.1", "@expo/env": "~2.1.1", "@expo/image-utils": "^0.8.12", "@expo/json-file": "^10.0.12", "@expo/log-box": "55.0.7", "@expo/metro": "~54.2.0", "@expo/metro-config": "~55.0.10", "@expo/osascript": "^2.4.2", "@expo/package-manager": "^1.10.3", "@expo/plist": "^0.5.2", "@expo/prebuild-config": "^55.0.9", "@expo/require-utils": "^55.0.3", "@expo/router-server": "^55.0.10", "@expo/schema-utils": "^55.0.2", "@expo/spawn-async": "^1.7.2", "@expo/ws-tunnel": "^1.0.1", "@expo/xcpretty": "^4.4.0", "@react-native/dev-middleware": "0.83.2", "accepts": "^1.3.8", "arg": "^5.0.2", "better-opn": "~3.0.2", "bplist-creator": "0.1.0", "bplist-parser": "^0.3.1", "chalk": "^4.0.0", "ci-info": "^3.3.0", "compression": "^1.7.4", "connect": "^3.7.0", "debug": "^4.3.4", "dnssd-advertise": "^1.1.3", "expo-server": "^55.0.6", "fetch-nodeshim": "^0.4.6", "getenv": "^2.0.0", "glob": "^13.0.0", "lan-network": "^0.2.0", "multitars": "^0.2.3", "node-forge": "^1.3.3", "npm-package-arg": "^11.0.0", "ora": "^3.4.0", "picomatch": "^4.0.3", "pretty-format": "^29.7.0", "progress": "^2.0.3", "prompts": "^2.3.2", "resolve-from": "^5.0.0", "semver": "^7.6.0", "send": "^0.19.0", "slugify": "^1.3.4", "source-map-support": "~0.5.21", "stacktrace-parser": "^0.1.10", "structured-headers": "^0.4.1", "terminal-link": "^2.1.1", "toqr": "^0.1.1", "wrap-ansi": "^7.0.0", "ws": "^8.12.1", "zod": "^3.25.76" }, "peerDependencies": { "expo": "*", "expo-router": "*", "react-native": "*" }, "optionalPeers": ["expo-router", "react-native"], "bin": { "expo-internal": "build/bin/cli" } }, "sha512-/x7rWkapzuBgHIBvQeJsvuaj0AVuYCX80ypCoJbF1fPnbFexOJ2iCnuaiCz+8d5b1OzLkuOgGs/s7OaaSPWmCQ=="],
"@expo/code-signing-certificates": ["@expo/code-signing-certificates@0.0.6", "", { "dependencies": { "node-forge": "^1.3.3" } }, "sha512-iNe0puxwBNEcuua9gmTGzq+SuMDa0iATai1FlFTMHJ/vUmKvN/V//drXoLJkVb5i5H3iE/n/qIJxyoBnXouD0w=="],
"@expo/config": ["@expo/config@55.0.8", "", { "dependencies": { "@expo/config-plugins": "~55.0.6", "@expo/config-types": "^55.0.5", "@expo/json-file": "^10.0.12", "@expo/require-utils": "^55.0.2", "deepmerge": "^4.3.1", "getenv": "^2.0.0", "glob": "^13.0.0", "resolve-from": "^5.0.0", "resolve-workspace-root": "^2.0.0", "semver": "^7.6.0", "slugify": "^1.3.4" } }, "sha512-D7RYYHfErCgEllGxNwdYdkgzLna7zkzUECBV3snbUpf7RvIpB5l1LpCgzuVoc5KVew5h7N1Tn4LnT/tBSUZsQg=="],
"@expo/config": ["@expo/config@55.0.9", "", { "dependencies": { "@expo/config-plugins": "~55.0.6", "@expo/config-types": "^55.0.5", "@expo/json-file": "^10.0.12", "@expo/require-utils": "^55.0.3", "deepmerge": "^4.3.1", "getenv": "^2.0.0", "glob": "^13.0.0", "resolve-from": "^5.0.0", "resolve-workspace-root": "^2.0.0", "semver": "^7.6.0", "slugify": "^1.3.4" } }, "sha512-uYPwTnBtp7aSGhNjvdhqUfi8SodvDlIqKzWq94WcWaFBr/RzcJ/pa0TZOy2E6YgjvrcZOcSj3xRQYwWoo+9jag=="],
"@expo/config-plugins": ["@expo/config-plugins@55.0.6", "", { "dependencies": { "@expo/config-types": "^55.0.5", "@expo/json-file": "~10.0.12", "@expo/plist": "^0.5.2", "@expo/sdk-runtime-versions": "^1.0.0", "chalk": "^4.1.2", "debug": "^4.3.5", "getenv": "^2.0.0", "glob": "^13.0.0", "resolve-from": "^5.0.0", "semver": "^7.5.4", "slugify": "^1.6.6", "xcode": "^3.0.1", "xml2js": "0.6.0" } }, "sha512-cIox6FjZlFaaX40rbQ3DvP9e87S5X85H9uw+BAxJE5timkMhuByy3GAlOsj1h96EyzSiol7Q6YIGgY1Jiz4M+A=="],
@@ -661,7 +655,7 @@
"@expo/metro": ["@expo/metro@54.2.0", "", { "dependencies": { "metro": "0.83.3", "metro-babel-transformer": "0.83.3", "metro-cache": "0.83.3", "metro-cache-key": "0.83.3", "metro-config": "0.83.3", "metro-core": "0.83.3", "metro-file-map": "0.83.3", "metro-minify-terser": "0.83.3", "metro-resolver": "0.83.3", "metro-runtime": "0.83.3", "metro-source-map": "0.83.3", "metro-symbolicate": "0.83.3", "metro-transform-plugins": "0.83.3", "metro-transform-worker": "0.83.3" } }, "sha512-h68TNZPGsk6swMmLm9nRSnE2UXm48rWwgcbtAHVMikXvbxdS41NDHHeqg1rcQ9AbznDRp6SQVC2MVpDnsRKU1w=="],
"@expo/metro-config": ["@expo/metro-config@55.0.9", "", { "dependencies": { "@babel/code-frame": "^7.20.0", "@babel/core": "^7.20.0", "@babel/generator": "^7.20.5", "@expo/config": "~55.0.8", "@expo/env": "~2.1.1", "@expo/json-file": "~10.0.12", "@expo/metro": "~54.2.0", "@expo/spawn-async": "^1.7.2", "browserslist": "^4.25.0", "chalk": "^4.1.0", "debug": "^4.3.2", "getenv": "^2.0.0", "glob": "^13.0.0", "hermes-parser": "^0.32.0", "jsc-safe-url": "^0.2.4", "lightningcss": "^1.30.1", "picomatch": "^4.0.3", "postcss": "~8.4.32", "resolve-from": "^5.0.0" }, "peerDependencies": { "expo": "*" }, "optionalPeers": ["expo"] }, "sha512-ZJFEfat/+dLUhFyFFWrzMjAqAwwUaJ3RD42QNqR7jh+RVYkAf6XYLynb5qrKJTHI1EcOx4KoO1717yXYYRFDBA=="],
"@expo/metro-config": ["@expo/metro-config@55.0.10", "", { "dependencies": { "@babel/code-frame": "^7.20.0", "@babel/core": "^7.20.0", "@babel/generator": "^7.20.5", "@expo/config": "~55.0.9", "@expo/env": "~2.1.1", "@expo/json-file": "~10.0.12", "@expo/metro": "~54.2.0", "@expo/spawn-async": "^1.7.2", "browserslist": "^4.25.0", "chalk": "^4.1.0", "debug": "^4.3.2", "getenv": "^2.0.0", "glob": "^13.0.0", "hermes-parser": "^0.32.0", "jsc-safe-url": "^0.2.4", "lightningcss": "^1.30.1", "picomatch": "^4.0.3", "postcss": "~8.4.32", "resolve-from": "^5.0.0" }, "peerDependencies": { "expo": "*" }, "optionalPeers": ["expo"] }, "sha512-3mZolGb90f0DnknjjJw8zNVjNu2m3ctiUs+DvEHmSy2K5Pag+6gLPPLHsnFJ1R0PJ6t7U+BuUp23E5jnFCDVcA=="],
"@expo/metro-runtime": ["@expo/metro-runtime@55.0.6", "", { "dependencies": { "@expo/log-box": "55.0.7", "anser": "^1.4.9", "pretty-format": "^29.7.0", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0" }, "peerDependencies": { "expo": "*", "react": "*", "react-dom": "*", "react-native": "*" }, "optionalPeers": ["react-dom"] }, "sha512-l8VvgKN9md+URjeQDB+DnHVmvpcWI6zFLH6yv7GTv4sfRDKyaZ5zDXYjTP1phYdgW6ea2NrRtCGNIxylWhsgtg=="],
@@ -671,9 +665,9 @@
"@expo/plist": ["@expo/plist@0.5.2", "", { "dependencies": { "@xmldom/xmldom": "^0.8.8", "base64-js": "^1.5.1", "xmlbuilder": "^15.1.1" } }, "sha512-o4xdVdBpe4aTl3sPMZ2u3fJH4iG1I768EIRk1xRZP+GaFI93MaR3JvoFibYqxeTmLQ1p1kNEVqylfUjezxx45g=="],
"@expo/prebuild-config": ["@expo/prebuild-config@55.0.8", "", { "dependencies": { "@expo/config": "~55.0.8", "@expo/config-plugins": "~55.0.6", "@expo/config-types": "^55.0.5", "@expo/image-utils": "^0.8.12", "@expo/json-file": "^10.0.12", "@react-native/normalize-colors": "0.83.2", "debug": "^4.3.1", "resolve-from": "^5.0.0", "semver": "^7.6.0", "xml2js": "0.6.0" }, "peerDependencies": { "expo": "*" } }, "sha512-VJNJiOmmZgyDnR7JMmc3B8Z0ZepZ17I8Wtw+wAH/2+UCUsFg588XU+bwgYcFGw+is28kwGjY46z43kfufpxOnA=="],
"@expo/prebuild-config": ["@expo/prebuild-config@55.0.9", "", { "dependencies": { "@expo/config": "~55.0.9", "@expo/config-plugins": "~55.0.6", "@expo/config-types": "^55.0.5", "@expo/image-utils": "^0.8.12", "@expo/json-file": "^10.0.12", "@react-native/normalize-colors": "0.83.2", "debug": "^4.3.1", "resolve-from": "^5.0.0", "semver": "^7.6.0", "xml2js": "0.6.0" }, "peerDependencies": { "expo": "*" } }, "sha512-834FhfnUh5fGUguJ46MNIBVsAsC5NO0zHD8Vz8FSvG/J07f6Fdtwf9zV5YTst/GXW4uWGGJKPERVUaGmsN8sAw=="],
"@expo/require-utils": ["@expo/require-utils@55.0.2", "", { "dependencies": { "@babel/code-frame": "^7.20.0", "@babel/core": "^7.25.2", "@babel/plugin-transform-modules-commonjs": "^7.24.8" }, "peerDependencies": { "typescript": "^5.0.0 || ^5.0.0-0" }, "optionalPeers": ["typescript"] }, "sha512-dV5oCShQ1umKBKagMMT4B/N+SREsQe3lU4Zgmko5AO0rxKV0tynZT6xXs+e2JxuqT4Rz997atg7pki0BnZb4uw=="],
"@expo/require-utils": ["@expo/require-utils@55.0.3", "", { "dependencies": { "@babel/code-frame": "^7.20.0", "@babel/core": "^7.25.2", "@babel/plugin-transform-modules-commonjs": "^7.24.8" }, "peerDependencies": { "typescript": "^5.0.0 || ^5.0.0-0" }, "optionalPeers": ["typescript"] }, "sha512-TS1m5tW45q4zoaTlt6DwmdYHxvFTIxoLrTHKOFrIirHIqIXnHCzpceg8wumiBi+ZXSaGY2gobTbfv+WVhJY6Fw=="],
"@expo/router-server": ["@expo/router-server@55.0.10", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "@expo/metro-runtime": "^55.0.6", "expo": "*", "expo-constants": "^55.0.7", "expo-font": "^55.0.4", "expo-router": "*", "expo-server": "^55.0.6", "react": "*", "react-dom": "*", "react-server-dom-webpack": "~19.0.1 || ~19.1.2 || ~19.2.1" }, "optionalPeers": ["@expo/metro-runtime", "expo-router", "react-dom", "react-server-dom-webpack"] }, "sha512-NZQzHwkaedufNPayVfPxsZGEMngOD3gDvYx9lld4sitRexrKDx5sHmmNHi6IByGbmCb4jwLXub5sIyWh6z1xPQ=="],
@@ -685,7 +679,7 @@
"@expo/sudo-prompt": ["@expo/sudo-prompt@9.3.2", "", {}, "sha512-HHQigo3rQWKMDzYDLkubN5WQOYXJJE2eNqIQC2axC2iO3mHdwnIR7FgZVvHWtBwAdzBgAP0ECp8KqS8TiMKvgw=="],
"@expo/ui": ["@expo/ui@55.0.2", "", { "dependencies": { "sf-symbols-typescript": "^2.1.0" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-nVWFz0/7w/+Tr0/Jd3TTTY3HUrMSQzOfbuL1nl257zGfJzp2cGmHNuaRlh3Yi72NcVlFNLRbZzLdH3Jwa1R0jg=="],
"@expo/ui": ["@expo/ui@55.0.3", "", { "dependencies": { "sf-symbols-typescript": "^2.1.0" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-MXWIcTExOBQ4wfVnKJ9mwyXeGdQwVsg7IY1CSlzuoNUxV/hnIcUG9rbQFoz/13GIXv9hNTPChxlbchQEfUileA=="],
"@expo/vector-icons": ["@expo/vector-icons@15.1.1", "", { "peerDependencies": { "expo-font": ">=14.0.4", "react": "*", "react-native": "*" } }, "sha512-Iu2VkcoI5vygbtYngm7jb4ifxElNVXQYdDrYkT7UCEIiKLeWnQY0wf2ZhHZ+Wro6Sc5TaumpKUOqDRpLi5rkvw=="],
@@ -1075,8 +1069,6 @@
"@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="],
"@tabby_ai/hijri-converter": ["@tabby_ai/hijri-converter@1.0.5", "", {}, "sha512-r5bClKrcIusDoo049dSL8CawnHR6mRdDwhlQuIgZRNty68q0x8k3Lf1BtPAMxRf/GgnHBnIO4ujd3+GQdLWzxQ=="],
"@tabler/icons": ["@tabler/icons@3.40.0", "", {}, "sha512-V/Q4VgNPKubRTiLdmWjV/zscYcj5IIk+euicUtaVVqF6luSC9rDngYWgST5/yh3Mrg/mYUwRv1YVTk71Jp0twQ=="],
"@tabler/icons-react": ["@tabler/icons-react@3.40.0", "", { "dependencies": { "@tabler/icons": "3.40.0" }, "peerDependencies": { "react": ">= 16" } }, "sha512-oO5+6QCnna4a//mYubx4euZfECtzQZFDGsDMIdzZUhbdyBCT+3bRVFBPueGIcemWld4Vb/0UQ39C/cmGfGylAg=="],
@@ -1111,7 +1103,7 @@
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.17", "", { "os": "win32", "cpu": "x64" }, "sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw=="],
"@tailwindcss/vite": ["@tailwindcss/vite@0.0.0-insiders.d24b112", "", { "dependencies": { "@tailwindcss/node": "0.0.0-insiders.d24b112", "@tailwindcss/oxide": "0.0.0-insiders.d24b112", "tailwindcss": "0.0.0-insiders.d24b112" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-gf07pywUiG1iZFjgMb9nNULTvogH/oOg7T501/1si7IGd2CijA0gs4/7cVumRySr7y4y4B/W091dARGEMhnzEw=="],
"@tailwindcss/vite": ["@tailwindcss/vite@0.0.0-insiders.f302fce", "", { "dependencies": { "@tailwindcss/node": "0.0.0-insiders.f302fce", "@tailwindcss/oxide": "0.0.0-insiders.f302fce", "tailwindcss": "0.0.0-insiders.f302fce" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-AxqaQLSEwc3NGnhD1xNfg7luDJXDNtb+P6Wl9xVd6z303TZEFl8Bly2n9naptt9ZEK6uiT+mx2/zUg96mWXYfQ=="],
"@tanstack/devtools": ["@tanstack/devtools@0.11.0", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/keyboard": "^1.3.3", "@solid-primitives/resize-observer": "^2.1.3", "@tanstack/devtools-client": "0.0.6", "@tanstack/devtools-event-bus": "0.4.1", "@tanstack/devtools-ui": "0.5.1", "clsx": "^2.1.1", "goober": "^2.1.16", "solid-js": "^1.9.9" }, "bin": { "intent": "bin/intent.js" } }, "sha512-ARRAnEm0HYjKlB2adC9YyDG3fbq5LVjpxPe6Jz583SanXRM1aKrZIGHIA//oRldX3mWIpM4kB6mCyd+CXCLqhA=="],
@@ -1127,7 +1119,7 @@
"@tanstack/form-core": ["@tanstack/form-core@1.28.5", "", { "dependencies": { "@tanstack/devtools-event-client": "^0.4.1", "@tanstack/pacer-lite": "^0.1.1", "@tanstack/store": "^0.9.1" } }, "sha512-8lYnduHHfP6uaXF9+2OLnh3Fo27tH4TdtekWLG2b/Bp26ynbrWG6L4qhBgEb7VcvTpJw/RjvJF/JyFhZkG3pfQ=="],
"@tanstack/history": ["@tanstack/history@1.161.5", "", {}, "sha512-g9rWt2rNC0Ztga4LAFWfBDT0VREij5mxBo+oH5+cQ5Q46HVIhtJNty/UU5wPzbTKMHJHGNJOWgrlEfcQ4GgjEA=="],
"@tanstack/history": ["@tanstack/history@1.161.6", "", {}, "sha512-NaOGLRrddszbQj9upGat6HG/4TKvXLvu+osAIgfxPYA+eIvYKv8GKDJOrY2D3/U9MRnKfMWD7bU4jeD4xmqyIg=="],
"@tanstack/hotkeys": ["@tanstack/hotkeys@0.4.1", "", { "dependencies": { "@tanstack/store": "^0.9.2" } }, "sha512-EGHqcdKP2jzy0dEkahA3ABtEXohMqPlU3Ac04sBQjgesJqr9xWuesJotOfWPh3P68kQQg8krNAtFTydIN3+WSw=="],
@@ -1153,25 +1145,25 @@
"@tanstack/react-query-persist-client": ["@tanstack/react-query-persist-client@5.90.24", "", { "dependencies": { "@tanstack/query-persist-client-core": "5.92.1" }, "peerDependencies": { "@tanstack/react-query": "^5.90.21", "react": "^18 || ^19" } }, "sha512-FkfU37vHq61Efr/qGiz+CUNmGfCky1jjsaZFuS5MsWwA9vPHudCwmdirgyTx+RfcQxyHON904q/pc48zrIEhxg=="],
"@tanstack/react-router": ["@tanstack/react-router@1.167.1", "", { "dependencies": { "@tanstack/history": "1.161.5", "@tanstack/react-store": "^0.9.1", "@tanstack/router-core": "1.167.1", "isbot": "^5.1.22", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-hjBvkqXAQBligGekD6wYidl0jlXYwigYMcVkBQz3kXdWQ9fP/Ifbwu5w8zKnlRbuFHF90k1vY9UHjaWdsY3ILA=="],
"@tanstack/react-router": ["@tanstack/react-router@1.167.4", "", { "dependencies": { "@tanstack/history": "1.161.6", "@tanstack/react-store": "^0.9.1", "@tanstack/router-core": "1.167.4", "isbot": "^5.1.22", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-VpbZh382zX3WF4+X2Z+EUyd8eJhJyjg9C6ByYwrVZiWbhgbMK4+zQQIG2+lCAlIlDi7SV8fDcGL09NA8Z2kpGQ=="],
"@tanstack/react-router-devtools": ["@tanstack/react-router-devtools@1.166.8", "", { "dependencies": { "@tanstack/router-devtools-core": "1.166.8" }, "peerDependencies": { "@tanstack/react-router": "^1.167.1", "@tanstack/router-core": "^1.167.1", "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" }, "optionalPeers": ["@tanstack/router-core"] }, "sha512-SkccGkqcy0Do98bAkAYJUnBT2xOahRL7MeybQbAN4KGijq/1GOAnhB9lMd+vG6UD415xiplmt+D0VUEYy2lSMQ=="],
"@tanstack/react-router-devtools": ["@tanstack/react-router-devtools@1.166.9", "", { "dependencies": { "@tanstack/router-devtools-core": "1.166.9" }, "peerDependencies": { "@tanstack/react-router": "^1.167.2", "@tanstack/router-core": "^1.167.2", "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" }, "optionalPeers": ["@tanstack/router-core"] }, "sha512-O49eZmaeEKB5YnKH/qd61AbxV/lW8ICm4stfZ4GNQNpzQQ6rhPIB0p3PMZDIgX+6DoMivdNvLRmXAOOpzpIpDg=="],
"@tanstack/react-store": ["@tanstack/react-store@0.9.2", "", { "dependencies": { "@tanstack/store": "0.9.2", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Vt5usJE5sHG/cMechQfmwvwne6ktGCELe89Lmvoxe3LKRoFrhPa8OCKWs0NliG8HTJElEIj7PLtaBQIcux5pAQ=="],
"@tanstack/router-core": ["@tanstack/router-core@1.167.1", "", { "dependencies": { "@tanstack/history": "1.161.5", "@tanstack/store": "^0.9.1", "cookie-es": "^2.0.0", "seroval": "^1.4.2", "seroval-plugins": "^1.4.2", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" } }, "sha512-8xvS3rWxJ/FZTLxqrh/5KetnDB2B/aFG393C1BjYbinhPXMUhsToW/5fN+l4qbtmQ4BfoNLUmrQUzVRNCN22mw=="],
"@tanstack/router-core": ["@tanstack/router-core@1.167.4", "", { "dependencies": { "@tanstack/history": "1.161.6", "@tanstack/store": "^0.9.1", "cookie-es": "^2.0.0", "seroval": "^1.4.2", "seroval-plugins": "^1.4.2", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" }, "bin": { "intent": "bin/intent.js" } }, "sha512-Gk5V9Zr5JFJ4SbLyCheQLJ3MnXddccENPA+DJRz+9g3QxtN8DJB8w8KCUCgDeYlWp4LvmO4nX3fy3tupqVP2Pw=="],
"@tanstack/router-devtools-core": ["@tanstack/router-devtools-core@1.166.8", "", { "dependencies": { "clsx": "^2.1.1", "goober": "^2.1.16", "tiny-invariant": "^1.3.3" }, "peerDependencies": { "@tanstack/router-core": "^1.167.1", "csstype": "^3.0.10" }, "optionalPeers": ["csstype"] }, "sha512-EFH/eeI9KU+EVKYSWSkvZpAdCKi5O9yu9nDdyWjSvselKzk+9l2Qv/6CWZ+eneRFz6r0Kg6NDWEc3JpqcfjQqA=="],
"@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=="],
"@tanstack/router-generator": ["@tanstack/router-generator@1.166.9", "", { "dependencies": { "@tanstack/router-core": "1.167.1", "@tanstack/router-utils": "1.161.5", "@tanstack/virtual-file-routes": "1.161.5", "prettier": "^3.5.0", "recast": "^0.23.11", "source-map": "^0.7.4", "tsx": "^4.19.2", "zod": "^3.24.2" } }, "sha512-A1PFuUi8YjCBcohCssX1qov8pQyTRZWLYvcnNUmS+//yPHsHyeyRmeMei/tkgh3/ZSrw/AZMt5LefpCwAEVBpQ=="],
"@tanstack/router-generator": ["@tanstack/router-generator@1.166.12", "", { "dependencies": { "@tanstack/router-core": "1.167.4", "@tanstack/router-utils": "1.161.6", "@tanstack/virtual-file-routes": "1.161.7", "prettier": "^3.5.0", "recast": "^0.23.11", "source-map": "^0.7.4", "tsx": "^4.19.2", "zod": "^3.24.2" } }, "sha512-2HdxSTbCkbU9JeYogKVigIlXoLtIJE1x5rbEov+ZLTPjGCO9kicNQuljqg9Js+u2/ahtWewNrE5u1QCAyxmpIg=="],
"@tanstack/router-plugin": ["@tanstack/router-plugin@1.166.10", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@tanstack/router-core": "1.167.1", "@tanstack/router-generator": "1.166.9", "@tanstack/router-utils": "1.161.5", "@tanstack/virtual-file-routes": "1.161.5", "chokidar": "^3.6.0", "unplugin": "^2.1.2", "zod": "^3.24.2" }, "peerDependencies": { "@rsbuild/core": ">=1.0.2", "@tanstack/react-router": "^1.167.1", "vite": ">=5.0.0 || >=6.0.0 || >=7.0.0", "vite-plugin-solid": "^2.11.10", "webpack": ">=5.92.0" }, "optionalPeers": ["@rsbuild/core", "@tanstack/react-router", "vite", "vite-plugin-solid", "webpack"] }, "sha512-fsWjXJslI4lBVVOSlJaaqEryWWoKEYnwQ6n7TKziqrN2IQ0KBXDhvl9Elc/iBziIHad5ANV9Mn9QltBhhpxlFw=="],
"@tanstack/router-plugin": ["@tanstack/router-plugin@1.166.13", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@tanstack/router-core": "1.167.4", "@tanstack/router-generator": "1.166.12", "@tanstack/router-utils": "1.161.6", "@tanstack/virtual-file-routes": "1.161.7", "chokidar": "^3.6.0", "unplugin": "^2.1.2", "zod": "^3.24.2" }, "peerDependencies": { "@rsbuild/core": ">=1.0.2", "@tanstack/react-router": "^1.167.4", "vite": ">=5.0.0 || >=6.0.0 || >=7.0.0", "vite-plugin-solid": "^2.11.10", "webpack": ">=5.92.0" }, "optionalPeers": ["@rsbuild/core", "@tanstack/react-router", "vite", "vite-plugin-solid", "webpack"], "bin": { "intent": "bin/intent.js" } }, "sha512-xG3ND3AlMe6DN9PihJAYUbQJevqJvVdzN1QpZbfU1/jkHurL97ynP2yXfmMTh8Qgi1K+SWRko4bi7iZlYP9SUw=="],
"@tanstack/router-utils": ["@tanstack/router-utils@1.161.5", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/generator": "^7.28.5", "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "ansis": "^4.1.0", "babel-dead-code-elimination": "^1.0.12", "diff": "^8.0.2", "pathe": "^2.0.3", "tinyglobby": "^0.2.15" } }, "sha512-pAhHPVFkUoRBVWcnFQZzExdabQGhm09NndUpoLUN8Txh/5mby1a87sMJxpjE4oQY2/rpuguequD1eiiOpRGG3w=="],
"@tanstack/router-utils": ["@tanstack/router-utils@1.161.6", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/generator": "^7.28.5", "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "ansis": "^4.1.0", "babel-dead-code-elimination": "^1.0.12", "diff": "^8.0.2", "pathe": "^2.0.3", "tinyglobby": "^0.2.15" } }, "sha512-nRcYw+w2OEgK6VfjirYvGyPLOK+tZQz1jkYcmH5AjMamQ9PycnlxZF2aEZtPpNoUsaceX2bHptn6Ub5hGXqNvw=="],
"@tanstack/store": ["@tanstack/store@0.9.2", "", {}, "sha512-K013lUJEFJK2ofFQ/hZKJUmCnpcV00ebLyOyFOWQvyQHUOZp/iYO84BM6aOGiV81JzwbX0APTVmW8YI7yiG5oA=="],
"@tanstack/virtual-file-routes": ["@tanstack/virtual-file-routes@1.161.5", "", {}, "sha512-0OFRlGengRDj0XpGFsnvKtyeLDShfGvjj12hGOuE5jGGXsPNAL5DUCEBL65AbwA0G10eZezA1ZhlHo+6Gn4Kbg=="],
"@tanstack/virtual-file-routes": ["@tanstack/virtual-file-routes@1.161.7", "", { "bin": { "intent": "bin/intent.js" } }, "sha512-olW33+Cn+bsCsZKPwEGhlkqS6w3M2slFv11JIobdnCFKMLG97oAI2kWKdx5/zsywTL8flpnoIgaZZPlQTFYhdQ=="],
"@tediousjs/connection-string": ["@tediousjs/connection-string@0.5.0", "", {}, "sha512-7qSgZbincDDDFyRweCIEvZULFAw5iz/DeunhvuxpL31nfntX3P4Yd4HkHBRg9H8CdqY1e5WFN1PZIz/REL9MVQ=="],
@@ -1327,11 +1319,11 @@
"babel-plugin-jest-hoist": ["babel-plugin-jest-hoist@29.6.3", "", { "dependencies": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", "@types/babel__core": "^7.1.14", "@types/babel__traverse": "^7.0.6" } }, "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg=="],
"babel-plugin-polyfill-corejs2": ["babel-plugin-polyfill-corejs2@0.4.16", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-define-polyfill-provider": "^0.6.7", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-xaVwwSfebXf0ooE11BJovZYKhFjIvQo7TsyVpETuIeH2JHv0k/T6Y5j22pPTvqYqmpkxdlPAJlyJ0tfOJAoMxw=="],
"babel-plugin-polyfill-corejs2": ["babel-plugin-polyfill-corejs2@0.4.17", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-define-polyfill-provider": "^0.6.8", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w=="],
"babel-plugin-polyfill-corejs3": ["babel-plugin-polyfill-corejs3@0.13.0", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.5", "core-js-compat": "^3.43.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A=="],
"babel-plugin-polyfill-regenerator": ["babel-plugin-polyfill-regenerator@0.6.7", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.7" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-OTYbUlSwXhNgr4g6efMZgsO8//jA61P7ZbRX3iTT53VON8l+WQS8IAUEVo4a4cWknrg2W8Cj4gQhRYNCJ8GkAA=="],
"babel-plugin-polyfill-regenerator": ["babel-plugin-polyfill-regenerator@0.6.8", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.8" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg=="],
"babel-plugin-react-compiler": ["babel-plugin-react-compiler@1.0.0", "", { "dependencies": { "@babel/types": "^7.26.0" } }, "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw=="],
@@ -1407,7 +1399,7 @@
"camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="],
"caniuse-lite": ["caniuse-lite@1.0.30001779", "", {}, "sha512-U5og2PN7V4DMgF50YPNtnZJGWVLFjjsN3zb6uMT5VGYIewieDj1upwfuVNXf4Kor+89c3iCRJnSzMD5LmTvsfA=="],
"caniuse-lite": ["caniuse-lite@1.0.30001780", "", {}, "sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ=="],
"ce-la-react": ["ce-la-react@0.3.2", "", { "peerDependencies": { "react": ">=17.0.0" } }, "sha512-QJ6k4lOD/btI08xG8jBPxRCGXvCnusGGkTsiXk0u3NqUu/W+BXRnFD4PYjwtqh8AWmGa5LDbGk0fLQsqr0nSMA=="],
@@ -1473,7 +1465,7 @@
"cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
"core-js-compat": ["core-js-compat@3.48.0", "", { "dependencies": { "browserslist": "^4.28.1" } }, "sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q=="],
"core-js-compat": ["core-js-compat@3.49.0", "", { "dependencies": { "browserslist": "^4.28.1" } }, "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA=="],
"cors": ["cors@2.8.6", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="],
@@ -1521,8 +1513,6 @@
"date-fns": ["date-fns@4.1.0", "", {}, "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg=="],
"date-fns-jalali": ["date-fns-jalali@4.1.0-0", "", {}, "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg=="],
"dateformat": ["dateformat@4.6.3", "", {}, "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA=="],
"dayjs": ["dayjs@1.11.20", "", {}, "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ=="],
@@ -1589,7 +1579,7 @@
"end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="],
"enhanced-resolve": ["enhanced-resolve@5.20.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ=="],
"enhanced-resolve": ["enhanced-resolve@5.20.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA=="],
"entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
@@ -1633,77 +1623,77 @@
"exif-parser": ["exif-parser@0.1.12", "", {}, "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw=="],
"expo": ["expo@55.0.6", "", { "dependencies": { "@babel/runtime": "^7.20.0", "@expo/cli": "55.0.16", "@expo/config": "~55.0.8", "@expo/config-plugins": "~55.0.6", "@expo/devtools": "55.0.2", "@expo/fingerprint": "0.16.6", "@expo/local-build-cache-provider": "55.0.6", "@expo/log-box": "55.0.7", "@expo/metro": "~54.2.0", "@expo/metro-config": "55.0.9", "@expo/vector-icons": "^15.0.2", "@ungap/structured-clone": "^1.3.0", "babel-preset-expo": "~55.0.11", "expo-asset": "~55.0.8", "expo-constants": "~55.0.7", "expo-file-system": "~55.0.10", "expo-font": "~55.0.4", "expo-keep-awake": "~55.0.4", "expo-modules-autolinking": "55.0.9", "expo-modules-core": "55.0.15", "pretty-format": "^29.7.0", "react-refresh": "^0.14.2", "whatwg-url-minimum": "^0.1.1" }, "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-gaF8bh5beWmrptz3d4Gr138CiPoLJtzjNbqNSOQ8kdQm3wMW8lJGT1dsY5NPJTZ7MNJBTN+pcRwshr4BMK4OiA=="],
"expo": ["expo@55.0.7", "", { "dependencies": { "@babel/runtime": "^7.20.0", "@expo/cli": "55.0.17", "@expo/config": "~55.0.9", "@expo/config-plugins": "~55.0.6", "@expo/devtools": "55.0.2", "@expo/fingerprint": "0.16.6", "@expo/local-build-cache-provider": "55.0.6", "@expo/log-box": "55.0.7", "@expo/metro": "~54.2.0", "@expo/metro-config": "55.0.10", "@expo/vector-icons": "^15.0.2", "@ungap/structured-clone": "^1.3.0", "babel-preset-expo": "~55.0.11", "expo-asset": "~55.0.9", "expo-constants": "~55.0.8", "expo-file-system": "~55.0.11", "expo-font": "~55.0.4", "expo-keep-awake": "~55.0.4", "expo-modules-autolinking": "55.0.10", "expo-modules-core": "55.0.16", "pretty-format": "^29.7.0", "react-refresh": "^0.14.2", "whatwg-url-minimum": "^0.1.1" }, "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-0k4wVQGRjWvxdKeZlmGUSIcntG/alOXfGqpsyGAp16y6ds5oeOe9dezARs8GbLS7U4cE9wjkT8T9z0uxyMoDuw=="],
"expo-application": ["expo-application@55.0.9", "", { "peerDependencies": { "expo": "*" } }, "sha512-jXTaLKdW4cvGSUjF2UQed9ao4P/7TsEo/To7TjxM+jNa74xCSUCBSTxdQftm6hZWRzXG8KT7rSoQDEL51neh1w=="],
"expo-application": ["expo-application@55.0.10", "", { "peerDependencies": { "expo": "*" } }, "sha512-5ccf+S6hsQz+doi907TOJxKzV5AKgAgw004z4FoDWSoGhfab0LUPg6uyvOspuU4cbNvqw8EAy08hZbVO8nKc9Q=="],
"expo-asset": ["expo-asset@55.0.8", "", { "dependencies": { "@expo/image-utils": "^0.8.12", "expo-constants": "~55.0.7" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-yEz2svDX67R0yiW2skx6dJmcE0q7sj9ECpGMcxBExMCbctc+nMoZCnjUuhzPl5vhClUsO5HFFXS5vIGmf1bgHQ=="],
"expo-asset": ["expo-asset@55.0.9", "", { "dependencies": { "@expo/image-utils": "^0.8.12", "expo-constants": "~55.0.8" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-cBZy8uG6eNyHnCCVIQaJsYYCxaQEWctq6xDjxJ8Hm03GTJkZF4OGCGgq2+OboBUGeOmJd7qlvMpXeXY5giMmOQ=="],
"expo-clipboard": ["expo-clipboard@55.0.8", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-s0Hkop+dc6m09LwzUAWweNI0gzLAaX5CgEGR8TMdOdSPKTPc2rCl8h8Ji/cUNM1wYoJQ4Wysa15E8If/Vlu7WA=="],
"expo-clipboard": ["expo-clipboard@55.0.9", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-WJ9ougE8fEDu3/RV5Vz3gE5aNgnj1C0WByXCz3WUj8y/06bJyaIUDMq8FDLv3n0AO3guiErmkUunsD0EzSDUUw=="],
"expo-constants": ["expo-constants@55.0.7", "", { "dependencies": { "@expo/config": "~55.0.8", "@expo/env": "~2.1.1" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-kdcO4TsQRRqt0USvjaY5vgQMO9H52K3kBZ/ejC7F6rz70mv08GoowrZ1CYOr5O4JpPDRlIpQfZJUucaS/c+KWQ=="],
"expo-constants": ["expo-constants@55.0.8", "", { "dependencies": { "@expo/config": "~55.0.9", "@expo/env": "~2.1.1" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-fhB+8EePHyHu2fVHFRObKV7QL4RCQc6OfJyNn34f6/KEoA3e0q/iCL24IUW2RoIYq+sdNHl09MjDU0Hhh1Ec4A=="],
"expo-dev-client": ["expo-dev-client@55.0.16", "", { "dependencies": { "expo-dev-launcher": "55.0.17", "expo-dev-menu": "55.0.14", "expo-dev-menu-interface": "55.0.1", "expo-manifests": "~55.0.9", "expo-updates-interface": "~55.1.3" }, "peerDependencies": { "expo": "*" } }, "sha512-PbbzWcmvm0Cqp5Y33hLxAX5ewsyph32e8UQL6gwz5iL8KL7JSi/Zbnotr+3cCeEUpLmRBF9pAigm6evAod7PIg=="],
"expo-dev-client": ["expo-dev-client@55.0.17", "", { "dependencies": { "expo-dev-launcher": "55.0.18", "expo-dev-menu": "55.0.15", "expo-dev-menu-interface": "55.0.1", "expo-manifests": "~55.0.10", "expo-updates-interface": "~55.1.3" }, "peerDependencies": { "expo": "*" } }, "sha512-N2rTkJxZdydF0ElCnOIXENf+TM1flBOhycq9V2wKhcair2trEkdbj7+2Jp4r5cwZ8S3KYhPCwKVvStAnW8nbUQ=="],
"expo-dev-launcher": ["expo-dev-launcher@55.0.17", "", { "dependencies": { "@expo/schema-utils": "^55.0.2", "expo-dev-menu": "55.0.14", "expo-manifests": "~55.0.9" }, "peerDependencies": { "expo": "*" } }, "sha512-ZiEPC6lYpWk5WDWAyHnBqSjCf2Mq/QBuuYxtKeV0s9FWzGCFDxB39W2oH2UmDve9PosnC3zCloX99IoJCdJmAw=="],
"expo-dev-launcher": ["expo-dev-launcher@55.0.18", "", { "dependencies": { "@expo/schema-utils": "^55.0.2", "expo-dev-menu": "55.0.15", "expo-manifests": "~55.0.10" }, "peerDependencies": { "expo": "*" } }, "sha512-PkS34hCPLTu41/4PuJwkj/yTEQza0uOKbP0Xisx4MX9L/vi5KNO0b6R2wAKthNthoEb7rGfJ14UBSbg8az5Owg=="],
"expo-dev-menu": ["expo-dev-menu@55.0.14", "", { "dependencies": { "expo-dev-menu-interface": "55.0.1" }, "peerDependencies": { "expo": "*" } }, "sha512-1YgtX6ejSTxIX4P6A+zWnP4ZKbikO+j8dd1ebpSacyHfdnF7kAKXf2nwUjlTBvmbd2zFtofSs7VHQJAIRpWsvA=="],
"expo-dev-menu": ["expo-dev-menu@55.0.15", "", { "dependencies": { "expo-dev-menu-interface": "55.0.1" }, "peerDependencies": { "expo": "*" } }, "sha512-OfievdxJCXUGKbERI6rT3YE7Eh8TtJA+KrdmGaU27tg5pW1zfeyvpmiYG3jy754jXXb0HbXI4aTRuX2f3CpISA=="],
"expo-dev-menu-interface": ["expo-dev-menu-interface@55.0.1", "", { "peerDependencies": { "expo": "*" } }, "sha512-FkNtwq1q6NmYoy28pj+ZLuHmirJgc039pQbJ167MZJIaprLcMN1yy67qA7xBHK+FNJ8AN8kGCtMTPByg5UC72A=="],
"expo-device": ["expo-device@55.0.9", "", { "dependencies": { "ua-parser-js": "^0.7.33" }, "peerDependencies": { "expo": "*" } }, "sha512-BzeuL7lwg2jh/tU+HTJ5dxygB1tpfgThaguPPH86K0ujcj/4RBkC27i/i7nhSoWvL1pQIgUqL0L7WTtjcS9t/w=="],
"expo-device": ["expo-device@55.0.10", "", { "dependencies": { "ua-parser-js": "^0.7.33" }, "peerDependencies": { "expo": "*" } }, "sha512-jvPImQg5LN7LNL57sGY6WaJxrWrPKXueUiDctZ7brLyiZfioS6wUieLwOTTkHUdtSSBtHkTvUg675w0sOzwlTg=="],
"expo-file-system": ["expo-file-system@55.0.10", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-ysFdVdUgtfj2ApY0Cn+pBg+yK4xp+SNwcaH8j2B91JJQ4OXJmnyCSmrNZYz7J4mdYVuv2GzxIP+N/IGlHQG3Yw=="],
"expo-file-system": ["expo-file-system@55.0.11", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-KMUd6OY375J9WD79ZvjvCDZMveT7YfgiGWdi58/gfuTBsr14TRuoPk8RRQHAtc4UquzWViKcHwna9aPY7/XPpw=="],
"expo-font": ["expo-font@55.0.4", "", { "dependencies": { "fontfaceobserver": "^2.1.0" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-ZKeGTFffPygvY5dM/9ATM2p7QDkhsaHopH7wFAWgP2lKzqUMS9B/RxCvw5CaObr9Ro7x9YptyeRKX2HmgmMfrg=="],
"expo-glass-effect": ["expo-glass-effect@55.0.8", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-IvUjHb/4t6r2H/LXDjcQ4uDoHrmO2cLOvEb9leLavQ4HX5+P4LRtQrMDMlkWAn5Wo5DkLcG8+1CrQU2nqgogTA=="],
"expo-haptics": ["expo-haptics@55.0.8", "", { "peerDependencies": { "expo": "*" } }, "sha512-yVR6EsQwl1WuhFITc0PpfI/7dsBdjK/F2YA8xB80UUW9iTa+Tqz21FpH4n/vtbargpzFxkhl5WNYMa419+QWFQ=="],
"expo-haptics": ["expo-haptics@55.0.9", "", { "peerDependencies": { "expo": "*" } }, "sha512-KCRyHr/uu4syXmoq3aIQ6ahuaX6FGtlPkWGlLlHJ836WF3nG+5+oCaCQiI7qMTpml+Tp/V/zP4ZaowM2KHgLNA=="],
"expo-image": ["expo-image@55.0.6", "", { "dependencies": { "sf-symbols-typescript": "^2.2.0" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*", "react-native-web": "*" }, "optionalPeers": ["react-native-web"] }, "sha512-TKuu0uBmgTZlhd91Glv+V4vSBMlfl0bdQxfl97oKKZUo3OBC13l3eLik7v3VNLJN7PZbiwOAiXkZkqSOBx/Xsw=="],
"expo-image-loader": ["expo-image-loader@55.0.0", "", { "peerDependencies": { "expo": "*" } }, "sha512-NOjp56wDrfuA5aiNAybBIjqIn1IxKeGJ8CECWZncQ/GzjZfyTYAHTCyeApYkdKkMBLHINzI4BbTGSlbCa0fXXQ=="],
"expo-image-picker": ["expo-image-picker@55.0.12", "", { "dependencies": { "expo-image-loader": "~55.0.0" }, "peerDependencies": { "expo": "*" } }, "sha512-ky8nzXTd5eLUDct5daAHng0xrWYRJyXfLCRmEdE9v/IUywYCnFU7aCnQ7PTQJvzGSzhePJJmP/POvTkVP//+qQ=="],
"expo-image-picker": ["expo-image-picker@55.0.13", "", { "dependencies": { "expo-image-loader": "~55.0.0" }, "peerDependencies": { "expo": "*" } }, "sha512-G+W11rcoUi3rK+6cnKWkTfZilMkGVZnYe90TiM3R98nPSlzGBoto3a/TkGGTJXedz/dmMzr49L+STlWhuKKIFw=="],
"expo-json-utils": ["expo-json-utils@55.0.0", "", {}, "sha512-aupt/o5PDAb8dXDCb0JcRdkqnTLxe/F+La7jrnyd/sXlYFfRgBJLFOa1SqVFXm1E/Xam1SE/yw6eAb+DGY7Arg=="],
"expo-keep-awake": ["expo-keep-awake@55.0.4", "", { "peerDependencies": { "expo": "*", "react": "*" } }, "sha512-vwfdMtMS5Fxaon8gC0AiE70SpxTsHJ+rjeoVJl8kdfdbxczF7OIaVmfjFJ5Gfigd/WZiLqxhfZk34VAkXF4PNg=="],
"expo-linear-gradient": ["expo-linear-gradient@55.0.8", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-nCMgZXcHesnFslH1TUFMdqlDiE4jYTTTSHb97g1jq1gyGX2xDEOyqBxQJmiIVGrZJ+kTWH6RljJ5tYyva9mrAg=="],
"expo-linear-gradient": ["expo-linear-gradient@55.0.9", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-S82iF+CVoSBVHdwusLQGh6Th/kcWLHU47jZhBPwyTrYWnsHZtb0oCqU96YvhDYvhbTdsuOaKEi+Xu+r/I2R8ow=="],
"expo-linking": ["expo-linking@55.0.7", "", { "dependencies": { "expo-constants": "~55.0.7", "invariant": "^2.2.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-MiGCedere1vzQTEi2aGrkzd7eh/rPSz4w6F3GMBuAJzYl+/0VhIuyhozpEGrueyDIXWfzaUVOcn3SfxVi+kwQQ=="],
"expo-localization": ["expo-localization@55.0.8", "", { "dependencies": { "rtl-detect": "^1.0.2" }, "peerDependencies": { "expo": "*", "react": "*" } }, "sha512-uFmpTsoDT7JE5Nwgt0EQ5gBvFVo7/u458SlY6V9Ep9wY/WPucL0o00VpXoFULaMtKHquKBgVUdHwk6E+JFz4dg=="],
"expo-localization": ["expo-localization@55.0.9", "", { "dependencies": { "rtl-detect": "^1.0.2" }, "peerDependencies": { "expo": "*", "react": "*" } }, "sha512-ABRg4wEt15OCp9/3XOLC4ltPVvXmJgeCKNTJ4Nb8N6byuHITJHvZ3PwcC2YGpTzlAqfGbcs3rJdfg1ObT54PJQ=="],
"expo-manifests": ["expo-manifests@55.0.9", "", { "dependencies": { "@expo/config": "~55.0.8", "expo-json-utils": "~55.0.0" }, "peerDependencies": { "expo": "*" } }, "sha512-i82j3X4hbxYDe6kxUw4u8WfvbvTj2w+9BD9WKuL0mFRy+MjvdzdyaqAjEViWCKo/alquP/hTApDTQBb3UmWhkg=="],
"expo-manifests": ["expo-manifests@55.0.10", "", { "dependencies": { "@expo/config": "~55.0.9", "expo-json-utils": "~55.0.0" }, "peerDependencies": { "expo": "*" } }, "sha512-/pttypEeUUkIu02akkwKbUVIT6AZrLXSlbcM3DsFdR4HMP4ZjnwBaR7ejF2QLqFGREq9UJR2ezKm9gY6SE7AdA=="],
"expo-modules-autolinking": ["expo-modules-autolinking@55.0.9", "", { "dependencies": { "@expo/require-utils": "^55.0.2", "@expo/spawn-async": "^1.7.2", "chalk": "^4.1.0", "commander": "^7.2.0" }, "bin": { "expo-modules-autolinking": "bin/expo-modules-autolinking.js" } }, "sha512-OXIrxSYKlT/1Av1AMyUWeSTW1GChGofWV14sB73o5eFbfuz6ocv18fnKx+Ji67ZC7a0RztDctcZTuEQK84S4iw=="],
"expo-modules-autolinking": ["expo-modules-autolinking@55.0.10", "", { "dependencies": { "@expo/require-utils": "^55.0.3", "@expo/spawn-async": "^1.7.2", "chalk": "^4.1.0", "commander": "^7.2.0" }, "bin": { "expo-modules-autolinking": "bin/expo-modules-autolinking.js" } }, "sha512-qBnoohB7YblKvFCPAwi2JvS2r7SKET8sSKDD1sihRTNEGEge579+NXM1HBa0oYYfCqOXByxWai86XrWeTYd03w=="],
"expo-modules-core": ["expo-modules-core@55.0.15", "", { "dependencies": { "invariant": "^2.2.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-MAGz1SYSVgQbwVeUysWgPtLh8ozbBwORatXoA4w0NZqZBZzEyBgUQNhuwaroaIi9W8Ir3wy1McmZcDYDJNGmVw=="],
"expo-modules-core": ["expo-modules-core@55.0.16", "", { "dependencies": { "invariant": "^2.2.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-VeZxwnyHM4Kf52FqonPwlkYdF6YQnZAIXWT3RpEv2Ng/mwheZ2PZGc3rxaHAo0FoeSXM9i0g3HCJjOJUkZwV3A=="],
"expo-network": ["expo-network@55.0.8", "", { "peerDependencies": { "expo": "*", "react": "*" } }, "sha512-IXxwFq5B2OImc6BvHHGN9QOCYfMk3wPnbBL+NiyBFqy+g2LsdnGzWLA2HXD8+1Ngdvi7PwckQO+q6WKHLRx4Vw=="],
"expo-network": ["expo-network@55.0.9", "", { "peerDependencies": { "expo": "*", "react": "*" } }, "sha512-vuL7s+Zbsbcqh2XxhaZ45mfuYZVn7ikukL27be7hN3zkLxoABNPgbVt+/gEKOxyv2Gzr/Q5JZvfVxCuwDDh0Fg=="],
"expo-router": ["expo-router@55.0.5", "", { "dependencies": { "@expo/metro-runtime": "^55.0.6", "@expo/schema-utils": "^55.0.2", "@radix-ui/react-slot": "^1.2.0", "@radix-ui/react-tabs": "^1.1.12", "@react-navigation/bottom-tabs": "^7.10.1", "@react-navigation/native": "^7.1.28", "@react-navigation/native-stack": "^7.10.1", "client-only": "^0.0.1", "debug": "^4.3.4", "escape-string-regexp": "^4.0.0", "expo-glass-effect": "^55.0.8", "expo-image": "^55.0.6", "expo-server": "^55.0.6", "expo-symbols": "^55.0.5", "fast-deep-equal": "^3.1.3", "invariant": "^2.2.4", "nanoid": "^3.3.8", "query-string": "^7.1.3", "react-fast-compare": "^3.2.2", "react-native-is-edge-to-edge": "^1.2.1", "semver": "~7.6.3", "server-only": "^0.0.1", "sf-symbols-typescript": "^2.1.0", "shallowequal": "^1.1.0", "use-latest-callback": "^0.2.1", "vaul": "^1.1.2" }, "peerDependencies": { "@expo/log-box": "55.0.7", "@react-navigation/drawer": "^7.7.2", "@testing-library/react-native": ">= 13.2.0", "expo": "*", "expo-constants": "^55.0.7", "expo-linking": "^55.0.7", "react": "*", "react-dom": "*", "react-native": "*", "react-native-gesture-handler": "*", "react-native-reanimated": "*", "react-native-safe-area-context": ">= 5.4.0", "react-native-screens": "*", "react-native-web": "*", "react-server-dom-webpack": "~19.0.4 || ~19.1.5 || ~19.2.4" }, "optionalPeers": ["@react-navigation/drawer", "@testing-library/react-native", "react-dom", "react-native-gesture-handler", "react-native-reanimated", "react-native-web", "react-server-dom-webpack"] }, "sha512-PzN545wLtznKuVQmJXnAKB/JFjSJJIPHatsjJe4Cl6bRADr/MbWv5d2fqOpqFD/C0ZGCRHY1uBalq7mb5IQ3ZQ=="],
"expo-router": ["expo-router@55.0.6", "", { "dependencies": { "@expo/metro-runtime": "^55.0.6", "@expo/schema-utils": "^55.0.2", "@radix-ui/react-slot": "^1.2.0", "@radix-ui/react-tabs": "^1.1.12", "@react-navigation/bottom-tabs": "^7.15.5", "@react-navigation/native": "^7.1.33", "@react-navigation/native-stack": "^7.14.5", "client-only": "^0.0.1", "debug": "^4.3.4", "escape-string-regexp": "^4.0.0", "expo-glass-effect": "^55.0.8", "expo-image": "^55.0.6", "expo-server": "^55.0.6", "expo-symbols": "^55.0.5", "fast-deep-equal": "^3.1.3", "invariant": "^2.2.4", "nanoid": "^3.3.8", "query-string": "^7.1.3", "react-fast-compare": "^3.2.2", "react-native-is-edge-to-edge": "^1.2.1", "semver": "~7.6.3", "server-only": "^0.0.1", "sf-symbols-typescript": "^2.1.0", "shallowequal": "^1.1.0", "use-latest-callback": "^0.2.1", "vaul": "^1.1.2" }, "peerDependencies": { "@expo/log-box": "55.0.7", "@react-navigation/drawer": "^7.9.4", "@testing-library/react-native": ">= 13.2.0", "expo": "*", "expo-constants": "^55.0.8", "expo-linking": "^55.0.7", "react": "*", "react-dom": "*", "react-native": "*", "react-native-gesture-handler": "*", "react-native-reanimated": "*", "react-native-safe-area-context": ">= 5.4.0", "react-native-screens": "*", "react-native-web": "*", "react-server-dom-webpack": "~19.0.4 || ~19.1.5 || ~19.2.4" }, "optionalPeers": ["@react-navigation/drawer", "@testing-library/react-native", "react-dom", "react-native-gesture-handler", "react-native-reanimated", "react-native-web", "react-server-dom-webpack"] }, "sha512-Vd9qgFv3eBkIi5vZkokMPAI6o86scl6b4yYkRBJjjkXCBTNo14KM9jfFBNkvLGxvoAvjVKx5IfgZo8k88vrlfA=="],
"expo-secure-store": ["expo-secure-store@55.0.8", "", { "peerDependencies": { "expo": "*" } }, "sha512-8w9tQe8U6oRo5YIzqCqVhRrOnfoODNDoitBtLXEx+zS6WLUnkRq5kH7ViJuOgiM7PzLr9pvAliRiDOKyvFbTuQ=="],
"expo-secure-store": ["expo-secure-store@55.0.9", "", { "peerDependencies": { "expo": "*" } }, "sha512-TIPGjM73LKlebpXwgAu/yL7lNWr6RQYmFw3vgYHOqLFYQMpsBqkQmopovbNX3c/0+RCE9KZlLAkcz8r6detILQ=="],
"expo-server": ["expo-server@55.0.6", "", {}, "sha512-xI72FTm469FfuuBL2R5aNtthgH+GR7ygOpsx/KcPS0K8AZaZd7VjtEExbzn9/qyyYkWW3T+3dAmCDKOMX8gdmQ=="],
"expo-splash-screen": ["expo-splash-screen@55.0.10", "", { "dependencies": { "@expo/prebuild-config": "^55.0.8" }, "peerDependencies": { "expo": "*" } }, "sha512-RN5qqrxudxFlRIjLFr/Ifmt+mUCLRc0gs66PekP6flzNS/JYEuoCbwJ+NmUwwJtPA+vyy60DYiky0QmS98ydmQ=="],
"expo-splash-screen": ["expo-splash-screen@55.0.11", "", { "dependencies": { "@expo/prebuild-config": "^55.0.9" }, "peerDependencies": { "expo": "*" } }, "sha512-H+hR4eWkAjsaU+pizrpA6iKZGGnkhHjk/quoSMIZZiKzUa1fRuPTq3VUlNLsfnFkWmmlKELMHKJzK5h394Hosg=="],
"expo-status-bar": ["expo-status-bar@55.0.4", "", { "dependencies": { "react-native-is-edge-to-edge": "^1.2.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-BPDjUXKqv1F9j2YNGLRZfkBEZXIEEpqj+t81y4c+4fdSN3Pos7goIHXgcl2ozbKQLgKRZQyNZQtbUgh5UjHYUQ=="],
"expo-symbols": ["expo-symbols@55.0.5", "", { "dependencies": { "@expo-google-fonts/material-symbols": "^0.4.1", "sf-symbols-typescript": "^2.0.0" }, "peerDependencies": { "expo": "*", "expo-font": "*", "react": "*", "react-native": "*" } }, "sha512-W/QYRvnYVes947ZYOHtuKL8Gobs7BUjeu9oknzbo4jGnou7Ks6bj1CwdT0ZWNBgaTopbS4/POXumJIkW4cTPSQ=="],
"expo-system-ui": ["expo-system-ui@55.0.9", "", { "dependencies": { "@react-native/normalize-colors": "0.83.2", "debug": "^4.3.2" }, "peerDependencies": { "expo": "*", "react-native": "*", "react-native-web": "*" }, "optionalPeers": ["react-native-web"] }, "sha512-8ygP1B0uFAFI8s7eHY2IcGnE83GhFeZYwHBr/fQ4dSXnc7iVT9zp2PvyTyiDiibQ69dBG+fauMQ4KlPcOO51kQ=="],
"expo-system-ui": ["expo-system-ui@55.0.10", "", { "dependencies": { "@react-native/normalize-colors": "0.83.2", "debug": "^4.3.2" }, "peerDependencies": { "expo": "*", "react-native": "*", "react-native-web": "*" }, "optionalPeers": ["react-native-web"] }, "sha512-s0Fyf37ma3TaWhh18uocg914Uz0tKPaYBf+mEME7Tp88d3FxOTg+tTv1S9mUe4lJNXY+0KlWFz9J4SMNyjGRWQ=="],
"expo-tracking-transparency": ["expo-tracking-transparency@55.0.8", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-I7yEZF3uZwgC4wDm0mJ/1WSd2fMOLB9cubYSxYq0yt2cElxsM3KuVa0f2EE1NyQSCzmczfBKwjBPEoQ49i+wbw=="],
"expo-tracking-transparency": ["expo-tracking-transparency@55.0.9", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-NHjc9tTK2kRg+mtjm1MF6ROsqFl48nYp/eftx+STy+ZGvqQUdwRC4QetCPsAkgY9P/A2Q3iZO+bmHBKo16vo8w=="],
"expo-updates-interface": ["expo-updates-interface@55.1.3", "", { "peerDependencies": { "expo": "*" } }, "sha512-UVVIiZqymQZJL+o/jh65kXOI97xdkbqBJJM0LMabaPMNLFnc6/WvOMOzmQs7SPyKb8+0PeBaFd7tj5DzF6JeQg=="],
"expo-web-browser": ["expo-web-browser@55.0.9", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-PvAVsG401QmZabtTsYh1cYcpPiqvBPs8oiOkSrp0jIXnneiM466HxmeNtvo+fNxqJ2nwOBz9qLPiWRO91VBfsQ=="],
"expo-web-browser": ["expo-web-browser@55.0.10", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-2d6qVrg/nt0JvW5uAqOMDG/xITIXFe1Prkq1ri+I3PrC0QmV5cMYNSagU9ykfC8S7YKWxF1qO7Qsih9fxNa9dw=="],
"exponential-backoff": ["exponential-backoff@3.1.3", "", {}, "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA=="],
@@ -1755,7 +1745,7 @@
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
"framer-motion": ["framer-motion@12.36.0", "", { "dependencies": { "motion-dom": "^12.36.0", "motion-utils": "^12.36.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-4PqYHAT7gev0ke0wos+PyrcFxI0HScjm3asgU8nSYa8YzJFuwgIvdj3/s3ZaxLq0bUSboIn19A2WS/MHwLCvfw=="],
"framer-motion": ["framer-motion@12.38.0", "", { "dependencies": { "motion-dom": "^12.38.0", "motion-utils": "^12.36.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g=="],
"fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="],
@@ -2043,7 +2033,7 @@
"mdn-data": ["mdn-data@2.0.14", "", {}, "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow=="],
"media-chrome": ["media-chrome@4.18.0", "", { "dependencies": { "ce-la-react": "^0.3.2" } }, "sha512-OQmpUCeMMWtUcbar3CiY7V0S6j43HaDhwa6uRUyYiWdfPIj3riWp0agu3x6EcIlDRFIf8iDGZiGeb26ll+mz0g=="],
"media-chrome": ["media-chrome@4.18.1", "", { "dependencies": { "ce-la-react": "^0.3.2" } }, "sha512-U8iHCId+HZaqnVm+VACY4lDzTnxgHHjOPOnku8d9qhqDr9VRl1v7rOEjEi35+U0qKYX8rizBF1fA0qPsoRsQMQ=="],
"media-played-ranges-mixin": ["media-played-ranges-mixin@0.1.0", "", {}, "sha512-zTsvkleu5sAyTsPVxDI+KUbCwy/lXwHgOPi3ER9S3lhtAWhGTQH6qxvfrVMym1cvoLU36SPbVr6Qe8Zxyc0WpA=="],
@@ -2111,9 +2101,9 @@
"mongodb-connection-string-url": ["mongodb-connection-string-url@7.0.1", "", { "dependencies": { "@types/whatwg-url": "^13.0.0", "whatwg-url": "^14.1.0" } }, "sha512-h0AZ9A7IDVwwHyMxmdMXKy+9oNlF0zFoahHiX3vQ8e3KFcSP3VmsmfvtRSuLPxmyv2vjIDxqty8smTgie/SNRQ=="],
"motion": ["motion@12.36.0", "", { "dependencies": { "framer-motion": "^12.36.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-5BMQuktYUX8aEByKWYx5tR4X3G08H2OMgp46wTxZ4o7CDDstyy4A0fe9RLNMjZiwvntCWGDvs16sC87/emz4Yw=="],
"motion": ["motion@12.38.0", "", { "dependencies": { "framer-motion": "^12.38.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-uYfXzeHlgThchzwz5Te47dlv5JOUC7OB4rjJ/7XTUgtBZD8CchMN8qEJ4ZVsUmTyYA44zjV0fBwsiktRuFnn+w=="],
"motion-dom": ["motion-dom@12.36.0", "", { "dependencies": { "motion-utils": "^12.36.0" } }, "sha512-Ep1pq8P88rGJ75om8lTCA13zqd7ywPGwCqwuWwin6BKc0hMLkVfcS6qKlRqEo2+t0DwoUcgGJfXwaiFn4AOcQA=="],
"motion-dom": ["motion-dom@12.38.0", "", { "dependencies": { "motion-utils": "^12.36.0" } }, "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA=="],
"motion-utils": ["motion-utils@12.36.0", "", {}, "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg=="],
@@ -2121,7 +2111,7 @@
"mssql": ["mssql@11.0.1", "", { "dependencies": { "@tediousjs/connection-string": "^0.5.0", "commander": "^11.0.0", "debug": "^4.3.3", "rfdc": "^1.3.0", "tarn": "^3.0.2", "tedious": "^18.2.1" }, "bin": { "mssql": "bin/mssql" } }, "sha512-KlGNsugoT90enKlR8/G36H0kTxPthDhmtNUCwEHvgRza5Cjpjoj+P2X6eMpFUDN7pFrJZsKadL4x990G8RBE1w=="],
"msw": ["msw@2.12.11", "", { "dependencies": { "@inquirer/confirm": "^5.0.0", "@mswjs/interceptors": "^0.41.2", "@open-draft/deferred-promise": "^2.2.0", "@types/statuses": "^2.0.6", "cookie": "^1.0.2", "graphql": "^16.12.0", "headers-polyfill": "^4.0.2", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "path-to-regexp": "^6.3.0", "picocolors": "^1.1.1", "rettime": "^0.10.1", "statuses": "^2.0.2", "strict-event-emitter": "^0.5.1", "tough-cookie": "^6.0.0", "type-fest": "^5.2.0", "until-async": "^3.0.2", "yargs": "^17.7.2" }, "peerDependencies": { "typescript": ">= 4.8.x" }, "optionalPeers": ["typescript"], "bin": { "msw": "cli/index.js" } }, "sha512-dVg20zi2I2EvnwH/+WupzsOC2mCa7qsIhyMAWtfRikn6RKtwL9+7SaF1IQ5LyZry4tlUtf6KyTVhnlQiZXozTQ=="],
"msw": ["msw@2.12.13", "", { "dependencies": { "@inquirer/confirm": "^5.0.0", "@mswjs/interceptors": "^0.41.2", "@open-draft/deferred-promise": "^2.2.0", "@types/statuses": "^2.0.6", "cookie": "^1.0.2", "graphql": "^16.12.0", "headers-polyfill": "^4.0.2", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "path-to-regexp": "^6.3.0", "picocolors": "^1.1.1", "rettime": "^0.10.1", "statuses": "^2.0.2", "strict-event-emitter": "^0.5.1", "tough-cookie": "^6.0.0", "type-fest": "^5.2.0", "until-async": "^3.0.2", "yargs": "^17.7.2" }, "peerDependencies": { "typescript": ">= 4.8.x" }, "optionalPeers": ["typescript"], "bin": { "msw": "cli/index.js" } }, "sha512-9CV2mXT9+z0J26MQDfEZZkj/psJ5Er/w0w+t95FWdaGH/DTlhNZBx8vBO5jSYv8AZEnl3ouX+AaTT68KXdAIag=="],
"multitars": ["multitars@0.2.4", "", {}, "sha512-XgLbg1HHchFauMCQPRwMj6MSyDd5koPlTA1hM3rUFkeXzGpjU/I9fP3to7yrObE9jcN8ChIOQGrM0tV0kUZaKg=="],
@@ -2297,8 +2287,6 @@
"react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="],
"react-day-picker": ["react-day-picker@9.14.0", "", { "dependencies": { "@date-fns/tz": "^1.4.1", "@tabby_ai/hijri-converter": "1.0.5", "date-fns": "^4.1.0", "date-fns-jalali": "4.1.0-0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-tBaoDWjPwe0M5pGrum4H0SR6Lyk+BO9oHnp9JbKpGKW2mlraNPgP9BMfsg5pWpwrssARmeqk7YBl2oXutZTaHA=="],
"react-devtools-core": ["react-devtools-core@6.1.5", "", { "dependencies": { "shell-quote": "^1.6.1", "ws": "^7" } }, "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA=="],
"react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="],
@@ -2415,7 +2403,7 @@
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
"sax": ["sax@1.5.0", "", {}, "sha512-21IYA3Q5cQf089Z6tgaUTr7lDAyzoTPx5HRtbhsME8Udispad8dC/+sziTNugOEx54ilvatQ9YCzl4KQLPcRHA=="],
"sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="],
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
@@ -2549,7 +2537,7 @@
"terminal-link": ["terminal-link@2.1.1", "", { "dependencies": { "ansi-escapes": "^4.2.1", "supports-hyperlinks": "^2.0.0" } }, "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ=="],
"terser": ["terser@5.46.0", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg=="],
"terser": ["terser@5.46.1", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ=="],
"test-exclude": ["test-exclude@6.0.0", "", { "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", "minimatch": "^3.0.4" } }, "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w=="],
@@ -2569,9 +2557,9 @@
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
"tldts": ["tldts@7.0.25", "", { "dependencies": { "tldts-core": "^7.0.25" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-keinCnPbwXEUG3ilrWQZU+CqcTTzHq9m2HhoUP2l7Xmi8l1LuijAXLpAJ5zRW+ifKTNscs4NdCkfkDCBYm352w=="],
"tldts": ["tldts@7.0.26", "", { "dependencies": { "tldts-core": "^7.0.26" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-WiGwQjr0qYdNNG8KpMKlSvpxz652lqa3Rd+/hSaDcY4Uo6SKWZq2LAF+hsAhUewTtYhXlorBKgNF3Kk8hnjGoQ=="],
"tldts-core": ["tldts-core@7.0.25", "", {}, "sha512-ZjCZK0rppSBu7rjHYDYsEaMOIbbT+nWF57hKkv4IUmZWBNrBWBOjIElc0mKRgLM8bm7x/BBlof6t2gi/Oq/Asw=="],
"tldts-core": ["tldts-core@7.0.26", "", {}, "sha512-5WJ2SqFsv4G2Dwi7ZFVRnz6b2H1od39QME1lc2y5Ew3eWiZMAeqOAfWpRP9jHvhUl881406QtZTODvjttJs+ew=="],
"tmpl": ["tmpl@1.0.5", "", {}, "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw=="],
@@ -2867,11 +2855,11 @@
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"@tailwindcss/vite/@tailwindcss/node": ["@tailwindcss/node@0.0.0-insiders.d24b112", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.31.1", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "0.0.0-insiders.d24b112" } }, "sha512-cO17Fb6CWfZxRwHE0hCpsTWvagdUyMeLc7QBnni9s9V2bkAkMRr6F8gMK+pNu7bGtnx4RuqKHDmNj5gQvmhY1g=="],
"@tailwindcss/vite/@tailwindcss/node": ["@tailwindcss/node@0.0.0-insiders.f302fce", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.31.1", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "0.0.0-insiders.f302fce" } }, "sha512-Qgr9sxS26VP12GmfSdfAHd0CfO7ZZ/1t5pJSkTo5wC2JkpPFok5y77junppTDP8gw+lJJ9mMFLF90rsOLQ+LRg=="],
"@tailwindcss/vite/@tailwindcss/oxide": ["@tailwindcss/oxide@0.0.0-insiders.d24b112", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "0.0.0-insiders.d24b112", "@tailwindcss/oxide-darwin-arm64": "0.0.0-insiders.d24b112", "@tailwindcss/oxide-darwin-x64": "0.0.0-insiders.d24b112", "@tailwindcss/oxide-freebsd-x64": "0.0.0-insiders.d24b112", "@tailwindcss/oxide-linux-arm-gnueabihf": "0.0.0-insiders.d24b112", "@tailwindcss/oxide-linux-arm64-gnu": "0.0.0-insiders.d24b112", "@tailwindcss/oxide-linux-arm64-musl": "0.0.0-insiders.d24b112", "@tailwindcss/oxide-linux-x64-gnu": "0.0.0-insiders.d24b112", "@tailwindcss/oxide-linux-x64-musl": "0.0.0-insiders.d24b112", "@tailwindcss/oxide-wasm32-wasi": "0.0.0-insiders.d24b112", "@tailwindcss/oxide-win32-arm64-msvc": "0.0.0-insiders.d24b112", "@tailwindcss/oxide-win32-x64-msvc": "0.0.0-insiders.d24b112" } }, "sha512-PjTp8gDsvB/akpy1BRQoZqnyKSpe0KvX7oyEMF63d33YkXxZz19maGRn/XRRUprisX2CLqz6aLYnoZq9J1fSvQ=="],
"@tailwindcss/vite/@tailwindcss/oxide": ["@tailwindcss/oxide@0.0.0-insiders.f302fce", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "0.0.0-insiders.f302fce", "@tailwindcss/oxide-darwin-arm64": "0.0.0-insiders.f302fce", "@tailwindcss/oxide-darwin-x64": "0.0.0-insiders.f302fce", "@tailwindcss/oxide-freebsd-x64": "0.0.0-insiders.f302fce", "@tailwindcss/oxide-linux-arm-gnueabihf": "0.0.0-insiders.f302fce", "@tailwindcss/oxide-linux-arm64-gnu": "0.0.0-insiders.f302fce", "@tailwindcss/oxide-linux-arm64-musl": "0.0.0-insiders.f302fce", "@tailwindcss/oxide-linux-x64-gnu": "0.0.0-insiders.f302fce", "@tailwindcss/oxide-linux-x64-musl": "0.0.0-insiders.f302fce", "@tailwindcss/oxide-wasm32-wasi": "0.0.0-insiders.f302fce", "@tailwindcss/oxide-win32-arm64-msvc": "0.0.0-insiders.f302fce", "@tailwindcss/oxide-win32-x64-msvc": "0.0.0-insiders.f302fce" } }, "sha512-dItC6gT3nTiCU1G7xGA0nfHH/evo9pwkRY72JB3AF/KEkuOm7TdKLIPuAByv73PlzoD9r5I1fbDGyqaODRDzGA=="],
"@tailwindcss/vite/tailwindcss": ["tailwindcss@0.0.0-insiders.d24b112", "", {}, "sha512-rjZ6AMPS2PZJ+8IrmW/FEod/+9z0XebcPxfE98cf7kpF5mgo/HApTsSzKyU4djSeAoXWEoAOHzcnfYdsg+qNLg=="],
"@tailwindcss/vite/tailwindcss": ["tailwindcss@0.0.0-insiders.f302fce", "", {}, "sha512-86DU/62/6BZhMorfAifJ2RLm6goaXSKFGQl7IHCe4zkmwzY6gak2FF4Eu+Dttepfc3LjFvZ7CWjk6lDeRlOHNw=="],
"@tanstack/devtools-event-bus/ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="],
@@ -3205,29 +3193,29 @@
"@tailwindcss/vite/@tailwindcss/node/lightningcss": ["lightningcss@1.31.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.31.1", "lightningcss-darwin-arm64": "1.31.1", "lightningcss-darwin-x64": "1.31.1", "lightningcss-freebsd-x64": "1.31.1", "lightningcss-linux-arm-gnueabihf": "1.31.1", "lightningcss-linux-arm64-gnu": "1.31.1", "lightningcss-linux-arm64-musl": "1.31.1", "lightningcss-linux-x64-gnu": "1.31.1", "lightningcss-linux-x64-musl": "1.31.1", "lightningcss-win32-arm64-msvc": "1.31.1", "lightningcss-win32-x64-msvc": "1.31.1" } }, "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ=="],
"@tailwindcss/vite/@tailwindcss/oxide/@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@0.0.0-insiders.d24b112", "", { "os": "android", "cpu": "arm64" }, "sha512-1wQYAR5o1829Ak2YynI0Lo/MZKVYxHbC3c12TRwy1C0oKq6ORne7p7RPogyUBSSbyTjYXNWWxhomxwDkewbjGw=="],
"@tailwindcss/vite/@tailwindcss/oxide/@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@0.0.0-insiders.f302fce", "", { "os": "android", "cpu": "arm64" }, "sha512-2jATq0KmE/HT76NdA05etl/4pwLT498l84tnC6N5hPlsCoU72TF56ZuT85yMSyn1aaKXc59t2+MHx3Thzubmhg=="],
"@tailwindcss/vite/@tailwindcss/oxide/@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@0.0.0-insiders.d24b112", "", { "os": "darwin", "cpu": "arm64" }, "sha512-wrbmTKgPMPvj/RsCoMwZFoIJnPOOM8rlMq5cMkiE1G04xaiuYh4kHPQt++2YOIsXxtcIOe502EegULpu4dlt2Q=="],
"@tailwindcss/vite/@tailwindcss/oxide/@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@0.0.0-insiders.f302fce", "", { "os": "darwin", "cpu": "arm64" }, "sha512-veYEnsM8wZRn+0FDRvrAwSMWTaapmkJKxHVXA+JaPNaMS6mfDVmBK2inGz4LtAQ0m/Sockc/2lcs/BMWe/rRmA=="],
"@tailwindcss/vite/@tailwindcss/oxide/@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@0.0.0-insiders.d24b112", "", { "os": "darwin", "cpu": "x64" }, "sha512-YBZu9w/KlPJ/APNcb6A0S+Lxxjjyznkj4XDm46Bvlgmqd/cB8Brm/1G2ox092mhT2c6wp52KUJbCe7WKMaZgGA=="],
"@tailwindcss/vite/@tailwindcss/oxide/@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@0.0.0-insiders.f302fce", "", { "os": "darwin", "cpu": "x64" }, "sha512-U97BlvYmmVpWnWzGdXPj2DXNpI8nEeO78LuGPUdGYl7UkfOJmJVGoi13pT/uMgtzAtPTV5NaPtkBGXr94MqT5g=="],
"@tailwindcss/vite/@tailwindcss/oxide/@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@0.0.0-insiders.d24b112", "", { "os": "freebsd", "cpu": "x64" }, "sha512-aIALDyG3+HuNP0hcyO4QAtEmXNLPUraEyJUBwrUj5u+lsjKU8fFR5JNjoeatnUmX73akm6ZYlZgLtAzAMO52Dw=="],
"@tailwindcss/vite/@tailwindcss/oxide/@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@0.0.0-insiders.f302fce", "", { "os": "freebsd", "cpu": "x64" }, "sha512-UPpsZX0zxGF6Zm2gE/1Qd/gpDxphv3CZevpgC4niBlV3hkSaDjTIQ19K+HAkkU6hiIpoSPNyRvfMhhmK9Lp0pg=="],
"@tailwindcss/vite/@tailwindcss/oxide/@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@0.0.0-insiders.d24b112", "", { "os": "linux", "cpu": "arm" }, "sha512-5iXPdGLlXQ2ZT8QD594Lz6/bIzXiG8FjgW+A6zcN2fAgIeqgbnNv6KS7Lu2S0EWIZdwQ6kIazNbUk1qWlIF4jQ=="],
"@tailwindcss/vite/@tailwindcss/oxide/@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@0.0.0-insiders.f302fce", "", { "os": "linux", "cpu": "arm" }, "sha512-AwI0+r7Y/gKv+Tdm2+v8HpC65JSvQg/QlIum6L+B7SeBAFAZx1tSS5Cz6CeJZx5yFtPDSYiL/Sqc9UbkyFfWBw=="],
"@tailwindcss/vite/@tailwindcss/oxide/@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@0.0.0-insiders.d24b112", "", { "os": "linux", "cpu": "arm64" }, "sha512-zp2ant6iL+6J/aOtLaqob1lariy6LsR3s3w6cNy51xWXJekv567gFD/pk3h4/Y0axlR/nrOqO88dpzf86bs+cg=="],
"@tailwindcss/vite/@tailwindcss/oxide/@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@0.0.0-insiders.f302fce", "", { "os": "linux", "cpu": "arm64" }, "sha512-L5Bx4oa/XgzgnRm+HMzSRzTocuMxr/QgVlgNwmdt6RzM0Z4ajR4kgZw6RXjd6guwtMXkFah8wPmVofY+ZFEdqw=="],
"@tailwindcss/vite/@tailwindcss/oxide/@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@0.0.0-insiders.d24b112", "", { "os": "linux", "cpu": "arm64" }, "sha512-y97kD0ftoV808M/EWpLTLRfhmq1ZQjwDFOwLgNf9k79yv24GuAYqtEBsVbld2HnS0llZcApi/YHxc6A7cs7cJg=="],
"@tailwindcss/vite/@tailwindcss/oxide/@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@0.0.0-insiders.f302fce", "", { "os": "linux", "cpu": "arm64" }, "sha512-kHroRI6gvmHBrjdqh8aRymDoRUFZuLRSrhs27GVDZ5ONc6rjyKE5TEeGfmAnCjdoV2RdP+fqaKsYo0xSmCaZog=="],
"@tailwindcss/vite/@tailwindcss/oxide/@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@0.0.0-insiders.d24b112", "", { "os": "linux", "cpu": "x64" }, "sha512-Wi7ZDOZxzeia+k7/KIL47WW7UGMSlyZGxwt+WG+TJGZVlwxhcSRWheSeD6vMKHedE0dk+u80ikHE3bHAq2oDqw=="],
"@tailwindcss/vite/@tailwindcss/oxide/@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@0.0.0-insiders.f302fce", "", { "os": "linux", "cpu": "x64" }, "sha512-t8cy3pbDhWuGP8zXC1p2SEqk3Br9yc0DQveKNOac9sLWfULHO4aIZDHlTTBWz4Cewbs1PrLLFHpfAJukHgVLCQ=="],
"@tailwindcss/vite/@tailwindcss/oxide/@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@0.0.0-insiders.d24b112", "", { "os": "linux", "cpu": "x64" }, "sha512-p/MXTlatfqK54iZMRRts5o75vezy/OKeX9C6YHUV593ZEAbVxzFDkij7Co4jBBd+T2IoX4V423kbBywQ2UqpXQ=="],
"@tailwindcss/vite/@tailwindcss/oxide/@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@0.0.0-insiders.f302fce", "", { "os": "linux", "cpu": "x64" }, "sha512-+J4sPybWyJXhMUWFwoNe57CsL7rVN1aHqraJNrAp5Y9WEQDxwV2psi5qcXsQpmp2qmBEUYMW5Y8KB0LW265rvw=="],
"@tailwindcss/vite/@tailwindcss/oxide/@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@0.0.0-insiders.d24b112", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-mtIIKEzSmwEUjK0/N1lQBVrfa0bjkwob+2QicWGNW9mY8+2rDXHmozCwNljmuE0yOj3va0xSf8wYp7eBo27FHg=="],
"@tailwindcss/vite/@tailwindcss/oxide/@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@0.0.0-insiders.f302fce", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-TRB2azDoajoAkG0iDs0398TlQ8EmLIHvvpWV0sTK+XMlLzG4tuZtfxoZUg9X2x7EE/cozvJIneSYLGFSuY/Hcg=="],
"@tailwindcss/vite/@tailwindcss/oxide/@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@0.0.0-insiders.d24b112", "", { "os": "win32", "cpu": "arm64" }, "sha512-F+ecmFmGYUz4+AaxRtGDwFSv5fW9BUmGn6CM9F3l+wtr9Ufeo7DvizZgfT7Z32yfORRDMdUpaNoBumzxDGi7xA=="],
"@tailwindcss/vite/@tailwindcss/oxide/@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@0.0.0-insiders.f302fce", "", { "os": "win32", "cpu": "arm64" }, "sha512-jIB0+F3uRWg5TG1/Vitjns5MA5UJyCQz/LAXFGldRS8wFLg/yKfhGVXmeK0TqkkDnCiMf7qQw8/Pc9VhhihpoA=="],
"@tailwindcss/vite/@tailwindcss/oxide/@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@0.0.0-insiders.d24b112", "", { "os": "win32", "cpu": "x64" }, "sha512-wdjE5CdaKv2quhpMlrBXXtaCKfm6uwtCScufK7p4/67HJOtbAVhdAldqVIk/lY0XZkc+gOKdCs2KhdMSnxCwTw=="],
"@tailwindcss/vite/@tailwindcss/oxide/@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@0.0.0-insiders.f302fce", "", { "os": "win32", "cpu": "x64" }, "sha512-adNYa9ocX3S18F5KD8FIEn+KD2pjVdlgHJbiPZmryhGPVwyu4pBrLeCnPxH8GX8IdbG3OWZVz393mNW5MdjrJQ=="],
"babel-jest/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
-1
View File
@@ -15,7 +15,6 @@
"generate-schema": "bunx openapi-typescript https://developer.themoviedb.org/openapi/tmdb-api.json -o ./src/schema.d.ts"
},
"dependencies": {
"@sofa/db": "workspace:*",
"@sofa/logger": "workspace:*",
"openapi-fetch": "0.17.0"
},