Files
pwap/src/pages/ProfileSettings.tsx
T
pezkuwichain 9073fa54f3 Fix USDT swap insufficient balance bug
Root cause: Token symbol mismatch between TokenSwap component and WalletContext
- WalletContext stored balance with key 'USDT'
- TokenSwap used symbol 'wUSDT' for lookups
- This caused balances['wUSDT'] to return undefined
- Triggered false "insufficient balance" errors

Changes:
- Updated TokenSwap.tsx to use 'USDT' symbol consistently
- Fixed token symbol in AVAILABLE_TOKENS array
- Updated asset ID mapping in swap transaction logic
- Fixed swap history token display

Also includes:
- Added new DEX components (SwapInterface, PoolBrowser, etc.)
- Added TypeScript type definitions for DEX
- Added DEX utility functions
- Removed obsolete test scripts
- Updated WalletContext with USDT balance support

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-12 02:23:15 +03:00

422 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { useAuth } from '@/contexts/AuthContext';
import { supabase } from '@/lib/supabase';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
import { Switch } from '@/components/ui/switch';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { useToast } from '@/hooks/use-toast';
import { Loader2, User, Mail, Shield, Bell, Palette, Globe, ArrowLeft } from 'lucide-react';
import { TwoFactorSetup } from '@/components/auth/TwoFactorSetup';
export default function ProfileSettings() {
const navigate = useNavigate();
const { user } = useAuth();
const { toast } = useToast();
const [loading, setLoading] = useState(false);
const [profile, setProfile] = useState({
username: '',
full_name: '',
bio: '',
phone_number: '',
location: '',
website: '',
language: 'en',
theme: 'light',
notifications_email: true,
notifications_push: false,
notifications_sms: false,
two_factor_enabled: false
});
useEffect(() => {
if (user) {
loadProfile();
}
}, [user]);
const loadProfile = async () => {
try {
const { data, error } = await supabase
.from('profiles')
.select('*')
.eq('id', user?.id)
.maybeSingle();
if (error) {
console.error('Error loading profile:', error);
return;
}
if (data) {
setProfile({
username: data.username || '',
full_name: data.full_name || '',
bio: data.bio || '',
phone_number: data.phone_number || '',
location: data.location || '',
website: data.website || '',
language: data.language || 'en',
theme: data.theme || 'light',
notifications_email: data.notifications_email ?? true,
notifications_push: data.notifications_push ?? false,
notifications_sms: data.notifications_sms ?? false,
two_factor_enabled: data.two_factor_enabled ?? false
});
}
} catch (error) {
console.error('Error loading profile:', error);
}
};
const updateProfile = async () => {
setLoading(true);
try {
// Call the secure upsert function
const { data, error } = await supabase.rpc('upsert_user_profile', {
p_username: profile.username || '',
p_full_name: profile.full_name || null,
p_bio: profile.bio || null,
p_phone_number: profile.phone_number || null,
p_location: profile.location || null,
p_website: profile.website || null,
p_language: profile.language || 'en',
p_theme: profile.theme || 'dark',
p_notifications_email: profile.notifications_email ?? true,
p_notifications_push: profile.notifications_push ?? false,
p_notifications_sms: profile.notifications_sms ?? false
});
if (error) throw error;
toast({
title: 'Success',
description: 'Profile updated successfully',
});
// Reload profile to ensure state is in sync
await loadProfile();
} catch (error: any) {
console.error('Profile update failed:', error);
toast({
title: 'Error',
description: error?.message || 'Failed to update profile',
variant: 'destructive',
});
} finally {
setLoading(false);
}
};
const updateNotificationSettings = async () => {
setLoading(true);
try {
// Call the upsert function with current profile data + notification settings
const { data, error } = await supabase.rpc('upsert_user_profile', {
p_username: profile.username || '',
p_full_name: profile.full_name || null,
p_bio: profile.bio || null,
p_phone_number: profile.phone_number || null,
p_location: profile.location || null,
p_website: profile.website || null,
p_language: profile.language || 'en',
p_theme: profile.theme || 'dark',
p_notifications_email: profile.notifications_email ?? true,
p_notifications_push: profile.notifications_push ?? false,
p_notifications_sms: profile.notifications_sms ?? false
});
if (error) throw error;
toast({
title: 'Success',
description: 'Notification settings updated',
});
} catch (error: any) {
toast({
title: 'Error',
description: error?.message || 'Failed to update notification settings',
variant: 'destructive',
});
} finally {
setLoading(false);
}
};
const updateSecuritySettings = async () => {
setLoading(true);
try {
const { error } = await supabase
.from('profiles')
.update({
two_factor_enabled: profile.two_factor_enabled,
updated_at: new Date().toISOString()
})
.eq('id', user?.id);
if (error) throw error;
toast({
title: 'Success',
description: 'Security settings updated',
});
} catch (error) {
toast({
title: 'Error',
description: 'Failed to update security settings',
variant: 'destructive',
});
} finally {
setLoading(false);
}
};
const changePassword = async () => {
const newPassword = prompt('Enter new password:');
if (!newPassword) return;
setLoading(true);
try {
const { error } = await supabase.auth.updateUser({
password: newPassword
});
if (error) throw error;
toast({
title: 'Success',
description: 'Password changed successfully',
});
} catch (error) {
toast({
title: 'Error',
description: 'Failed to change password',
variant: 'destructive',
});
} finally {
setLoading(false);
}
};
return (
<div className="container mx-auto py-8 max-w-4xl relative">
<button
onClick={() => navigate('/')}
className="absolute top-4 left-4 text-gray-400 hover:text-white transition-colors"
>
<ArrowLeft className="w-5 h-5" />
</button>
<h1 className="text-3xl font-bold mb-8">Profile Settings</h1>
<Tabs defaultValue="profile" className="space-y-4">
<TabsList className="grid w-full grid-cols-4">
<TabsTrigger value="profile">
<User className="mr-2 h-4 w-4" />
Profile
</TabsTrigger>
<TabsTrigger value="notifications">
<Bell className="mr-2 h-4 w-4" />
Notifications
</TabsTrigger>
<TabsTrigger value="security">
<Shield className="mr-2 h-4 w-4" />
Security
</TabsTrigger>
<TabsTrigger value="preferences">
<Palette className="mr-2 h-4 w-4" />
Preferences
</TabsTrigger>
</TabsList>
<TabsContent value="profile">
<Card>
<CardHeader>
<CardTitle>Profile Information</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div>
<Label>Username</Label>
<Input
value={profile.username}
onChange={(e) => setProfile({ ...profile, username: e.target.value })}
placeholder="Enter username"
/>
</div>
<div>
<Label>Full Name</Label>
<Input
value={profile.full_name}
onChange={(e) => setProfile({ ...profile, full_name: e.target.value })}
placeholder="Enter full name"
/>
</div>
<div>
<Label>Bio</Label>
<Textarea
value={profile.bio}
onChange={(e) => setProfile({ ...profile, bio: e.target.value })}
placeholder="Tell us about yourself"
rows={4}
/>
</div>
<div>
<Label>Phone Number</Label>
<Input
value={profile.phone_number}
onChange={(e) => setProfile({ ...profile, phone_number: e.target.value })}
placeholder="+1234567890"
/>
</div>
<div>
<Label>Location</Label>
<Input
value={profile.location}
onChange={(e) => setProfile({ ...profile, location: e.target.value })}
placeholder="City, Country"
/>
</div>
<div>
<Label>Website</Label>
<Input
value={profile.website}
onChange={(e) => setProfile({ ...profile, website: e.target.value })}
placeholder="https://example.com"
/>
</div>
<Button onClick={updateProfile} disabled={loading}>
Save Changes
</Button>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="notifications">
<Card>
<CardHeader>
<CardTitle>Notification Preferences</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center justify-between">
<div>
<Label>Email Notifications</Label>
<p className="text-sm text-muted-foreground">
Receive notifications via email
</p>
</div>
<Switch
checked={profile.notifications_email}
onCheckedChange={(checked) =>
setProfile({ ...profile, notifications_email: checked })
}
/>
</div>
<div className="flex items-center justify-between">
<div>
<Label>Push Notifications</Label>
<p className="text-sm text-muted-foreground">
Receive push notifications in browser
</p>
</div>
<Switch
checked={profile.notifications_push}
onCheckedChange={(checked) =>
setProfile({ ...profile, notifications_push: checked })
}
/>
</div>
<div className="flex items-center justify-between">
<div>
<Label>SMS Notifications</Label>
<p className="text-sm text-muted-foreground">
Receive notifications via SMS
</p>
</div>
<Switch
checked={profile.notifications_sms}
onCheckedChange={(checked) =>
setProfile({ ...profile, notifications_sms: checked })
}
/>
</div>
<Button onClick={updateNotificationSettings} disabled={loading}>
Save Preferences
</Button>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="security">
<div className="space-y-4">
<TwoFactorSetup />
<Card>
<CardHeader>
<CardTitle>Password Security</CardTitle>
<CardDescription>
Manage your password settings
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<Button variant="outline" onClick={changePassword}>
Change Password
</Button>
</CardContent>
</Card>
</div>
</TabsContent>
<TabsContent value="preferences">
<Card>
<CardHeader>
<CardTitle>App Preferences</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div>
<Label>Language</Label>
<Select
value={profile.language}
onValueChange={(value) => setProfile({ ...profile, language: value })}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="en">English</SelectItem>
<SelectItem value="tr">Türkçe</SelectItem>
<SelectItem value="ar">العربية</SelectItem>
<SelectItem value="kmr">Kurdî</SelectItem>
<SelectItem value="ckb">کوردی</SelectItem>
<SelectItem value="fa">فارسی</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label>Theme</Label>
<Select
value={profile.theme}
onValueChange={(value) => setProfile({ ...profile, theme: value })}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="light">Light</SelectItem>
<SelectItem value="dark">Dark</SelectItem>
<SelectItem value="system">System</SelectItem>
</SelectContent>
</Select>
</div>
<Button onClick={updateProfile} disabled={loading}>
Save Preferences
</Button>
</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
);
}