update google app id, dan fb app id
This commit is contained in:
@@ -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": {
|
||||
|
||||
Generated
+94
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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) {
|
||||
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) {
|
||||
if (!error.message?.includes('cancel')) {
|
||||
showAlert('Facebook sign-in failed', error.message);
|
||||
}
|
||||
} finally {
|
||||
setFacebookLoading(false);
|
||||
}
|
||||
|
||||
+106
-17
@@ -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;
|
||||
}
|
||||
throw new Error('Google login is not available in offline mode.');
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
export async function signInWithFacebook() {
|
||||
if (!USE_OFFLINE) {
|
||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'facebook',
|
||||
options: { redirectTo: 'nova40://auth/callback' },
|
||||
});
|
||||
if (error) throw error;
|
||||
return data;
|
||||
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.');
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 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() {
|
||||
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 (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) {
|
||||
|
||||
@@ -48,7 +48,11 @@ const useAuthStore = create((set) => ({
|
||||
set({ loading: true, error: null });
|
||||
try {
|
||||
const data = await authService.signInWithGoogle();
|
||||
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();
|
||||
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 });
|
||||
|
||||
Reference in New Issue
Block a user