fix: switch to supabase

This commit is contained in:
Sosokker 2025-05-10 00:39:48 +07:00
parent 3c1aad96b1
commit 0a554e69d1
10 changed files with 413 additions and 984 deletions

View File

@ -1,27 +0,0 @@
import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
import { getFirestore } from "firebase/firestore";
import Constants from 'expo-constants';
const {
FIREBASE_API_KEY,
FIREBASE_AUTH_DOMAIN,
FIREBASE_PROJECT_ID,
FIREBASE_STORAGE_BUCKET,
FIREBASE_MESSAGING_SENDER_ID,
FIREBASE_APP_ID,
} = Constants.expoConfig?.extra || {};
const firebaseConfig = {
apiKey: FIREBASE_API_KEY,
authDomain: FIREBASE_AUTH_DOMAIN,
projectId: FIREBASE_PROJECT_ID,
storageBucket: FIREBASE_STORAGE_BUCKET,
messagingSenderId: FIREBASE_MESSAGING_SENDER_ID,
appId: FIREBASE_APP_ID,
};
export const FIREBASE_APP = initializeApp(firebaseConfig);
export const FIREBASE_AUTH = getAuth(FIREBASE_APP);
export const FIREBASE_DB = getFirestore(FIREBASE_APP);

View File

@ -1,4 +1,5 @@
import { useAuth } from "@/context/auth-context"; import { useAuth } from "@/context/auth-context";
import "@/global.css";
import { Redirect } from "expo-router"; import { Redirect } from "expo-router";
import React from "react"; import React from "react";
import { ActivityIndicator, View } from "react-native"; import { ActivityIndicator, View } from "react-native";

View File

@ -36,7 +36,9 @@ export default function SignupScreen() {
try { try {
setIsLoading(true); setIsLoading(true);
await signup(name, email, password); // Only pass email and password to signup, as per new auth-context
await signup(email, password);
// Optionally, save name to profile after signup here in the future
} catch (error) { } catch (error) {
Alert.alert("Error", "Failed to sign up. Please try again."); Alert.alert("Error", "Failed to sign up. Please try again.");
} finally { } finally {

View File

@ -1,9 +1,9 @@
module.exports = function (api) { module.exports = function (api) {
api.cache(true); api.cache(true);
return { return {
presets: [ presets: [
["babel-preset-expo", { jsxImportSource: "nativewind" }], ["babel-preset-expo", { jsxImportSource: "nativewind" }],
"nativewind/babel", "nativewind/babel",
], ],
}; };
}; };

View File

@ -1,14 +1,13 @@
import { router } from 'expo-router'; import { router } from "expo-router";
import * as SecureStore from 'expo-secure-store'; import * as SecureStore from "expo-secure-store";
import { createUserWithEmailAndPassword, signInWithEmailAndPassword, signOut, User } from 'firebase/auth'; import React, { createContext, useContext, useEffect, useState } from "react";
import React, { createContext, useContext, useEffect, useState } from 'react'; import { supabase } from "../services/supabase";
import { FIREBASE_AUTH } from '../FirebaseConfig';
type AuthContextType = { type AuthContextType = {
isAuthenticated: boolean; isAuthenticated: boolean;
isLoading: boolean; isLoading: boolean;
login: (email: string, password: string) => Promise<void>; login: (email: string, password: string) => Promise<void>;
signup: (name: string, email: string, password: string) => Promise<void>; signup: (email: string, password: string) => Promise<void>;
logout: () => Promise<void>; logout: () => Promise<void>;
}; };
@ -17,89 +16,132 @@ const AuthContext = createContext<AuthContextType | null>(null);
export function AuthProvider({ children }: { children: React.ReactNode }) { export function AuthProvider({ children }: { children: React.ReactNode }) {
const [authState, setAuthState] = useState({ const [authState, setAuthState] = useState({
isAuthenticated: false, isAuthenticated: false,
isLoading: true isLoading: true,
}); });
// Use a single useEffect to check authentication status only once on mount // Use a single useEffect to check authentication status only once on mount
useEffect(() => { useEffect(() => {
// Check if user is logged in on app start // Check if user is logged in on app start
async function loadToken() { async function loadSession() {
try { try {
const token = await SecureStore.getItemAsync('userToken'); const sessionStr = await SecureStore.getItemAsync("sbSession");
// Update state only once with both values let session = null;
if (sessionStr) {
session = JSON.parse(sessionStr);
}
setAuthState({ setAuthState({
isAuthenticated: !!token, isAuthenticated: !!session,
isLoading: false isLoading: false,
}); });
} catch (error) { } catch (error) {
console.log('Error loading token:', error); console.log("Error loading session:", error);
setAuthState({ setAuthState({
isAuthenticated: false, isAuthenticated: false,
isLoading: false isLoading: false,
}); });
} }
} }
loadToken(); loadSession();
}, []); // Empty dependency array ensures this runs only once
// Listen to Supabase auth state changes
const { data: listener } = supabase.auth.onAuthStateChange(
async (event, session) => {
if (event === "SIGNED_IN" && session) {
await SecureStore.setItemAsync("sbSession", JSON.stringify(session));
setAuthState({ isAuthenticated: true, isLoading: false });
} else if (event === "SIGNED_OUT") {
await SecureStore.deleteItemAsync("sbSession");
setAuthState({ isAuthenticated: false, isLoading: false });
}
}
);
return () => {
listener?.subscription.unsubscribe();
};
}, []);
const login = async (email: string, password: string) => { const login = async (email: string, password: string) => {
try { try {
const userCredential = await signInWithEmailAndPassword(FIREBASE_AUTH, email, password); setAuthState((prev) => ({ ...prev, isLoading: true }));
const user: User = userCredential.user; const { error, data } = await supabase.auth.signInWithPassword({
const idToken = await user.getIdToken(); email,
await SecureStore.setItemAsync('userToken', idToken); password,
setAuthState({
...authState,
isAuthenticated: true
}); });
router.replace('../(tabs)/home'); if (error) {
throw error;
}
if (data.session) {
await SecureStore.setItemAsync(
"sbSession",
JSON.stringify(data.session)
);
setAuthState({ ...authState, isAuthenticated: true, isLoading: false });
router.replace("../(tabs)/home");
} else {
setAuthState({
...authState,
isAuthenticated: false,
isLoading: false,
});
}
} catch (error) { } catch (error) {
console.error('Login error:', error); console.error("Login error:", error);
setAuthState((prev) => ({ ...prev, isLoading: false }));
throw error; throw error;
} }
}; };
const signup = async (name: string, email: string, password: string) => { const signup = async (email: string, password: string) => {
try { try {
const userCredential = await createUserWithEmailAndPassword(FIREBASE_AUTH, email, password); setAuthState((prev) => ({ ...prev, isLoading: true }));
const user: User = userCredential.user; const { data, error } = await supabase.auth.signUp({ email, password });
const idToken = await user.getIdToken(); if (error) {
await SecureStore.setItemAsync('userToken', idToken); throw error;
setAuthState({ }
...authState, if (data.session) {
isAuthenticated: true await SecureStore.setItemAsync(
}); "sbSession",
router.replace('./(tabs)/home'); JSON.stringify(data.session)
);
setAuthState({ ...authState, isAuthenticated: true, isLoading: false });
router.replace("./(tabs)/home");
} else {
setAuthState({
...authState,
isAuthenticated: false,
isLoading: false,
});
// Optionally, prompt user to check email for verification
}
} catch (error) { } catch (error) {
console.error('Signup error:', error); console.error("Signup error:", error);
setAuthState((prev) => ({ ...prev, isLoading: false }));
throw error; throw error;
} }
}; };
const logout = async () => { const logout = async () => {
try { try {
await signOut(FIREBASE_AUTH); await supabase.auth.signOut();
await SecureStore.deleteItemAsync('userToken'); await SecureStore.deleteItemAsync("sbSession");
setAuthState({ setAuthState({ ...authState, isAuthenticated: false });
...authState, router.replace("/");
isAuthenticated: false
});
router.replace('/');
} catch (error) { } catch (error) {
console.error('Logout error:', error); console.error("Logout error:", error);
throw error; throw error;
} }
}; };
return ( return (
<AuthContext.Provider <AuthContext.Provider
value={{ value={{
isAuthenticated: authState.isAuthenticated, isAuthenticated: authState.isAuthenticated,
isLoading: authState.isLoading, isLoading: authState.isLoading,
login, login,
signup, signup,
logout logout,
}} }}
> >
{children} {children}
@ -110,7 +152,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
export function useAuth() { export function useAuth() {
const context = useContext(AuthContext); const context = useContext(AuthContext);
if (!context) { if (!context) {
throw new Error('useAuth must be used within an AuthProvider'); throw new Error("useAuth must be used within an AuthProvider");
} }
return context; return context;
} }

View File

@ -1,8 +1,6 @@
// Learn more https://docs.expo.io/guides/customizing-metro const { getDefaultConfig } = require("expo/metro-config");
const { getDefaultConfig } = require('expo/metro-config'); const { withNativeWind } = require('nativewind/metro');
/** @type {import('expo/metro-config').MetroConfig} */ const config = getDefaultConfig(__dirname)
const config = getDefaultConfig(__dirname);
config.resolver.assetExts.push('cjs'); module.exports = withNativeWind(config, { input: './global.css' })
module.exports = config;

1150
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -13,10 +13,12 @@
"dependencies": { "dependencies": {
"@expo/ngrok": "^4.1.3", "@expo/ngrok": "^4.1.3",
"@expo/vector-icons": "^14.1.0", "@expo/vector-icons": "^14.1.0",
"@react-native-async-storage/async-storage": "2.1.2",
"@react-navigation/bottom-tabs": "^7.3.10", "@react-navigation/bottom-tabs": "^7.3.10",
"@react-navigation/elements": "^2.3.8", "@react-navigation/elements": "^2.3.8",
"@react-navigation/native": "^7.1.6", "@react-navigation/native": "^7.1.6",
"expo": "~53.0.8", "@supabase/supabase-js": "2.49.5-next.1",
"expo": "^53.0.9",
"expo-blur": "~14.1.4", "expo-blur": "~14.1.4",
"expo-constants": "~17.1.6", "expo-constants": "~17.1.6",
"expo-font": "~13.3.1", "expo-font": "~13.3.1",
@ -31,14 +33,13 @@
"expo-symbols": "~0.4.4", "expo-symbols": "~0.4.4",
"expo-system-ui": "~5.0.7", "expo-system-ui": "~5.0.7",
"expo-web-browser": "~14.1.6", "expo-web-browser": "~14.1.6",
"firebase": "^11.7.1",
"nativewind": "^4.1.23", "nativewind": "^4.1.23",
"react": "19.0.0", "react": "19.0.0",
"react-dom": "19.0.0", "react-dom": "19.0.0",
"react-native": "0.79.2", "react-native": "0.79.2",
"react-native-gesture-handler": "~2.24.0", "react-native-gesture-handler": "~2.24.0",
"react-native-reanimated": "~3.17.4", "react-native-reanimated": "^3.16.2",
"react-native-safe-area-context": "5.4.0", "react-native-safe-area-context": "^5.4.0",
"react-native-screens": "~4.10.0", "react-native-screens": "~4.10.0",
"react-native-web": "~0.20.0", "react-native-web": "~0.20.0",
"react-native-webview": "13.13.5", "react-native-webview": "13.13.5",

14
services/supabase.ts Normal file
View File

@ -0,0 +1,14 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import { createClient } from '@supabase/supabase-js';
const supabaseUrl = process.env.EXPO_PUBLIC_SUPABASE_PROJECT_URL as string;
const supabaseAnonKey = process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY as string;
export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
auth: {
storage: AsyncStorage,
autoRefreshToken: true,
persistSession: true,
detectSessionInUrl: false,
},
})

View File

@ -4,7 +4,7 @@
"strict": true, "strict": true,
"paths": { "paths": {
"@/*": [ "@/*": [
"./*" "./*",
] ]
} }
}, },