diff --git a/app.json b/app.json index 0844c12f..0cc093bc 100644 --- a/app.json +++ b/app.json @@ -8,13 +8,14 @@ }, "assetBundlePatterns": ["**/*"], "ios": { - "jsEngine": "hermes", "buildNumber": "17", - "supportsTablet": true, - "googleServicesFile": "./GoogleService-Info.plist", "config": { "usesNonExemptEncryption": false - } + }, + "googleServicesFile": "./GoogleService-Info.plist", + "jsEngine": "hermes", + "usesAppleSignIn": true, + "supportsTablet": true }, "name": "BACA", "orientation": "portrait", @@ -32,6 +33,7 @@ "version": "2.2.0", "plugins": [ "@react-native-google-signin/google-signin", + "expo-apple-authentication", "expo-font", "expo-localization", "expo-secure-store", diff --git a/assets/social/apple-icon-dark.png b/assets/social/apple-icon-dark.png deleted file mode 100644 index 213c5404..00000000 Binary files a/assets/social/apple-icon-dark.png and /dev/null differ diff --git a/assets/social/apple-icon.png b/assets/social/apple-icon.png deleted file mode 100644 index 34a7babe..00000000 Binary files a/assets/social/apple-icon.png and /dev/null differ diff --git a/assets/social/facebook-icon.png b/assets/social/facebook-icon.png deleted file mode 100644 index cc110986..00000000 Binary files a/assets/social/facebook-icon.png and /dev/null differ diff --git a/package.json b/package.json index 981bf60f..3b70b3ea 100644 --- a/package.json +++ b/package.json @@ -109,6 +109,7 @@ "@tanstack/react-query": "^4.29.19", "axios": "^1.6.7", "expo": "~50.0.13", + "expo-apple-authentication": "~6.3.0", "expo-application": "~5.8.3", "expo-asset": "~9.0.2", "expo-build-properties": "~0.11.1", diff --git a/src/components/molecules/SocialButtons/AppleButton/AppleButton.tsx b/src/components/molecules/SocialButtons/AppleButton/AppleButton.tsx new file mode 100644 index 00000000..8cd268a6 --- /dev/null +++ b/src/components/molecules/SocialButtons/AppleButton/AppleButton.tsx @@ -0,0 +1,77 @@ +import { useAuthAppleControllerLogin } from '@baca/api/query/auth-social/auth-social' +import { assignPushToken, setToken } from '@baca/services' +import { isSignedInAtom, store } from '@baca/store' +import * as ExpoAppleAuthentication from 'expo-apple-authentication' +import { Dispatch, FC, SetStateAction, useCallback, useEffect, useState } from 'react' + +import { SocialButton } from '../SocialButton' + +type AppleButtonProps = { + isDisabled: boolean + setIsDisabled: Dispatch> +} + +export const AppleButton: FC = ({ isDisabled, setIsDisabled }) => { + const { mutate: signInApple } = useAuthAppleControllerLogin() + const [isLoading, setIsLoading] = useState(false) + const [showAppleButton, setShowAppleButton] = useState(false) + + const checkIfAvailable = useCallback(async () => { + const isAvailable = await ExpoAppleAuthentication.isAvailableAsync() + setShowAppleButton(isAvailable) + }, []) + + useEffect(() => { + checkIfAvailable() + }, [checkIfAvailable]) + + const onPress = useCallback(async () => { + setIsDisabled(true) + setIsLoading(true) + try { + const appleResponse = await ExpoAppleAuthentication.signInAsync({ + requestedScopes: [ + ExpoAppleAuthentication.AppleAuthenticationScope.EMAIL, + ExpoAppleAuthentication.AppleAuthenticationScope.FULL_NAME, + ], + }) + + signInApple( + { + data: { + idToken: appleResponse.identityToken!, + firstName: appleResponse.fullName?.givenName || '', + lastName: appleResponse.fullName?.familyName || '', + }, + }, + + { + onError: (e) => { + if (e.code === 'ERR_REQUEST_CANCELED') { + console.log('Request cancelled by the user') + } else return e + }, + onSuccess: async (response) => { + const { user, ...token } = response + if (token) { + await setToken(token) + } + store.set(isSignedInAtom, true) + + // Send push token to backend + await assignPushToken() + }, + } + ) + } catch (e) { + console.log('Cannot login with apple', e) + } finally { + setIsLoading(false) + setIsDisabled(false) + } + }, [setIsDisabled, signInApple]) + + if (!showAppleButton) return null + + return +} diff --git a/src/components/molecules/SocialButtons/AppleButton/index.ts b/src/components/molecules/SocialButtons/AppleButton/index.ts new file mode 100644 index 00000000..a47ce11d --- /dev/null +++ b/src/components/molecules/SocialButtons/AppleButton/index.ts @@ -0,0 +1 @@ +export * from './AppleButton' diff --git a/src/components/molecules/SocialButtons/GoogleButton/GoogleButton.tsx b/src/components/molecules/SocialButtons/GoogleButton/GoogleButton.tsx new file mode 100644 index 00000000..03684ec0 --- /dev/null +++ b/src/components/molecules/SocialButtons/GoogleButton/GoogleButton.tsx @@ -0,0 +1,9 @@ +import { isExpoGo, isWeb } from '@baca/constants' + +import { GoogleButtonProps, NativeGoogleButton } from './NativeGoogleButton' + +export const GoogleButton = (props: GoogleButtonProps) => { + //TODO: Add google button for web + if (isExpoGo || isWeb) return null + return +} diff --git a/src/components/molecules/SocialButtons/GoogleButton/NativeGoogleButton.tsx b/src/components/molecules/SocialButtons/GoogleButton/NativeGoogleButton.tsx index cf582110..2b69a067 100644 --- a/src/components/molecules/SocialButtons/GoogleButton/NativeGoogleButton.tsx +++ b/src/components/molecules/SocialButtons/GoogleButton/NativeGoogleButton.tsx @@ -4,22 +4,29 @@ import { useCallback, useEffect, useState } from '@baca/hooks' import { assignPushToken, setToken } from '@baca/services' import { isSignedInAtom, store } from '@baca/store' import { showErrorToast } from '@baca/utils' -import { FC } from 'react' +import { Dispatch, FC, SetStateAction } from 'react' import { useTranslation } from 'react-i18next' import { SocialButton } from '../SocialButton' -let NativeGoogleButton: FC = () => null +export type GoogleButtonProps = { + isDisabled: boolean + setIsDisabled: Dispatch> +} + +let NativeGoogleButton: FC = () => null if (!isExpoGo && !isWeb) { // Conditionally import makes it work with expo go import('@react-native-google-signin/google-signin').then(({ GoogleSignin, statusCodes }) => { type GoogleSignInError = Error & { code: keyof typeof statusCodes } - NativeGoogleButton = () => { - const [isDisabled, setIsDisabled] = useState(false) - const { mutate: signInByGoogle } = useAuthGoogleControllerLogin() + NativeGoogleButton = ({ isDisabled, setIsDisabled }) => { const { t } = useTranslation() + const { mutate: signInByGoogle } = useAuthGoogleControllerLogin() + + const [isGoogleButtonDisabled, setIsGoogleButtonDisabled] = useState(false) + const [isLoading, setIsLoading] = useState(false) useEffect(() => { // No extra configuration is needed, @@ -31,7 +38,7 @@ if (!isExpoGo && !isWeb) { }, []) const verifyPlayServices = useCallback(async (): Promise => { - setIsDisabled(!(await GoogleSignin?.hasPlayServices?.())) + setIsGoogleButtonDisabled(!(await GoogleSignin?.hasPlayServices?.())) }, []) useEffect(() => { @@ -65,6 +72,8 @@ if (!isExpoGo && !isWeb) { }, [signInByGoogle]) const signIn = useCallback(async (): Promise => { + setIsLoading(true) + setIsDisabled(true) try { await GoogleSignin?.signIn?.() await verifyToken() @@ -88,10 +97,20 @@ if (!isExpoGo && !isWeb) { } showErrorToast({ description: t('errors.something_went_wrong') }) + } finally { + setIsDisabled(false) + setIsLoading(false) } - }, [t, verifyToken]) - - return + }, [setIsDisabled, t, verifyToken]) + + return ( + + ) } }) } diff --git a/src/components/molecules/SocialButtons/GoogleButton/index.ts b/src/components/molecules/SocialButtons/GoogleButton/index.ts new file mode 100644 index 00000000..e38dad87 --- /dev/null +++ b/src/components/molecules/SocialButtons/GoogleButton/index.ts @@ -0,0 +1 @@ +export * from './GoogleButton' diff --git a/src/components/molecules/SocialButtons/GoogleButton/index.tsx b/src/components/molecules/SocialButtons/GoogleButton/index.tsx deleted file mode 100644 index 184d596c..00000000 --- a/src/components/molecules/SocialButtons/GoogleButton/index.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { isExpoGo, isWeb } from '@baca/constants' - -import { NativeGoogleButton } from './NativeGoogleButton' - -export const GoogleButton = () => { - //TODO: Add google button for web - if (isExpoGo || isWeb) return null - return -} diff --git a/src/components/molecules/SocialButtons/SocialButton.tsx b/src/components/molecules/SocialButtons/SocialButton.tsx index a8e96848..11e51700 100644 --- a/src/components/molecules/SocialButtons/SocialButton.tsx +++ b/src/components/molecules/SocialButtons/SocialButton.tsx @@ -1,24 +1,22 @@ -import { appleIconDark, appleIcon, facebookIcon, googleIcon } from '@baca/constants' +import { googleIcon } from '@baca/constants' import { useColorScheme } from '@baca/contexts' -import { Button, ButtonProps } from '@baca/design-system' +import { Button, ButtonProps, Icon } from '@baca/design-system' import i18n from '@baca/i18n' -import { FC } from 'react' +import { FC, useCallback } from 'react' import { Image, ImageSourcePropType } from 'react-native' type SocialMediaType = 'apple' | 'facebook' | 'google' const socialButtonVariants: { [key in SocialMediaType]: { - source: { light: ImageSourcePropType; dark?: ImageSourcePropType } + source?: { light: ImageSourcePropType; dark?: ImageSourcePropType } text: () => string } } = { apple: { - source: { light: appleIcon, dark: appleIconDark }, text: () => i18n.t('sign_in_screen.sign_in_by_apple'), }, facebook: { - source: { light: facebookIcon }, text: () => i18n.t('sign_in_screen.sign_in_by_facebook'), }, google: { @@ -37,11 +35,34 @@ export const SocialButton: FC = ({ type = 'google', ...rest } const { source, text } = socialButtonVariants[type] + const generateLeftElement = useCallback( + (type: SocialMediaType) => { + switch (type) { + case 'apple': + return ( + + ) + case 'facebook': + // @ts-expect-error icon type accepts only ColorNames in order to make suggestion in code, but strings works as well + return + case 'google': + return ( + + ) + } + }, + [colorScheme, source] + ) + return ( - - - + {t('sign_in_screen.do_not_have_an_account')} diff --git a/src/screens/auth/SignUpScreen.tsx b/src/screens/auth/SignUpScreen.tsx index 4706bc52..e0312370 100644 --- a/src/screens/auth/SignUpScreen.tsx +++ b/src/screens/auth/SignUpScreen.tsx @@ -1,8 +1,7 @@ -import { CompanyLogo, ControlledField, FormWrapper, GoogleButton } from '@baca/components' +import { CompanyLogo, ControlledField, FormWrapper, SocialButtons } from '@baca/components' import { Box, Button, Center, Display, Row, Spacer, Text } from '@baca/design-system' -import { useSignUpForm, useTranslation } from '@baca/hooks' +import { useCallback, useSignUpForm, useState, useTranslation } from '@baca/hooks' import { router } from 'expo-router' -import { useCallback } from 'react' import { Keyboard } from 'react-native' import { usePasswordValidation } from '../../hooks/usePasswordValidation' @@ -14,7 +13,11 @@ const navigateToLogIn = () => { export const SignUpScreen = () => { const { t } = useTranslation() - const { control, errors, register, isSubmitting, setFocus } = useSignUpForm() + const [isSignUpButtonDisabled, setIsSignUpButtonDisabled] = useState(false) + + const { control, errors, register, isSubmitting, setFocus } = useSignUpForm({ + setIsSignUpButtonDisabled, + }) const { isPasswordError, passwordSuggestions, validationFn } = usePasswordValidation() @@ -118,9 +121,10 @@ export const SignUpScreen = () => { {t('sign_up_screen.get_started')} - - - + diff --git a/src/store/auth/authActions.ts b/src/store/auth/authActions.ts index 16ad41bd..8aa967a5 100644 --- a/src/store/auth/authActions.ts +++ b/src/store/auth/authActions.ts @@ -1,3 +1,6 @@ +import { QueryKeys } from '@baca/enums' +import { queryClient } from '@baca/queryClient' + import { isSignedInAtom } from './authState' import { removePushToken } from '../../services/NotificationService' import { deleteToken } from '../../services/TokenService' @@ -7,6 +10,9 @@ export async function signOut() { // set user logged out store.set(isSignedInAtom, false) + // remove user profile data from device + queryClient.removeQueries({ queryKey: [QueryKeys.USER_DATA] }) + // remove auth token from device await deleteToken() diff --git a/yarn.lock b/yarn.lock index 685d71c9..a291c7dc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6655,6 +6655,11 @@ expect@^29.0.0, expect@^29.7.0: jest-message-util "^29.7.0" jest-util "^29.7.0" +expo-apple-authentication@~6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/expo-apple-authentication/-/expo-apple-authentication-6.3.0.tgz#0e591efbb70e97f87c1fcadfb8ca97a1d78bf47b" + integrity sha512-eYoRMlh7qKkWdbNe5TV5n0/1mLMRDFDxt+uetyu0XMdn70vZSTaPr5yewyGzoFHRx5t4QCEj8wTobcjoqH5PGw== + expo-application@~5.8.0, expo-application@~5.8.3: version "5.8.3" resolved "https://registry.yarnpkg.com/expo-application/-/expo-application-5.8.3.tgz#43991bd81d05c987b07b2f430c036cda1572bc62"