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:
2026-02-11 02:14:32 +03:00
parent b173810c27
commit 12874822fc
13 changed files with 81 additions and 52 deletions
+2 -2
View File
@@ -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>
+1 -1
View File
@@ -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>
+15 -7
View File
@@ -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') && (
+1 -1
View File
@@ -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"
+14 -14
View File
@@ -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">
+1 -1
View File
@@ -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}
+2 -2
View File
@@ -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}
+10
View File
@@ -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;
}
+3 -3
View File
@@ -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
View File
@@ -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>
+6 -6
View File
@@ -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">
+8
View File
@@ -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
View File
@@ -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'],
},