Enhance dashboard UI and add order timeline
All checks were successful
Build Frontend / build (push) Successful in 1m12s
All checks were successful
Build Frontend / build (push) Successful in 1m12s
Refactored dashboard pages for improved layout and visual consistency using Card components, motion animations, and updated color schemes. Added an OrderTimeline component to the order details page to visualize order lifecycle. Improved customer management page with better sorting, searching, and a detailed customer dialog. Updated storefront settings page with a modernized layout and clearer sectioning.
This commit is contained in:
@@ -6,11 +6,15 @@ import Layout from "@/components/layout/layout";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Save, Send, Key, MessageSquare, Shield, Globe, Wallet } from "lucide-react";
|
||||
import { Save, Send, Key, MessageSquare, Shield, Globe, Wallet, RefreshCw } from "lucide-react";
|
||||
import { apiRequest } from "@/lib/api";
|
||||
import { toast } from "sonner";
|
||||
import BroadcastDialog from "@/components/modals/broadcast-dialog";
|
||||
import Dashboard from "@/components/dashboard/dashboard";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle, CardFooter } from "@/components/ui/card";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -166,251 +170,298 @@ export default function StorefrontPage() {
|
||||
return (
|
||||
<Dashboard>
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-6">
|
||||
<h1 className="text-2xl font-semibold text-gray-900 dark:text-white flex items-center">
|
||||
<Globe className="mr-2 h-6 w-6" />
|
||||
Storefront Settings
|
||||
</h1>
|
||||
<div className="flex items-center gap-2">
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch
|
||||
checked={storefront.isEnabled}
|
||||
onCheckedChange={(checked) =>
|
||||
setStorefront((prev) => ({
|
||||
...prev,
|
||||
isEnabled: checked,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
<span className={`text-sm font-medium ${storefront.isEnabled ? 'text-emerald-400' : 'text-zinc-400'}`}>
|
||||
{storefront.isEnabled ? 'Store Open' : 'Store Closed'}
|
||||
</span>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{storefront.isEnabled ? 'Click to close store' : 'Click to open store'}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<div className="flex items-center justify-between mb-8">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="p-3 rounded-xl bg-primary/10 text-primary">
|
||||
<Globe className="h-8 w-8" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight text-foreground">
|
||||
Storefront Settings
|
||||
</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Manage your shop's appearance, policies, and configuration
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<div className="flex gap-3">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setBroadcastOpen(true)}
|
||||
className="gap-2"
|
||||
size="sm"
|
||||
className="gap-2 h-10"
|
||||
>
|
||||
<Send className="h-4 w-4" />
|
||||
Broadcast
|
||||
</Button>
|
||||
<Button
|
||||
onClick={saveStorefront}
|
||||
<Button
|
||||
onClick={saveStorefront}
|
||||
disabled={saving}
|
||||
className="gap-2"
|
||||
size="sm"
|
||||
className="gap-2 h-10 min-w-[120px]"
|
||||
>
|
||||
<Save className="h-4 w-4" />
|
||||
{saving ? <RefreshCw className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />}
|
||||
{saving ? "Saving..." : "Save Changes"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 xl:grid-cols-2 gap-4 lg:gap-6">
|
||||
{/* Security Settings */}
|
||||
<div className="space-y-3">
|
||||
<div className="bg-[#0F0F12] rounded-lg p-4 border border-zinc-800">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Shield className="h-4 w-4 text-purple-400" />
|
||||
<h2 className="text-base font-medium text-zinc-100">
|
||||
Security
|
||||
</h2>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<label className="text-xs font-medium mb-1 block text-zinc-400">PGP Public Key</label>
|
||||
<Textarea
|
||||
value={storefront.pgpKey}
|
||||
onChange={(e) => setStorefront(prev => ({ ...prev, pgpKey: e.target.value }))}
|
||||
placeholder="Enter your PGP public key"
|
||||
className="font-mono text-sm h-24 bg-[#1C1C1C] border-zinc-800 resize-none"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs font-medium mb-1 block text-zinc-400">Telegram Bot Token</label>
|
||||
<Input
|
||||
type="password"
|
||||
value={storefront.telegramToken}
|
||||
onChange={(e) => setStorefront(prev => ({ ...prev, telegramToken: e.target.value }))}
|
||||
placeholder="Enter your Telegram bot token"
|
||||
className="bg-[#1C1C1C] border-zinc-800 h-8 text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
{/* Main Column */}
|
||||
<div className="lg:col-span-2 space-y-6">
|
||||
|
||||
{/* Store Status Card */}
|
||||
<motion.div initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }}>
|
||||
<Card className="border-border/40 bg-background/50 backdrop-blur-sm shadow-sm overflow-hidden relative">
|
||||
<div className={`absolute top-0 left-0 w-1 h-full ${storefront.isEnabled ? 'bg-emerald-500' : 'bg-destructive'}`} />
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-1">
|
||||
<CardTitle>Store Status</CardTitle>
|
||||
<CardDescription>Control your store's visibility to customers</CardDescription>
|
||||
</div>
|
||||
<Badge variant={storefront.isEnabled ? "default" : "destructive"} className="h-6">
|
||||
{storefront.isEnabled ? "Open for Business" : "Store Closed"}
|
||||
</Badge>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="pb-6">
|
||||
<div className="flex items-center gap-4 p-4 rounded-lg bg-card border border-border/50">
|
||||
<Switch
|
||||
checked={storefront.isEnabled}
|
||||
onCheckedChange={(checked) =>
|
||||
setStorefront((prev) => ({
|
||||
...prev,
|
||||
isEnabled: checked,
|
||||
}))
|
||||
}
|
||||
className="data-[state=checked]:bg-emerald-500"
|
||||
/>
|
||||
<div>
|
||||
<h4 className="font-medium text-sm">
|
||||
{storefront.isEnabled ? 'Your store is currently online' : 'Your store is currently offline'}
|
||||
</h4>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{storefront.isEnabled
|
||||
? 'Customers can browse listings and place orders normally.'
|
||||
: 'Customers will see a maintenance page. No new orders can be placed.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Welcome & Policy */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<motion.div initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.1 }}>
|
||||
<Card className="h-full border-border/40 bg-background/50 backdrop-blur-sm shadow-sm">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-lg">
|
||||
<MessageSquare className="h-4 w-4 text-primary" />
|
||||
Welcome Message
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Textarea
|
||||
value={storefront.welcomeMessage}
|
||||
onChange={(e) => setStorefront(prev => ({ ...prev, welcomeMessage: e.target.value }))}
|
||||
placeholder="Enter the welcome message for new customers..."
|
||||
className="min-h-[180px] bg-background/50 border-border/50 resize-none focus:ring-primary/20"
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
<motion.div initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.2 }}>
|
||||
<Card className="h-full border-border/40 bg-background/50 backdrop-blur-sm shadow-sm">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-lg">
|
||||
<Shield className="h-4 w-4 text-orange-400" />
|
||||
Store Policy
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Textarea
|
||||
value={storefront.storePolicy}
|
||||
onChange={(e) => setStorefront(prev => ({ ...prev, storePolicy: e.target.value }))}
|
||||
placeholder="Enter your store's policies, terms, and conditions..."
|
||||
className="min-h-[180px] bg-background/50 border-border/50 resize-none focus:ring-primary/20"
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Shipping Settings */}
|
||||
<div className="bg-[#0F0F12] rounded-lg p-4 border border-zinc-800">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Globe className="h-4 w-4 text-blue-400" />
|
||||
<h2 className="text-base font-medium text-zinc-100">
|
||||
Shipping
|
||||
</h2>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label className="text-xs font-medium mb-1 block text-zinc-400">Ships From</label>
|
||||
<Select
|
||||
value={storefront.shipsFrom}
|
||||
onValueChange={(value) =>
|
||||
setStorefront((prev) => ({ ...prev, shipsFrom: value as any }))
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="bg-[#1C1C1C] border-zinc-800 h-8 text-sm">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{SHIPPING_REGIONS.map((region) => (
|
||||
<SelectItem key={region.value} value={region.value}>
|
||||
{region.emoji} {region.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs font-medium mb-1 block text-zinc-400">Ships To</label>
|
||||
<Select
|
||||
value={storefront.shipsTo}
|
||||
onValueChange={(value) =>
|
||||
setStorefront((prev) => ({ ...prev, shipsTo: value as any }))
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="bg-[#1C1C1C] border-zinc-800 h-8 text-sm">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{SHIPPING_REGIONS.map((region) => (
|
||||
<SelectItem key={region.value} value={region.value}>
|
||||
{region.emoji} {region.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Security Settings */}
|
||||
<motion.div initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.3 }}>
|
||||
<Card className="border-border/40 bg-background/50 backdrop-blur-sm shadow-sm">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Key className="h-5 w-5 text-purple-400" />
|
||||
Security Configuration
|
||||
</CardTitle>
|
||||
<CardDescription>Manage keys and access tokens for your store security</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">PGP Public Key</Label>
|
||||
<Textarea
|
||||
value={storefront.pgpKey}
|
||||
onChange={(e) => setStorefront(prev => ({ ...prev, pgpKey: e.target.value }))}
|
||||
placeholder="-----BEGIN PGP PUBLIC KEY BLOCK-----..."
|
||||
className="font-mono text-xs h-32 bg-zinc-950/50 border-zinc-800/50 resize-none"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">Telegram Bot Token</Label>
|
||||
<div className="relative">
|
||||
<Input
|
||||
type="password"
|
||||
value={storefront.telegramToken}
|
||||
onChange={(e) => setStorefront(prev => ({ ...prev, telegramToken: e.target.value }))}
|
||||
placeholder="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"
|
||||
className="bg-background/50 border-border/50 font-mono text-sm pl-10"
|
||||
/>
|
||||
<div className="absolute left-3 top-2.5 text-muted-foreground">
|
||||
<Shield className="h-4 w-4" />
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-[10px] text-muted-foreground">Used for notifications and bot integration.</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Messaging and Payments */}
|
||||
<div className="space-y-3">
|
||||
<div className="bg-[#0F0F12] rounded-lg p-4 border border-zinc-800">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<MessageSquare className="h-4 w-4 text-emerald-400" />
|
||||
<h2 className="text-base font-medium text-zinc-100">
|
||||
Welcome Message
|
||||
</h2>
|
||||
</div>
|
||||
<Textarea
|
||||
value={storefront.welcomeMessage}
|
||||
onChange={(e) => setStorefront(prev => ({ ...prev, welcomeMessage: e.target.value }))}
|
||||
placeholder="Enter the welcome message for new customers"
|
||||
className="h-36 bg-[#1C1C1C] border-zinc-800 text-sm resize-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="bg-[#0F0F12] rounded-lg p-4 border border-zinc-800">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Shield className="h-4 w-4 text-orange-400" />
|
||||
<h2 className="text-base font-medium text-zinc-100">
|
||||
Store Policy
|
||||
</h2>
|
||||
</div>
|
||||
<Textarea
|
||||
value={storefront.storePolicy}
|
||||
onChange={(e) => setStorefront(prev => ({ ...prev, storePolicy: e.target.value }))}
|
||||
placeholder="Enter your store's policies, terms, and conditions"
|
||||
className="h-48 bg-[#1C1C1C] border-zinc-800 text-sm resize-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="bg-[#0F0F12] rounded-lg p-4 border border-zinc-800">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Wallet className="h-4 w-4 text-yellow-400" />
|
||||
<h2 className="text-base font-medium text-zinc-100">
|
||||
Payment Methods
|
||||
</h2>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{WALLET_OPTIONS.map((wallet) => (
|
||||
<div key={wallet.id} className="space-y-1.5">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="text-xs font-medium flex items-center gap-2 text-zinc-400">
|
||||
<span>{wallet.emoji}</span>
|
||||
{wallet.name}
|
||||
{wallet.comingSoon && (
|
||||
<span className="text-[10px] bg-purple-900/50 text-purple-400 px-1.5 py-0.5 rounded">
|
||||
Coming Soon
|
||||
</span>
|
||||
)}
|
||||
</label>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div>
|
||||
<Switch
|
||||
checked={storefront.enabledWallets[wallet.id]}
|
||||
onCheckedChange={(checked) =>
|
||||
setStorefront((prev) => ({
|
||||
...prev,
|
||||
enabledWallets: {
|
||||
...prev.enabledWallets,
|
||||
[wallet.id]: checked,
|
||||
},
|
||||
}))
|
||||
}
|
||||
disabled={wallet.disabled}
|
||||
/>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
{wallet.disabled && (
|
||||
<TooltipContent>
|
||||
<p>Coming soon</p>
|
||||
</TooltipContent>
|
||||
)}
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
{storefront.enabledWallets[wallet.id] && !wallet.disabled && (
|
||||
<Input
|
||||
value={storefront.wallets[wallet.id]}
|
||||
onChange={(e) =>
|
||||
setStorefront((prev) => ({
|
||||
...prev,
|
||||
wallets: {
|
||||
...prev.wallets,
|
||||
[wallet.id]: e.target.value,
|
||||
},
|
||||
}))
|
||||
}
|
||||
placeholder={wallet.placeholder}
|
||||
className="font-mono text-sm h-8 bg-[#1C1C1C] border-zinc-800"
|
||||
/>
|
||||
)}
|
||||
{/* Sidebar Column */}
|
||||
<div className="space-y-6">
|
||||
{/* Shipping Settings */}
|
||||
<motion.div initial={{ opacity: 0, x: 10 }} animate={{ opacity: 1, x: 0 }} transition={{ delay: 0.4 }}>
|
||||
<Card className="border-border/40 bg-background/50 backdrop-blur-sm shadow-sm">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-lg">
|
||||
<Globe className="h-4 w-4 text-blue-400" />
|
||||
Shipping & Logistics
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label>Ships From</Label>
|
||||
<Select
|
||||
value={storefront.shipsFrom}
|
||||
onValueChange={(value) =>
|
||||
setStorefront((prev) => ({ ...prev, shipsFrom: value as any }))
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="bg-background/50 border-border/50">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{SHIPPING_REGIONS.map((region) => (
|
||||
<SelectItem key={region.value} value={region.value}>
|
||||
<span className="flex items-center gap-2">
|
||||
<span className="text-lg">{region.emoji}</span>
|
||||
{region.label}
|
||||
</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Ships To</Label>
|
||||
<Select
|
||||
value={storefront.shipsTo}
|
||||
onValueChange={(value) =>
|
||||
setStorefront((prev) => ({ ...prev, shipsTo: value as any }))
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="bg-background/50 border-border/50">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{SHIPPING_REGIONS.map((region) => (
|
||||
<SelectItem key={region.value} value={region.value}>
|
||||
<span className="flex items-center gap-2">
|
||||
<span className="text-lg">{region.emoji}</span>
|
||||
{region.label}
|
||||
</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Payment Methods */}
|
||||
<motion.div initial={{ opacity: 0, x: 10 }} animate={{ opacity: 1, x: 0 }} transition={{ delay: 0.5 }}>
|
||||
<Card className="border-border/40 bg-background/50 backdrop-blur-sm shadow-sm">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-lg">
|
||||
<Wallet className="h-4 w-4 text-yellow-500" />
|
||||
Crypto Wallets
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{WALLET_OPTIONS.map((wallet) => (
|
||||
<div key={wallet.id} className="p-3 rounded-lg border border-border/50 bg-card/30 hover:bg-card/50 transition-colors">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<label className="text-sm font-medium flex items-center gap-2">
|
||||
<span className="text-lg">{wallet.emoji}</span>
|
||||
{wallet.name}
|
||||
</label>
|
||||
<div className="flex items-center gap-2">
|
||||
{wallet.comingSoon && (
|
||||
<Badge variant="secondary" className="text-[10px] h-5">Soon</Badge>
|
||||
)}
|
||||
<Switch
|
||||
checked={storefront.enabledWallets[wallet.id]}
|
||||
onCheckedChange={(checked) =>
|
||||
setStorefront((prev) => ({
|
||||
...prev,
|
||||
enabledWallets: {
|
||||
...prev.enabledWallets,
|
||||
[wallet.id]: checked,
|
||||
},
|
||||
}))
|
||||
}
|
||||
disabled={wallet.disabled}
|
||||
className="scale-90"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{storefront.enabledWallets[wallet.id] && !wallet.disabled && (
|
||||
<motion.div initial={{ height: 0, opacity: 0 }} animate={{ height: 'auto', opacity: 1 }}>
|
||||
<Input
|
||||
value={storefront.wallets[wallet.id]}
|
||||
onChange={(e) =>
|
||||
setStorefront((prev) => ({
|
||||
...prev,
|
||||
wallets: {
|
||||
...prev.wallets,
|
||||
[wallet.id]: e.target.value,
|
||||
},
|
||||
}))
|
||||
}
|
||||
placeholder={wallet.placeholder}
|
||||
className="font-mono text-xs h-9 bg-background/50"
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<BroadcastDialog open={broadcastOpen} setOpen={setBroadcastOpen} />
|
||||
|
||||
</Dashboard>
|
||||
|
||||
</Dashboard >
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user