diff --git a/app/(app)/(authorized)/_layout.tsx b/app/(app)/(authorized)/_layout.tsx
index efe1be08..7dca3eed 100644
--- a/app/(app)/(authorized)/_layout.tsx
+++ b/app/(app)/(authorized)/_layout.tsx
@@ -1,13 +1,14 @@
import { Redirect, Stack } from 'expo-router'
+import { useAtomValue } from 'jotai'
-import { useAuth } from '~hooks'
+import { isSignedInAtom } from '~store/auth'
export const unstable_settings = {
initialRouteName: '(tabs)',
}
export default function AuthorizedLayout() {
- const { isSignedIn } = useAuth()
+ const isSignedIn = useAtomValue(isSignedInAtom)
if (isSignedIn === false) {
return
diff --git a/app/(app)/(not-authorized)/_layout.tsx b/app/(app)/(not-authorized)/_layout.tsx
index 2eca7350..f545ee2f 100644
--- a/app/(app)/(not-authorized)/_layout.tsx
+++ b/app/(app)/(not-authorized)/_layout.tsx
@@ -1,13 +1,13 @@
import { Redirect, Stack } from 'expo-router'
+import { useAtomValue } from 'jotai'
-import { useAuth } from '~hooks'
-
+import { isSignedInAtom } from '~store/auth'
export const unstable_settings = {
initialRouteName: 'sign-in',
}
export default function NotAuthorizedLayout() {
- const { isSignedIn } = useAuth()
+ const isSignedIn = useAtomValue(isSignedInAtom)
if (isSignedIn === true) {
return
diff --git a/app/_layout.tsx b/app/_layout.tsx
index 0be0a5c3..8fbaf315 100644
--- a/app/_layout.tsx
+++ b/app/_layout.tsx
@@ -1,16 +1,18 @@
import { ThemeProvider } from '@react-navigation/native'
import { Slot } from 'expo-router'
+import { useAtomValue } from 'jotai'
import { AbsoluteFullFill, Loader, StatusBar } from '~components'
-import { useAuth, useNavigationTheme, useRouterNotifications } from '~hooks'
+import { useNavigationTheme, useRouterNotifications } from '~hooks'
import { Providers } from '~providers'
+import { isSignedInAtom } from '~store/auth'
export const unstable_settings = {
initialRouteName: 'index',
}
const Layout = () => {
- const { isSignedIn } = useAuth()
+ const isSignedIn = useAtomValue(isSignedInAtom)
const { navigationTheme } = useNavigationTheme()
useRouterNotifications() // TODO: check if handling notification deeplinks works correctly
diff --git a/app/index.tsx b/app/index.tsx
index 156571bb..1c450294 100644
--- a/app/index.tsx
+++ b/app/index.tsx
@@ -1,11 +1,12 @@
import { Redirect } from 'expo-router'
+import { useAtomValue } from 'jotai'
import { Platform } from 'react-native'
-import { useAuth } from '~hooks'
import { LandingScreen } from '~screens/LandingScreen'
+import { isSignedInAtom } from '~store/auth'
export default function Root() {
- const { isSignedIn } = useAuth()
+ const isSignedIn = useAtomValue(isSignedInAtom)
if (isSignedIn === true) {
return
diff --git a/package.json b/package.json
index c0354eb0..4b6029f7 100644
--- a/package.json
+++ b/package.json
@@ -127,6 +127,7 @@
"expo-updates": "~0.24.8",
"expo-web-browser": "~12.8.2",
"i18next": "^23.7.20",
+ "jotai": "^2.4.3",
"moti": "^0.25.3",
"react": "18.2.0",
"react-dom": "18.2.0",
diff --git a/src/components/AppLoading.tsx b/src/components/AppLoading.tsx
index 5dc39826..6c4d248b 100644
--- a/src/components/AppLoading.tsx
+++ b/src/components/AppLoading.tsx
@@ -1,10 +1,12 @@
import * as SplashScreen from 'expo-splash-screen'
+import { useAtomValue } from 'jotai'
import { FC, PropsWithChildren, useCallback, useEffect } from 'react'
import { View, StyleSheet } from 'react-native'
import { AbsoluteFullFill, Loader, Center } from './atoms'
-import { useAuth, useBoolean, useCachedResources, useFonts } from '~hooks'
+import { useBoolean, useCachedResources, useFonts } from '~hooks'
+import { isSignedInAtom } from '~store/auth'
SplashScreen.preventAutoHideAsync()
@@ -21,7 +23,8 @@ export const AppLoading: FC = ({ children }) => {
// Delay loading logic was made to prevent displaying empty screen after splash screen will hide
const [isDelayLoading, setIsDelayLoading] = useBoolean(true)
- const { isSignedIn } = useAuth()
+
+ const isSignedIn = useAtomValue(isSignedInAtom)
useEffect(() => {
async function prepare() {
diff --git a/src/components/Header.tsx b/src/components/Header.tsx
index a2fdde7e..f2a539b1 100644
--- a/src/components/Header.tsx
+++ b/src/components/Header.tsx
@@ -1,23 +1,13 @@
import { NativeStackHeaderProps } from '@react-navigation/native-stack'
import { useRouter } from 'expo-router'
-import { Touchable } from './atoms'
-
-import { Box, Column, Row, Icon, Text } from '~components/atoms'
+import { Box, Column, Row, Icon, Text, Touchable } from '~components/atoms'
const logoHeight = 24
-export const Header = ({ options, ...rest }: NativeStackHeaderProps) => {
+export const Header = ({ options }: NativeStackHeaderProps) => {
const router = useRouter()
- console.log('options', {
- options,
- rest,
- canGoBack: router.canGoBack(),
- restxd: rest.navigation.getState(),
- canGoBack2: rest.navigation.canGoBack(),
- })
-
return (
diff --git a/src/components/LandingHeader.tsx b/src/components/LandingHeader.tsx
index 63adf02d..3932041e 100644
--- a/src/components/LandingHeader.tsx
+++ b/src/components/LandingHeader.tsx
@@ -1,20 +1,21 @@
import { useRouter } from 'expo-router'
+import { useAtomValue } from 'jotai'
import { Image, StyleSheet, Platform, View } from 'react-native'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { Box, Button, Icon, Pressable } from '~components'
import { darkLogoFull, lightLogoFull } from '~constants'
import { useColorScheme } from '~contexts'
-import { useAuth, useTranslation } from '~hooks'
+import { useTranslation } from '~hooks'
import { TabColorsStrings } from '~navigation/tabNavigator/config'
-
+import { isSignedInAtom } from '~store/auth'
export function LandingHeader() {
const { colorScheme } = useColorScheme()
const { top } = useSafeAreaInsets()
const { t } = useTranslation()
const { push, canGoBack, back } = useRouter()
- const { isSignedIn } = useAuth()
+ const isSignedIn = useAtomValue(isSignedInAtom)
const navigateToLogin = () => push('/sign-in')
diff --git a/src/constants/theme.ts b/src/constants/theme.ts
index 43d7c2bf..ba143d42 100644
--- a/src/constants/theme.ts
+++ b/src/constants/theme.ts
@@ -356,8 +356,8 @@ export const theme = {
export const lightNavigationTheme: Theme = {
colors: {
background: themeColors.lightMode.bg.primary,
- border: themeColors.lightMode.border.primary,
- card: themeColors.lightMode.button.primary.bg,
+ border: 'transparent',
+ card: themeColors.lightMode.bg.primary,
text: themeColors.lightMode.alpha.black[70],
notification: themeColors.lightMode.avatar.bg,
primary: themeColors.lightMode.utility.purple[500],
@@ -368,8 +368,8 @@ export const lightNavigationTheme: Theme = {
export const darkNavigationTheme: Theme = {
colors: {
background: themeColors.darkMode.bg.primary,
- border: themeColors.darkMode.border.primary,
- card: themeColors.darkMode.button.primary.bg,
+ border: 'transparent',
+ card: themeColors.darkMode.bg.primary,
text: themeColors.darkMode.alpha.black[70],
notification: themeColors.darkMode.avatar.bg,
primary: themeColors.darkMode.utility.purple[500],
diff --git a/src/contexts/AuthContext.ts b/src/contexts/AuthContext.ts
deleted file mode 100644
index 0aa12d8f..00000000
--- a/src/contexts/AuthContext.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { createRef, MutableRefObject } from 'react'
-
-import { SignInFormValues, SignUpFormValues } from '~types/authForms'
-import createGenericContext from '~utils/createGenericContext'
-
-// TODO: modify return options from signIn, signOut, signUp and add sendPasswordResetEmail and confirmPasswordReset functions
-export type AuthContextType = {
- isSignedIn: boolean | null
- signIn: (data: SignInFormValues) => void
- signOut: () => void
- signUp: (data: SignUpFormValues) => void
-}
-
-export const [useAuthContext, AuthContextProvider] =
- createGenericContext('AuthContext')
-
-export const authContextRef: MutableRefObject = createRef()
diff --git a/src/contexts/index.ts b/src/contexts/index.ts
index a595c424..ba71af47 100644
--- a/src/contexts/index.ts
+++ b/src/contexts/index.ts
@@ -1,3 +1,2 @@
-export * from './AuthContext'
export * from './NotificationContext'
export * from './ColorSchemeContext'
diff --git a/src/hooks/forms/useSignInForm.ts b/src/hooks/forms/useSignInForm.ts
index a7065f23..6e08a527 100644
--- a/src/hooks/forms/useSignInForm.ts
+++ b/src/hooks/forms/useSignInForm.ts
@@ -1,12 +1,13 @@
import { isError } from '@tanstack/react-query'
+import { useSetAtom } from 'jotai'
import { useState } from 'react'
import { useForm } from 'react-hook-form'
import { useTranslation } from 'react-i18next'
-import { useAuth } from '../useAuth'
-
+import { setToken } from '~services'
+import { isSignedInAtom } from '~store/auth'
import { SignInFormValues } from '~types/authForms'
-import { hapticImpact } from '~utils'
+import { hapticImpact, wait } from '~utils'
const defaultValues: SignInFormValues = {
// TODO: Reset this values when building production app
@@ -16,11 +17,13 @@ const defaultValues: SignInFormValues = {
}
export const useSignInForm = () => {
- const { signIn } = useAuth()
const [error, setError] = useState('')
const [isSubmitting, setIsSubmitting] = useState(false)
+
const { t } = useTranslation()
+ const setIsSignedIn = useSetAtom(isSignedInAtom)
+
const {
control,
formState: { errors },
@@ -35,7 +38,18 @@ export const useSignInForm = () => {
try {
setIsSubmitting(true)
setError('')
- await signIn(data)
+ // Errors are handled on UI side
+ // if you want to stop this function with error just throw new Error.
+ // Remember to pass readable error message for user, because this error will be displayed for him
+
+ // TODO: Add some backend call here, you can use react query for this
+ await wait(500)
+
+ if (data.email !== 'test@example.com' || data.password !== '123456') {
+ throw new Error('Incorrect email or password')
+ }
+ await setToken('token_here')
+ setIsSignedIn(true)
} catch (e) {
if (isError(e)) {
setError(e.message)
diff --git a/src/hooks/forms/useSignUpForm.ts b/src/hooks/forms/useSignUpForm.ts
index 5c2f179b..5de48f4c 100644
--- a/src/hooks/forms/useSignUpForm.ts
+++ b/src/hooks/forms/useSignUpForm.ts
@@ -1,12 +1,12 @@
import { isError } from '@tanstack/react-query'
+import { useSetAtom } from 'jotai'
import { useState } from 'react'
import { useForm } from 'react-hook-form'
import { useTranslation } from 'react-i18next'
-import { useAuth } from '../useAuth'
-
+import { isSignedInAtom } from '~store/auth'
import { SignUpFormValues } from '~types/authForms'
-import { hapticImpact } from '~utils'
+import { hapticImpact, wait } from '~utils'
const defaultValues: SignUpFormValues = {
user: '',
@@ -17,11 +17,13 @@ const defaultValues: SignUpFormValues = {
}
export const useSignUpForm = () => {
- const { signUp } = useAuth()
const [error, setError] = useState('')
const [isSubmitting, setIsSubmitting] = useState(false)
+
const { t } = useTranslation()
+ const setIsSignedIn = useSetAtom(isSignedInAtom)
+
const {
control,
formState: { errors },
@@ -36,7 +38,9 @@ export const useSignUpForm = () => {
try {
setIsSubmitting(true)
setError('')
- await signUp(data)
+ await wait(500)
+ // TODO: Add some backend call here, you can use react query for this
+ setIsSignedIn(true)
} catch (e) {
if (isError(e)) {
setError(e.message)
diff --git a/src/hooks/index.ts b/src/hooks/index.ts
index c2a1c702..0635df19 100644
--- a/src/hooks/index.ts
+++ b/src/hooks/index.ts
@@ -57,7 +57,6 @@ export * from './navigation'
// Custom hooks implemented in app
export * from './useAppStateActive'
-export * from './useAuth'
export * from './useBoolean'
export * from './useCachedResources'
export * from './useKeyboardHeight'
diff --git a/src/hooks/useAuth.ts b/src/hooks/useAuth.ts
deleted file mode 100644
index cbfe7d29..00000000
--- a/src/hooks/useAuth.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-// This was made to prevent require cycle in the app
-export { useAuthContext as useAuth } from '~contexts'
diff --git a/src/logic/AuthLogic.tsx b/src/logic/AuthLogic.tsx
new file mode 100644
index 00000000..692f26bc
--- /dev/null
+++ b/src/logic/AuthLogic.tsx
@@ -0,0 +1,22 @@
+import { useSetAtom } from 'jotai'
+import { FC } from 'react'
+
+import { useEffect } from '~hooks'
+import { getToken } from '~services'
+import { isSignedInAtom } from '~store/auth'
+
+export const AuthLogic: FC = () => {
+ const setIsSignedIn = useSetAtom(isSignedInAtom)
+
+ useEffect(() => {
+ const bootstrap = async () => {
+ // TODO: This should be moved to backend calls, in this bootstrap function we should fetch user info and not token
+ const token = await getToken()
+ setIsSignedIn(!!token)
+ }
+
+ bootstrap()
+ }, [setIsSignedIn])
+
+ return null
+}
diff --git a/src/logic/index.ts b/src/logic/index.ts
new file mode 100644
index 00000000..e69de29b
diff --git a/src/navigation/tabNavigator/components/SideBar.tsx b/src/navigation/tabNavigator/components/SideBar.tsx
index 8c480e91..1da2dff0 100644
--- a/src/navigation/tabNavigator/components/SideBar.tsx
+++ b/src/navigation/tabNavigator/components/SideBar.tsx
@@ -6,14 +6,13 @@ import { TabColorsStrings, upperSideTabs } from '../config'
import { useWidth } from '../hooks'
import { cns } from '../utils'
-import { useAuth } from '~hooks'
+import { signOut } from '~store/auth'
import cssStyles from '~styles'
const NAV_MEDIUM_WIDTH = 244
export function SideBar({ visible }: { visible: boolean }) {
const isLarge = useWidth(1264)
- const { signOut } = useAuth()
return (
= ({ children }) => {
- const [isSignedIn, setIsSignedIn] = useState(null)
-
- useEffect(() => {
- const bootstrap = async () => {
- // TODO: This should be moved to backend calls, in this bootstrap function we should fetch user info and not token
- const token = await getToken()
- setIsSignedIn(!!token)
- }
-
- bootstrap()
- }, [])
-
- const signIn: AuthContextType['signIn'] = useCallback(async (data) => {
- // Errors are handled on UI side
- // if you want to stop this function with error just throw new Error.
- // Remember to pass readable error message for user, because this error will be displayed for him
- await wait(500)
-
- if (data.email !== 'test@example.com' || data.password !== '123456') {
- throw new Error('Incorrect email or password')
- }
- await setToken('token_here')
- setIsSignedIn(true)
- }, [])
-
- const signOut = useCallback(async () => {
- await deleteToken()
- setIsSignedIn(false)
- }, [])
-
- const signUp = useCallback(async (data: SignUpFormValues) => {
- // temporary solution
- console.log(data)
- await wait(500)
- setIsSignedIn(true)
- }, [])
-
- const value = useMemo(() => {
- return {
- isSignedIn,
- signIn,
- signOut,
- signUp,
- }
- }, [isSignedIn, signIn, signOut, signUp])
-
- authContextRef.current = value
-
- return {children}
-}
diff --git a/src/providers/Providers.tsx b/src/providers/Providers.tsx
index d9548d3c..510d9c85 100644
--- a/src/providers/Providers.tsx
+++ b/src/providers/Providers.tsx
@@ -1,18 +1,20 @@
import { BottomSheetModalProvider } from '@gorhom/bottom-sheet'
import { PortalProvider } from '@gorhom/portal'
import { QueryClientProvider, QueryClient } from '@tanstack/react-query'
+import { Provider } from 'jotai'
import { FC, PropsWithChildren } from 'react'
import { StyleSheet } from 'react-native'
import { GestureHandlerRootView } from 'react-native-gesture-handler'
import { SafeAreaProvider } from 'react-native-safe-area-context'
-import { AuthProvider } from './AuthProvider'
import { ColorSchemeProvider } from './ColorSchemeProvider'
import { NotificationsProvider } from './NotificatedProvider'
import { NotificationProvider as ExpoNotificationsProvider } from './NotificationProvider'
import { AppLoading } from '~components'
import { useAppStateActive } from '~hooks'
+import { AuthLogic } from '~logic/AuthLogic'
+import { store } from '~store'
import { checkForUpdates } from '~utils'
const queryClient = new QueryClient({})
@@ -25,18 +27,19 @@ export const Providers: FC = ({ children }) => {
-
- {/* @ts-expect-error: error comes from a react-native-notificated library which doesn't have declared children in types required in react 18 */}
-
-
-
+
+
+ {/* @ts-expect-error: error comes from a react-native-notificated library which doesn't have declared children in types required in react 18 */}
+
+
{children}
-
-
-
-
+
+
+
+
+
diff --git a/src/providers/index.ts b/src/providers/index.ts
index 1d89e6ff..dd3bcfff 100644
--- a/src/providers/index.ts
+++ b/src/providers/index.ts
@@ -1,5 +1,4 @@
export { SafeAreaProvider } from 'react-native-safe-area-context'
-export * from './AuthProvider'
export * from './NotificatedProvider'
export * from './NotificationProvider'
export * from './Providers'
diff --git a/src/screens/SettingsScreen.tsx b/src/screens/SettingsScreen.tsx
index a5610fc3..e88abfa6 100644
--- a/src/screens/SettingsScreen.tsx
+++ b/src/screens/SettingsScreen.tsx
@@ -1,7 +1,8 @@
import { Version, Spacer, Button, Center, Text, ScrollView } from '~components'
import { colorSchemesList } from '~constants'
import { useColorScheme } from '~contexts'
-import { useAuth, useCallback, useScreenOptions, useTranslation } from '~hooks'
+import { useCallback, useScreenOptions, useTranslation } from '~hooks'
+import { signOut } from '~store/auth'
import { noop } from '~utils'
export const SettingsScreen = (): JSX.Element => {
@@ -12,8 +13,6 @@ export const SettingsScreen = (): JSX.Element => {
title: t('navigation.screen_titles.settings'),
})
- const { signOut } = useAuth()
-
const handleColorSchemeSettingChange = useCallback(
(scheme: typeof colorSchemeSetting) => () => {
setColorSchemeSetting(scheme)
diff --git a/src/store/auth/authActions.ts b/src/store/auth/authActions.ts
new file mode 100644
index 00000000..c3c3c66c
--- /dev/null
+++ b/src/store/auth/authActions.ts
@@ -0,0 +1,9 @@
+import { isSignedInAtom } from './authState'
+
+import { deleteToken } from '~services'
+import { store } from '~store/store'
+
+export async function signOut() {
+ await deleteToken()
+ store.set(isSignedInAtom, false)
+}
diff --git a/src/store/auth/authState.ts b/src/store/auth/authState.ts
new file mode 100644
index 00000000..2c75bb7a
--- /dev/null
+++ b/src/store/auth/authState.ts
@@ -0,0 +1,3 @@
+import { atom } from 'jotai'
+
+export const isSignedInAtom = atom(null)
diff --git a/src/store/auth/index.ts b/src/store/auth/index.ts
new file mode 100644
index 00000000..b221e524
--- /dev/null
+++ b/src/store/auth/index.ts
@@ -0,0 +1,2 @@
+export * from './authState'
+export * from './authActions'
diff --git a/src/store/index.ts b/src/store/index.ts
new file mode 100644
index 00000000..16c86332
--- /dev/null
+++ b/src/store/index.ts
@@ -0,0 +1 @@
+export * from './store'
diff --git a/src/store/store.ts b/src/store/store.ts
new file mode 100644
index 00000000..1ce2b50f
--- /dev/null
+++ b/src/store/store.ts
@@ -0,0 +1,3 @@
+import { createStore } from 'jotai'
+
+export const store = createStore()
diff --git a/src/utils/testUtils.tsx b/src/utils/testUtils.tsx
index 37666204..617d7e6e 100644
--- a/src/utils/testUtils.tsx
+++ b/src/utils/testUtils.tsx
@@ -4,18 +4,15 @@ import { PropsWithChildren, ReactElement } from 'react'
import { I18nextProvider } from 'react-i18next'
import i18n from '~i18n/i18nForTests'
-import { AuthProvider } from '~providers'
import { ColorSchemeProvider } from '~providers/ColorSchemeProvider'
type RenderOptions = Parameters[1]
const ProvidersWrapper: React.FC = ({ children }) => (
-
-
- {children}
-
-
+
+ {children}
+
)
diff --git a/yarn.lock b/yarn.lock
index 71bd7153..8e5df435 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -8373,6 +8373,11 @@ join-component@^1.1.0:
resolved "https://registry.yarnpkg.com/join-component/-/join-component-1.1.0.tgz#b8417b750661a392bee2c2537c68b2a9d4977cd5"
integrity sha512-bF7vcQxbODoGK1imE2P9GS9aw4zD0Sd+Hni68IMZLj7zRnquH7dXUmMw9hDI5S/Jzt7q+IyTXN0rSg2GI0IKhQ==
+jotai@^2.4.3:
+ version "2.4.3"
+ resolved "https://registry.yarnpkg.com/jotai/-/jotai-2.4.3.tgz#a8eff8ca6de968d6a04616329dd1335ce52e70f3"
+ integrity sha512-CSAHX9LqWG5WCrU8OgBoZbBJ+Bo9rQU0mPusEF4e0CZ/SNFgurG26vb3UpgvCSJZgYVcUQNiUBM5q86PA8rstQ==
+
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"