mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-07-14 18:15:56 -04:00
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:
@@ -102,6 +102,7 @@
|
||||
}
|
||||
],
|
||||
["expo-system-ui"],
|
||||
["expo-web-browser"],
|
||||
[
|
||||
"expo-image-picker",
|
||||
{
|
||||
|
||||
+19
-18
@@ -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",
|
||||
|
||||
@@ -16,6 +16,8 @@ export default function AuthLayout() {
|
||||
<Stack
|
||||
screenOptions={{
|
||||
contentStyle,
|
||||
headerShown: false,
|
||||
navigationBarHidden: true,
|
||||
animation: "fade",
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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} />;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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]}
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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
@@ -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:",
|
||||
|
||||
@@ -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 };
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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=="],
|
||||
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user