diff --git a/mobile/metro.config.js b/mobile/metro.config.js new file mode 100644 index 00000000..18401def --- /dev/null +++ b/mobile/metro.config.js @@ -0,0 +1,71 @@ +// Learn more https://docs.expo.io/guides/customizing-metro +const { getDefaultConfig } = require('expo/metro-config'); +const path = require('path'); + +/** @type {import('expo/metro-config').MetroConfig} */ +const config = getDefaultConfig(__dirname); + +// Monorepo support: Watch and resolve modules from parent directory +const projectRoot = __dirname; +const workspaceRoot = path.resolve(projectRoot, '..'); + +// Watch all files in the monorepo +config.watchFolders = [workspaceRoot]; + +// Let Metro resolve modules from the workspace root +config.resolver.nodeModulesPaths = [ + path.resolve(projectRoot, 'node_modules'), + path.resolve(workspaceRoot, 'node_modules'), +]; + +// Enable symlinks for shared library +config.resolver.resolveRequest = (context, moduleName, platform) => { + // Handle @pezkuwi/* imports (shared library) + if (moduleName.startsWith('@pezkuwi/')) { + const sharedPath = moduleName.replace('@pezkuwi/', ''); + const sharedDir = path.resolve(workspaceRoot, 'shared', sharedPath); + + // Try .ts extension first, then .tsx, then .js + const extensions = ['.ts', '.tsx', '.js', '.json']; + for (const ext of extensions) { + const filePath = sharedDir + ext; + if (require('fs').existsSync(filePath)) { + return { + filePath, + type: 'sourceFile', + }; + } + } + + // Try index files + for (const ext of extensions) { + const indexPath = path.join(sharedDir, `index${ext}`); + if (require('fs').existsSync(indexPath)) { + return { + filePath: indexPath, + type: 'sourceFile', + }; + } + } + } + + // Fall back to the default resolver + return context.resolveRequest(context, moduleName, platform); +}; + +// Ensure all file extensions are resolved +config.resolver.sourceExts = [ + 'expo.ts', + 'expo.tsx', + 'expo.js', + 'expo.jsx', + 'ts', + 'tsx', + 'js', + 'jsx', + 'json', + 'wasm', + 'svg', +]; + +module.exports = config; diff --git a/mobile/src/screens/ForumScreen.tsx b/mobile/src/screens/ForumScreen.tsx index 189750df..c5af1d08 100644 --- a/mobile/src/screens/ForumScreen.tsx +++ b/mobile/src/screens/ForumScreen.tsx @@ -12,6 +12,7 @@ import { import { useTranslation } from 'react-i18next'; import { Card, Badge } from '../components'; import { KurdistanColors, AppColors } from '../theme/colors'; +import { supabase } from '../lib/supabase'; interface ForumThread { id: string; @@ -123,18 +124,54 @@ const ForumScreen: React.FC = () => { const fetchThreads = async (categoryId?: string) => { setLoading(true); try { - // TODO: Fetch from Supabase - // const { data } = await supabase - // .from('forum_threads') - // .select('*') - // .eq('category_id', categoryId) - // .order('is_pinned', { ascending: false }) - // .order('last_activity', { ascending: false }); + // Fetch from Supabase + let query = supabase + .from('forum_threads') + .select(` + *, + forum_categories(name) + `) + .order('is_pinned', { ascending: false }) + .order('last_activity', { ascending: false }); - await new Promise((resolve) => setTimeout(resolve, 500)); - setThreads(MOCK_THREADS); + // Filter by category if provided + if (categoryId) { + query = query.eq('category_id', categoryId); + } + + const { data, error } = await query; + + if (error) { + if (__DEV__) console.error('Supabase fetch error:', error); + // Fallback to mock data on error + setThreads(MOCK_THREADS); + return; + } + + if (data && data.length > 0) { + // Transform Supabase data to match ForumThread interface + const transformedThreads: ForumThread[] = data.map((thread: any) => ({ + id: thread.id, + title: thread.title, + content: thread.content, + author: thread.author_id, + category: thread.forum_categories?.name || 'Unknown', + replies_count: thread.replies_count || 0, + views_count: thread.views_count || 0, + created_at: thread.created_at, + last_activity: thread.last_activity || thread.created_at, + is_pinned: thread.is_pinned || false, + is_locked: thread.is_locked || false, + })); + setThreads(transformedThreads); + } else { + // No data, use mock data + setThreads(MOCK_THREADS); + } } catch (error) { if (__DEV__) console.error('Failed to fetch threads:', error); + // Fallback to mock data on error + setThreads(MOCK_THREADS); } finally { setLoading(false); setRefreshing(false); diff --git a/mobile/src/screens/P2PScreen.tsx b/mobile/src/screens/P2PScreen.tsx index f48e5b47..71d00f15 100644 --- a/mobile/src/screens/P2PScreen.tsx +++ b/mobile/src/screens/P2PScreen.tsx @@ -9,6 +9,9 @@ import { FlatList, ActivityIndicator, RefreshControl, + Modal, + TextInput, + Alert, } from 'react-native'; import { useTranslation } from 'react-i18next'; import { Card, Button, Badge } from '../components'; @@ -39,6 +42,9 @@ const P2PScreen: React.FC = () => { const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); const [showCreateOffer, setShowCreateOffer] = useState(false); + const [showTradeModal, setShowTradeModal] = useState(false); + const [selectedOffer, setSelectedOffer] = useState(null); + const [tradeAmount, setTradeAmount] = useState(''); useEffect(() => { fetchOffers(); @@ -190,8 +196,8 @@ const P2PScreen: React.FC = () => { + + )} + + + + + + {/* Create Offer Modal */} + setShowCreateOffer(false)} + > + + + + Create Offer + setShowCreateOffer(false)}> + + + + + + + 🚧 + Coming Soon + + Create P2P offer functionality will be available in the next update. + The blockchain integration is ready and waiting for final testing! + + + + + + + ); }; @@ -483,6 +642,136 @@ const styles = StyleSheet.create({ textAlign: 'center', marginBottom: 24, }, + modalOverlay: { + flex: 1, + backgroundColor: 'rgba(0, 0, 0, 0.5)', + justifyContent: 'flex-end', + }, + modalContent: { + backgroundColor: '#FFFFFF', + borderTopLeftRadius: 20, + borderTopRightRadius: 20, + paddingTop: 20, + paddingHorizontal: 20, + paddingBottom: 40, + maxHeight: '90%', + }, + modalHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: 20, + paddingBottom: 16, + borderBottomWidth: 1, + borderBottomColor: '#E0E0E0', + }, + modalTitle: { + fontSize: 20, + fontWeight: '700', + color: '#000', + }, + modalClose: { + fontSize: 24, + color: '#666', + fontWeight: '600', + }, + modalSection: { + marginBottom: 20, + }, + modalSectionTitle: { + fontSize: 12, + color: '#666', + marginBottom: 8, + textTransform: 'uppercase', + }, + modalAddress: { + fontSize: 16, + fontWeight: '600', + color: '#000', + }, + priceSection: { + backgroundColor: '#F5F5F5', + padding: 16, + borderRadius: 12, + }, + priceRow: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: 8, + }, + priceLabel: { + fontSize: 14, + color: '#666', + }, + priceValue: { + fontSize: 16, + fontWeight: '700', + color: KurdistanColors.kesk, + }, + inputLabel: { + fontSize: 14, + fontWeight: '600', + color: '#000', + marginBottom: 8, + }, + modalInput: { + backgroundColor: '#F5F5F5', + borderRadius: 12, + padding: 16, + fontSize: 16, + borderWidth: 1, + borderColor: '#E0E0E0', + }, + inputHint: { + fontSize: 12, + color: '#666', + marginTop: 4, + }, + calculationSection: { + backgroundColor: 'rgba(0, 169, 79, 0.1)', + padding: 16, + borderRadius: 12, + borderWidth: 1, + borderColor: 'rgba(0, 169, 79, 0.3)', + }, + calculationLabel: { + fontSize: 12, + color: '#666', + marginBottom: 4, + }, + calculationValue: { + fontSize: 24, + fontWeight: '700', + color: KurdistanColors.kesk, + }, + tradeModalButton: { + marginTop: 20, + }, + comingSoonContainer: { + alignItems: 'center', + paddingVertical: 40, + }, + comingSoonIcon: { + fontSize: 64, + marginBottom: 16, + }, + comingSoonTitle: { + fontSize: 20, + fontWeight: '700', + color: '#000', + marginBottom: 12, + }, + comingSoonText: { + fontSize: 14, + color: '#666', + textAlign: 'center', + marginBottom: 24, + lineHeight: 20, + }, + comingSoonButton: { + minWidth: 120, + }, }); export default P2PScreen; diff --git a/mobile/src/screens/ReferralScreen.tsx b/mobile/src/screens/ReferralScreen.tsx index 3fce2087..d748d11d 100644 --- a/mobile/src/screens/ReferralScreen.tsx +++ b/mobile/src/screens/ReferralScreen.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { View, Text, @@ -13,6 +13,7 @@ import { } from 'react-native'; import { LinearGradient } from 'expo-linear-gradient'; import { useTranslation } from 'react-i18next'; +import { usePolkadot } from '../contexts/PolkadotContext'; import AppColors, { KurdistanColors } from '../theme/colors'; interface ReferralStats { @@ -32,12 +33,21 @@ interface Referral { const ReferralScreen: React.FC = () => { const { t } = useTranslation(); + const { selectedAccount, api, connectWallet } = usePolkadot(); const [isConnected, setIsConnected] = useState(false); - // Mock referral code - will be generated from blockchain - const referralCode = 'PZK-XYZABC123'; + // Check connection status + useEffect(() => { + setIsConnected(!!selectedAccount); + }, [selectedAccount]); + + // Generate referral code from wallet address + const referralCode = selectedAccount + ? `PZK-${selectedAccount.address.slice(0, 8).toUpperCase()}` + : 'PZK-CONNECT-WALLET'; // Mock stats - will be fetched from pallet_referral + // TODO: Fetch real stats from blockchain const stats: ReferralStats = { totalReferrals: 0, activeReferrals: 0, @@ -46,12 +56,20 @@ const ReferralScreen: React.FC = () => { }; // Mock referrals - will be fetched from blockchain + // TODO: Query pallet-trust or referral pallet for actual referrals const referrals: Referral[] = []; - const handleConnectWallet = () => { - // TODO: Implement Polkadot.js wallet connection - setIsConnected(true); - Alert.alert('Connected', 'Your wallet has been connected to the referral system!'); + const handleConnectWallet = async () => { + try { + await connectWallet(); + if (selectedAccount) { + setIsConnected(true); + Alert.alert('Connected', 'Your wallet has been connected to the referral system!'); + } + } catch (error) { + if (__DEV__) console.error('Wallet connection error:', error); + Alert.alert('Error', 'Failed to connect wallet. Please try again.'); + } }; const handleCopyCode = () => {