diff --git a/app/dashboard/layout.tsx b/app/dashboard/layout.tsx index cb0795e..86826ea 100644 --- a/app/dashboard/layout.tsx +++ b/app/dashboard/layout.tsx @@ -27,18 +27,17 @@ export default function Layout({ children }: LayoutProps) { const { isCollapsed } = useSidebarStore(); return ( -
- +
{/* Main content */}
-
- {children} -
+ className={`transition-all duration-300 md:pr-4 ${ + isCollapsed ? "lg:ml-20" : "lg:ml-68" + }`} + > + +
{children}
); diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index 75c821c..b7d6cac 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -1,35 +1,25 @@ -import { ArrowDown, ArrowUp, ChevronRight } from "lucide-react"; +"use client"; + +import { useState } from "react"; +import { Download, Upload, Copy, ChevronDown, Eye, EyeOff } from "lucide-react"; import { Button } from "@/components/ui/button"; -import { BalanceCard } from "@/components/dashboard/home/balance-card"; -import { PortfolioCard } from "@/components/dashboard/convert/portfolio-card"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; import { TransactionsTable } from "@/components/dashboard/transaction/transactions-table"; - -const portfolioData = [ - { - symbol: "BTC", - value: 150.27, - change: 0.01, - isPositive: true, - }, - { - symbol: "SOL", - value: 178.52, - change: -4.38, - isPositive: false, - }, - { - symbol: "SOL", - value: 178.52, - change: -4.38, - isPositive: false, - }, - { - symbol: "SOL", - value: 178.52, - change: -4.38, - isPositive: false, - }, -]; +import { + BalanceCard, + ActionButton, + MobileActionButton, + CurrencyDropdownItem, + CryptoRateCard, + currencyOptions, + mobileActions, + currencyValues, + cryptoRates, +} from "@/components/dashboard/home"; const transactionData = [ { @@ -64,56 +54,143 @@ const transactionData = [ }, ]; -export default function Dashboard() { +export default function DashboardContent() { + const [balanceVisible, setBalanceVisible] = useState(true); + const [selectedCurrency, setSelectedCurrency] = useState("ETH"); + return ( -
-
- {/* Header Actions */} -
- - +
+
+
+

+ Overview +

+
+
+
+
+ + Total balance + + +
+
+
+ 0x5A8...beF1832 + +
+
+
- {/* Balance Card */} - +
+ + {balanceVisible ? "₦" : "₦"} + + + {balanceVisible ? "325,980" : "••••••••"} + + + {balanceVisible ? "." : ""} + + + {balanceVisible ? "65" : ""} + +
+
- {/* Portfolio Cards */} -
- {portfolioData.map((item, index) => ( - - ))} +
+ + +
+ +
+ + + + + + + + {currencyOptions.map((currency) => ( + setSelectedCurrency(currency.code)} + /> + ))} + + +
+ {currencyValues[selectedCurrency]} +
+
+
+
+ +
+ {mobileActions.map((action, index) => ( + + ))} +
- {/* Recent Transactions */} -
-
-

- Recent Transactions -

- +
+
+
+ Exchange Rates +
+
+ See All
+
+
+ {cryptoRates.map((crypto, index) => ( + + ))} +
+
- +
+
+ Recent Transactions
+
); diff --git a/app/dashboard/settings/page.tsx b/app/dashboard/settings/page.tsx new file mode 100644 index 0000000..3ca261a --- /dev/null +++ b/app/dashboard/settings/page.tsx @@ -0,0 +1,7 @@ +import React from "react"; + +const Settings = () => { + return
Settings
; +}; + +export default Settings; diff --git a/app/globals.css b/app/globals.css index 78f79d5..c11f91b 100644 --- a/app/globals.css +++ b/app/globals.css @@ -42,6 +42,48 @@ --radius-md: calc(var(--radius) - 2px); --radius-lg: var(--radius); --radius-xl: calc(var(--radius) + 4px); + + /* Custom App Colors */ + --color-brand-primary: var(--brand-primary); + --color-brand-primary-hover: var(--brand-primary-hover); + --color-brand-secondary: var(--brand-secondary); + --color-brand-secondary-hover: var(--brand-secondary-hover); + --color-text-primary: var(--text-primary); + --color-text-secondary: var(--text-secondary); + --color-text-tertiary: var(--text-tertiary); + --color-text-crypto-symbol: var(--text-crypto-symbol); + --color-text-crypto-name: var(--text-crypto-name); + --color-text-crypto-price: var(--text-crypto-price); + --color-text-success: var(--text-success); + --color-text-error: var(--text-error); + --color-text-muted: var(--text-muted); + --color-text-contact: var(--text-contact); + --color-text-link: var(--text-link); + --color-text-naira: var(--text-naira); + --color-text-see-all: var(--text-see-all); + --color-bg-main-desktop: var(--bg-main-desktop); + --color-bg-balance-card-desktop: var(--bg-balance-card-desktop); + --color-bg-sidebar: var(--bg-sidebar); + --color-bg-icon-container: var(--bg-icon-container); + --color-bg-input: var(--bg-input); + --color-bg-selector: var(--bg-selector); + --color-bg-conversion-form: var(--bg-conversion-form); + --color-bg-transactions-header: var(--bg-transactions-header); + --color-bg-glass-light: var(--bg-glass-light); + --color-bg-glass-medium: var(--bg-glass-medium); + --color-bg-dropdown: var(--bg-dropdown); + --color-bg-dropdown-hover: var(--bg-dropdown-hover); + --color-bg-selector-icon: var(--bg-selector-icon); + --color-bg-contact-blue: var(--bg-contact-blue); + --color-bg-contact-yellow: var(--bg-contact-yellow); + --color-border-crypto-card: var(--border-crypto-card); + --color-border-glass: var(--border-glass); + --color-success-bg: var(--success-bg); + --color-success-text: var(--success-text); + --color-error-bg: var(--error-bg); + --color-error-text: var(--error-text); + --color-contact-icon-blue: var(--contact-icon-blue); + --color-contact-icon-yellow: var(--contact-icon-yellow); } :root { @@ -77,6 +119,59 @@ --sidebar-accent-foreground: oklch(0.205 0 0); --sidebar-border: oklch(0.922 0 0); --sidebar-ring: oklch(0.708 0 0); + + /* Custom App Colors */ + /* Brand Colors */ + --brand-primary: #ffd552; + --brand-primary-hover: #e6c04a; + --brand-secondary: #e9eaee; + --brand-secondary-hover: #d3d4d8; + + /* Text Colors */ + --text-primary: #262626; + --text-secondary: #011028; + --text-tertiary: #5e5e5e; + --text-crypto-symbol: #070707; + --text-crypto-name: #adabb6; + --text-crypto-price: #000816; + --text-success: #009411; + --text-error: #c80808; + --text-muted: #787878; + --text-contact: #71717a; + --text-link: #3c78f4; + --text-naira: rgba(54, 54, 54, 0.7); + --text-see-all: rgba(48, 48, 48, 0.8); + + /* Background Colors */ + --bg-main-desktop: #fbfbfb; + --bg-balance-card-desktop: #eff0f5; + --bg-sidebar: #f5f5f5; + --bg-icon-container: #f5f5f5; + --bg-input: #e5e5e5; + --bg-selector: #ebebeb; + --bg-conversion-form: #efeded; + --bg-transactions-header: rgba(206, 206, 206, 0.15); + --bg-glass-light: rgba(255, 255, 255, 0.3); + --bg-glass-medium: rgba(255, 255, 255, 0.6); + --bg-dropdown: rgba(224, 224, 224, 0.6); + --bg-dropdown-hover: rgba(224, 224, 224, 0.7); + --bg-selector-icon: rgba(183, 183, 183, 0.6); + --bg-contact-blue: #d9e9ff; + --bg-contact-yellow: #fdfcbd; + + /* Border Colors */ + --border-crypto-card: rgba(121, 121, 121, 0.4); + --border-glass: rgba(255, 255, 255, 0.3); + + /* Success/Error States */ + --success-bg: rgba(27, 183, 45, 0.2); + --success-text: rgba(0, 148, 17, 0.9); + --error-bg: rgba(200, 8, 8, 0.2); + --error-text: rgba(200, 8, 8, 0.9); + + /* Contact Page Colors */ + --contact-icon-blue: #3c78f4; + --contact-icon-yellow: #e8b300; } .dark { @@ -121,5 +216,4 @@ body { @apply bg-background text-foreground; } - -} \ No newline at end of file +} diff --git a/components/dashboard/convert/conversion-form.tsx b/components/dashboard/convert/conversion-form.tsx index 485b912..0ea8d93 100644 --- a/components/dashboard/convert/conversion-form.tsx +++ b/components/dashboard/convert/conversion-form.tsx @@ -40,7 +40,7 @@ export function ConversionForm() {
{/* From Section */}
-
+
@@ -77,7 +77,12 @@ export function ConversionForm() { {/* Swap Icon */}
- arrow + arrow
diff --git a/components/dashboard/convert/currency-selector.tsx b/components/dashboard/convert/currency-selector.tsx index 61cdb74..1866073 100644 --- a/components/dashboard/convert/currency-selector.tsx +++ b/components/dashboard/convert/currency-selector.tsx @@ -41,7 +41,8 @@ export function CurrencySelector({ variant === "default" ? "bg-blue-500 text-lg hover:bg-blue-600 text-white rounded-full" : "" - } ${className}`}> + } ${className}`} + > {currency} @@ -49,14 +50,16 @@ export function CurrencySelector({ onFocusOutside={() => { setOpen(false); }} - className="sm:max-w-[611px] h-[223px] px-6 py-3 overflow-hidden mt-1 -ml-64"> + className="sm:max-w-[611px] h-[223px] px-6 py-3 overflow-hidden mt-1 -ml-64" + >
Select Token
@@ -65,7 +68,7 @@ export function CurrencySelector({
@@ -76,8 +79,9 @@ export function CurrencySelector({ onSelect(currency); setOpen(false); }} - className="flex bg-[#EBEBEB] cursor-pointer flex-col items-center px-3 py-1 rounded-lg border border-gray-200 hover:border-gray-300 transition-colors"> -
+ className="flex bg-bg-selector cursor-pointer flex-col items-center px-3 py-1 rounded-lg border border-gray-200 hover:border-gray-300 transition-colors" + > +
{/* {currency.icon} */}
{currency.code} @@ -89,7 +93,8 @@ export function CurrencySelector({
diff --git a/components/dashboard/home/action-button.tsx b/components/dashboard/home/action-button.tsx new file mode 100644 index 0000000..e4d8843 --- /dev/null +++ b/components/dashboard/home/action-button.tsx @@ -0,0 +1,28 @@ +import React from "react"; +import { Button } from "@/components/ui/button"; + +interface ActionButtonProps { + icon: React.ComponentType; + label: string; + variant?: "primary" | "secondary"; +} + +export function ActionButton({ + icon: Icon, + label, + variant = "primary", +}: ActionButtonProps) { + const isPrimary = variant === "primary"; + const variantClasses = isPrimary + ? "bg-brand-primary text-black hover:bg-brand-primary-hover" + : "border-[0.035rem] border-border-glass bg-brand-secondary text-black hover:bg-brand-secondary-hover"; + + return ( + + ); +} diff --git a/components/dashboard/home/balance-card.tsx b/components/dashboard/home/balance-card.tsx index d1fcc7b..1dcde2b 100644 --- a/components/dashboard/home/balance-card.tsx +++ b/components/dashboard/home/balance-card.tsx @@ -1,38 +1,27 @@ -import { Info, ChevronDown } from "lucide-react"; +import React from "react"; -export function BalanceCard() { - return ( -
-
-
- Total Balance - -
- -
-

N325,980.65

-
+interface BalanceCardProps { + title: string; + value: string; + children?: React.ReactNode; +} -
-
-
-
-
NGN
-
N325,980.65
+export function BalanceCard({ title, value, children }: BalanceCardProps) { + return ( +
+
+ {children ? ( + children + ) : ( + <> +
+ {title}
-
- -
-
-
-
- USD - -
-
$1,160.52
+
+ {value}
-
-
+ + )}
); diff --git a/components/dashboard/home/constants.ts b/components/dashboard/home/constants.ts new file mode 100644 index 0000000..35a7796 --- /dev/null +++ b/components/dashboard/home/constants.ts @@ -0,0 +1,63 @@ +import { CurrencyOption } from "./currency-dropdown-item"; +import { CryptoRate } from "./crypto-rate-card"; + +export interface MobileAction { + icon: string; + label: string; + alt: string; +} + +export const currencyOptions: CurrencyOption[] = [ + // { code: "USD", name: "US Dollar", icon: "/usd.svg" }, + { code: "BTC", name: "Bitcoin", icon: "/bnb.svg" }, + { code: "ETH", name: "Ethereum", icon: "/eth.svg" }, +]; + +export const mobileActions: MobileAction[] = [ + { icon: "/deposit.svg", label: "Deposit", alt: "deposit" }, + { icon: "/withdraw.svg", label: "Withdrawal", alt: "withdraw" }, + { icon: "/convert.svg", label: "Convert", alt: "convert" }, +]; + +export const currencyValues: Record = { + BTC: "₿0.012", + ETH: "Ξ0.45", +}; + +export const cryptoRates: CryptoRate[] = [ + { + symbol: "ETH", + name: "Ethereum", + price: "$150.27", + change: "+0.01%", + positive: true, + }, + { + symbol: "BNB", + name: "Binance usd", + price: "$617.78", + change: "+0.01%", + positive: true, + }, + { + symbol: "BNB", + name: "Binance usd", + price: "$617.78", + change: "+0.01%", + positive: true, + }, + { + symbol: "BNB", + name: "Binance usd", + price: "$617.78", + change: "+0.01%", + positive: true, + }, + { + symbol: "BNB", + name: "Binance usd", + price: "$617.78", + change: "+0.01%", + positive: true, + }, +]; diff --git a/components/dashboard/home/crypto-rate-card.tsx b/components/dashboard/home/crypto-rate-card.tsx new file mode 100644 index 0000000..1484323 --- /dev/null +++ b/components/dashboard/home/crypto-rate-card.tsx @@ -0,0 +1,61 @@ +import React from "react"; +import Image from "next/image"; + +export interface CryptoRate { + symbol: string; + name: string; + price: string; + change: string; + positive: boolean; +} + +interface CryptoRateCardProps { + crypto: CryptoRate; +} + +export function CryptoRateCard({ crypto }: CryptoRateCardProps) { + const renderIcon = () => { + switch (crypto.symbol) { + case "ETH": + return ETH; + case "BNB": + return BNB; + default: + return ( +
+ B +
+ ); + } + }; + + return ( +
+
+
+
+ {crypto.symbol} +
+
+ {crypto.name} +
+
+
+ {renderIcon()} +
+
+
+
+ {crypto.price} +
+
+ {crypto.change} +
+
+
+ ); +} diff --git a/components/dashboard/home/currency-dropdown-item.tsx b/components/dashboard/home/currency-dropdown-item.tsx new file mode 100644 index 0000000..d04b83c --- /dev/null +++ b/components/dashboard/home/currency-dropdown-item.tsx @@ -0,0 +1,29 @@ +import React from "react"; +import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; + +export interface CurrencyOption { + code: string; + name: string; + icon: string; // image path for the currency icon +} + +interface CurrencyDropdownItemProps { + currency: CurrencyOption; + onClick: () => void; +} + +export function CurrencyDropdownItem({ + currency, + onClick, +}: CurrencyDropdownItemProps) { + return ( + +
+ {currency.code} + + {currency.code} - {currency.name} + +
+
+ ); +} diff --git a/components/dashboard/home/index.ts b/components/dashboard/home/index.ts new file mode 100644 index 0000000..93a8bcf --- /dev/null +++ b/components/dashboard/home/index.ts @@ -0,0 +1,17 @@ + +export { BalanceCard } from "./balance-card"; +export { ActionButton } from "./action-button"; +export { MobileActionButton } from "./mobile-action-button"; +export { CurrencyDropdownItem } from "./currency-dropdown-item"; +export { CryptoRateCard } from "./crypto-rate-card"; + +export { + currencyOptions, + mobileActions, + currencyValues, + cryptoRates, +} from "./constants"; + +export type { CurrencyOption } from "./currency-dropdown-item"; +export type { CryptoRate } from "./crypto-rate-card"; +export type { MobileAction } from "./constants"; diff --git a/components/dashboard/home/mobile-action-button.tsx b/components/dashboard/home/mobile-action-button.tsx new file mode 100644 index 0000000..27febbf --- /dev/null +++ b/components/dashboard/home/mobile-action-button.tsx @@ -0,0 +1,27 @@ +import React from "react"; +import { Button } from "@/components/ui/button"; +import Image from "next/image"; + +interface MobileActionButtonProps { + icon: string; + label: string; + alt: string; +} + +export function MobileActionButton({ + icon, + label, + alt, +}: MobileActionButtonProps) { + return ( + + ); +} diff --git a/components/dashboard/transaction/TransactionList.tsx b/components/dashboard/transaction/TransactionList.tsx index 86c5a8c..ac9fedf 100644 --- a/components/dashboard/transaction/TransactionList.tsx +++ b/components/dashboard/transaction/TransactionList.tsx @@ -77,7 +77,8 @@ function StatusBadge({ status }: { status: "Success" | "Failed" }) { return ( + className="bg-success-bg text-success-text font-bold flex items-center gap-1 rounded-full px-2 py-1.5 min-w-[106px] hover:bg-green-100" + > Success @@ -87,7 +88,8 @@ function StatusBadge({ status }: { status: "Success" | "Failed" }) { return ( + className="bg-error-bg text-error-text border-red-200 font-bold flex items-center gap-1 rounded-full min-w-[106px] px-2 py-1.5 hover:bg-red-100" + > Failed @@ -102,7 +104,8 @@ function MobileAccordionRow({ transaction }: { transaction: Transaction }) { <> setIsOpen(!isOpen)}> + onClick={() => setIsOpen(!isOpen)} + > {/* TYPE */}
@@ -165,7 +168,7 @@ function MobileAccordionRow({ transaction }: { transaction: Transaction }) { {/* USD Value */}
USD Value: - + {transaction.usdAmount}
@@ -239,7 +242,8 @@ function Pagination({ size="sm" onClick={() => onPageChange(currentPage - 1)} disabled={currentPage === 1} - className="px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"> + className="px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed" + > Previous @@ -254,7 +258,8 @@ function Pagination({ currentPage === pageNum ? "px-3 py-2 text-sm font-medium text-white bg-orange-500 border border-orange-500 rounded-md hover:bg-orange-600" : "px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50" - }> + } + > {pageNum} ))} @@ -265,7 +270,8 @@ function Pagination({ size="sm" onClick={() => onPageChange(currentPage + 1)} disabled={currentPage === totalPages} - className="px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"> + className="px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed" + > Next
@@ -330,35 +336,40 @@ export function TransactionList() {
setActiveFilter(value as TransactionFilter)} - className="w-auto"> + className="w-auto" + > + className="data-[state=active]:bg-brand-primary data-[state=active]:text-black/70 text-black/60 px-5 py-3 text-xs rounded-xl" + > All {counts.All > 0 && {counts.All}} + className="data-[state=active]:bg-brand-primary data-[state=active]:text-black/70 + text-black/60 px-5 py-3 text-xs rounded-xl" + > Deposit + className="data-[state=active]:bg-brand-primary data-[state=active]:text-black/70 + text-black/60 px-5 py-3 text-xs rounded-xl" + > Withdrawal + className="data-[state=active]:bg-brand-primary data-[state=active]:text-black/70 text-black/60 px-5 py-3 text-xs rounded-xl" + > Convert @@ -367,7 +378,7 @@ export function TransactionList() { {/* Transactions Table or Empty State */}
- {(isLoading || !data) ? ( + {isLoading || !data ? (
Loading transactions...
@@ -413,7 +424,8 @@ export function TransactionList() { {/* Desktop Row */} + className="border-b border-black/20 hover:bg-gray-50/50 hidden md:table-row" + > {/* Type */}
@@ -454,7 +466,7 @@ export function TransactionList() {
{transaction.amount}
-
+
{transaction.usdAmount}
diff --git a/components/header/Navbar.tsx b/components/header/Navbar.tsx index c00d032..43c3dbb 100644 --- a/components/header/Navbar.tsx +++ b/components/header/Navbar.tsx @@ -1,7 +1,7 @@ // components/Navbar.tsx "use client"; -import { Menu, ChevronLeft, BellDot } from "lucide-react"; +import { Menu, BellDot, ChevronLeft } from "lucide-react"; import { useSidebarStore } from "@/store/sidebarStore"; import Image from "next/image"; // import clsx from "clsx"; @@ -23,103 +23,47 @@ export default function Navbar() { const pathname = usePathname(); const currentPageName = pageNames[pathname] || "Dashboard"; - // const [active, setActive] = useState("Dashboard"); - const { isCollapsed, toggleCollapse, toggleMobile } = useSidebarStore(); + const { toggleMobile, isCollapsed, isMobileOpen } = useSidebarStore(); return ( - + {/* Avatar */} +
+ Avatar
diff --git a/components/header/Sidebar.tsx b/components/header/Sidebar.tsx index 478cc60..f31aa54 100644 --- a/components/header/Sidebar.tsx +++ b/components/header/Sidebar.tsx @@ -1,21 +1,29 @@ // components/Sidebar.tsx "use client"; -import { Home, Wallet, Bell, User, ArrowDownUp, X } from "lucide-react"; +import { + Home, + Wallet, + User, + ArrowDownUp, + ChevronLeft, + Copy, +} from "lucide-react"; import { useSidebarStore } from "@/store/sidebarStore"; import { useEffect, useState } from "react"; import { useRouter } from "next/navigation"; +import Image from "next/image"; const initialMenuItems = [ { icon: Home, label: "Dashboard", href: "/dashboard", active: true }, { icon: ArrowDownUp, label: "Convert", href: "/dashboard/convert" }, - { icon: Wallet, label: "Wallet", href: "/dashboard/wallet" }, - { icon: Bell, label: "Notifications", href: "/dashboard/notifications" }, + { icon: Wallet, label: "Transaction", href: "/dashboard/transactions" }, { icon: User, label: "Profile", href: "/dashboard/profile" }, ]; export default function Sidebar() { - const { isCollapsed, isMobileOpen, closeMobile } = useSidebarStore(); + const { isCollapsed, isMobileOpen, closeMobile, toggleCollapse } = + useSidebarStore(); const [menuItems, setMenuItems] = useState(initialMenuItems); const router = useRouter(); @@ -35,6 +43,18 @@ export default function Sidebar() { const sidebar = document.getElementById("sidebar"); const target = event.target as Node; + // Also close on click of a menu item on mobile + const menuButton = (event.target as HTMLElement).closest("button"); + if ( + isMobileOpen && + menuButton && + sidebar && + sidebar.contains(menuButton) + ) { + closeMobile(); + return; + } + if (isMobileOpen && sidebar && !sidebar.contains(target)) { closeMobile(); } @@ -53,25 +73,97 @@ export default function Sidebar() { <> {/* Mobile overlay */} {isMobileOpen && ( -
+
)} {/* Sidebar */}