mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-04-28 15:27:57 +00:00
feat(p2p): add Phase 4 merchant tier system and migrations
- Add merchant tier system (Lite/Super/Diamond) with tier badges - Add advanced order filters (token, fiat, payment method, amount range) - Add merchant dashboard with stats, ads management, tier upgrade - Add fraud prevention system with risk scoring and trade limits - Rename migrations to timestamp format for Supabase CLI compatibility - Add new migrations: phase2_phase3_tables, fraud_prevention, merchant_system
This commit is contained in:
@@ -30,6 +30,8 @@ import { SecurityAudit } from '@/components/security/SecurityAudit';
|
||||
import { KycApprovalTab } from '@/components/admin/KycApprovalTab';
|
||||
import { CommissionVotingTab } from '@/components/admin/CommissionVotingTab';
|
||||
import { CommissionSetupTab } from '@/components/admin/CommissionSetupTab';
|
||||
import { DisputeResolutionPanel } from '@/components/admin/DisputeResolutionPanel';
|
||||
import { Gavel } from 'lucide-react';
|
||||
|
||||
export default function AdminPanel() {
|
||||
const navigate = useNavigate();
|
||||
@@ -149,7 +151,7 @@ export default function AdminPanel() {
|
||||
<h1 className="text-3xl font-bold mb-8">Admin Panel</h1>
|
||||
|
||||
<Tabs defaultValue="setup" className="space-y-4">
|
||||
<TabsList className="grid w-full grid-cols-10 h-auto">
|
||||
<TabsList className="grid w-full grid-cols-11 h-auto">
|
||||
<TabsTrigger value="setup" className="flex-col h-auto py-3">
|
||||
<Shield className="h-4 w-4 mb-1" />
|
||||
<span className="text-xs leading-tight">Commission<br/>Setup</span>
|
||||
@@ -162,6 +164,10 @@ export default function AdminPanel() {
|
||||
<Activity className="h-4 w-4 mb-1" />
|
||||
<span className="text-xs leading-tight">Commission<br/>Voting</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="disputes" className="flex-col h-auto py-3">
|
||||
<Gavel className="h-4 w-4 mb-1" />
|
||||
<span className="text-xs leading-tight">P2P<br/>Disputes</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="users" className="flex-col h-auto py-3">
|
||||
<Users className="h-4 w-4 mb-1" />
|
||||
<span className="text-xs leading-tight">Users</span>
|
||||
@@ -200,6 +206,10 @@ export default function AdminPanel() {
|
||||
<CommissionVotingTab />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="disputes">
|
||||
<DisputeResolutionPanel />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="users">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
|
||||
@@ -0,0 +1,660 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { supabase } from '@/lib/supabase';
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { toast } from 'sonner';
|
||||
import { MerchantTierBadge } from '@/components/p2p/MerchantTierBadge';
|
||||
import { MerchantApplication } from '@/components/p2p/MerchantApplication';
|
||||
import {
|
||||
AreaChart,
|
||||
Area,
|
||||
XAxis,
|
||||
YAxis,
|
||||
CartesianGrid,
|
||||
Tooltip,
|
||||
ResponsiveContainer,
|
||||
BarChart,
|
||||
Bar
|
||||
} from 'recharts';
|
||||
import {
|
||||
ArrowLeft,
|
||||
BarChart3,
|
||||
Clock,
|
||||
Crown,
|
||||
DollarSign,
|
||||
Edit,
|
||||
Loader2,
|
||||
MessageSquare,
|
||||
Pause,
|
||||
Play,
|
||||
Plus,
|
||||
Settings,
|
||||
ShoppingBag,
|
||||
Star,
|
||||
TrendingUp,
|
||||
Trash2
|
||||
} from 'lucide-react';
|
||||
|
||||
// Types
|
||||
interface MerchantStats {
|
||||
total_volume_30d: number;
|
||||
total_trades_30d: number;
|
||||
buy_volume_30d: number;
|
||||
sell_volume_30d: number;
|
||||
completion_rate_30d: number;
|
||||
avg_release_time_minutes: number;
|
||||
avg_payment_time_minutes: number;
|
||||
total_volume_lifetime: number;
|
||||
total_trades_lifetime: number;
|
||||
}
|
||||
|
||||
interface ActiveAd {
|
||||
id: string;
|
||||
token: string;
|
||||
fiat_currency: string;
|
||||
amount_crypto: number;
|
||||
remaining_amount: number;
|
||||
fiat_amount: number;
|
||||
price_per_unit: number;
|
||||
status: string;
|
||||
created_at: string;
|
||||
is_featured: boolean;
|
||||
}
|
||||
|
||||
interface MerchantTier {
|
||||
tier: 'lite' | 'super' | 'diamond';
|
||||
max_pending_orders: number;
|
||||
max_order_amount: number;
|
||||
featured_ads_allowed: number;
|
||||
}
|
||||
|
||||
// Mock data for charts (will be replaced with real data)
|
||||
const generateChartData = () => {
|
||||
const data = [];
|
||||
for (let i = 29; i >= 0; i--) {
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() - i);
|
||||
data.push({
|
||||
date: date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }),
|
||||
volume: Math.floor(Math.random() * 5000) + 1000,
|
||||
trades: Math.floor(Math.random() * 10) + 1
|
||||
});
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
export default function P2PMerchantDashboard() {
|
||||
const navigate = useNavigate();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [stats, setStats] = useState<MerchantStats | null>(null);
|
||||
const [tierInfo, setTierInfo] = useState<MerchantTier | null>(null);
|
||||
const [activeAds, setActiveAds] = useState<ActiveAd[]>([]);
|
||||
const [chartData] = useState(generateChartData());
|
||||
const [autoReplyOpen, setAutoReplyOpen] = useState(false);
|
||||
const [autoReplyMessage, setAutoReplyMessage] = useState('');
|
||||
const [savingAutoReply, setSavingAutoReply] = useState(false);
|
||||
|
||||
// Fetch merchant data
|
||||
const fetchData = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
if (!user) {
|
||||
navigate('/login');
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch stats
|
||||
const { data: statsData } = await supabase
|
||||
.from('p2p_merchant_stats')
|
||||
.select('*')
|
||||
.eq('user_id', user.id)
|
||||
.single();
|
||||
|
||||
if (statsData) {
|
||||
setStats(statsData);
|
||||
}
|
||||
|
||||
// Fetch tier info
|
||||
const { data: tierData } = await supabase
|
||||
.from('p2p_merchant_tiers')
|
||||
.select('tier, max_pending_orders, max_order_amount, featured_ads_allowed')
|
||||
.eq('user_id', user.id)
|
||||
.single();
|
||||
|
||||
if (tierData) {
|
||||
setTierInfo(tierData);
|
||||
}
|
||||
|
||||
// Fetch active ads
|
||||
const { data: adsData } = await supabase
|
||||
.from('p2p_fiat_offers')
|
||||
.select('*')
|
||||
.eq('seller_id', user.id)
|
||||
.in('status', ['open', 'paused'])
|
||||
.order('created_at', { ascending: false });
|
||||
|
||||
if (adsData) {
|
||||
setActiveAds(adsData);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching merchant data:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [navigate]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, [fetchData]);
|
||||
|
||||
// Toggle ad status
|
||||
const toggleAdStatus = async (adId: string, currentStatus: string) => {
|
||||
try {
|
||||
const newStatus = currentStatus === 'open' ? 'paused' : 'open';
|
||||
const { error } = await supabase
|
||||
.from('p2p_fiat_offers')
|
||||
.update({ status: newStatus })
|
||||
.eq('id', adId);
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
toast.success(`Ad ${newStatus === 'open' ? 'activated' : 'paused'}`);
|
||||
fetchData();
|
||||
} catch (error) {
|
||||
console.error('Error toggling ad status:', error);
|
||||
toast.error('Failed to update ad status');
|
||||
}
|
||||
};
|
||||
|
||||
// Delete ad
|
||||
const deleteAd = async (adId: string) => {
|
||||
if (!confirm('Are you sure you want to delete this ad?')) return;
|
||||
|
||||
try {
|
||||
const { error } = await supabase
|
||||
.from('p2p_fiat_offers')
|
||||
.delete()
|
||||
.eq('id', adId);
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
toast.success('Ad deleted successfully');
|
||||
fetchData();
|
||||
} catch (error) {
|
||||
console.error('Error deleting ad:', error);
|
||||
toast.error('Failed to delete ad');
|
||||
}
|
||||
};
|
||||
|
||||
// Save auto-reply message
|
||||
const saveAutoReply = async () => {
|
||||
setSavingAutoReply(true);
|
||||
try {
|
||||
// Save to all active ads
|
||||
const { error } = await supabase
|
||||
.from('p2p_fiat_offers')
|
||||
.update({ auto_reply_message: autoReplyMessage })
|
||||
.eq('seller_id', (await supabase.auth.getUser()).data.user?.id);
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
toast.success('Auto-reply message saved');
|
||||
setAutoReplyOpen(false);
|
||||
} catch (error) {
|
||||
console.error('Error saving auto-reply:', error);
|
||||
toast.error('Failed to save auto-reply');
|
||||
} finally {
|
||||
setSavingAutoReply(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-kurdish-green" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto py-6 px-4 space-y-6">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<Button variant="ghost" size="icon" onClick={() => navigate('/p2p')}>
|
||||
<ArrowLeft className="h-5 w-5" />
|
||||
</Button>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold flex items-center gap-2">
|
||||
<Crown className="h-6 w-6 text-kurdish-green" />
|
||||
Merchant Dashboard
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
Manage your P2P trading business
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{tierInfo && <MerchantTierBadge tier={tierInfo.tier} size="lg" />}
|
||||
</div>
|
||||
|
||||
<Tabs defaultValue="overview" className="space-y-6">
|
||||
<TabsList>
|
||||
<TabsTrigger value="overview" className="gap-1">
|
||||
<BarChart3 className="h-4 w-4" />
|
||||
Overview
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="ads" className="gap-1">
|
||||
<ShoppingBag className="h-4 w-4" />
|
||||
My Ads
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="upgrade" className="gap-1">
|
||||
<TrendingUp className="h-4 w-4" />
|
||||
Upgrade Tier
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="settings" className="gap-1">
|
||||
<Settings className="h-4 w-4" />
|
||||
Settings
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{/* Overview Tab */}
|
||||
<TabsContent value="overview" className="space-y-6">
|
||||
{/* Stats Cards */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<Card>
|
||||
<CardContent className="pt-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">30-Day Volume</p>
|
||||
<p className="text-2xl font-bold">
|
||||
${stats?.total_volume_30d?.toLocaleString() || '0'}
|
||||
</p>
|
||||
</div>
|
||||
<DollarSign className="h-8 w-8 text-kurdish-green/50" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardContent className="pt-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">30-Day Trades</p>
|
||||
<p className="text-2xl font-bold">{stats?.total_trades_30d || 0}</p>
|
||||
</div>
|
||||
<ShoppingBag className="h-8 w-8 text-blue-500/50" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardContent className="pt-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">Completion Rate</p>
|
||||
<p className="text-2xl font-bold">
|
||||
{stats?.completion_rate_30d?.toFixed(1) || '0'}%
|
||||
</p>
|
||||
</div>
|
||||
<Star className="h-8 w-8 text-yellow-500/50" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardContent className="pt-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">Avg Release Time</p>
|
||||
<p className="text-2xl font-bold">
|
||||
{stats?.avg_release_time_minutes || 0}m
|
||||
</p>
|
||||
</div>
|
||||
<Clock className="h-8 w-8 text-purple-500/50" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Charts */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* Volume Chart */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg">Volume Trend</CardTitle>
|
||||
<CardDescription>Last 30 days trading volume</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ResponsiveContainer width="100%" height={250}>
|
||||
<AreaChart data={chartData}>
|
||||
<CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
|
||||
<XAxis dataKey="date" className="text-xs" />
|
||||
<YAxis className="text-xs" />
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
backgroundColor: 'hsl(var(--card))',
|
||||
borderColor: 'hsl(var(--border))'
|
||||
}}
|
||||
/>
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey="volume"
|
||||
stroke="#00A94F"
|
||||
fill="#00A94F"
|
||||
fillOpacity={0.2}
|
||||
/>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Trades Chart */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg">Trade Count</CardTitle>
|
||||
<CardDescription>Daily trades over last 30 days</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ResponsiveContainer width="100%" height={250}>
|
||||
<BarChart data={chartData}>
|
||||
<CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
|
||||
<XAxis dataKey="date" className="text-xs" />
|
||||
<YAxis className="text-xs" />
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
backgroundColor: 'hsl(var(--card))',
|
||||
borderColor: 'hsl(var(--border))'
|
||||
}}
|
||||
/>
|
||||
<Bar dataKey="trades" fill="#00A94F" radius={[4, 4, 0, 0]} />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Quick Stats */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg">Lifetime Statistics</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<div className="text-center p-4 bg-muted rounded-lg">
|
||||
<p className="text-2xl font-bold">
|
||||
${stats?.total_volume_lifetime?.toLocaleString() || '0'}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">Total Volume</p>
|
||||
</div>
|
||||
<div className="text-center p-4 bg-muted rounded-lg">
|
||||
<p className="text-2xl font-bold">{stats?.total_trades_lifetime || 0}</p>
|
||||
<p className="text-sm text-muted-foreground">Total Trades</p>
|
||||
</div>
|
||||
<div className="text-center p-4 bg-muted rounded-lg">
|
||||
<p className="text-2xl font-bold">
|
||||
${stats?.buy_volume_30d?.toLocaleString() || '0'}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">Buy Volume (30d)</p>
|
||||
</div>
|
||||
<div className="text-center p-4 bg-muted rounded-lg">
|
||||
<p className="text-2xl font-bold">
|
||||
${stats?.sell_volume_30d?.toLocaleString() || '0'}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">Sell Volume (30d)</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
{/* My Ads Tab */}
|
||||
<TabsContent value="ads" className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-lg font-semibold">
|
||||
Active Advertisements ({activeAds.length})
|
||||
</h2>
|
||||
<Button
|
||||
className="bg-kurdish-green hover:bg-kurdish-green-dark"
|
||||
onClick={() => navigate('/p2p/create-ad')}
|
||||
>
|
||||
<Plus className="h-4 w-4 mr-1" />
|
||||
Create New Ad
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{activeAds.length === 0 ? (
|
||||
<Card>
|
||||
<CardContent className="py-12 text-center">
|
||||
<ShoppingBag className="h-12 w-12 mx-auto text-muted-foreground/50 mb-4" />
|
||||
<p className="text-muted-foreground mb-4">
|
||||
You don't have any active ads yet.
|
||||
</p>
|
||||
<Button onClick={() => navigate('/p2p/create-ad')}>
|
||||
<Plus className="h-4 w-4 mr-1" />
|
||||
Create Your First Ad
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{activeAds.map((ad) => (
|
||||
<Card key={ad.id} className="hover:border-kurdish-green/50 transition-colors">
|
||||
<CardContent className="py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<Badge variant={ad.status === 'open' ? 'default' : 'secondary'}>
|
||||
{ad.status.toUpperCase()}
|
||||
</Badge>
|
||||
<span className="font-medium">
|
||||
Sell {ad.token} for {ad.fiat_currency}
|
||||
</span>
|
||||
{ad.is_featured && (
|
||||
<Badge className="bg-yellow-500/20 text-yellow-500 border-yellow-500/30">
|
||||
<Star className="h-3 w-3 mr-1" />
|
||||
Featured
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{ad.remaining_amount} / {ad.amount_crypto} {ad.token} remaining
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-right">
|
||||
<p className="font-medium">
|
||||
{ad.price_per_unit?.toFixed(2)} {ad.fiat_currency}/{ad.token}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Total: {ad.fiat_amount?.toLocaleString()} {ad.fiat_currency}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => toggleAdStatus(ad.id, ad.status)}
|
||||
title={ad.status === 'open' ? 'Pause' : 'Activate'}
|
||||
>
|
||||
{ad.status === 'open' ? (
|
||||
<Pause className="h-4 w-4" />
|
||||
) : (
|
||||
<Play className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => navigate(`/p2p/edit-ad/${ad.id}`)}
|
||||
title="Edit"
|
||||
>
|
||||
<Edit className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => deleteAd(ad.id)}
|
||||
title="Delete"
|
||||
className="text-destructive hover:text-destructive"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Tier limits info */}
|
||||
{tierInfo && (
|
||||
<Card className="bg-muted/50">
|
||||
<CardContent className="py-4">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-muted-foreground">
|
||||
Active ads: {activeAds.filter(a => a.status === 'open').length} / {tierInfo.max_pending_orders}
|
||||
</span>
|
||||
<span className="text-muted-foreground">
|
||||
Max order: ${tierInfo.max_order_amount.toLocaleString()}
|
||||
</span>
|
||||
<span className="text-muted-foreground">
|
||||
Featured ads: {activeAds.filter(a => a.is_featured).length} / {tierInfo.featured_ads_allowed}
|
||||
</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
{/* Upgrade Tier Tab */}
|
||||
<TabsContent value="upgrade">
|
||||
<MerchantApplication />
|
||||
</TabsContent>
|
||||
|
||||
{/* Settings Tab */}
|
||||
<TabsContent value="settings" className="space-y-6">
|
||||
{/* Auto-reply */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg flex items-center gap-2">
|
||||
<MessageSquare className="h-5 w-5" />
|
||||
Auto-Reply Message
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Automatically send this message when someone starts a trade with you
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Button variant="outline" onClick={() => setAutoReplyOpen(true)}>
|
||||
<Edit className="h-4 w-4 mr-2" />
|
||||
Configure Auto-Reply
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Notification Settings */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg">Notification Settings</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="font-medium">New Order Notifications</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Get notified when someone accepts your offer
|
||||
</p>
|
||||
</div>
|
||||
<Switch defaultChecked />
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="font-medium">Payment Notifications</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Get notified when buyer marks payment as sent
|
||||
</p>
|
||||
</div>
|
||||
<Switch defaultChecked />
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="font-medium">Chat Notifications</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Get notified for new chat messages
|
||||
</p>
|
||||
</div>
|
||||
<Switch defaultChecked />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Danger Zone */}
|
||||
<Card className="border-destructive/30">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg text-destructive">Danger Zone</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="font-medium">Pause All Ads</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Temporarily pause all your active advertisements
|
||||
</p>
|
||||
</div>
|
||||
<Button variant="outline">Pause All</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
{/* Auto-reply Dialog */}
|
||||
<Dialog open={autoReplyOpen} onOpenChange={setAutoReplyOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Auto-Reply Message</DialogTitle>
|
||||
<DialogDescription>
|
||||
This message will be automatically sent when someone starts a trade with you.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Label>Message</Label>
|
||||
<Textarea
|
||||
value={autoReplyMessage}
|
||||
onChange={(e) => setAutoReplyMessage(e.target.value)}
|
||||
placeholder="Hello! Thank you for choosing to trade with me. Please follow the payment instructions and mark as paid once done."
|
||||
rows={4}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setAutoReplyOpen(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
className="bg-kurdish-green hover:bg-kurdish-green-dark"
|
||||
onClick={saveAutoReply}
|
||||
disabled={savingAutoReply}
|
||||
>
|
||||
{savingAutoReply && <Loader2 className="h-4 w-4 mr-2 animate-spin" />}
|
||||
Save Message
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user