Apple sign in is mostly working. Did some other stuff too
This commit is contained in:
99
components/auth/AppleSignIn.native.tsx
Normal file
99
components/auth/AppleSignIn.native.tsx
Normal file
@ -0,0 +1,99 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { supabase } from '@/lib/supabase';
|
||||
import * as AppleAuthentication from 'expo-apple-authentication'
|
||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
||||
import { ThemedView } from '@/components/theme';
|
||||
import { StyleSheet, Platform } from 'react-native';
|
||||
import Constants from 'expo-constants';
|
||||
import * as Notifications from 'expo-notifications';
|
||||
|
||||
const AppleSignInButton = () => {
|
||||
const scheme = useColorScheme() ?? 'dark';
|
||||
|
||||
const signInWithApple = async () => {
|
||||
try {
|
||||
const credential = await AppleAuthentication.signInAsync({
|
||||
requestedScopes: [
|
||||
AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
|
||||
AppleAuthentication.AppleAuthenticationScope.EMAIL,
|
||||
],
|
||||
});
|
||||
//const projectId = Constants.expoConfig?.extra?.projectId;
|
||||
//if (!projectId) throw new Error('No projectId found in expo.config.json');
|
||||
//const pushToken = await Notifications.getExpoPushTokenAsync({
|
||||
//projectId,
|
||||
//});
|
||||
if (credential.identityToken) {
|
||||
const full_name = (
|
||||
credential.fullName &&
|
||||
credential.fullName.givenName &&
|
||||
credential.fullName.familyName
|
||||
)
|
||||
? `${credential.fullName.givenName} ${credential.fullName.familyName}`
|
||||
: null;
|
||||
|
||||
const {
|
||||
error,
|
||||
data: { user, session },
|
||||
} = await supabase.auth.signInWithIdToken({
|
||||
provider: 'apple',
|
||||
token: credential.identityToken,
|
||||
});
|
||||
console.log(JSON.stringify({ error, user }, null, 2))
|
||||
if (!error) {
|
||||
if (user) {
|
||||
const data: any = {
|
||||
full_name,
|
||||
};
|
||||
const { error: updateError } = await supabase.auth.updateUser({
|
||||
data,
|
||||
});
|
||||
if (updateError) {
|
||||
console.error('Error updating user metadata:', updateError);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error('No identityToken.')
|
||||
}
|
||||
} catch (e: any) {
|
||||
if (e.code === 'ERR_REQUEST_CANCELED') {
|
||||
// handle that the user canceled the sign-in flow
|
||||
console.log('User canceled sign-in flow');
|
||||
} else {
|
||||
// handle other errors
|
||||
console.log('Error signing in with Apple:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Platform.OS !== 'ios') return <div/>;
|
||||
else return (
|
||||
|
||||
<ThemedView style={[styles.verticallySpaced, styles.mt20]}>
|
||||
<AppleAuthentication.AppleAuthenticationButton
|
||||
buttonType={AppleAuthentication.AppleAuthenticationButtonType.SIGN_IN}
|
||||
buttonStyle={
|
||||
scheme === 'light'
|
||||
? AppleAuthentication.AppleAuthenticationButtonStyle.BLACK
|
||||
: AppleAuthentication.AppleAuthenticationButtonStyle.WHITE
|
||||
}
|
||||
cornerRadius={5}
|
||||
style={{ width: 200, height: 64 }}
|
||||
onPress={signInWithApple}
|
||||
/>
|
||||
</ThemedView>
|
||||
);
|
||||
}
|
||||
export default AppleSignInButton;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
verticallySpaced: {
|
||||
paddingTop: 4,
|
||||
paddingBottom: 4,
|
||||
alignItems: 'center',
|
||||
},
|
||||
mt20: {
|
||||
marginTop: 20,
|
||||
},
|
||||
});
|
@ -1,128 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { StyleSheet, Alert, Platform } from 'react-native';
|
||||
import * as AppleAuthentication from 'expo-apple-authentication';
|
||||
import { supabase } from '@/lib/supabase';
|
||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
||||
|
||||
type AppleSignInProps = {
|
||||
onSignInStart?: () => void;
|
||||
onSignInComplete?: () => void;
|
||||
onSignInError?: (error: any) => void;
|
||||
};
|
||||
|
||||
const AppleSignIn: React.FC<AppleSignInProps> = ({
|
||||
onSignInStart,
|
||||
onSignInComplete,
|
||||
onSignInError,
|
||||
}) => {
|
||||
const scheme = useColorScheme() ?? 'dark';
|
||||
const [isAppleAuthAvailable, setIsAppleAuthAvailable] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (Platform.OS === 'ios') {
|
||||
AppleAuthentication.isAvailableAsync().then(setIsAppleAuthAvailable);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleAppleSignIn = async () => {
|
||||
try {
|
||||
onSignInStart?.();
|
||||
|
||||
// Get credentials from Apple
|
||||
const credential = await AppleAuthentication.signInAsync({
|
||||
requestedScopes: [
|
||||
AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
|
||||
AppleAuthentication.AppleAuthenticationScope.EMAIL,
|
||||
],
|
||||
});
|
||||
|
||||
if (!credential.email) {
|
||||
throw new Error('Email is required for Apple Sign In');
|
||||
}
|
||||
|
||||
// Extract user information
|
||||
const { email, fullName, user: appleUserId } = credential;
|
||||
|
||||
// Create a name from the fullName object if available
|
||||
let name = null;
|
||||
if (fullName?.givenName || fullName?.familyName) {
|
||||
name = `${fullName?.givenName || ''} ${fullName?.familyName || ''}`.trim();
|
||||
}
|
||||
|
||||
// Create a deterministic password based on the Apple user ID
|
||||
// This way the user can sign in again with the same password
|
||||
const password = `Apple-${appleUserId.substring(0, 16)}`;
|
||||
|
||||
// First try to sign in (in case the user already exists)
|
||||
const { data: signInData, error: signInError } = await supabase.auth.signInWithPassword({
|
||||
email,
|
||||
password,
|
||||
});
|
||||
|
||||
if (!signInError && signInData?.user) {
|
||||
// User exists and signed in successfully
|
||||
onSignInComplete?.();
|
||||
return;
|
||||
}
|
||||
|
||||
// If sign-in failed, create a new user
|
||||
const { data: signUpData, error: signUpError } = await supabase.auth.signUp({
|
||||
email,
|
||||
password,
|
||||
options: {
|
||||
data: {
|
||||
full_name: name,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (signUpError) {
|
||||
throw signUpError;
|
||||
}
|
||||
|
||||
// User created successfully
|
||||
onSignInComplete?.();
|
||||
} catch (error) {
|
||||
console.error('Apple sign in error:', error);
|
||||
|
||||
if (error.code === 'ERR_REQUEST_CANCELED') {
|
||||
console.log('Sign in was canceled');
|
||||
} else {
|
||||
Alert.alert(
|
||||
'Sign in error',
|
||||
'An error occurred while signing in with Apple. Please try again.',
|
||||
);
|
||||
onSignInError?.(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Only render on iOS and if Apple Authentication is available
|
||||
if (Platform.OS !== 'ios' || !isAppleAuthAvailable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<AppleAuthentication.AppleAuthenticationButton
|
||||
buttonType={AppleAuthentication.AppleAuthenticationButtonType.SIGN_IN}
|
||||
buttonStyle={
|
||||
scheme === 'light'
|
||||
? AppleAuthentication.AppleAuthenticationButtonStyle.BLACK
|
||||
: AppleAuthentication.AppleAuthenticationButtonStyle.WHITE
|
||||
}
|
||||
cornerRadius={10}
|
||||
style={styles.button}
|
||||
onPress={handleAppleSignIn}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
button: {
|
||||
width: 320,
|
||||
height: 50,
|
||||
marginVertical: 10,
|
||||
},
|
||||
});
|
||||
|
||||
export default AppleSignIn;
|
@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
|
||||
import { Alert, StyleSheet, AppState, Image, Platform } from 'react-native';
|
||||
import { supabase } from '@/lib/supabase';
|
||||
import { ThemedView, ThemedText, ThemedTextButton, ThemedTextInput } from '@/components/theme';
|
||||
import AppleSignIn from '@/components/auth/AppleSignIn';
|
||||
import AppleSignInButton from '@/components/auth/AppleSignIn.native';
|
||||
|
||||
// Tells Supabase Auth to continuously refresh the session automatically if
|
||||
// the app is in the foreground. When this is added, you will continue to receive
|
||||
@ -18,7 +18,7 @@ if (Platform.OS !== 'web') {
|
||||
});
|
||||
}
|
||||
|
||||
const LoginPage = () => {
|
||||
const Auth = () => {
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
@ -102,20 +102,12 @@ const LoginPage = () => {
|
||||
fontSize={24}
|
||||
/>
|
||||
</ThemedView>
|
||||
|
||||
{/* Apple Sign In - Only shows on iOS */}
|
||||
<ThemedView style={[styles.verticallySpaced, styles.mt20]}>
|
||||
<AppleSignIn
|
||||
onSignInStart={() => setLoading(true)}
|
||||
onSignInComplete={() => setLoading(false)}
|
||||
onSignInError={() => setLoading(false)}
|
||||
/>
|
||||
</ThemedView>
|
||||
<AppleSignInButton />
|
||||
</ThemedView>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginPage;
|
||||
export default Auth;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
Reference in New Issue
Block a user