diff --git a/apps/app/package.json b/apps/app/package.json
index cb31748..fa99549 100644
--- a/apps/app/package.json
+++ b/apps/app/package.json
@@ -11,6 +11,8 @@
"@expo/metro-runtime": "~3.2.1",
"@expo/vector-icons": "^14.0.0",
"@react-native/assets-registry": "0.75.0-main",
+ "@tanstack/react-query": "^5.40.0",
+ "@trpc/client": "^11.0.0-rc.382",
"babel-preset-expo": "~11.0.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
@@ -29,17 +31,18 @@
"lucide-react-native": "^0.381.0",
"nativewind": "^4.0.1",
"position-strings": "^2.0.1",
- "react": "18.3.1",
- "react-dom": "^18.2.0",
+ "react": "18.2.0",
+ "react-dom": "18.2.0",
"react-native": "0.74.1",
"react-native-libsodium": "^1.3.1",
- "react-native-reanimated": "~3.11.0",
- "react-native-safe-area-context": "4.10.3",
+ "react-native-opaque": "^0.3.1",
+ "react-native-reanimated": "~3.10.1",
+ "react-native-safe-area-context": "4.10.1",
"react-native-screens": "3.31.1",
- "react-native-svg": "15.3.0",
+ "react-native-svg": "15.2.0",
"react-native-web": "~0.19.6",
- "secsync-react-yjs": "^0.4.0",
"secsync": "^0.4.0",
+ "secsync-react-yjs": "^0.4.0",
"tailwind-merge": "^2.3.0",
"tailwindcss": "^3.4.0",
"tailwindcss-animate": "^1.0.7",
@@ -49,14 +52,13 @@
"devDependencies": {
"@babel/core": "^7.24.3",
"@babel/runtime": "^7.24.1",
- "@types/react": "~18.3.3",
- "babel-plugin-transform-vite-meta-env": "^1.0.3",
+ "@types/react": "~18.2.79",
"eslint": "^9.3.0",
"husky": "^9.0.11",
"lint-staged": "^15.2.5",
"prettier": "3.2.5",
"prettier-plugin-tailwindcss": "^0.6.0",
- "typescript": "~5.4.5"
+ "typescript": "~5.3.3"
},
"resolutions": {
"@effect/schema": "=0.64.16"
diff --git a/apps/app/src/app/_layout.tsx b/apps/app/src/app/_layout.tsx
index 6b7e0e3..c85a7ee 100644
--- a/apps/app/src/app/_layout.tsx
+++ b/apps/app/src/app/_layout.tsx
@@ -1,12 +1,21 @@
import { Theme, ThemeProvider } from "@react-navigation/native";
+import {
+ MutationCache,
+ QueryCache,
+ QueryClient,
+ QueryClientProvider,
+} from "@tanstack/react-query";
+import { httpBatchLink } from "@trpc/client";
import { Stack } from "expo-router";
import { StatusBar } from "expo-status-bar";
import * as React from "react";
+import { useState } from "react";
import { SafeAreaProvider } from "react-native-safe-area-context";
import { NAV_THEME } from "~/lib/constants";
import { useColorScheme } from "~/lib/useColorScheme";
import "../global.css";
import useLoadingLibsodium from "../hooks/useLoadingLibsodium";
+import { trpc } from "../utils/trpc";
const LIGHT_THEME: Theme = {
dark: false,
@@ -17,29 +26,92 @@ const DARK_THEME: Theme = {
colors: NAV_THEME.dark,
};
+// TODO PROD API URL
+const apiUrl = "http://localhost:3030/api";
+
export default function Layout() {
const { isDarkColorScheme } = useColorScheme();
const isLoadingComplete = useLoadingLibsodium();
+ const [queryClient] = useState(
+ () =>
+ new QueryClient({
+ queryCache: new QueryCache({
+ // TODO
+ // onError: (error) => {
+ // if (
+ // error instanceof TRPCClientError &&
+ // error.data?.code === "UNAUTHORIZED" &&
+ // window.location.pathname !== "/login"
+ // ) {
+ // removeLocalDb();
+ // queryClient.clear();
+ // router.navigate({
+ // to: "/login",
+ // search: { redirect: window.location.pathname },
+ // });
+ // }
+ // },
+ }),
+ mutationCache: new MutationCache({
+ // TODO
+ // onError: (error) => {
+ // if (
+ // error instanceof TRPCClientError &&
+ // error.data?.code === "UNAUTHORIZED" &&
+ // window.location.pathname !== "/login"
+ // ) {
+ // removeLocalDb();
+ // queryClient.clear();
+ // router.navigate({
+ // to: "/login",
+ // search: { redirect: window.location.pathname },
+ // });
+ // }
+ // },
+ }),
+ })
+ );
+ const [trpcClient] = useState(() =>
+ trpc.createClient({
+ links: [
+ httpBatchLink({
+ url: apiUrl,
+ fetch(url, options) {
+ return fetch(url, {
+ ...options,
+ credentials: "include",
+ });
+ },
+ }),
+ ],
+ })
+ );
+
if (!isLoadingComplete) {
return null;
}
return (
-
-
-
-
-
-
- {/* Default Portal Host (one per app) */}
-
-
+
+
+
+
+
+
+
+
+ {/* Default Portal Host (one per app) */}
+ {/* */}
+
+
+
+
);
}
diff --git a/apps/app/src/app/index.tsx b/apps/app/src/app/index.tsx
index d80cde9..ce0bec6 100644
--- a/apps/app/src/app/index.tsx
+++ b/apps/app/src/app/index.tsx
@@ -3,11 +3,19 @@ import * as React from "react";
import { Text } from "react-native";
const Lists: React.FC = () => {
- const workouts: { id: string; startedAt: string }[] = [];
-
return (
- Hello WOrld LIST A
+ Hello WOrld{" "}
+
+ LIST A
+
+ Login
+ Register
);
};
diff --git a/apps/app/src/app/login.tsx b/apps/app/src/app/login.tsx
new file mode 100644
index 0000000..b5913f4
--- /dev/null
+++ b/apps/app/src/app/login.tsx
@@ -0,0 +1,48 @@
+import { router, useLocalSearchParams } from "expo-router";
+import { useState } from "react";
+import { View } from "react-native";
+import { AuthForm } from "src/components/authForm";
+import { Text } from "~/components/ui/text";
+import { AlertCircle } from "~/lib/icons/AlertCircle";
+import { useLogin } from "../hooks/useLogin";
+
+const Login = () => {
+ const { login, isPending } = useLogin();
+ const [error, setError] = useState(null);
+ const { redirect } = useLocalSearchParams<{ redirect?: string }>();
+
+ return (
+
+ {
+ const sessionKey = await login({
+ userIdentifier: username,
+ password,
+ });
+ if (!sessionKey) {
+ setError("Failed to login");
+ return;
+ }
+ if (redirect) {
+ router.navigate(redirect);
+ return;
+ }
+ router.navigate("/");
+ }}
+ children={Login}
+ isPending={isPending}
+ />
+
+ {error && (
+
+
+ {/* TODO proper styling */}
+ Error
+ Failed to log in
+
+ )}
+
+ );
+};
+
+export default Login;
diff --git a/apps/app/src/app/register.tsx b/apps/app/src/app/register.tsx
new file mode 100644
index 0000000..eb46c39
--- /dev/null
+++ b/apps/app/src/app/register.tsx
@@ -0,0 +1,47 @@
+import { router, useLocalSearchParams } from "expo-router";
+import { useState } from "react";
+import { View } from "react-native";
+import { Text } from "~/components/ui/text";
+import { AlertCircle } from "~/lib/icons/AlertCircle";
+import { AuthForm } from "../components/authForm";
+import { useRegisterAndLogin } from "../hooks/useRegisterAndLogin";
+
+const Register = () => {
+ const { registerAndLogin, isPending } = useRegisterAndLogin();
+ const { redirect } = useLocalSearchParams<{ redirect?: string }>();
+ const [error, setError] = useState(null);
+
+ return (
+
+ {
+ const sessionKey = await registerAndLogin({
+ userIdentifier: username,
+ password,
+ });
+ if (!sessionKey) {
+ setError("Failed to register");
+ return;
+ }
+ if (redirect) {
+ router.navigate(redirect);
+ return;
+ }
+ router.navigate("/");
+ }}
+ children={Register}
+ isPending={isPending}
+ />
+ {error && (
+
+
+ {/* TODO proper styling */}
+ Error
+ Failed to register
+
+ )}
+
+ );
+};
+
+export default Register;
diff --git a/apps/app/src/components/authForm.tsx b/apps/app/src/components/authForm.tsx
new file mode 100644
index 0000000..b515af3
--- /dev/null
+++ b/apps/app/src/components/authForm.tsx
@@ -0,0 +1,57 @@
+import { useState } from "react";
+import { View } from "react-native";
+import { Button } from "~/components/ui/button";
+import { Input } from "~/components/ui/input";
+import { Text } from "~/components/ui/text";
+
+type Props = {
+ onSubmit: (params: { username: string; password: string }) => void;
+ isPending: boolean;
+ children: React.ReactNode;
+};
+
+export const AuthForm = ({ onSubmit, isPending, children }: Props) => {
+ const [username, setUsername] = useState("");
+ const [password, setPassword] = useState("");
+
+ return (
+
+
+ {children}
+
+
+
+ {
+ setUsername(value);
+ }}
+ />
+
+ {
+ setPassword(value);
+ }}
+ />
+
+
+
+
+ );
+};
diff --git a/apps/app/src/hooks/useInterval.ts b/apps/app/src/hooks/useInterval.ts
new file mode 100644
index 0000000..754b8b5
--- /dev/null
+++ b/apps/app/src/hooks/useInterval.ts
@@ -0,0 +1,24 @@
+import { useEffect, useRef } from "react";
+
+type IntervalFunc = () => unknown | void;
+
+// Inspired by https://overreacted.io/making-setinterval-declarative-with-react-hooks/
+export const useInterval = (callback: IntervalFunc, delay: number | null) => {
+ const savedCallback = useRef(null);
+
+ useEffect(() => {
+ if (delay === null) return;
+ savedCallback.current = callback;
+ });
+
+ useEffect(() => {
+ if (delay === null) return;
+ function tick() {
+ if (savedCallback.current !== null) {
+ savedCallback.current();
+ }
+ }
+ const id = setInterval(tick, delay);
+ return () => clearInterval(id);
+ }, [delay]);
+};
diff --git a/apps/app/src/hooks/useLogin.ts b/apps/app/src/hooks/useLogin.ts
new file mode 100644
index 0000000..18113e5
--- /dev/null
+++ b/apps/app/src/hooks/useLogin.ts
@@ -0,0 +1,58 @@
+import { useQueryClient } from "@tanstack/react-query";
+import { useState } from "react";
+import * as opaque from "react-native-opaque";
+import { trpc } from "../utils/trpc";
+
+type LoginParams = {
+ userIdentifier: string;
+ password: string;
+};
+
+export const useLogin = () => {
+ // TODO
+ return { isPending: true, login: () => {} };
+
+ const loginStartMutation = trpc.loginStart.useMutation();
+ const loginFinishMutation = trpc.loginFinish.useMutation();
+
+ const queryClient = useQueryClient();
+ const [isPending, setIsPending] = useState(false);
+
+ const login = async ({ userIdentifier, password }: LoginParams) => {
+ setIsPending(true);
+ try {
+ const { clientLoginState, startLoginRequest } = opaque.client.startLogin({
+ password,
+ });
+
+ const { loginResponse } = await loginStartMutation.mutateAsync({
+ userIdentifier,
+ startLoginRequest,
+ });
+
+ const loginResult = opaque.client.finishLogin({
+ clientLoginState,
+ loginResponse,
+ password,
+ });
+ if (!loginResult) {
+ return null;
+ }
+ const { sessionKey, finishLoginRequest } = loginResult;
+
+ const { success } = await loginFinishMutation.mutateAsync({
+ finishLoginRequest,
+ userIdentifier,
+ });
+
+ queryClient.invalidateQueries();
+
+ return success ? sessionKey : null;
+ } catch (error) {
+ return null;
+ } finally {
+ setIsPending(false);
+ }
+ };
+ return { isPending, login };
+};
diff --git a/apps/app/src/hooks/useRegisterAndLogin.ts b/apps/app/src/hooks/useRegisterAndLogin.ts
new file mode 100644
index 0000000..ef31f9b
--- /dev/null
+++ b/apps/app/src/hooks/useRegisterAndLogin.ts
@@ -0,0 +1,54 @@
+import { useState } from "react";
+import * as opaque from "react-native-opaque";
+import { trpc } from "../utils/trpc";
+import { useLogin } from "./useLogin";
+
+type RegisterParams = {
+ userIdentifier: string;
+ password: string;
+};
+
+export const useRegisterAndLogin = () => {
+ // TODO
+ return { isPending: true, registerAndLogin: () => {} };
+
+ const [isPending, setIsPending] = useState(false);
+ const registerStartMutation = trpc.registerStart.useMutation();
+ const registerFinishMutation = trpc.registerFinish.useMutation();
+ const { login } = useLogin();
+
+ const registerAndLogin = async ({
+ userIdentifier,
+ password,
+ }: RegisterParams) => {
+ setIsPending(true);
+ try {
+ const { clientRegistrationState, registrationRequest } =
+ opaque.client.startRegistration({ password });
+ const { registrationResponse } = await registerStartMutation.mutateAsync({
+ userIdentifier,
+ registrationRequest,
+ });
+
+ const { registrationRecord } = opaque.client.finishRegistration({
+ clientRegistrationState,
+ registrationResponse,
+ password,
+ });
+
+ await registerFinishMutation.mutateAsync({
+ userIdentifier,
+ registrationRecord,
+ });
+
+ const result = await login({ userIdentifier, password });
+ return result;
+ } catch (error) {
+ return null;
+ } finally {
+ setIsPending(false);
+ }
+ };
+
+ return { isPending, registerAndLogin };
+};
diff --git a/apps/app/src/rnr/components/primitives/hooks/index.ts b/apps/app/src/rnr/components/primitives/hooks/index.ts
new file mode 100644
index 0000000..58c0d1b
--- /dev/null
+++ b/apps/app/src/rnr/components/primitives/hooks/index.ts
@@ -0,0 +1,3 @@
+export { useAugmentedRef } from './useAugmentedRef';
+export { useRelativePosition, type LayoutPosition } from './useRelativePosition';
+export { useControllableState } from './useControllableState';
diff --git a/apps/app/src/rnr/components/primitives/hooks/useAugmentedRef.tsx b/apps/app/src/rnr/components/primitives/hooks/useAugmentedRef.tsx
new file mode 100644
index 0000000..13a5669
--- /dev/null
+++ b/apps/app/src/rnr/components/primitives/hooks/useAugmentedRef.tsx
@@ -0,0 +1,29 @@
+import * as React from 'react';
+
+interface AugmentRefProps {
+ ref: React.Ref;
+ methods?: Record any>;
+ deps?: any[];
+}
+
+export function useAugmentedRef({
+ ref,
+ methods,
+ deps = [],
+}: AugmentRefProps) {
+ const augmentedRef = React.useRef(null);
+ React.useImperativeHandle(
+ ref,
+ () => {
+ if (typeof augmentedRef === 'function' || !augmentedRef?.current) {
+ return {} as T;
+ }
+ return {
+ ...augmentedRef.current,
+ ...methods,
+ };
+ },
+ deps
+ );
+ return augmentedRef;
+}
diff --git a/apps/app/src/rnr/components/primitives/hooks/useControllableState.tsx b/apps/app/src/rnr/components/primitives/hooks/useControllableState.tsx
new file mode 100644
index 0000000..3b42b53
--- /dev/null
+++ b/apps/app/src/rnr/components/primitives/hooks/useControllableState.tsx
@@ -0,0 +1,75 @@
+// This project uses code from WorkOS/Radix Primitives.
+// The code is licensed under the MIT License.
+// https://github.com/radix-ui/primitives/tree/main
+
+import * as React from 'react';
+
+type UseControllableStateParams = {
+ prop?: T | undefined;
+ defaultProp?: T | undefined;
+ onChange?: (state: T) => void;
+};
+
+type SetStateFn = (prevState?: T) => T;
+
+function useControllableState({
+ prop,
+ defaultProp,
+ onChange = () => {},
+}: UseControllableStateParams) {
+ const [uncontrolledProp, setUncontrolledProp] = useUncontrolledState({ defaultProp, onChange });
+ const isControlled = prop !== undefined;
+ const value = isControlled ? prop : uncontrolledProp;
+ const handleChange = useCallbackRef(onChange);
+
+ const setValue: React.Dispatch> = React.useCallback(
+ (nextValue) => {
+ if (isControlled) {
+ const setter = nextValue as SetStateFn;
+ const value = typeof nextValue === 'function' ? setter(prop) : nextValue;
+ if (value !== prop) handleChange(value as T);
+ } else {
+ setUncontrolledProp(nextValue);
+ }
+ },
+ [isControlled, prop, setUncontrolledProp, handleChange]
+ );
+
+ return [value, setValue] as const;
+}
+
+function useUncontrolledState({
+ defaultProp,
+ onChange,
+}: Omit, 'prop'>) {
+ const uncontrolledState = React.useState(defaultProp);
+ const [value] = uncontrolledState;
+ const prevValueRef = React.useRef(value);
+ const handleChange = useCallbackRef(onChange);
+
+ React.useEffect(() => {
+ if (prevValueRef.current !== value) {
+ handleChange(value as T);
+ prevValueRef.current = value;
+ }
+ }, [value, prevValueRef, handleChange]);
+
+ return uncontrolledState;
+}
+
+/**
+ * A custom hook that converts a callback to a ref to avoid triggering re-renders when passed as a
+ * prop or avoid re-executing effects when passed as a dependency
+ */
+function useCallbackRef any>(callback: T | undefined): T {
+ const callbackRef = React.useRef(callback);
+
+ React.useEffect(() => {
+ callbackRef.current = callback;
+ });
+
+ // https://github.com/facebook/react/issues/19240
+ return React.useMemo(() => ((...args) => callbackRef.current?.(...args)) as T, []);
+}
+
+export { useControllableState };
diff --git a/apps/app/src/rnr/components/primitives/hooks/useRelativePosition.tsx b/apps/app/src/rnr/components/primitives/hooks/useRelativePosition.tsx
new file mode 100644
index 0000000..f1544be
--- /dev/null
+++ b/apps/app/src/rnr/components/primitives/hooks/useRelativePosition.tsx
@@ -0,0 +1,227 @@
+import * as React from 'react';
+import {
+ useWindowDimensions,
+ type LayoutRectangle,
+ type ScaledSize,
+ type ViewStyle,
+} from 'react-native';
+import type { Insets } from '~/components/primitives/types';
+
+const POSITION_ABSOLUTE: ViewStyle = {
+ position: 'absolute',
+};
+
+const HIDDEN_CONTENT: ViewStyle = {
+ position: 'absolute',
+ opacity: 0,
+ zIndex: -9999999,
+};
+
+type UseRelativePositionArgs = Omit<
+ GetContentStyleArgs,
+ 'triggerPosition' | 'contentLayout' | 'dimensions'
+> & {
+ triggerPosition: LayoutPosition | null;
+ contentLayout: LayoutRectangle | null;
+ disablePositioningStyle?: boolean;
+};
+
+export function useRelativePosition({
+ align,
+ avoidCollisions,
+ triggerPosition,
+ contentLayout,
+ alignOffset,
+ insets,
+ sideOffset,
+ side,
+ disablePositioningStyle,
+}: UseRelativePositionArgs) {
+ const dimensions = useWindowDimensions();
+ return React.useMemo(() => {
+ if (disablePositioningStyle) {
+ return {};
+ }
+ if (!triggerPosition || !contentLayout) {
+ return HIDDEN_CONTENT;
+ }
+ return getContentStyle({
+ align,
+ avoidCollisions,
+ contentLayout,
+ side,
+ triggerPosition,
+ alignOffset,
+ insets,
+ sideOffset,
+ dimensions,
+ });
+ }, [triggerPosition, contentLayout, dimensions.width, dimensions.height]);
+}
+
+export interface LayoutPosition {
+ pageY: number;
+ pageX: number;
+ width: number;
+ height: number;
+}
+
+interface GetPositionArgs {
+ dimensions: ScaledSize;
+ avoidCollisions: boolean;
+ triggerPosition: LayoutPosition;
+ contentLayout: LayoutRectangle;
+ insets?: Insets;
+}
+
+interface GetSidePositionArgs extends GetPositionArgs {
+ side: 'top' | 'bottom';
+ sideOffset: number;
+}
+
+function getSidePosition({
+ side,
+ triggerPosition,
+ contentLayout,
+ sideOffset,
+ insets,
+ avoidCollisions,
+ dimensions,
+}: GetSidePositionArgs) {
+ const insetTop = insets?.top ?? 0;
+ const insetBottom = insets?.bottom ?? 0;
+ const positionTop = triggerPosition?.pageY - sideOffset - contentLayout.height;
+ const positionBottom = triggerPosition.pageY + triggerPosition.height + sideOffset;
+
+ if (!avoidCollisions) {
+ return {
+ top: side === 'top' ? positionTop : positionBottom,
+ };
+ }
+
+ if (side === 'top') {
+ return {
+ top: Math.max(insetTop, positionTop),
+ };
+ }
+
+ return {
+ top: Math.min(dimensions.height - insetBottom - contentLayout.height, positionBottom),
+ };
+}
+
+interface GetAlignPositionArgs extends GetPositionArgs {
+ align: 'start' | 'center' | 'end';
+ alignOffset: number;
+}
+
+function getAlignPosition({
+ align,
+ avoidCollisions,
+ contentLayout,
+ triggerPosition,
+ alignOffset,
+ insets,
+ dimensions,
+}: GetAlignPositionArgs) {
+ const insetLeft = insets?.left ?? 0;
+ const insetRight = insets?.right ?? 0;
+ const maxContentWidth = dimensions.width - insetLeft - insetRight;
+
+ const contentWidth = Math.min(contentLayout.width, maxContentWidth);
+
+ let left = getLeftPosition(
+ align,
+ triggerPosition.pageX,
+ triggerPosition.width,
+ contentWidth,
+ alignOffset,
+ insetLeft,
+ insetRight,
+ dimensions
+ );
+
+ if (avoidCollisions) {
+ const doesCollide = left < insetLeft || left + contentWidth > dimensions.width - insetRight;
+ if (doesCollide) {
+ const spaceLeft = left - insetLeft;
+ const spaceRight = dimensions.width - insetRight - (left + contentWidth);
+
+ if (spaceLeft > spaceRight && spaceLeft >= contentWidth) {
+ left = insetLeft;
+ } else if (spaceRight >= contentWidth) {
+ left = dimensions.width - insetRight - contentWidth;
+ } else {
+ const centeredPosition = Math.max(
+ insetLeft,
+ (dimensions.width - contentWidth - insetRight) / 2
+ );
+ left = centeredPosition;
+ }
+ }
+ }
+
+ return { left, maxWidth: maxContentWidth };
+}
+
+function getLeftPosition(
+ align: 'start' | 'center' | 'end',
+ triggerPageX: number,
+ triggerWidth: number,
+ contentWidth: number,
+ alignOffset: number,
+ insetLeft: number,
+ insetRight: number,
+ dimensions: ScaledSize
+) {
+ let left = 0;
+ if (align === 'start') {
+ left = triggerPageX;
+ }
+ if (align === 'center') {
+ left = triggerPageX + triggerWidth / 2 - contentWidth / 2;
+ }
+ if (align === 'end') {
+ left = triggerPageX + triggerWidth - contentWidth;
+ }
+ return Math.max(
+ insetLeft,
+ Math.min(left + alignOffset, dimensions.width - contentWidth - insetRight)
+ );
+}
+
+type GetContentStyleArgs = GetPositionArgs & GetSidePositionArgs & GetAlignPositionArgs;
+
+function getContentStyle({
+ align,
+ avoidCollisions,
+ contentLayout,
+ side,
+ triggerPosition,
+ alignOffset,
+ insets,
+ sideOffset,
+ dimensions,
+}: GetContentStyleArgs) {
+ return Object.assign(
+ POSITION_ABSOLUTE,
+ getSidePosition({
+ side,
+ triggerPosition,
+ contentLayout,
+ sideOffset,
+ insets,
+ avoidCollisions,
+ dimensions,
+ }),
+ getAlignPosition({
+ align,
+ avoidCollisions,
+ triggerPosition,
+ contentLayout,
+ alignOffset,
+ insets,
+ dimensions,
+ })
+ );
+}
diff --git a/apps/app/src/rnr/components/primitives/portal.tsx b/apps/app/src/rnr/components/primitives/portal.tsx
new file mode 100644
index 0000000..bd3ede8
--- /dev/null
+++ b/apps/app/src/rnr/components/primitives/portal.tsx
@@ -0,0 +1,82 @@
+import * as React from 'react';
+import { Platform, type View, type ViewStyle } from 'react-native';
+import { create } from 'zustand';
+
+const DEFAULT_PORTAL_HOST = 'INTERNAL_PRIMITIVE_DEFAULT_HOST_NAME';
+
+type PortalMap = Map;
+type PortalHostMap = Map;
+
+const usePortal = create<{ map: PortalHostMap }>(() => ({
+ map: new Map().set(DEFAULT_PORTAL_HOST, new Map()),
+}));
+
+const updatePortal = (hostName: string, name: string, children: React.ReactNode) => {
+ usePortal.setState((prev) => {
+ const next = new Map(prev.map);
+ const portal = next.get(hostName) ?? new Map();
+ portal.set(name, children);
+ next.set(hostName, portal);
+ return { map: next };
+ });
+};
+const removePortal = (hostName: string, name: string) => {
+ usePortal.setState((prev) => {
+ const next = new Map(prev.map);
+ const portal = next.get(hostName) ?? new Map();
+ portal.delete(name);
+ next.set(hostName, portal);
+ return { map: next };
+ });
+};
+
+export function PortalHost({ name = DEFAULT_PORTAL_HOST }: { name?: string }) {
+ const portalMap = usePortal((state) => state.map).get(name) ?? new Map();
+ if (portalMap.size === 0) return null;
+ return <>{Array.from(portalMap.values())}>;
+}
+
+export function Portal({
+ name,
+ hostName = DEFAULT_PORTAL_HOST,
+ children,
+}: {
+ name: string;
+ hostName?: string;
+ children: React.ReactNode;
+}) {
+ React.useEffect(() => {
+ updatePortal(hostName, name, children);
+ }, [hostName, name, children]);
+
+ React.useEffect(() => {
+ return () => {
+ removePortal(hostName, name);
+ };
+ }, [hostName, name]);
+
+ return null;
+}
+
+const ROOT: ViewStyle = {
+ flex: 1,
+};
+
+export function useModalPortalRoot() {
+ const ref = React.useRef(null);
+ const [sideOffset, setSideOffSet] = React.useState(0);
+
+ const onLayout = React.useCallback(() => {
+ if (Platform.OS === 'web') return;
+ ref.current?.measure((_x, _y, _width, _height, _pageX, pageY) => {
+ setSideOffSet(-pageY);
+ });
+ }, []);
+
+ return {
+ ref,
+ sideOffset,
+ onLayout,
+ style: ROOT,
+ };
+}
diff --git a/apps/app/src/rnr/components/ui/alert-dialog.tsx b/apps/app/src/rnr/components/ui/alert-dialog.tsx
new file mode 100644
index 0000000..40ebaec
--- /dev/null
+++ b/apps/app/src/rnr/components/ui/alert-dialog.tsx
@@ -0,0 +1,167 @@
+import * as React from 'react';
+import { Platform, StyleSheet, View } from 'react-native';
+import Animated, { FadeIn, FadeOut } from 'react-native-reanimated';
+import { buttonTextVariants, buttonVariants } from '~/components/ui/button';
+import * as AlertDialogPrimitive from '~/components/primitives/alert-dialog';
+import { cn } from '~/lib/utils';
+import { TextClassContext } from '~/components/ui/text';
+
+const AlertDialog = AlertDialogPrimitive.Root;
+
+const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
+
+const AlertDialogPortal = AlertDialogPrimitive.Portal;
+
+const AlertDialogOverlayWeb = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => {
+ const { open } = AlertDialogPrimitive.useRootContext();
+ return (
+
+ );
+});
+
+AlertDialogOverlayWeb.displayName = 'AlertDialogOverlayWeb';
+
+const AlertDialogOverlayNative = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => {
+ return (
+
+
+ {children}
+
+
+ );
+});
+
+AlertDialogOverlayNative.displayName = 'AlertDialogOverlayNative';
+
+const AlertDialogOverlay = Platform.select({
+ web: AlertDialogOverlayWeb,
+ default: AlertDialogOverlayNative,
+});
+
+const AlertDialogContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & { portalHost?: string }
+>(({ className, portalHost, ...props }, ref) => {
+ const { open } = AlertDialogPrimitive.useRootContext();
+
+ return (
+
+
+
+
+
+ );
+});
+AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
+
+const AlertDialogHeader = ({
+ className,
+ ...props
+}: React.ComponentPropsWithoutRef) => (
+
+);
+AlertDialogHeader.displayName = 'AlertDialogHeader';
+
+const AlertDialogFooter = ({
+ className,
+ ...props
+}: React.ComponentPropsWithoutRef) => (
+
+);
+AlertDialogFooter.displayName = 'AlertDialogFooter';
+
+const AlertDialogTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;
+
+const AlertDialogDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName;
+
+const AlertDialogAction = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+));
+AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;
+
+const AlertDialogCancel = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+));
+AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;
+
+export {
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogOverlay,
+ AlertDialogPortal,
+ AlertDialogTitle,
+ AlertDialogTrigger,
+};
diff --git a/apps/app/src/rnr/lib/icons/AlertCircle.ts b/apps/app/src/rnr/lib/icons/AlertCircle.ts
new file mode 100644
index 0000000..adaaeeb
--- /dev/null
+++ b/apps/app/src/rnr/lib/icons/AlertCircle.ts
@@ -0,0 +1,4 @@
+import { AlertCircle } from "lucide-react-native";
+import { iconWithClassName } from "./iconWithClassName";
+iconWithClassName(AlertCircle);
+export { AlertCircle };
diff --git a/apps/app/src/utils/trpc.ts b/apps/app/src/utils/trpc.ts
new file mode 100644
index 0000000..82e3bd1
--- /dev/null
+++ b/apps/app/src/utils/trpc.ts
@@ -0,0 +1,4 @@
+import { createTRPCReact } from "@trpc/react-query";
+import type { AppRouter } from "../../../server/src/index";
+
+export const trpc = createTRPCReact();
diff --git a/apps/server/package.json b/apps/server/package.json
index 5211f62..81314e6 100644
--- a/apps/server/package.json
+++ b/apps/server/package.json
@@ -13,15 +13,16 @@
"dependencies": {
"@prisma/client": "5.14.0",
"@serenity-kit/opaque": "^0.8.4",
- "@trpc/server": "11.0.0-rc.366",
+ "@trpc/react-query": "^10.45.2",
+ "@trpc/server": "^11.0.0-rc.382",
"cookie": "^0.6.0",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"isomorphic-ws": "^5.0.0",
- "secsync-server": "^0.4.0",
"secsync": "^0.4.0",
+ "secsync-server": "^0.4.0",
"ws": "^8.17.0",
"zod": "^3.23.8"
},
diff --git a/yarn.lock b/yarn.lock
index 5ffebd3..3d8017a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -262,7 +262,7 @@
js-tokens "^4.0.0"
picocolors "^1.0.0"
-"@babel/parser@^7.1.0", "@babel/parser@^7.13.16", "@babel/parser@^7.20.0", "@babel/parser@^7.20.7", "@babel/parser@^7.24.6":
+"@babel/parser@^7.13.16", "@babel/parser@^7.20.0", "@babel/parser@^7.24.6":
version "7.24.6"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.6.tgz#5e030f440c3c6c78d195528c3b688b101a365328"
integrity sha512-eNZXdfU35nJC2h24RznROuOpO94h6x8sg9ju0tT9biNtLZ2vuP8SduLqqV+/8+cebSLV9SJEAN5Z3zQbJG/M+Q==
@@ -766,7 +766,7 @@
resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310"
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
-"@babel/runtime@^7.0.0", "@babel/runtime@^7.13.10", "@babel/runtime@^7.13.9", "@babel/runtime@^7.18.6", "@babel/runtime@^7.20.0", "@babel/runtime@^7.24.1":
+"@babel/runtime@^7.0.0", "@babel/runtime@^7.13.10", "@babel/runtime@^7.18.6", "@babel/runtime@^7.20.0", "@babel/runtime@^7.24.1":
version "7.24.6"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.6.tgz#5b76eb89ad45e2e4a0a8db54c456251469a3358e"
integrity sha512-Ja18XcETdEl5mzzACGd+DKgaGJzPTCow7EglgwTmHdwokzDFYh/MHua6lU6DV/hjF2IaOJ4oX2nqnjG7RElKOw==
@@ -798,7 +798,7 @@
debug "^4.3.1"
globals "^11.1.0"
-"@babel/types@^7.0.0", "@babel/types@^7.20.0", "@babel/types@^7.20.7", "@babel/types@^7.23.0", "@babel/types@^7.24.6":
+"@babel/types@^7.20.0", "@babel/types@^7.23.0", "@babel/types@^7.24.6":
version "7.24.6"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.6.tgz#ba4e1f59870c10dc2fa95a274ac4feec23b21912"
integrity sha512-WaMsgi6Q8zMgMth93GvWPXkhAIEobfsIkLTacoVZoK1J0CevIPGYY2Vo5YvJGqyHqXM6P4ppOYGsIRU8MM9pFQ==
@@ -815,9 +815,9 @@
"@jridgewell/trace-mapping" "0.3.9"
"@effect/schema@^0.67.15":
- version "0.67.15"
- resolved "https://registry.yarnpkg.com/@effect/schema/-/schema-0.67.15.tgz#b57ce1617a1ed6ec689a486f1ba392a1c521b9a4"
- integrity sha512-+AO29qX0GIDARbQE1TcnWz3cUBCOm3x8KP6SXpWUot1cfG4ccoWrrfzVBaaCl1FDT5egvAtwfQ26GPVMJEobEg==
+ version "0.67.16"
+ resolved "https://registry.yarnpkg.com/@effect/schema/-/schema-0.67.16.tgz#b0d4cee4629d1cde467ee0b04590fa60da31b9cd"
+ integrity sha512-fUHo1t6k2w9UcJF/JlppRVD+KXXKtAN8fZRB48NMocLJfQnJC117c9Zio0SQWN+xP9byLl4Y/MbtFeuxcublxg==
dependencies:
fast-check "^3.17.2"
@@ -1949,7 +1949,7 @@
component-type "^1.2.1"
join-component "^1.1.0"
-"@serenity-kit/opaque@^0.8.4":
+"@serenity-kit/opaque@^0.8.0", "@serenity-kit/opaque@^0.8.4":
version "0.8.4"
resolved "https://registry.yarnpkg.com/@serenity-kit/opaque/-/opaque-0.8.4.tgz#c765905b338abcd8eaee8d44f6b9c40850925b40"
integrity sha512-sFxGxZWdBciJ1Bp2F5Pey5q3kW+bN/RYpsZueLogb/KAWF50XuYk5bSG0Y8gH784jV+2XI/G8ptX+kWA0w6N3Q==
@@ -1990,10 +1990,32 @@
dependencies:
"@sinonjs/commons" "^3.0.0"
-"@trpc/server@11.0.0-rc.366":
- version "11.0.0-rc.366"
- resolved "https://registry.yarnpkg.com/@trpc/server/-/server-11.0.0-rc.366.tgz#ffea6f1616bad4e9d77160c7aa4fe7a381317117"
- integrity sha512-Pr7SdpIVrOGtIGt9vs7i3v0hqzlpXbi8/8RV7XfrXVJ5hlzKYOz5VE0AjckhHXuhrUlpTGTDm0/2nz0ywLcQ2g==
+"@tanstack/query-core@5.40.0":
+ version "5.40.0"
+ resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.40.0.tgz#c74ae8303752ed4b5a0ab848ec71a0e6e8179f83"
+ integrity sha512-eD8K8jsOIq0Z5u/QbvOmfvKKE/XC39jA7yv4hgpl/1SRiU+J8QCIwgM/mEHuunQsL87dcvnHqSVLmf9pD4CiaA==
+
+"@tanstack/react-query@^5.40.0":
+ version "5.40.0"
+ resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.40.0.tgz#654afa2d9ab328c22be7e1f025ec9b6267c6baa9"
+ integrity sha512-iv/W0Axc4aXhFzkrByToE1JQqayxTPNotCoSCnarR/A1vDIHaoKpg7FTIfP3Ev2mbKn1yrxq0ZKYUdLEJxs6Tg==
+ dependencies:
+ "@tanstack/query-core" "5.40.0"
+
+"@trpc/client@^11.0.0-rc.382":
+ version "11.0.0-rc.382"
+ resolved "https://registry.yarnpkg.com/@trpc/client/-/client-11.0.0-rc.382.tgz#0bb2222aaa5cbbb7c8d5c9cdde21572bd148a20e"
+ integrity sha512-O+MSRed5r8AJJ+j3peZkd/b7WINEkhKaFilRuPH8VQsrlMZToxUzQl9aShPIgnOhhQGxvWqdh2lXwUvrFCea/A==
+
+"@trpc/react-query@^10.45.2":
+ version "10.45.2"
+ resolved "https://registry.yarnpkg.com/@trpc/react-query/-/react-query-10.45.2.tgz#22564b370a1fb8920ba3a1554f4149a151039198"
+ integrity sha512-BAqb9bGZIscroradlNx+Cc9522R+idY3BOSf5z0jHUtkxdMbjeGKxSSMxxu7JzoLqSIEC+LVzL3VvF8sdDWaZQ==
+
+"@trpc/server@^11.0.0-rc.382":
+ version "11.0.0-rc.382"
+ resolved "https://registry.yarnpkg.com/@trpc/server/-/server-11.0.0-rc.382.tgz#ef2a561cbbf97d14eb283a979be849be0cf08c78"
+ integrity sha512-a3A0osTgFhbClRjE+fZ3nA3TEjupnWM23L4hidjecg3xN+QghwYTjwXJcYXPsdKyT6PhFtzWBKJxShmsZRElZw==
"@tsconfig/node10@^1.0.7":
version "1.0.11"
@@ -2015,39 +2037,6 @@
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9"
integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==
-"@types/babel__core@^7.1.12":
- version "7.20.5"
- resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017"
- integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==
- dependencies:
- "@babel/parser" "^7.20.7"
- "@babel/types" "^7.20.7"
- "@types/babel__generator" "*"
- "@types/babel__template" "*"
- "@types/babel__traverse" "*"
-
-"@types/babel__generator@*":
- version "7.6.8"
- resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.8.tgz#f836c61f48b1346e7d2b0d93c6dacc5b9535d3ab"
- integrity sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==
- dependencies:
- "@babel/types" "^7.0.0"
-
-"@types/babel__template@*":
- version "7.4.4"
- resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f"
- integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==
- dependencies:
- "@babel/parser" "^7.1.0"
- "@babel/types" "^7.0.0"
-
-"@types/babel__traverse@*":
- version "7.20.6"
- resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.6.tgz#8dc9f0ae0f202c08d8d4dab648912c8d6038e3f7"
- integrity sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==
- dependencies:
- "@babel/types" "^7.20.7"
-
"@types/body-parser@*":
version "1.19.5"
resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.5.tgz#04ce9a3b677dc8bd681a17da1ab9835dc9d3ede4"
@@ -2193,10 +2182,10 @@
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb"
integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==
-"@types/react@~18.3.3":
- version "18.3.3"
- resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.3.tgz#9679020895318b0915d7a3ab004d92d33375c45f"
- integrity sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==
+"@types/react@~18.2.79":
+ version "18.2.79"
+ resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.79.tgz#c40efb4f255711f554d47b449f796d1c7756d865"
+ integrity sha512-RwGAGXPl9kSXwdNTafkOEuFrTBD5SA2B3iEB96xi8+xu5ddUa/cpvyVCSNn+asgLCTHkb5ZxN8gbuibYJi4s1w==
dependencies:
"@types/prop-types" "*"
csstype "^3.0.2"
@@ -2633,14 +2622,6 @@ babel-plugin-transform-flow-enums@^0.0.2:
dependencies:
"@babel/plugin-syntax-flow" "^7.12.1"
-babel-plugin-transform-vite-meta-env@^1.0.3:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/babel-plugin-transform-vite-meta-env/-/babel-plugin-transform-vite-meta-env-1.0.3.tgz#cbf81becc95b71dcc170ee4863cb7f6919ed99bb"
- integrity sha512-eyfuDEXrMu667TQpmctHeTlJrZA6jXYHyEJFjcM0yEa60LS/LXlOg2PBbMb8DVS+V9CnTj/j9itdlDVMcY2zEg==
- dependencies:
- "@babel/runtime" "^7.13.9"
- "@types/babel__core" "^7.1.12"
-
babel-preset-expo@~11.0.0, babel-preset-expo@~11.0.6:
version "11.0.6"
resolved "https://registry.yarnpkg.com/babel-preset-expo/-/babel-preset-expo-11.0.6.tgz#b1ea2bd9f13338a9f7ca8d7089b5d6d6c7c03f79"
@@ -7085,13 +7066,13 @@ react-devtools-core@^5.0.0:
shell-quote "^1.6.1"
ws "^7"
-react-dom@^18.2.0:
- version "18.3.1"
- resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4"
- integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==
+react-dom@18.2.0:
+ version "18.2.0"
+ resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
+ integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
dependencies:
loose-envify "^1.1.0"
- scheduler "^0.23.2"
+ scheduler "^0.23.0"
react-fast-compare@^3.2.2:
version "3.2.2"
@@ -7149,10 +7130,17 @@ react-native-libsodium@^1.3.1:
libsodium-wrappers "^0.7.13"
libsodium-wrappers-sumo "^0.7.13"
-react-native-reanimated@~3.11.0:
- version "3.11.0"
- resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-3.11.0.tgz#d4265d4e0232623f5958ed60e1686ca884fc3452"
- integrity sha512-BNw/XDgUfs8UhfY1X6IniU8kWpnotWGyt8qmQviaHisTi5lvwnaOdXQKfN1KGONx6ekdFRHRP5EFwLi0UajwKA==
+react-native-opaque@^0.3.1:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/react-native-opaque/-/react-native-opaque-0.3.1.tgz#a01672064ad0d9b12b0c07d18555ed872f39956c"
+ integrity sha512-DShK07S9uUquI0L9ErHvhudQOzoOrCQSdyieHrXCL2RMb2IXPhS3Qmb+av47vlHbYQQvhXRwd4fUAKGsa+vN1A==
+ dependencies:
+ "@serenity-kit/opaque" "^0.8.0"
+
+react-native-reanimated@~3.10.1:
+ version "3.10.1"
+ resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-3.10.1.tgz#3c37d1100bbba0065df39c96aab0c1ff1b50c0fa"
+ integrity sha512-sfxg6vYphrDc/g4jf/7iJ7NRi+26z2+BszPmvmk0Vnrz6FL7HYljJqTf531F1x6tFmsf+FEAmuCtTUIXFLVo9w==
dependencies:
"@babel/plugin-transform-arrow-functions" "^7.0.0-0"
"@babel/plugin-transform-nullish-coalescing-operator" "^7.0.0-0"
@@ -7163,10 +7151,10 @@ react-native-reanimated@~3.11.0:
convert-source-map "^2.0.0"
invariant "^2.2.4"
-react-native-safe-area-context@4.10.3:
- version "4.10.3"
- resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-4.10.3.tgz#ad132b64e1e7cdd043b4e82a7a2449d99f4f7630"
- integrity sha512-nW9B0fydpJSN798awtdslamYzRqDM/FIEh80ZPDEXVYpqYNsDLpz52pMtuKyhF5aOgJlfiroQrgdOxQNFtSM8A==
+react-native-safe-area-context@4.10.1:
+ version "4.10.1"
+ resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-4.10.1.tgz#29fb27395ff7dfa2fa38788a27226330d73a81cc"
+ integrity sha512-w8tCuowDorUkPoWPXmhqosovBr33YsukkwYCDERZFHAxIkx6qBadYxfeoaJ91nCQKjkNzGrK5qhoNOeSIcYSpA==
react-native-screens@3.31.1:
version "3.31.1"
@@ -7176,10 +7164,10 @@ react-native-screens@3.31.1:
react-freeze "^1.0.0"
warn-once "^0.1.0"
-react-native-svg@15.3.0:
- version "15.3.0"
- resolved "https://registry.yarnpkg.com/react-native-svg/-/react-native-svg-15.3.0.tgz#e24b833fe330714c99f1dd894bb0da52ad859a4c"
- integrity sha512-mBHu/fdlzUbpGX8SZFxgbKvK/sgqLfDLP8uh8G7Us+zJgdjO8OSEeqHQs+kPRdQmdLJQiqPJX2WXgCl7ToTWqw==
+react-native-svg@15.2.0:
+ version "15.2.0"
+ resolved "https://registry.yarnpkg.com/react-native-svg/-/react-native-svg-15.2.0.tgz#9561a6b3bd6b44689f437ba13182afee33bd5557"
+ integrity sha512-R0E6IhcJfVLsL0lRmnUSm72QO+mTqcAOM5Jb8FVGxJqX3NfJMlMP0YyvcajZiaRR8CqQUpEoqrY25eyZb006kw==
dependencies:
css-select "^5.1.0"
css-tree "^1.1.3"
@@ -7254,10 +7242,10 @@ react-shallow-renderer@^16.15.0:
object-assign "^4.1.1"
react-is "^16.12.0 || ^17.0.0 || ^18.0.0"
-react@18.3.1:
- version "18.3.1"
- resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891"
- integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==
+react@18.2.0:
+ version "18.2.0"
+ resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
+ integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
dependencies:
loose-envify "^1.1.0"
@@ -7548,7 +7536,7 @@ scheduler@0.24.0-canary-efb381bbf-20230505:
dependencies:
loose-envify "^1.1.0"
-scheduler@^0.23.2:
+scheduler@^0.23.0:
version "0.23.2"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3"
integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==
@@ -8471,11 +8459,16 @@ typedarray.prototype.slice@^1.0.3:
typed-array-buffer "^1.0.2"
typed-array-byte-offset "^1.0.2"
-typescript@^5.4.5, typescript@~5.4.5:
+typescript@^5.4.5:
version "5.4.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.5.tgz#42ccef2c571fdbd0f6718b1d1f5e6e5ef006f611"
integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==
+typescript@~5.3.3:
+ version "5.3.3"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37"
+ integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==
+
ua-parser-js@^1.0.35:
version "1.0.38"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.38.tgz#66bb0c4c0e322fe48edfe6d446df6042e62f25e2"