diff --git a/packages/app/modules/auth/components/AuthWrapper.tsx b/packages/app/modules/auth/components/AuthWrapper.tsx
index 5e2ff4875..c7a20e7ca 100644
--- a/packages/app/modules/auth/components/AuthWrapper.tsx
+++ b/packages/app/modules/auth/components/AuthWrapper.tsx
@@ -1,10 +1,9 @@
-import React, { useEffect, useState } from 'react';
+import React from 'react';
import { AuthLoader } from './AuthLoader';
import { Redirect } from 'app/components/Redirect';
-import { RSpinner, RText, RButton } from '@packrat/ui';
-import { Platform, View, Alert } from 'react-native';
+import { RSpinner, RText } from '@packrat/ui';
+import { Platform, View } from 'react-native';
import LandingPage from 'app/components/landing_page';
-import * as LocalAuthentication from 'expo-local-authentication';
import useTheme from 'app/hooks/useTheme';
interface AuthWrapperProps {
@@ -16,65 +15,8 @@ export const AuthWrapper = ({
children,
unauthorizedElement,
}: AuthWrapperProps) => {
- const [isAuthenticated, setIsAuthenticated] = useState(false);
const { currentTheme } = useTheme();
- const authenticate = async () => {
- if (Platform.OS === 'web') {
- setIsAuthenticated(true);
- return;
- }
-
- const hasHardware = await LocalAuthentication.hasHardwareAsync();
- if (!hasHardware) {
- Alert.alert(
- 'Error',
- 'Your device does not support biometric authentication.',
- );
- return;
- }
-
- const hasBiometrics = await LocalAuthentication.isEnrolledAsync();
- if (!hasBiometrics) {
- Alert.alert(
- 'Error',
- 'No biometrics are enrolled. Please set up biometrics in your device settings.',
- );
- return;
- }
-
- const result = await LocalAuthentication.authenticateAsync({
- promptMessage: 'Authenticate to continue',
- fallbackLabel: 'Use Passcode',
- });
-
- if (result.success) {
- setIsAuthenticated(true);
- }
- };
-
- useEffect(() => {
- authenticate();
- }, []);
-
- if (!isAuthenticated) {
- return (
-
-
- Please unlock to continue
-
- Unlock
-
- );
- }
-
const loadingElement =
Platform.OS === 'web' ? (
Loading...
diff --git a/packages/app/modules/auth/hooks/useUser.ts b/packages/app/modules/auth/hooks/useUser.ts
index 4a98c9c3c..c25b9c75e 100644
--- a/packages/app/modules/auth/hooks/useUser.ts
+++ b/packages/app/modules/auth/hooks/useUser.ts
@@ -17,6 +17,8 @@ export const useUserQuery = () => {
isLoading: isRequestLoading,
} = queryTrpc.getMe.useQuery(undefined, {
enabled: isRequestEnabled,
+ staleTime: Infinity,
+ cacheTime: Infinity,
});
// Sometimes the isLoading state don't work as expected so we have this solution here
diff --git a/packages/ui/src/Bento/elements/tables/Basic.tsx b/packages/ui/src/Bento/elements/tables/Basic.tsx
index be6b1f990..6c04480f9 100644
--- a/packages/ui/src/Bento/elements/tables/Basic.tsx
+++ b/packages/ui/src/Bento/elements/tables/Basic.tsx
@@ -10,12 +10,13 @@ import { Text, View, getTokenValue } from 'tamagui';
import { Table } from './common/tableParts';
import { AddItem } from 'app/modules/item';
import { DeletePackItemModal, EditPackItemModal } from 'app/modules/pack';
-import { ThreeDotsMenu, YStack, RButton } from '@packrat/ui';
+import { ThreeDotsMenu, YStack, RButton, RText } from '@packrat/ui';
import { Platform } from 'react-native';
import { RDropdownMenu } from '../../../ZDropdown';
import RIconButton from '../../../RIconButton';
import { ChevronDown } from '@tamagui/lucide-icons';
+import { BaseAlert } from '@packrat/ui';
type ModalName = 'edit' | 'delete';
@@ -100,13 +101,34 @@ export function BasicTable({
/>
)}
-
- onDelete({ itemId: item.id, packId: currentPack.id })
- }
- />
+ hideIcon={true}
+ title={'Delete Item'}
+ footerButtons={[
+ {
+ label: 'Cancel',
+ onClick: () => {
+ closeModal();
+ },
+ color: 'gray',
+ disabled: false,
+ },
+ {
+ label: 'Delete',
+ onClick: () => {
+ closeModal();
+ onDelete({ itemId: item.id, packId: currentPack.id });
+ },
+ color: '#B22222',
+ disabled: false,
+ },
+ ]}
+ >
+ Are you sure you want to delete this item?
+
+
{hasPermissions ? (
Platform.OS === 'android' ||
Platform.OS === 'ios' ||
diff --git a/packages/ui/src/Bento/forms/layouts/SignInScreen.tsx b/packages/ui/src/Bento/forms/layouts/SignInScreen.tsx
index c6b6653b6..50a04582c 100644
--- a/packages/ui/src/Bento/forms/layouts/SignInScreen.tsx
+++ b/packages/ui/src/Bento/forms/layouts/SignInScreen.tsx
@@ -1,4 +1,4 @@
-// import { Facebook, Github } from '@tamagui/lucide-icons';
+import * as LocalAuthentication from 'expo-local-authentication';
import {
AnimatePresence,
H1,
@@ -9,13 +9,11 @@ import {
Theme,
View,
} from 'tamagui';
-import { Text, Platform } from 'react-native';
+import { Text, Platform, Alert } from 'react-native';
import { FormCard } from './components/layoutParts';
import { RLink } from '@packrat/ui';
import { Form, FormInput, SubmitButton } from '@packrat/ui';
import { userSignIn } from '@packrat/validations';
-import { FontAwesome } from '@expo/vector-icons';
-import { RIconButton } from '@packrat/ui';
import useTheme from 'app/hooks/useTheme';
import useResponsive from 'app/hooks/useResponsive';
@@ -27,6 +25,49 @@ export function SignInScreen({
}) {
const { currentTheme } = useTheme();
const { xxs, xs } = useResponsive();
+
+ const handleBiometricAuth = async () => {
+ if (Platform.OS === 'web') {
+ return true;
+ }
+
+ const hasHardware = await LocalAuthentication.hasHardwareAsync();
+ if (!hasHardware) {
+ Alert.alert(
+ 'Error',
+ 'Your device does not support biometric authentication.',
+ );
+ return false;
+ }
+
+ const hasBiometrics = await LocalAuthentication.isEnrolledAsync();
+ if (!hasBiometrics) {
+ Alert.alert(
+ 'Error',
+ 'No biometrics are enrolled. Please set up biometrics in your device settings.',
+ );
+ return false;
+ }
+
+ const result = await LocalAuthentication.authenticateAsync({
+ promptMessage: 'Authenticate to continue',
+ fallbackLabel: 'Use Passcode',
+ });
+
+ if (!result.success) {
+ Alert.alert('Authentication failed', 'Please try again.');
+ }
+
+ return result.success;
+ };
+
+ const handleSubmit = async (data) => {
+ const isBiometricallyAuthenticated = await handleBiometricAuth();
+ if (isBiometricallyAuthenticated) {
+ signIn(data);
+ }
+ };
+
return (
signIn(data)}
+ onSubmit={handleSubmit}
style={{
marginTop: 16,
backgroundColor: currentTheme.colors.tertiaryBlue,
@@ -121,20 +162,6 @@ export function SignInScreen({
Or
- {/*
- {
- event.preventDefault();
- await promptAsync();
- }}
- icon={}
- >
- Continue with Google
-
- */}
- {/* */}
diff --git a/packages/ui/src/alert/BaseAlert.tsx b/packages/ui/src/alert/BaseAlert.tsx
new file mode 100644
index 000000000..a425431e4
--- /dev/null
+++ b/packages/ui/src/alert/BaseAlert.tsx
@@ -0,0 +1,165 @@
+import React, { useEffect, useMemo, useState } from 'react';
+import { Button, AlertDialog } from 'tamagui';
+import { X } from '@tamagui/lucide-icons';
+import RButton from '@packrat/ui/src/RButton';
+import RStack from '@packrat/ui/src/RStack';
+import { useAlert, AlertProvider } from './provider';
+
+export interface BaseAlertProps {
+ id?: string;
+ title: string;
+ trigger?: string;
+ children: React.ReactNode;
+ buttonColor?: string;
+ footerButtons?: any[];
+ triggerComponent?: React.DetailedReactHTMLElement;
+ footerComponent: React.DetailedReactHTMLElement;
+ hideIcon?: boolean;
+ isOpen?: boolean | undefined;
+ toggle?: any;
+ onClose: any;
+}
+
+export const BaseAlert = ({
+ triggerComponent,
+ title,
+ trigger,
+ footerButtons,
+ footerComponent,
+ children,
+ hideIcon = false,
+ isOpen = undefined,
+ toggle,
+ onClose,
+}: BaseAlertProps) => {
+ const [isAlertOpen, setIsAlertOpen] = useState(false);
+
+ useEffect(() => {
+ if(isOpen !== undefined) {
+ setIsAlertOpen(isOpen)
+ }
+ }, [isOpen])
+
+ const triggerElement = useMemo(() => {
+ return triggerComponent ? (
+ setIsAlertOpen(true)}
+ style={{ backgroundColor: 'transparent' }}
+ backgroundColor={'transparent'}
+ >
+ {React.cloneElement(triggerComponent, { setIsAlertOpen })}
+
+ ) : (
+ setIsAlertOpen(true)}
+ >
+ {trigger}
+
+ );
+ }, [triggerComponent]);
+
+ const memoFooterButtons = useMemo(() => {
+ if (!Array.isArray(footerButtons)) return null;
+
+ return footerButtons.map(({ color, label, onClick, ...button }, index) => (
+ {
+ setIsAlertOpen(false)
+ if(toggle) {
+ toggle()
+ }
+ })}
+ backgroundColor={color}
+ disabled={button.disabled}
+ color="white"
+ {...button}
+ >
+ {label}
+
+ ));
+ }, [footerButtons]);
+
+ const footerElement = useMemo(() => {
+ return (
+ footerComponent && React.cloneElement(footerComponent, { setIsAlertOpen })
+ );
+ }, [footerComponent]);
+
+ return (
+ {
+ setIsAlertOpen(open);
+ }}
+ >
+ {!hideIcon && {triggerElement}}
+
+
+
+
+ {title}
+
+
+ {children}
+
+
+
+
+ {memoFooterButtons}
+
+ {footerElement}
+
+
+
+
+
+
+ );
+};
+
+const withAlertCloseHandler =
+ (fn, closeHandler) =>
+ (...args) =>
+ fn?.(...args, closeHandler.bind(null, false));
\ No newline at end of file
diff --git a/packages/ui/src/alert/index.ts b/packages/ui/src/alert/index.ts
new file mode 100644
index 000000000..a47dad46e
--- /dev/null
+++ b/packages/ui/src/alert/index.ts
@@ -0,0 +1,2 @@
+export { BaseAlert } from './BaseAlert';
+export { useAlert, AlertProvider } from './provider';
\ No newline at end of file
diff --git a/packages/ui/src/alert/provider/AlertProvider.tsx b/packages/ui/src/alert/provider/AlertProvider.tsx
new file mode 100644
index 000000000..73c530da3
--- /dev/null
+++ b/packages/ui/src/alert/provider/AlertProvider.tsx
@@ -0,0 +1,26 @@
+import { createContext, useContext, useState } from 'react';
+
+interface AlertProviderProps {
+ children?: any;
+ isAlertOpen: boolean;
+ setIsAlertOpen: (value: boolean) => void;
+}
+
+export const AlertContext = createContext({
+ isAlertOpen: false,
+ setIsAlertOpen: () => {},
+});
+
+export const AlertProvider = ({
+ children,
+ isAlertOpen,
+ setIsAlertOpen,
+}: AlertProviderProps) => {
+ const _value = { isAlertOpen, setIsAlertOpen };
+
+ return (
+ {children}
+ );
+};
+
+export const useAlert = () => useContext(AlertContext);
\ No newline at end of file
diff --git a/packages/ui/src/alert/provider/index.ts b/packages/ui/src/alert/provider/index.ts
new file mode 100644
index 000000000..abcc34805
--- /dev/null
+++ b/packages/ui/src/alert/provider/index.ts
@@ -0,0 +1 @@
+export { AlertProvider, useAlert } from './AlertProvider';
\ No newline at end of file
diff --git a/packages/ui/src/index.tsx b/packages/ui/src/index.tsx
index 38c8417df..a6dc17b74 100644
--- a/packages/ui/src/index.tsx
+++ b/packages/ui/src/index.tsx
@@ -90,7 +90,7 @@ export * from './dialog';
export * from './list';
export * from './modal';
export * from './toast';
-
+export * from './alert';
export * from './RCard';
export * from './RImage';
export * from './RInput';