mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-04-28 14:17:56 +00:00
feat: add Telegram mini app connect for P2P access
This commit is contained in:
@@ -54,6 +54,7 @@ const PezkuwiWebView: React.FC<PezkuwiWebViewProps> = ({
|
||||
const { user } = useAuth();
|
||||
const [sessionToken, setSessionToken] = useState<string | null>(null);
|
||||
const [refreshToken, setRefreshToken] = useState<string | null>(null);
|
||||
const [isSessionReady, setIsSessionReady] = useState(false);
|
||||
|
||||
// Get Supabase session token for WebView authentication
|
||||
React.useEffect(() => {
|
||||
@@ -63,9 +64,12 @@ const PezkuwiWebView: React.FC<PezkuwiWebViewProps> = ({
|
||||
if (session?.access_token) {
|
||||
setSessionToken(session.access_token);
|
||||
setRefreshToken(session.refresh_token || null);
|
||||
if (__DEV__) console.log('[WebView] Session token retrieved for SSO');
|
||||
}
|
||||
} catch (error) {
|
||||
if (__DEV__) console.warn('[WebView] Failed to get session:', error);
|
||||
} finally {
|
||||
setIsSessionReady(true);
|
||||
}
|
||||
};
|
||||
getSession();
|
||||
@@ -89,6 +93,31 @@ const PezkuwiWebView: React.FC<PezkuwiWebViewProps> = ({
|
||||
${user ? `window.PEZKUWI_USER_ID = '${user.id}';` : ''}
|
||||
${user?.email ? `window.PEZKUWI_USER_EMAIL = '${user.email}';` : ''}
|
||||
|
||||
// Pre-populate localStorage with session so Supabase client finds it on init
|
||||
${sessionToken && user ? `
|
||||
try {
|
||||
var supabaseUrl = 'https://sihawipngjtgvfzukfew.supabase.co';
|
||||
var storageKey = 'sb-' + supabaseUrl.replace('https://', '').split('.')[0] + '-auth-token';
|
||||
var sessionData = {
|
||||
access_token: '${sessionToken}',
|
||||
refresh_token: '${refreshToken || ''}',
|
||||
token_type: 'bearer',
|
||||
expires_in: 3600,
|
||||
expires_at: Math.floor(Date.now() / 1000) + 3600,
|
||||
user: {
|
||||
id: '${user.id}',
|
||||
email: '${user.email || ''}',
|
||||
aud: 'authenticated',
|
||||
role: 'authenticated'
|
||||
}
|
||||
};
|
||||
localStorage.setItem(storageKey, JSON.stringify(sessionData));
|
||||
console.log('[Mobile] Pre-populated localStorage with session');
|
||||
} catch(e) {
|
||||
console.warn('[Mobile] Failed to set localStorage:', e);
|
||||
}
|
||||
` : ''}
|
||||
|
||||
// Auto-authenticate with Supabase if session token exists
|
||||
if (window.PEZKUWI_SESSION_TOKEN) {
|
||||
(function autoAuth(attempts = 0) {
|
||||
@@ -96,14 +125,25 @@ const PezkuwiWebView: React.FC<PezkuwiWebViewProps> = ({
|
||||
console.warn('[Mobile] Auto-auth timed out: window.supabase not found');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (window.supabase && window.supabase.auth) {
|
||||
window.supabase.auth.setSession({
|
||||
access_token: window.PEZKUWI_SESSION_TOKEN,
|
||||
refresh_token: window.PEZKUWI_REFRESH_TOKEN || ''
|
||||
}).then(function(res) {
|
||||
if (res.error) console.warn('[Mobile] Auto-auth error:', res.error);
|
||||
else console.log('[Mobile] Auto-authenticated successfully');
|
||||
if (res.error) {
|
||||
console.warn('[Mobile] Auto-auth error:', res.error);
|
||||
} else {
|
||||
console.log('[Mobile] Auto-authenticated successfully');
|
||||
// Dispatch event to notify app of successful auth
|
||||
window.dispatchEvent(new CustomEvent('pezkuwi-session-restored', {
|
||||
detail: { userId: window.PEZKUWI_USER_ID }
|
||||
}));
|
||||
// Force auth state refresh if the app has an auth store
|
||||
if (window.__refreshAuthState) {
|
||||
window.__refreshAuthState();
|
||||
}
|
||||
}
|
||||
}).catch(function(err) {
|
||||
console.warn('[Mobile] Auto-auth promise failed:', err);
|
||||
});
|
||||
@@ -367,6 +407,25 @@ const PezkuwiWebView: React.FC<PezkuwiWebViewProps> = ({
|
||||
// Build the full URL
|
||||
const fullUrl = `${WEB_BASE_URL}${path}`;
|
||||
|
||||
// Wait for session to be ready before loading WebView (ensures SSO works)
|
||||
if (!isSessionReady) {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{title && (
|
||||
<View style={styles.header}>
|
||||
<View style={{ width: 40 }} />
|
||||
<Text style={styles.headerTitle}>{title}</Text>
|
||||
<View style={{ width: 40 }} />
|
||||
</View>
|
||||
)}
|
||||
<View style={styles.loadingOverlay}>
|
||||
<ActivityIndicator size="large" color={KurdistanColors.kesk} />
|
||||
<Text style={styles.loadingText}>Preparing session...</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
// Error view
|
||||
if (error) {
|
||||
return (
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
Platform,
|
||||
KeyboardAvoidingView,
|
||||
AlertButton,
|
||||
Image,
|
||||
} from 'react-native';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
@@ -74,6 +75,12 @@ const getEmojiFromAvatarId = (avatarId: string): string => {
|
||||
return avatar ? avatar.emoji : '👤';
|
||||
};
|
||||
|
||||
// Check if avatar is an uploaded image URL vs emoji avatar ID
|
||||
const isUploadedImageUrl = (avatarUrl: string | null): boolean => {
|
||||
if (!avatarUrl) return false;
|
||||
return avatarUrl.startsWith('http://') || avatarUrl.startsWith('https://');
|
||||
};
|
||||
|
||||
const EditProfileScreen: React.FC = () => {
|
||||
const navigation = useNavigation();
|
||||
const { user } = useAuth();
|
||||
@@ -242,7 +249,11 @@ const EditProfileScreen: React.FC = () => {
|
||||
>
|
||||
<View style={[styles.avatarCircle, { backgroundColor: colors.surface }]}>
|
||||
{avatarUrl ? (
|
||||
<Text style={styles.avatarEmoji}>{getEmojiFromAvatarId(avatarUrl)}</Text>
|
||||
isUploadedImageUrl(avatarUrl) ? (
|
||||
<Image source={{ uri: avatarUrl }} style={styles.avatarImage} />
|
||||
) : (
|
||||
<Text style={styles.avatarEmoji}>{getEmojiFromAvatarId(avatarUrl)}</Text>
|
||||
)
|
||||
) : (
|
||||
<Text style={[styles.avatarInitial, { color: colors.textSecondary }]}>
|
||||
{fullName?.charAt(0)?.toUpperCase() || user?.email?.charAt(0)?.toUpperCase() || '?'}
|
||||
@@ -382,6 +393,11 @@ const styles = StyleSheet.create({
|
||||
avatarEmoji: {
|
||||
fontSize: 70,
|
||||
},
|
||||
avatarImage: {
|
||||
width: 120,
|
||||
height: 120,
|
||||
borderRadius: 60,
|
||||
},
|
||||
avatarInitial: {
|
||||
fontSize: 48,
|
||||
fontWeight: 'bold',
|
||||
|
||||
@@ -392,16 +392,9 @@ const SettingsScreen: React.FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SafeAreaView style={[styles.container, { backgroundColor: colors.background }]} testID="settings-screen">
|
||||
<View style={[styles.container, { backgroundColor: colors.background }]} testID="settings-screen">
|
||||
<StatusBar barStyle={isDarkMode ? "light-content" : "dark-content"} />
|
||||
|
||||
{/* Header */}
|
||||
<View style={[styles.header, { backgroundColor: colors.surface, borderBottomColor: colors.border }]}>
|
||||
<View style={{ width: 40 }} />
|
||||
<Text style={[styles.headerTitle, { color: colors.text }]}>Settings</Text>
|
||||
<View style={{ width: 40 }} />
|
||||
</View>
|
||||
|
||||
<ScrollView showsVerticalScrollIndicator={false}>
|
||||
|
||||
{/* ACCOUNT SECTION */}
|
||||
@@ -526,42 +519,76 @@ const SettingsScreen: React.FC = () => {
|
||||
|
||||
{/* DEVELOPER OPTIONS (only in DEV) */}
|
||||
{__DEV__ && (
|
||||
<View style={[styles.section, { backgroundColor: colors.surface, marginTop: 20 }]}>
|
||||
<Text style={[styles.sectionHeader, { color: colors.textSecondary }]}>DEVELOPER</Text>
|
||||
<SettingItem
|
||||
icon="🗑️"
|
||||
title="Reset Wallet"
|
||||
subtitle="Clear all wallet data"
|
||||
textColor="#FF9500"
|
||||
showArrow={false}
|
||||
onPress={() => {
|
||||
showAlert(
|
||||
'Reset Wallet',
|
||||
'This will delete all wallet data including saved accounts and keys. Are you sure?',
|
||||
[
|
||||
{ text: 'Cancel', style: 'cancel' },
|
||||
{
|
||||
text: 'Reset',
|
||||
style: 'destructive',
|
||||
onPress: async () => {
|
||||
try {
|
||||
await AsyncStorage.multiRemove([
|
||||
'@pezkuwi_wallets',
|
||||
'@pezkuwi_selected_account',
|
||||
'@pezkuwi_selected_network'
|
||||
]);
|
||||
showAlert('Success', 'Wallet data cleared. Restart the app to see changes.');
|
||||
} catch {
|
||||
showAlert('Error', 'Failed to clear wallet data');
|
||||
<>
|
||||
<SectionHeader title="DEVELOPER" />
|
||||
<View style={[styles.section, { backgroundColor: colors.surface }]}>
|
||||
<SettingItem
|
||||
icon="🔄"
|
||||
title="Reset Onboarding"
|
||||
subtitle="Show Welcome & Verify screens again"
|
||||
textColor="#FF9500"
|
||||
showArrow={false}
|
||||
onPress={() => {
|
||||
showAlert(
|
||||
'Reset Onboarding',
|
||||
'This will reset onboarding state. Restart the app to see the Welcome screen.',
|
||||
[
|
||||
{ text: 'Cancel', style: 'cancel' },
|
||||
{
|
||||
text: 'Reset',
|
||||
style: 'destructive',
|
||||
onPress: async () => {
|
||||
try {
|
||||
await AsyncStorage.multiRemove([
|
||||
'@pezkuwi/privacy_consent_accepted',
|
||||
'@pezkuwi_human_verified'
|
||||
]);
|
||||
showAlert('Success', 'Onboarding reset. Restart the app to see changes.');
|
||||
} catch {
|
||||
showAlert('Error', 'Failed to reset onboarding');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
);
|
||||
}}
|
||||
testID="reset-wallet-button"
|
||||
/>
|
||||
</View>
|
||||
]
|
||||
);
|
||||
}}
|
||||
testID="reset-onboarding-button"
|
||||
/>
|
||||
<SettingItem
|
||||
icon="🗑️"
|
||||
title="Reset Wallet"
|
||||
subtitle="Clear all wallet data"
|
||||
textColor="#FF9500"
|
||||
showArrow={false}
|
||||
onPress={() => {
|
||||
showAlert(
|
||||
'Reset Wallet',
|
||||
'This will delete all wallet data including saved accounts and keys. Are you sure?',
|
||||
[
|
||||
{ text: 'Cancel', style: 'cancel' },
|
||||
{
|
||||
text: 'Reset',
|
||||
style: 'destructive',
|
||||
onPress: async () => {
|
||||
try {
|
||||
await AsyncStorage.multiRemove([
|
||||
'@pezkuwi_wallets',
|
||||
'@pezkuwi_selected_account',
|
||||
'@pezkuwi_selected_network'
|
||||
]);
|
||||
showAlert('Success', 'Wallet data cleared. Restart the app to see changes.');
|
||||
} catch {
|
||||
showAlert('Error', 'Failed to clear wallet data');
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
);
|
||||
}}
|
||||
testID="reset-wallet-button"
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* LOGOUT */}
|
||||
@@ -925,7 +952,7 @@ const SettingsScreen: React.FC = () => {
|
||||
</SafeAreaView>
|
||||
</Modal>
|
||||
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user