mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-04-21 23:47:56 +00:00
fix(mobile): comprehensive mobile UI redesign for better UX
- Header: icon-only wallet button on mobile, fix hamburger overflow - Dashboard: responsive tabs with flex-wrap and smaller text - Citizens: responsive ID card layout, stack NFT badges on mobile - BeCitizen: stack header buttons vertically on mobile - GovernanceInterface: add scrollable tabs - DEXDashboard: responsive admin buttons grid (2 cols on mobile) - P2PDashboard: responsive header and scrollable tabs - Dialog: mobile-first sizing with proper padding and max-height - Tabs UI: base responsive classes for all tab components - Add xs:480px breakpoint and scrollbar-hide utility - Fix vite polyfills config to prevent initialization errors
This commit is contained in:
@@ -72,11 +72,11 @@ const AppLayout: React.FC = () => {
|
||||
</div>
|
||||
|
||||
{/* Mobile: Wallet + Hamburger */}
|
||||
<div className="flex lg:hidden items-center gap-2 w-full justify-end">
|
||||
<div className="flex lg:hidden items-center gap-2 ml-auto">
|
||||
<PezkuwiWalletButton />
|
||||
<button
|
||||
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||
className="p-2 rounded-lg text-gray-400 hover:text-white hover:bg-gray-800 transition-colors"
|
||||
className="p-2 rounded-lg text-gray-400 hover:text-white hover:bg-gray-800 transition-colors flex-shrink-0"
|
||||
>
|
||||
{mobileMenuOpen ? <X className="w-6 h-6" /> : <Menu className="w-6 h-6" />}
|
||||
</button>
|
||||
|
||||
@@ -26,7 +26,7 @@ const GovernanceInterface: React.FC = () => {
|
||||
</div>
|
||||
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
|
||||
<TabsList className="grid grid-cols-3 lg:grid-cols-6 gap-2 bg-gray-900/50 p-1 rounded-lg">
|
||||
<TabsList className="grid grid-cols-3 lg:grid-cols-6 gap-2 bg-gray-900/50 p-1 rounded-lg overflow-x-auto scrollbar-hide">
|
||||
<TabsTrigger value="overview" className="flex items-center space-x-2">
|
||||
<TrendingUp className="w-4 h-4" />
|
||||
<span>Overview</span>
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from '@/components/ui/dialog';
|
||||
import { Wallet, Check, ExternalLink, Copy, LogOut } from 'lucide-react';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { useIsMobile } from '@/hooks/use-mobile';
|
||||
|
||||
export const PezkuwiWalletButton: React.FC = () => {
|
||||
const {
|
||||
@@ -24,6 +25,7 @@ export const PezkuwiWalletButton: React.FC = () => {
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const { toast } = useToast();
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const handleConnect = async () => {
|
||||
await connectWallet();
|
||||
@@ -70,12 +72,17 @@ export const PezkuwiWalletButton: React.FC = () => {
|
||||
variant="outline"
|
||||
className="bg-green-500/20 border-green-500/50 text-green-400 hover:bg-green-500/30"
|
||||
onClick={() => setIsOpen(true)}
|
||||
size={isMobile ? "icon" : "default"}
|
||||
>
|
||||
<Wallet className="w-4 h-4 mr-2" />
|
||||
{selectedAccount.meta.name || 'Account'}
|
||||
<Badge className="ml-2 bg-green-500/30 text-green-300 border-0">
|
||||
{formatAddress(selectedAccount.address)}
|
||||
</Badge>
|
||||
<Wallet className={isMobile ? "w-4 h-4" : "w-4 h-4 mr-2"} />
|
||||
{!isMobile && (
|
||||
<>
|
||||
{selectedAccount.meta.name || 'Account'}
|
||||
<Badge className="ml-2 bg-green-500/30 text-green-300 border-0">
|
||||
{formatAddress(selectedAccount.address)}
|
||||
</Badge>
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -169,9 +176,10 @@ export const PezkuwiWalletButton: React.FC = () => {
|
||||
<Button
|
||||
onClick={handleConnect}
|
||||
className="bg-gradient-to-r from-green-600 to-yellow-400 hover:from-green-700 hover:to-yellow-500 text-white"
|
||||
size={isMobile ? "icon" : "default"}
|
||||
>
|
||||
<Wallet className="w-4 h-4 mr-2" />
|
||||
Connect Wallet
|
||||
<Wallet className={isMobile ? "w-4 h-4" : "w-4 h-4 mr-2"} />
|
||||
{!isMobile && "Connect Wallet"}
|
||||
</Button>
|
||||
|
||||
{error && error.includes('not found') && (
|
||||
|
||||
@@ -137,7 +137,7 @@ export const DEXDashboard: React.FC = () => {
|
||||
<p className="text-gray-400 mb-6">
|
||||
Mint wrapped tokens for testing and liquidity provision
|
||||
</p>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
|
||||
<button
|
||||
onClick={() => setShowInitializeUsdtModal(true)}
|
||||
className="flex items-center justify-center gap-2 px-4 py-3 bg-green-600 hover:bg-green-700 text-white rounded-lg transition-colors font-medium"
|
||||
|
||||
@@ -82,7 +82,7 @@ export function P2PDashboard() {
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8 max-w-7xl">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3 mb-4">
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => navigate('/')}
|
||||
@@ -91,7 +91,7 @@ export function P2PDashboard() {
|
||||
<Home className="w-4 h-4 mr-2" />
|
||||
Back to Home
|
||||
</Button>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<NotificationBell />
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -168,12 +168,12 @@ export function P2PDashboard() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center justify-between mb-8">
|
||||
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4 mb-8">
|
||||
<div>
|
||||
<h1 className="text-4xl font-bold text-white">P2P Trading</h1>
|
||||
<p className="text-gray-400">Buy and sell crypto with your local currency.</p>
|
||||
<h1 className="text-3xl sm:text-4xl font-bold text-white">P2P Trading</h1>
|
||||
<p className="text-gray-400 text-sm sm:text-base">Buy and sell crypto with your local currency.</p>
|
||||
</div>
|
||||
<Button onClick={() => setShowCreateAd(true)}>
|
||||
<Button onClick={() => setShowCreateAd(true)} className="w-full sm:w-auto">
|
||||
<PlusCircle className="w-4 h-4 mr-2" />
|
||||
Post a New Ad
|
||||
</Button>
|
||||
@@ -187,17 +187,17 @@ export function P2PDashboard() {
|
||||
<QuickFilterBar filters={filters} onFiltersChange={setFilters} />
|
||||
|
||||
<Tabs defaultValue="buy">
|
||||
<TabsList className="grid w-full grid-cols-5">
|
||||
<TabsTrigger value="express" className="flex items-center gap-1">
|
||||
<TabsList className="grid w-full grid-cols-5 overflow-x-auto scrollbar-hide">
|
||||
<TabsTrigger value="express" className="flex items-center gap-1 text-xs sm:text-sm px-1 sm:px-3">
|
||||
<Zap className="w-3 h-3" />
|
||||
Express
|
||||
<span className="hidden xs:inline">Express</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="buy">Buy</TabsTrigger>
|
||||
<TabsTrigger value="sell">Sell</TabsTrigger>
|
||||
<TabsTrigger value="my-ads">My Ads</TabsTrigger>
|
||||
<TabsTrigger value="otc" className="flex items-center gap-1">
|
||||
<TabsTrigger value="buy" className="text-xs sm:text-sm px-1 sm:px-3">Buy</TabsTrigger>
|
||||
<TabsTrigger value="sell" className="text-xs sm:text-sm px-1 sm:px-3">Sell</TabsTrigger>
|
||||
<TabsTrigger value="my-ads" className="text-xs sm:text-sm px-1 sm:px-3">My Ads</TabsTrigger>
|
||||
<TabsTrigger value="otc" className="flex items-center gap-1 text-xs sm:text-sm px-1 sm:px-3">
|
||||
<Blocks className="w-3 h-3" />
|
||||
OTC
|
||||
<span className="hidden xs:inline">OTC</span>
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="express">
|
||||
|
||||
@@ -36,7 +36,7 @@ const DialogContent = React.forwardRef<
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-border/40 bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-[calc(100vw-2rem)] sm:w-full max-w-lg max-h-[90vh] overflow-y-auto translate-x-[-50%] translate-y-[-50%] gap-4 border border-border/40 bg-background p-4 sm:p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] rounded-lg sm:rounded-lg",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -12,7 +12,7 @@ const TabsList = React.forwardRef<
|
||||
<TabsPrimitive.List
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"inline-flex h-10 items-center justify-center rounded-md bg-muted/50 p-1 text-muted-foreground",
|
||||
"inline-flex h-auto min-h-10 items-center justify-center rounded-md bg-muted/50 p-1 text-muted-foreground flex-wrap gap-1",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -27,7 +27,7 @@ const TabsTrigger = React.forwardRef<
|
||||
<TabsPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-primary data-[state=active]:shadow-sm hover:text-foreground",
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-2 sm:px-3 py-1.5 text-xs sm:text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-primary data-[state=active]:shadow-sm hover:text-foreground",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -121,3 +121,13 @@
|
||||
.markdown-preview ol {
|
||||
@apply my-4 ml-6;
|
||||
}
|
||||
|
||||
/* Scrollbar hide utility */
|
||||
.scrollbar-hide {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.scrollbar-hide::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -27,11 +27,11 @@ const BeCitizen: React.FC = () => {
|
||||
<div className="min-h-screen bg-gradient-to-br from-green-700 via-white to-red-600">
|
||||
<div className="container mx-auto px-4 py-16">
|
||||
{/* Back to Home Button and Invite Friend */}
|
||||
<div className="mb-8 flex justify-between items-center">
|
||||
<div className="mb-8 flex flex-col sm:flex-row gap-3 sm:justify-between sm:items-center">
|
||||
<Button
|
||||
onClick={() => navigate('/')}
|
||||
variant="outline"
|
||||
className="bg-red-600 hover:bg-red-700 border-yellow-400 border-2 text-white font-semibold shadow-lg"
|
||||
className="w-full sm:w-auto bg-red-600 hover:bg-red-700 border-yellow-400 border-2 text-white font-semibold shadow-lg"
|
||||
>
|
||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||
Back to Home
|
||||
@@ -39,7 +39,7 @@ const BeCitizen: React.FC = () => {
|
||||
|
||||
<Button
|
||||
onClick={() => setIsInviteModalOpen(true)}
|
||||
className="bg-green-600 hover:bg-green-700 border-yellow-400 border-2 text-white font-semibold shadow-lg"
|
||||
className="w-full sm:w-auto bg-green-600 hover:bg-green-700 border-yellow-400 border-2 text-white font-semibold shadow-lg"
|
||||
>
|
||||
<UserPlus className="mr-2 h-4 w-4" />
|
||||
Invite Friend
|
||||
|
||||
+10
-10
@@ -470,7 +470,7 @@ export default function Citizens() {
|
||||
</div>
|
||||
|
||||
{/* Main Content Area */}
|
||||
<div className="absolute top-16 bottom-10 left-0 right-0 px-5 flex">
|
||||
<div className="absolute top-16 bottom-10 left-0 right-0 px-5 flex flex-col sm:flex-row">
|
||||
|
||||
{/* Left Section - Photo */}
|
||||
<div className="w-1/4 flex flex-col items-center justify-center">
|
||||
@@ -550,20 +550,20 @@ export default function Citizens() {
|
||||
</div>
|
||||
|
||||
{/* Right Section - NFT & ID Numbers */}
|
||||
<div className="w-1/4 flex flex-col justify-center items-center space-y-2">
|
||||
<div className="w-full sm:w-1/4 flex flex-row sm:flex-col justify-center items-center gap-2 sm:space-y-2 mt-2 sm:mt-0">
|
||||
{/* NFT Badge */}
|
||||
<div className="bg-gradient-to-br from-green-700 to-green-900 rounded-xl p-3 text-center shadow-lg w-full max-w-[110px]">
|
||||
<div className="text-[9px] text-green-200 font-medium uppercase tracking-wider">NFT ID</div>
|
||||
<div className="text-white font-bold text-sm mt-1">
|
||||
<div className="bg-gradient-to-br from-green-700 to-green-900 rounded-xl p-2 sm:p-3 text-center shadow-lg flex-1 sm:w-full sm:max-w-[110px]">
|
||||
<div className="text-[8px] sm:text-[9px] text-green-200 font-medium uppercase tracking-wider">NFT ID</div>
|
||||
<div className="text-white font-bold text-xs sm:text-sm mt-1">
|
||||
{nftDetails.citizenNFT ? `#${nftDetails.citizenNFT.collectionId}-${nftDetails.citizenNFT.itemId}` : 'N/A'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Citizen Number Badge */}
|
||||
<div className="bg-gradient-to-br from-red-700 to-red-900 rounded-xl p-3 text-center shadow-lg w-full max-w-[110px]">
|
||||
<div className="text-[9px] text-red-200 font-medium uppercase tracking-wider">Hejmara Welatî</div>
|
||||
<div className="text-[8px] text-red-300 mb-1">Citizen No</div>
|
||||
<div className="text-white font-bold text-[11px]">
|
||||
<div className="bg-gradient-to-br from-red-700 to-red-900 rounded-xl p-2 sm:p-3 text-center shadow-lg flex-1 sm:w-full sm:max-w-[110px]">
|
||||
<div className="text-[8px] sm:text-[9px] text-red-200 font-medium uppercase tracking-wider">Hejmara Welatî</div>
|
||||
<div className="text-[7px] sm:text-[8px] text-red-300 mb-1">Citizen No</div>
|
||||
<div className="text-white font-bold text-[10px] sm:text-[11px]">
|
||||
{nftDetails.citizenNFT ? `#${nftDetails.citizenNFT.collectionId}-${nftDetails.citizenNFT.itemId}-${citizenNumber}` : 'N/A'}
|
||||
</div>
|
||||
</div>
|
||||
@@ -571,7 +571,7 @@ export default function Citizens() {
|
||||
{/* Verified Badge */}
|
||||
<div className="flex items-center gap-1 bg-white/90 rounded-full px-2 py-1 shadow">
|
||||
<ShieldCheck className="h-3 w-3 text-green-600" />
|
||||
<span className="text-[9px] font-semibold text-green-700">VERIFIED</span>
|
||||
<span className="text-[8px] sm:text-[9px] font-semibold text-green-700">VERIFIED</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -530,12 +530,12 @@ export default function Dashboard() {
|
||||
</div>
|
||||
|
||||
<Tabs defaultValue="profile" className="space-y-4">
|
||||
<TabsList>
|
||||
<TabsTrigger value="profile">Profile</TabsTrigger>
|
||||
<TabsTrigger value="roles">Roles & Tikis</TabsTrigger>
|
||||
<TabsTrigger value="referrals">Referrals</TabsTrigger>
|
||||
<TabsTrigger value="security">Security</TabsTrigger>
|
||||
<TabsTrigger value="activity">Activity</TabsTrigger>
|
||||
<TabsList className="flex flex-wrap gap-1">
|
||||
<TabsTrigger value="profile" className="text-xs sm:text-sm px-2 sm:px-3">Profile</TabsTrigger>
|
||||
<TabsTrigger value="roles" className="text-xs sm:text-sm px-2 sm:px-3">Roles & Tikis</TabsTrigger>
|
||||
<TabsTrigger value="referrals" className="text-xs sm:text-sm px-2 sm:px-3">Referrals</TabsTrigger>
|
||||
<TabsTrigger value="security" className="text-xs sm:text-sm px-2 sm:px-3">Security</TabsTrigger>
|
||||
<TabsTrigger value="activity" className="text-xs sm:text-sm px-2 sm:px-3">Activity</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="profile" className="space-y-4">
|
||||
|
||||
@@ -19,6 +19,14 @@ export default {
|
||||
'2xl': '1400px'
|
||||
}
|
||||
},
|
||||
screens: {
|
||||
'xs': '480px',
|
||||
'sm': '640px',
|
||||
'md': '768px',
|
||||
'lg': '1024px',
|
||||
'xl': '1280px',
|
||||
'2xl': '1536px',
|
||||
},
|
||||
extend: {
|
||||
colors: {
|
||||
border: 'hsl(var(--border))',
|
||||
|
||||
+8
-5
@@ -30,7 +30,14 @@ export default defineConfig(() => ({
|
||||
},
|
||||
plugins: [
|
||||
react(),
|
||||
nodePolyfills(),
|
||||
nodePolyfills({
|
||||
globals: {
|
||||
Buffer: true,
|
||||
global: true,
|
||||
process: true,
|
||||
},
|
||||
protocolImports: true,
|
||||
}),
|
||||
].filter(Boolean),
|
||||
resolve: {
|
||||
mainFields: ['module', 'main', 'exports'],
|
||||
@@ -43,10 +50,6 @@ export default defineConfig(() => ({
|
||||
"@local/types": path.resolve(__dirname, "../shared/types"),
|
||||
"@pezkuwi/components": path.resolve(__dirname, "../shared/components"),
|
||||
"@shared": path.resolve(__dirname, "../shared"),
|
||||
// Node polyfill shims for shared folder (outside web workspace)
|
||||
'vite-plugin-node-polyfills/shims/buffer': path.resolve(__dirname, './src/tests/mocks/buffer-shim.ts'),
|
||||
'vite-plugin-node-polyfills/shims/global': path.resolve(__dirname, './src/tests/mocks/global-shim.ts'),
|
||||
'vite-plugin-node-polyfills/shims/process': path.resolve(__dirname, './src/tests/mocks/process-shim.ts'),
|
||||
},
|
||||
dedupe: ['react', 'lucide-react', 'sonner', '@pezkuwi/util-crypto', '@pezkuwi/util', '@pezkuwi/api', '@pezkuwi/extension-dapp', '@pezkuwi/keyring'],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user