From db7231e60f2399337f19a59908250b70cb8171d6 Mon Sep 17 00:00:00 2001 From: "dios.one" Date: Thu, 7 May 2026 15:11:02 +0700 Subject: [PATCH] update google app id, dan fb app id --- app.json | 4 +- package-lock.json | 94 +++++++++++++++++++++++++++++ package.json | 3 + src/screens/LoginScreen.js | 16 +++-- src/services/authService.js | 117 +++++++++++++++++++++++++++++++----- src/store/useAuthStore.js | 12 +++- 6 files changed, 223 insertions(+), 23 deletions(-) diff --git a/app.json b/app.json index e56a6a1..601d973 100644 --- a/app.json +++ b/app.json @@ -6,6 +6,7 @@ "orientation": "portrait", "icon": "./assets/icon.png", "userInterfaceStyle": "dark", + "scheme": "nova40", "splash": { "image": "./assets/splash-icon.png", "resizeMode": "contain", @@ -33,7 +34,8 @@ }, "plugins": [ "@react-native-firebase/app", - "@react-native-firebase/crashlytics" + "@react-native-firebase/crashlytics", + "expo-web-browser" ], "extra": { "eas": { diff --git a/package-lock.json b/package-lock.json index 51e611c..079f1c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,8 @@ "@supabase/supabase-js": "^2.103.0", "babel-preset-expo": "~54.0.10", "expo": "~54.0.0", + "expo-auth-session": "~7.0.11", + "expo-crypto": "~15.0.9", "expo-dev-client": "~6.0.21", "expo-image-picker": "~17.0.11", "expo-linear-gradient": "~15.0.8", @@ -27,6 +29,7 @@ "expo-print": "~15.0.8", "expo-sharing": "~14.0.8", "expo-status-bar": "~3.0.9", + "expo-web-browser": "~15.0.11", "react": "19.1.0", "react-native": "0.81.5", "react-native-gesture-handler": "~2.28.0", @@ -5153,6 +5156,59 @@ } } }, + "node_modules/expo-auth-session": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/expo-auth-session/-/expo-auth-session-7.0.11.tgz", + "integrity": "sha512-AhWtt/m9rb1Po77X/VBFbeE6UTgbm2vXP2iCblUSRsHCw2qD6lO0ulKUB8Xyxy9FtoI9yrNQ1iwCNgIIgo8VYQ==", + "license": "MIT", + "dependencies": { + "expo-application": "~7.0.8", + "expo-constants": "~18.0.13", + "expo-crypto": "~15.0.9", + "expo-linking": "~8.0.12", + "expo-web-browser": "~15.0.11", + "invariant": "^2.2.4" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/expo-auth-session/node_modules/expo-application": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/expo-application/-/expo-application-7.0.8.tgz", + "integrity": "sha512-qFGyxk7VJbrNOQWBbE09XUuGuvkOgFS9QfToaK2FdagM2aQ+x3CvGV2DuVgl/l4ZxPgIf3b/MNh9xHpwSwn74Q==", + "license": "MIT", + "peerDependencies": { + "expo": "*" + } + }, + "node_modules/expo-auth-session/node_modules/expo-constants": { + "version": "18.0.13", + "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-18.0.13.tgz", + "integrity": "sha512-FnZn12E1dRYKDHlAdIyNFhBurKTS3F9CrfrBDJI5m3D7U17KBHMQ6JEfYlSj7LG7t+Ulr+IKaj58L1k5gBwTcQ==", + "license": "MIT", + "dependencies": { + "@expo/config": "~12.0.13", + "@expo/env": "~2.0.8" + }, + "peerDependencies": { + "expo": "*", + "react-native": "*" + } + }, + "node_modules/expo-crypto": { + "version": "15.0.9", + "resolved": "https://registry.npmjs.org/expo-crypto/-/expo-crypto-15.0.9.tgz", + "integrity": "sha512-SNWKa2fXx7v9gkp1h/7nqXY5XN7qgNDn3yRc2aO0gWGbeMbvob/haMxxsPFe9f51aqH5NjNCqHf2kvLhvAd8KQ==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.0" + }, + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-dev-client": { "version": "6.0.21", "resolved": "https://registry.npmjs.org/expo-dev-client/-/expo-dev-client-6.0.21.tgz", @@ -5242,6 +5298,34 @@ "react-native": "*" } }, + "node_modules/expo-linking": { + "version": "8.0.12", + "resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-8.0.12.tgz", + "integrity": "sha512-FpXeIpFgZuxihwT9lBo86YD3y6LphBuAhN680MMxm/Y7fmsc57vimn2d3vFu68VI0+Z9w457t494mu2wvlgWTQ==", + "license": "MIT", + "dependencies": { + "expo-constants": "~18.0.13", + "invariant": "^2.2.4" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/expo-linking/node_modules/expo-constants": { + "version": "18.0.13", + "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-18.0.13.tgz", + "integrity": "sha512-FnZn12E1dRYKDHlAdIyNFhBurKTS3F9CrfrBDJI5m3D7U17KBHMQ6JEfYlSj7LG7t+Ulr+IKaj58L1k5gBwTcQ==", + "license": "MIT", + "dependencies": { + "@expo/config": "~12.0.13", + "@expo/env": "~2.0.8" + }, + "peerDependencies": { + "expo": "*", + "react-native": "*" + } + }, "node_modules/expo-manifests": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/expo-manifests/-/expo-manifests-1.0.11.tgz", @@ -5344,6 +5428,16 @@ "expo": "*" } }, + "node_modules/expo-web-browser": { + "version": "15.0.11", + "resolved": "https://registry.npmjs.org/expo-web-browser/-/expo-web-browser-15.0.11.tgz", + "integrity": "sha512-r2LS4Ro6DgUPZkcaEfgt8mp9eJuoA93x11Jh7S6utFe0FEzvUNn2yFhxg8XVwESaaHGt2k5V8LuK36rsp0BeIw==", + "license": "MIT", + "peerDependencies": { + "expo": "*", + "react-native": "*" + } + }, "node_modules/expo/node_modules/@expo/cli": { "version": "54.0.24", "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-54.0.24.tgz", diff --git a/package.json b/package.json index a7116af..608b583 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,8 @@ "@supabase/supabase-js": "^2.103.0", "babel-preset-expo": "~54.0.10", "expo": "~54.0.0", + "expo-auth-session": "~7.0.11", + "expo-crypto": "~15.0.9", "expo-dev-client": "~6.0.21", "expo-image-picker": "~17.0.11", "expo-linear-gradient": "~15.0.8", @@ -28,6 +30,7 @@ "expo-print": "~15.0.8", "expo-sharing": "~14.0.8", "expo-status-bar": "~3.0.9", + "expo-web-browser": "~15.0.11", "react": "19.1.0", "react-native": "0.81.5", "react-native-gesture-handler": "~2.28.0", diff --git a/src/screens/LoginScreen.js b/src/screens/LoginScreen.js index ded42f9..d0494f1 100644 --- a/src/screens/LoginScreen.js +++ b/src/screens/LoginScreen.js @@ -97,10 +97,12 @@ export default function LoginScreen({ navigation }) { const handleGoogleLogin = async () => { setGoogleLoading(true); try { - const data = await loginWithGoogle(); - if (data?.url) await Linking.openURL(data.url); + await loginWithGoogle(); + goToApp(); } catch (error) { - showAlert('Google sign-in failed', error.message); + if (!error.message?.includes('cancel')) { + showAlert('Google sign-in failed', error.message); + } } finally { setGoogleLoading(false); } @@ -109,10 +111,12 @@ export default function LoginScreen({ navigation }) { const handleFacebookLogin = async () => { setFacebookLoading(true); try { - const data = await loginWithFacebook(); - if (data?.url) await Linking.openURL(data.url); + await loginWithFacebook(); + goToApp(); } catch (error) { - showAlert('Facebook sign-in failed', error.message); + if (!error.message?.includes('cancel')) { + showAlert('Facebook sign-in failed', error.message); + } } finally { setFacebookLoading(false); } diff --git a/src/services/authService.js b/src/services/authService.js index dc1bfd3..798c497 100644 --- a/src/services/authService.js +++ b/src/services/authService.js @@ -1,6 +1,10 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; +import * as AuthSession from 'expo-auth-session'; +import * as WebBrowser from 'expo-web-browser'; import { supabase } from './supabase'; +WebBrowser.maybeCompleteAuthSession(); + // ============================================ // OFFLINE-FIRST AUTH (dummy mode) // Uses AsyncStorage when Supabase is unreachable. @@ -203,28 +207,113 @@ export async function signInAsDemo() { }); } +// ============================================ +// Google OAuth +// To setup: +// 1. Go to https://console.cloud.google.com/apis/credentials +// 2. Create OAuth 2.0 Client ID (Web application) +// 3. Add redirect URI: https://auth.expo.io/@heyaciell/Nova40 +// 4. Paste Client ID below +// ============================================ +const GOOGLE_CLIENT_ID = '112566583381-29vsg1bg7ifnq5csg5o8fisdgtllb2sn.apps.googleusercontent.com'; + export async function signInWithGoogle() { - if (!USE_OFFLINE) { - const { data, error } = await supabase.auth.signInWithOAuth({ - provider: 'google', - options: { redirectTo: 'nova40://auth/callback' }, + try { + const redirectUri = AuthSession.makeRedirectUri({ scheme: 'nova40' }); + + const discovery = { + authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth', + tokenEndpoint: 'https://oauth2.googleapis.com/token', + }; + + const request = new AuthSession.AuthRequest({ + clientId: GOOGLE_CLIENT_ID, + scopes: ['openid', 'profile', 'email'], + redirectUri, + responseType: AuthSession.ResponseType.Token, }); - if (error) throw error; - return data; + + const result = await request.promptAsync(discovery); + + if (result.type === 'success' && result.authentication?.accessToken) { + // Fetch user info from Google + const userInfo = await fetch('https://www.googleapis.com/userinfo/v2/me', { + headers: { Authorization: `Bearer ${result.authentication.accessToken}` }, + }).then((r) => r.json()); + + // Create offline session with Google user data + const email = userInfo.email || 'google-user@nova40.app'; + const user = makeUser(email); + + // Store user if not exists + const users = await getStoredUsers(); + const key = email.toLowerCase(); + if (!users[key]) { + users[key] = { id: user.id, password: '__google_oauth__', provider: 'google', name: userInfo.name, picture: userInfo.picture }; + await saveUsers(users); + } else { + user.id = users[key].id; + } + + const session = makeSession(user); + await saveSession(session); + return { session, user }; + } + + if (result.type === 'cancel') throw new Error('Google sign-in was cancelled.'); + throw new Error('Google sign-in failed.'); + } catch (e) { + if (e.message?.includes('cancel')) throw e; + throw new Error(e.message || 'Google sign-in failed. Please try again.'); } - throw new Error('Google login is not available in offline mode.'); } +// ============================================ +// Facebook OAuth +// To setup: +// 1. Go to https://developers.facebook.com +// 2. Create app → Facebook Login +// 3. Add redirect URI: https://auth.expo.io/@heyaciell/Nova40 +// 4. Paste App ID below +// ============================================ +const FACEBOOK_APP_ID = '1696508695097482'; + export async function signInWithFacebook() { - if (!USE_OFFLINE) { - const { data, error } = await supabase.auth.signInWithOAuth({ - provider: 'facebook', - options: { redirectTo: 'nova40://auth/callback' }, + try { + const redirectUri = AuthSession.makeRedirectUri({ scheme: 'nova40' }); + + const result = await AuthSession.startAsync({ + authUrl: `https://www.facebook.com/v18.0/dialog/oauth?client_id=${FACEBOOK_APP_ID}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=token&scope=email,public_profile`, }); - if (error) throw error; - return data; + + if (result.type === 'success' && result.params?.access_token) { + // Fetch user info from Facebook + const userInfo = await fetch(`https://graph.facebook.com/me?fields=id,name,email,picture&access_token=${result.params.access_token}`) + .then((r) => r.json()); + + const email = userInfo.email || `fb-${userInfo.id}@nova40.app`; + const user = makeUser(email); + + const users = await getStoredUsers(); + const key = email.toLowerCase(); + if (!users[key]) { + users[key] = { id: user.id, password: '__facebook_oauth__', provider: 'facebook', name: userInfo.name, picture: userInfo.picture?.data?.url }; + await saveUsers(users); + } else { + user.id = users[key].id; + } + + const session = makeSession(user); + await saveSession(session); + return { session, user }; + } + + if (result.type === 'cancel') throw new Error('Facebook sign-in was cancelled.'); + throw new Error('Facebook sign-in failed.'); + } catch (e) { + if (e.message?.includes('cancel')) throw e; + throw new Error(e.message || 'Facebook sign-in failed. Please try again.'); } - throw new Error('Facebook login is not available in offline mode.'); } export function onAuthStateChange(callback) { diff --git a/src/store/useAuthStore.js b/src/store/useAuthStore.js index ef478ce..3e8800f 100644 --- a/src/store/useAuthStore.js +++ b/src/store/useAuthStore.js @@ -48,7 +48,11 @@ const useAuthStore = create((set) => ({ set({ loading: true, error: null }); try { const data = await authService.signInWithGoogle(); - set({ loading: false }); + if (data?.session) { + set({ session: data.session, user: data.session?.user ?? data.user ?? null, loading: false }); + } else { + set({ loading: false }); + } return data; } catch (e) { set({ loading: false, error: e.message }); @@ -60,7 +64,11 @@ const useAuthStore = create((set) => ({ set({ loading: true, error: null }); try { const data = await authService.signInWithFacebook(); - set({ loading: false }); + if (data?.session) { + set({ session: data.session, user: data.session?.user ?? data.user ?? null, loading: false }); + } else { + set({ loading: false }); + } return data; } catch (e) { set({ loading: false, error: e.message });