Enhance layout and styling: Update index.html for full-width root div, adjust tailwind.config.js for container padding and screen sizes, refactor NavBar and LogIn components for improved user experience, and apply global styles for consistent layout across the application
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
<title>Managing</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root" class="min-h-screen flex flex-col"></div>
|
||||
<div id="root" class="min-h-screen flex flex-col w-full"></div>
|
||||
<script>
|
||||
global = globalThis
|
||||
</script>
|
||||
|
||||
@@ -7,7 +7,6 @@ import useApiUrlStore from '../../../app/store/apiStore'
|
||||
import {UserClient} from '../../../generated/ManagingApi'
|
||||
import type {ILoginFormInput} from '../../../global/type.tsx'
|
||||
import useCookie from '../../../hooks/useCookie'
|
||||
import {SecondaryNavbar} from '../NavBar/NavBar'
|
||||
import Toast from '../Toast/Toast'
|
||||
|
||||
const LogIn = () => {
|
||||
@@ -82,10 +81,6 @@ const LogIn = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{ display: 'flex', justifyContent: 'right' }}>
|
||||
<SecondaryNavbar />
|
||||
</div>
|
||||
|
||||
<section className="bg-gray-50 dark:bg-gray-900">
|
||||
<div className="md:h-screen lg:py-0 flex flex-col items-center justify-center px-6 py-8 mx-auto">
|
||||
<div className="dark:border md:mt-0 sm:max-w-md xl:p-0 dark:bg-gray-800 dark:border-gray-700 w-full bg-white rounded-lg shadow">
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import {useIsFetching, useQuery} from '@tanstack/react-query'
|
||||
import {usePrivy} from '@privy-io/react-auth'
|
||||
import type {ReactNode} from 'react'
|
||||
import {useState} from 'react'
|
||||
import {Link} from 'react-router-dom'
|
||||
import {Link, NavLink} from 'react-router-dom'
|
||||
|
||||
import {NavItem} from '..'
|
||||
import useApiUrlStore from '../../../app/store/apiStore'
|
||||
import {UserClient} from '../../../generated/ManagingApi'
|
||||
import Logo from '../../../assets/img/logo.png'
|
||||
@@ -20,46 +18,51 @@ const navigation = [
|
||||
{ href: '/admin', name: 'Admin' },
|
||||
]
|
||||
|
||||
function navItems(isMobile = false) {
|
||||
return navigation.map((item) => (
|
||||
<NavItem key={item.name} href={item.href} isMobile={isMobile}>
|
||||
{item.name}
|
||||
</NavItem>
|
||||
))
|
||||
// Global Loader Component
|
||||
const GlobalLoader = () => {
|
||||
const isFetching = useIsFetching()
|
||||
return isFetching ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<Loader size="xs"></Loader>
|
||||
<span className="text-xs text-base-content/70 hidden lg:inline">Loading...</span>
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
|
||||
// Environment Badge Component
|
||||
const EnvironmentBadge = ({ environment }: { environment: 'local' | 'sandbox' | 'production' }) => {
|
||||
const badgeColors = {
|
||||
local: 'badge-warning',
|
||||
sandbox: 'badge-info',
|
||||
production: 'badge-success',
|
||||
}
|
||||
|
||||
const badgeLabels = {
|
||||
local: 'Local',
|
||||
sandbox: 'Sandbox',
|
||||
production: 'Production',
|
||||
}
|
||||
|
||||
function PrimaryNavbar() {
|
||||
return (
|
||||
<div className="flex">
|
||||
<Link className="btn btn-ghost text-xl normal-case" to={'/'}>
|
||||
<img src={Logo} className="App-logo" alt="logo" />
|
||||
</Link>
|
||||
{/* <NavItem href="#" /> */}
|
||||
<div className="md:flex items-center hidden space-x-1">{navItems()}</div>
|
||||
<div className={`badge ${badgeColors[environment]} badge-sm font-semibold`}>
|
||||
{badgeLabels[environment]}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const GlobalLoader = () => {
|
||||
const isFetching = useIsFetching()
|
||||
return isFetching ? <Loader size="xs"></Loader> : null
|
||||
}
|
||||
|
||||
// Custom Privy wallet button component
|
||||
const PrivyWalletButton = () => {
|
||||
// User Profile Dropdown Component
|
||||
const UserProfileDropdown = () => {
|
||||
const { login, logout, authenticated, user } = usePrivy()
|
||||
const { apiUrl } = useApiUrlStore()
|
||||
const { getCookie } = useCookie()
|
||||
const api = new UserClient({}, apiUrl)
|
||||
|
||||
// Get JWT token from cookies
|
||||
const jwtToken = getCookie('token')
|
||||
|
||||
// Fetch user information from the API
|
||||
const { data: userInfo } = useQuery({
|
||||
queryKey: ['user'],
|
||||
queryFn: () => api.user_GetCurrentUser(),
|
||||
enabled: authenticated && !!jwtToken, // Only fetch when authenticated AND JWT token exists
|
||||
enabled: authenticated && !!jwtToken,
|
||||
})
|
||||
|
||||
if (!authenticated) {
|
||||
@@ -73,8 +76,7 @@ const PrivyWalletButton = () => {
|
||||
)
|
||||
}
|
||||
|
||||
// Display username (agentName) or fallback to wallet address
|
||||
const displayName = userInfo?.name || (user?.wallet?.address
|
||||
const displayName = userInfo?.agentName || userInfo?.name || (user?.wallet?.address
|
||||
? `${user.wallet.address.slice(0, 6)}...${user.wallet.address.slice(-4)}`
|
||||
: 'Connected')
|
||||
|
||||
@@ -83,7 +85,6 @@ const PrivyWalletButton = () => {
|
||||
const copyToClipboard = async (text: string) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text)
|
||||
// You could add a toast notification here if you have a toast system
|
||||
} catch (err) {
|
||||
console.error('Failed to copy to clipboard:', err)
|
||||
}
|
||||
@@ -91,83 +92,134 @@ const PrivyWalletButton = () => {
|
||||
|
||||
return (
|
||||
<div className="dropdown dropdown-end">
|
||||
<label tabIndex={0} className="btn btn-primary btn-sm">
|
||||
{displayName}
|
||||
</label>
|
||||
<ul tabIndex={0} className="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-52">
|
||||
<li>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
{userInfo?.avatarUrl && (
|
||||
<label tabIndex={0} className="btn btn-ghost btn-sm gap-2 h-auto py-2 px-3">
|
||||
<div className="flex items-center gap-2">
|
||||
{userInfo?.avatarUrl ? (
|
||||
<img
|
||||
src={userInfo.avatarUrl}
|
||||
alt="User Avatar"
|
||||
className="w-6 h-6 rounded-full object-cover"
|
||||
className="w-8 h-8 rounded-full object-cover border-2 border-base-content/20"
|
||||
onError={(e) => {
|
||||
// Fallback to a default avatar if image fails to load
|
||||
e.currentTarget.style.display = 'none'
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
) : (
|
||||
<div className="w-8 h-8 rounded-full bg-primary/20 flex items-center justify-center border-2 border-base-content/20">
|
||||
<span className="text-xs font-semibold">
|
||||
{userInfo?.name?.[0]?.toUpperCase() || user?.wallet?.address?.[0]?.toUpperCase() || '?'}
|
||||
</span>
|
||||
</div>
|
||||
{userInfo?.agentName && (
|
||||
<span className="text-xs opacity-70 font-semibold">
|
||||
{userInfo.agentName}
|
||||
)}
|
||||
<div className="flex flex-col items-start hidden lg:flex">
|
||||
<span className="text-sm font-semibold leading-tight">{displayName}</span>
|
||||
{userInfo?.name && userInfo.name !== displayName && (
|
||||
<span className="text-xs opacity-60 leading-tight">{userInfo.name}</span>
|
||||
)}
|
||||
{walletAddress && (
|
||||
<span className="text-xs opacity-50 font-mono leading-tight">
|
||||
{walletAddress.slice(0, 6)}...{walletAddress.slice(-4)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<ul tabIndex={0} className="dropdown-content z-[1] menu p-3 shadow-lg bg-base-100 rounded-box w-64 mt-2">
|
||||
<li className="mb-2 pb-2 border-b border-base-300">
|
||||
<div className="flex items-center gap-3">
|
||||
{userInfo?.avatarUrl ? (
|
||||
<img
|
||||
src={userInfo.avatarUrl}
|
||||
alt="User Avatar"
|
||||
className="w-12 h-12 rounded-full object-cover"
|
||||
onError={(e) => {
|
||||
e.currentTarget.style.display = 'none'
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div className="w-12 h-12 rounded-full bg-primary/20 flex items-center justify-center">
|
||||
<span className="text-lg font-semibold">
|
||||
{userInfo?.name?.[0]?.toUpperCase() || user?.wallet?.address?.[0]?.toUpperCase() || '?'}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col">
|
||||
{userInfo?.agentName && (
|
||||
<span className="font-semibold">{userInfo.agentName}</span>
|
||||
)}
|
||||
{userInfo?.name && (
|
||||
<span className="text-sm opacity-70">{userInfo.name}</span>
|
||||
)}
|
||||
{walletAddress && (
|
||||
<div className="flex items-center gap-1 mt-1">
|
||||
<span className="text-xs opacity-60 font-mono">
|
||||
{walletAddress.slice(0, 8)}...{walletAddress.slice(-6)}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => copyToClipboard(walletAddress)}
|
||||
className="btn btn-xs btn-ghost p-0 h-4 w-4 min-h-0"
|
||||
title="Copy wallet address"
|
||||
>
|
||||
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{user?.linkedAccounts && user.linkedAccounts.length > 0 && (
|
||||
<>
|
||||
<li className="text-xs opacity-70 font-semibold">Linked Accounts:</li>
|
||||
<li className="text-xs font-semibold opacity-70 mb-1">Linked Accounts</li>
|
||||
{user.linkedAccounts.map((account, index) => (
|
||||
<li key={index} className="text-xs opacity-70">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="capitalize">{account.type}</span>
|
||||
<li key={index}>
|
||||
<div className="flex items-center justify-between py-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs capitalize font-medium">{account.type.replace('_', ' ')}</span>
|
||||
{account.type === 'wallet' && account.address && (
|
||||
<span className="opacity-60">
|
||||
{account.address.slice(0, 4)}...{account.address.slice(-4)}
|
||||
<span className="text-xs opacity-60 font-mono">
|
||||
{account.address.slice(0, 6)}...{account.address.slice(-4)}
|
||||
</span>
|
||||
)}
|
||||
{account.type === 'twitter_oauth' && (
|
||||
<span className="opacity-60">Twitter Connected</span>
|
||||
)}
|
||||
{account.type === 'google_oauth' && (
|
||||
<span className="opacity-60">Google Connected</span>
|
||||
)}
|
||||
{account.type === 'email' && account.address && (
|
||||
<span className="opacity-60">{account.address}</span>
|
||||
<span className="text-xs opacity-60">{account.address}</span>
|
||||
)}
|
||||
{(account.type === 'twitter_oauth' || account.type === 'google_oauth') && (
|
||||
<span className="text-xs opacity-60">Connected</span>
|
||||
)}
|
||||
</div>
|
||||
{(account.type === 'wallet' && account.address) || (account.type === 'email' && account.address) ? (
|
||||
{((account.type === 'wallet' && account.address) || (account.type === 'email' && account.address)) && (
|
||||
<button
|
||||
onClick={() => copyToClipboard(account.address)}
|
||||
className="btn btn-xs btn-ghost"
|
||||
className="btn btn-xs btn-ghost p-1 h-6 w-6 min-h-0"
|
||||
title="Copy to clipboard"
|
||||
>
|
||||
📋
|
||||
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</button>
|
||||
) : null}
|
||||
)}
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
<li className="divider my-1"></li>
|
||||
</>
|
||||
)}
|
||||
<li><a onClick={logout}>Disconnect</a></li>
|
||||
<li>
|
||||
<a onClick={logout} className="text-error font-medium">Disconnect</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function SecondaryNavbar() {
|
||||
// Environment Dropdown Component
|
||||
const EnvironmentDropdown = ({ isInSidebar = false }: { isInSidebar?: boolean }) => {
|
||||
const { environment, prepareEnvironmentChange } = useApiUrlStore()
|
||||
const { logout } = usePrivy()
|
||||
|
||||
const handleEnvironmentChange = async (newEnv: 'local' | 'sandbox' | 'production') => {
|
||||
prepareEnvironmentChange(newEnv)
|
||||
|
||||
if (logout) {
|
||||
await logout()
|
||||
}
|
||||
@@ -175,83 +227,184 @@ export function SecondaryNavbar() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="md:flex items-center hidden space-x-3">
|
||||
<GlobalLoader />
|
||||
<div className="form-control">
|
||||
<select
|
||||
className="select select-bordered select-sm"
|
||||
value={environment}
|
||||
onChange={(e) => handleEnvironmentChange(e.target.value as 'local' | 'sandbox' | 'production')}
|
||||
<div className={`dropdown ${isInSidebar ? 'dropdown-start w-full' : 'dropdown-end'}`}>
|
||||
<label tabIndex={0} className={`cursor-pointer ${isInSidebar ? 'w-full' : ''}`}>
|
||||
<EnvironmentBadge environment={environment} />
|
||||
</label>
|
||||
<ul tabIndex={0} className="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-40">
|
||||
<li>
|
||||
<a
|
||||
onClick={() => handleEnvironmentChange('local')}
|
||||
className={environment === 'local' ? 'active' : ''}
|
||||
>
|
||||
<option value="local">Local</option>
|
||||
<option value="sandbox">Sandbox</option>
|
||||
<option value="production">Production</option>
|
||||
</select>
|
||||
</div>
|
||||
<PrivyWalletButton />
|
||||
Local
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
onClick={() => handleEnvironmentChange('sandbox')}
|
||||
className={environment === 'sandbox' ? 'active' : ''}
|
||||
>
|
||||
Sandbox
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
onClick={() => handleEnvironmentChange('production')}
|
||||
className={environment === 'production' ? 'active' : ''}
|
||||
>
|
||||
Production
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
type MobileMenuButtonProps = {
|
||||
onClick: VoidFunction
|
||||
export default function NavBar() {
|
||||
const [sidebarOpen, setSidebarOpen] = useState<boolean>(false)
|
||||
const { environment } = useApiUrlStore()
|
||||
const { authenticated } = usePrivy()
|
||||
|
||||
const closeSidebar = () => {
|
||||
setSidebarOpen(false)
|
||||
}
|
||||
|
||||
const toggleSidebar = () => {
|
||||
setSidebarOpen(!sidebarOpen)
|
||||
}
|
||||
|
||||
function MobileMenuButton({ onClick }: MobileMenuButtonProps) {
|
||||
return (
|
||||
<div className="md:hidden flex items-center">
|
||||
<button className="mobile-menu-button outline-none" onClick={onClick}>
|
||||
<>
|
||||
{/* Navbar */}
|
||||
<div className="navbar bg-base-300 shadow-lg border-b border-base-content/10 w-full relative z-50">
|
||||
{/* Navbar Start - Mobile Menu Button and Logo */}
|
||||
<div className="navbar-start">
|
||||
<button
|
||||
className="btn btn-ghost lg:hidden"
|
||||
onClick={toggleSidebar}
|
||||
aria-label="Open sidebar"
|
||||
>
|
||||
<svg
|
||||
className=" hover:text-primary text-accent w-6 h-6"
|
||||
x-show="!showMenu"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-5 w-5"
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path d="M4 6h16M4 12h16M4 18h16" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 6h16M4 12h16M4 18h16" />
|
||||
</svg>
|
||||
</button>
|
||||
<Link className="btn btn-ghost text-xl" to="/">
|
||||
<img src={Logo} className="h-8 w-8 object-contain" alt="logo" />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Navbar Center - Desktop Navigation */}
|
||||
<div className="navbar-center hidden lg:flex">
|
||||
<ul className="menu menu-horizontal px-1 gap-1">
|
||||
{navigation.map((item) => (
|
||||
<li key={item.name}>
|
||||
<NavLink
|
||||
to={item.href}
|
||||
className={({ isActive }) =>
|
||||
`px-3 py-2 rounded-md font-medium transition-all duration-200 ${
|
||||
isActive
|
||||
? 'text-primary bg-base-200/30 font-semibold'
|
||||
: 'text-base-content hover:text-primary hover:bg-base-200/50'
|
||||
}`
|
||||
}
|
||||
>
|
||||
{item.name}
|
||||
</NavLink>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Navbar End - Loader, Environment, User */}
|
||||
<div className="navbar-end gap-2">
|
||||
<GlobalLoader />
|
||||
<div className="hidden md:flex items-center gap-2">
|
||||
<span className="text-xs opacity-70 hidden xl:inline">Environment:</span>
|
||||
<EnvironmentDropdown isInSidebar={false} />
|
||||
</div>
|
||||
{/* Show environment badge on mobile */}
|
||||
<div className="md:hidden flex items-center">
|
||||
<EnvironmentBadge environment={environment} />
|
||||
</div>
|
||||
<UserProfileDropdown />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Sidebar */}
|
||||
{sidebarOpen && (
|
||||
<>
|
||||
{/* Backdrop */}
|
||||
<div
|
||||
className="fixed inset-0 bg-black/50 z-40 lg:hidden"
|
||||
onClick={closeSidebar}
|
||||
/>
|
||||
|
||||
{/* Sidebar */}
|
||||
<aside className={`fixed top-0 left-0 h-full w-64 bg-base-200 text-base-content z-50 lg:hidden transform transition-transform duration-300 ease-in-out ${
|
||||
sidebarOpen ? 'translate-x-0' : '-translate-x-full'
|
||||
}`}>
|
||||
{/* Sidebar Header */}
|
||||
<div className="p-4 border-b border-base-300">
|
||||
<div className="flex items-center justify-between">
|
||||
<Link className="flex items-center gap-2" to="/" onClick={closeSidebar}>
|
||||
<img src={Logo} className="h-8 w-8 object-contain" alt="logo" />
|
||||
<span className="font-bold text-lg">Managing</span>
|
||||
</Link>
|
||||
<button
|
||||
className="btn btn-sm btn-circle btn-ghost"
|
||||
onClick={closeSidebar}
|
||||
aria-label="Close sidebar"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
type MobileMenuProps = {
|
||||
isOpen: boolean
|
||||
}
|
||||
|
||||
function MobileMenu({ isOpen }: MobileMenuProps) {
|
||||
return (
|
||||
<div className={isOpen ? 'mobile-menu' : 'hidden mobile-menu'}>
|
||||
<ul>{navItems(true)}</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
type NavContainerProps = {
|
||||
children: ReactNode
|
||||
isMenuOpen: boolean
|
||||
}
|
||||
|
||||
function NavContainer({ children, isMenuOpen }: NavContainerProps) {
|
||||
return (
|
||||
<nav className="bg-base-300 shadow-lg">
|
||||
<div className="max-w-6xl px-4 mx-auto">
|
||||
<div className="flex justify-between">{children}</div>
|
||||
</div>
|
||||
|
||||
<MobileMenu isOpen={isMenuOpen} />
|
||||
</nav>
|
||||
)
|
||||
{/* Sidebar Menu */}
|
||||
<ul className="menu p-4 w-full">
|
||||
{navigation.map((item) => (
|
||||
<li key={item.name}>
|
||||
<NavLink
|
||||
to={item.href}
|
||||
className={({ isActive }) =>
|
||||
isActive ? 'active' : ''
|
||||
}
|
||||
export default function NavBar() {
|
||||
const [isMenuOpen, setIsMenuOpen] = useState<boolean>(false)
|
||||
onClick={closeSidebar}
|
||||
>
|
||||
{item.name}
|
||||
</NavLink>
|
||||
</li>
|
||||
))}
|
||||
|
||||
return (
|
||||
<NavContainer isMenuOpen={isMenuOpen}>
|
||||
<PrimaryNavbar />
|
||||
<SecondaryNavbar />
|
||||
<MobileMenuButton onClick={() => setIsMenuOpen(!isMenuOpen)} />
|
||||
</NavContainer>
|
||||
{authenticated && (
|
||||
<li>
|
||||
<div className="px-4 py-2">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-sm opacity-70">Environment:</span>
|
||||
</div>
|
||||
<EnvironmentDropdown isInSidebar={true} />
|
||||
</div>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</aside>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,28 +3,30 @@ import { NavLink } from 'react-router-dom'
|
||||
import type {INavItemProps} from '../../../global/interface'
|
||||
|
||||
function navLinkClasses(isActive: boolean, isMobile: boolean) {
|
||||
let commonClasses = 'block text-sm px-2 py-4'
|
||||
let commonClasses = 'block text-sm px-3 py-2 rounded-md transition-all duration-200'
|
||||
if (isMobile) {
|
||||
return `${commonClasses} ${
|
||||
isActive
|
||||
? 'text-base-content bg-primary font-semibold'
|
||||
: 'hover:bg-primary transition duration-300'
|
||||
: 'hover:bg-base-200 transition duration-300'
|
||||
}`
|
||||
}
|
||||
commonClasses =
|
||||
'py-4 px-2 font-semibold hover:text-primary transition duration-300'
|
||||
return `${commonClasses} ${isActive ? 'text-primary' : 'text-base-content'}`
|
||||
'px-3 py-2 rounded-md font-medium hover:text-primary hover:bg-base-200/50 transition-all duration-200'
|
||||
return `${commonClasses} ${isActive ? 'text-primary bg-base-200/30 font-semibold' : 'text-base-content'}`
|
||||
}
|
||||
|
||||
export default function NavItem({
|
||||
children,
|
||||
href,
|
||||
isMobile = false,
|
||||
onClick,
|
||||
}: INavItemProps) {
|
||||
const item = (
|
||||
<NavLink
|
||||
to={href}
|
||||
className={({ isActive }) => navLinkClasses(isActive, isMobile)}
|
||||
onClick={onClick}
|
||||
>
|
||||
{children}
|
||||
</NavLink>
|
||||
|
||||
@@ -1,26 +1,9 @@
|
||||
import {ArrowDownIcon, ArrowUpIcon} from '@heroicons/react/solid'
|
||||
import React from 'react'
|
||||
import {useExpanded, useFilters, usePagination, useSortBy, useTable,} from 'react-table'
|
||||
import {useExpanded, usePagination, useSortBy, useTable,} from 'react-table'
|
||||
|
||||
import type {TableInstanceWithHooks} from '../../../global/type.tsx'
|
||||
|
||||
// Define a default UI for filtering
|
||||
function DefaultColumnFilter({
|
||||
column: { filterValue, preFilteredRows, setFilter },
|
||||
}: any) {
|
||||
const count = preFilteredRows.length
|
||||
|
||||
return (
|
||||
<input
|
||||
value={filterValue || ''}
|
||||
onChange={(e) => {
|
||||
setFilter(e.target.value || undefined) // Set undefined to remove the filter entirely
|
||||
}}
|
||||
placeholder={`Search ${count} records...`}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default function Table({
|
||||
columns,
|
||||
data,
|
||||
@@ -29,13 +12,6 @@ export default function Table({
|
||||
hiddenColumns,
|
||||
showTotal = false,
|
||||
}: any) {
|
||||
const defaultColumn = React.useMemo<any>(
|
||||
() => ({
|
||||
// Let's set up our default Filter UI
|
||||
Filter: DefaultColumnFilter,
|
||||
}),
|
||||
[]
|
||||
)
|
||||
// Use the state and functions returned from useTable to build your UI
|
||||
const {
|
||||
getTableProps,
|
||||
@@ -57,12 +33,10 @@ export default function Table({
|
||||
{
|
||||
columns,
|
||||
data,
|
||||
defaultColumn, // Be sure to pass the defaultColumn option
|
||||
initialState: {
|
||||
hiddenColumns: hiddenColumns ? hiddenColumns : [],
|
||||
},
|
||||
},
|
||||
useFilters,
|
||||
useSortBy,
|
||||
useExpanded,
|
||||
usePagination
|
||||
@@ -101,9 +75,6 @@ export default function Table({
|
||||
''
|
||||
)}
|
||||
</span>
|
||||
<div>
|
||||
{column.canFilter ? column.render('Filter') : null}
|
||||
</div>
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
|
||||
@@ -220,6 +220,7 @@ export type INavItemProps = {
|
||||
children: ReactNode
|
||||
href: string
|
||||
isMobile?: boolean
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
export type IStatusBadge = {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {Outlet} from 'react-router-dom'
|
||||
import { ToastContainer } from 'react-toastify'
|
||||
|
||||
import '../styles/app.css'
|
||||
import {NavBar} from '../components/mollecules'
|
||||
@@ -8,8 +7,8 @@ const LayoutMain = (): JSX.Element => {
|
||||
return (
|
||||
<>
|
||||
<NavBar></NavBar>
|
||||
<main className="layout mt-6">
|
||||
<div className="container mx-auto">
|
||||
<main className="mt-6">
|
||||
<div className="w-full">
|
||||
<Outlet />
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -12,6 +12,7 @@ const WhitelistSettings: React.FC = () => {
|
||||
const [pageSize, setPageSize] = useState(20)
|
||||
const [searchExternalEthereumAccount, setSearchExternalEthereumAccount] = useState<string>('')
|
||||
const [searchTwitterAccount, setSearchTwitterAccount] = useState<string>('')
|
||||
const [filtersOpen, setFiltersOpen] = useState<boolean>(false)
|
||||
|
||||
const whitelistClient = new WhitelistClient({}, apiUrl)
|
||||
|
||||
@@ -60,10 +61,18 @@ const WhitelistSettings: React.FC = () => {
|
||||
<div>
|
||||
<div className="container mx-auto">
|
||||
<div className="mb-4">
|
||||
<h2 className="text-2xl font-bold mb-4">Whitelist Accounts</h2>
|
||||
|
||||
{/* Search Filters */}
|
||||
<div className="flex gap-4 mb-4">
|
||||
{/* Search Filters Collapsible */}
|
||||
<div className="collapse collapse-arrow bg-base-200 mb-4">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={filtersOpen}
|
||||
onChange={(e) => setFiltersOpen(e.target.checked)}
|
||||
/>
|
||||
<div className="collapse-title text-lg font-medium">
|
||||
Search Filters
|
||||
</div>
|
||||
<div className="collapse-content">
|
||||
<div className="flex gap-4 pt-4">
|
||||
<div className="form-control w-full max-w-xs">
|
||||
<label className="label">
|
||||
<span className="label-text">Search by Ethereum Account</span>
|
||||
@@ -90,6 +99,8 @@ const WhitelistSettings: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<WhitelistTable
|
||||
list={accounts}
|
||||
|
||||
@@ -12,6 +12,7 @@ const WhitelistSettings: React.FC = () => {
|
||||
const [pageSize, setPageSize] = useState(20)
|
||||
const [searchExternalEthereumAccount, setSearchExternalEthereumAccount] = useState<string>('')
|
||||
const [searchTwitterAccount, setSearchTwitterAccount] = useState<string>('')
|
||||
const [filtersOpen, setFiltersOpen] = useState<boolean>(false)
|
||||
|
||||
const whitelistClient = new WhitelistClient({}, apiUrl)
|
||||
|
||||
@@ -60,10 +61,18 @@ const WhitelistSettings: React.FC = () => {
|
||||
<div>
|
||||
<div className="container mx-auto">
|
||||
<div className="mb-4">
|
||||
<h2 className="text-2xl font-bold mb-4">Whitelist Accounts</h2>
|
||||
|
||||
{/* Search Filters */}
|
||||
<div className="flex gap-4 mb-4">
|
||||
{/* Search Filters Collapsible */}
|
||||
<div className="collapse collapse-arrow bg-base-200 mb-4">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={filtersOpen}
|
||||
onChange={(e) => setFiltersOpen(e.target.checked)}
|
||||
/>
|
||||
<div className="collapse-title text-lg font-medium">
|
||||
Search Filters
|
||||
</div>
|
||||
<div className="collapse-content">
|
||||
<div className="flex gap-4 pt-4">
|
||||
<div className="form-control w-full max-w-xs">
|
||||
<label className="label">
|
||||
<span className="label-text">Search by Ethereum Account</span>
|
||||
@@ -90,6 +99,8 @@ const WhitelistSettings: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<WhitelistTable
|
||||
list={accounts}
|
||||
|
||||
@@ -3,9 +3,15 @@
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
body {
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
@@ -21,3 +27,10 @@
|
||||
@apply sm:w-11/12 w-10/12 mx-auto;
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.container {
|
||||
max-width: 100% !important;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,14 @@ module.exports = {
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
padding: '1rem',
|
||||
screens: {
|
||||
sm: '640px',
|
||||
md: '768px',
|
||||
lg: '1024px',
|
||||
xl: '1280px',
|
||||
'2xl': '1536px',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user