From 54b94ff6532f0ec42f8073b82f3a43531686da75 Mon Sep 17 00:00:00 2001 From: legend4tech Date: Fri, 31 Oct 2025 09:54:05 +0100 Subject: [PATCH 1/6] feat: added ai simulation and about page --- .../nextjs/app/dashboard/investor/page.tsx | 43 +++ .../nextjs/app/dashboard/realtor/page.tsx | 159 +++++++++ packages/nextjs/app/layout.tsx | 3 + .../components/auth/investor-signup-form.tsx | 25 +- .../components/auth/realtor-signup-form.tsx | 4 +- .../investor-dashboard/business-metrics.tsx | 41 +++ .../investor-dashboard/dashboard-stats.tsx | 66 ++++ .../investor-dashboard/my-properties.tsx | 123 +++++++ .../investor-dashboard/portfolio-chart.tsx | 78 +++++ .../investor-dashboard/quick-actions.tsx | 50 +++ .../recent-transactions.tsx | 61 ++++ packages/nextjs/components/landing/hero.tsx | 2 +- .../nextjs/components/landing/listings.tsx | 12 +- .../components/landing/properties-list.tsx | 11 +- .../realtor-dashboard/commission-chart.tsx | 91 ++++++ .../realtor-dashboard/monthly-stats.tsx | 24 ++ .../quick-actions-realtor.tsx | 57 ++++ .../realtor-dashboard/realtor-stats.tsx | 58 ++++ .../realtor-dashboard/recent-activity.tsx | 54 ++++ packages/nextjs/package.json | 2 + packages/nextjs/pnpm-lock.yaml | 302 +++++++++++++++++- 21 files changed, 1250 insertions(+), 16 deletions(-) create mode 100644 packages/nextjs/app/dashboard/investor/page.tsx create mode 100644 packages/nextjs/app/dashboard/realtor/page.tsx create mode 100644 packages/nextjs/components/investor-dashboard/business-metrics.tsx create mode 100644 packages/nextjs/components/investor-dashboard/dashboard-stats.tsx create mode 100644 packages/nextjs/components/investor-dashboard/my-properties.tsx create mode 100644 packages/nextjs/components/investor-dashboard/portfolio-chart.tsx create mode 100644 packages/nextjs/components/investor-dashboard/quick-actions.tsx create mode 100644 packages/nextjs/components/investor-dashboard/recent-transactions.tsx create mode 100644 packages/nextjs/components/realtor-dashboard/commission-chart.tsx create mode 100644 packages/nextjs/components/realtor-dashboard/monthly-stats.tsx create mode 100644 packages/nextjs/components/realtor-dashboard/quick-actions-realtor.tsx create mode 100644 packages/nextjs/components/realtor-dashboard/realtor-stats.tsx create mode 100644 packages/nextjs/components/realtor-dashboard/recent-activity.tsx diff --git a/packages/nextjs/app/dashboard/investor/page.tsx b/packages/nextjs/app/dashboard/investor/page.tsx new file mode 100644 index 0000000..48fded4 --- /dev/null +++ b/packages/nextjs/app/dashboard/investor/page.tsx @@ -0,0 +1,43 @@ +import { BusinessMetrics } from "~~/components/investor-dashboard/business-metrics" +import { DashboardStats } from "~~/components/investor-dashboard/dashboard-stats" +import { MyProperties } from "~~/components/investor-dashboard/my-properties" +import { PortfolioChart } from "~~/components/investor-dashboard/portfolio-chart" +import { QuickActions } from "~~/components/investor-dashboard/quick-actions" +import { RecentTransactions } from "~~/components/investor-dashboard/recent-transactions" + +export const metadata = { + title: "Investor Dashboard | reAI", + description: "Manage your tokenized property investments", +} + +export default function InvestorDashboard() { + return ( +
+
+
+

Welcome Back, Alex Johnson!

+

Track your tokenized property investments and portfolio performance

+
+ + {/* Stats Cards */} + + + {/* Main Content Grid */} +
+ {/* Left Column - 2/3 width */} +
+ + +
+ + {/* Right Column - 1/3 width */} +
+ + + +
+
+
+
+ ) +} diff --git a/packages/nextjs/app/dashboard/realtor/page.tsx b/packages/nextjs/app/dashboard/realtor/page.tsx new file mode 100644 index 0000000..29827c4 --- /dev/null +++ b/packages/nextjs/app/dashboard/realtor/page.tsx @@ -0,0 +1,159 @@ +"use client" + +import { useState } from "react" +import { Search, Filter, Plus, Eye, Calendar } from "lucide-react" +import { Card } from "~~/components/ui/card" +import { Button } from "~~/components/ui/button" +import { Input } from "~~/components/ui/input" +import { Badge } from "~~/components/ui/badge" +import { Progress } from "~~/components/ui/progress" +import { properties } from "~~/data/properties-data" +import { RealtorStats } from "~~/components/realtor-dashboard/realtor-stats" +import { CommissionChart } from "~~/components/realtor-dashboard/commission-chart" +import { RecentActivity } from "~~/components/realtor-dashboard/recent-activity" +import { QuickActionsRealtor } from "~~/components/realtor-dashboard/quick-actions-realtor" +import { MonthlyStats } from "~~/components/realtor-dashboard/monthly-stats" + + +export default function RealtorDashboard() { + const [searchQuery, setSearchQuery] = useState("") + + // Use our existing properties data + const realtorProperties = properties.map((property, index) => ({ + ...property, + title: property.name, // Add this line to create a title field from name + investors: [45, 67, 23, 38, 52][index] || 30, + fundingProgress: [85, 92, 68, 75, 88][index] || 70, + addedDate: ["2024-01-15", "2024-01-10", "2024-01-20", "2024-01-08", "2024-01-12"][index] || "2024-01-15", + status: index < 2 ? "Active" : index === 2 ? "Funding" : "Active", +})) + + const filteredProperties = realtorProperties.filter((property) => + property.title.toLowerCase().includes(searchQuery.toLowerCase()), + ) + + return ( +
+ {/* Header */} +
+
+

Welcome back, Sarah Mitchell

+

+ Manage your property listings, track commissions, and grow your business. +

+
+
+ +
+ {/* Stats Grid */} + + + {/* Main Content Grid */} +
+ {/* Left Column - Property Listings & Commission Chart */} +
+ {/* Property Listings */} + +
+

My Property Listings

+ +
+ + {/* Search and Filter */} +
+
+ + setSearchQuery(e.target.value)} + className="pl-10 h-12" + /> +
+ +
+ + {/* Property Cards */} +
+ {filteredProperties.map((property) => ( + +
+
+
+

{property.title}

+ + {property.status} + +
+

+ 📍 + {property.location} +

+ +
+
+

Value

+

{property.totalValue} STRK

+
+
+

APY

+

{property.apy}

+
+
+

Investors

+

{property.investors}

+
+
+ +
+
+

Funding Progress

+

{property.fundingProgress}%

+
+ +
+ +
+
+ + Added {property.addedDate} +
+ +
+
+
+
+ ))} +
+
+ + {/* Commission Performance Chart */} + +
+ + {/* Right Column - Recent Activity, Quick Actions, Monthly Stats */} +
+ + + +
+
+
+
+ ) +} diff --git a/packages/nextjs/app/layout.tsx b/packages/nextjs/app/layout.tsx index f7b3979..bced2c8 100644 --- a/packages/nextjs/app/layout.tsx +++ b/packages/nextjs/app/layout.tsx @@ -2,6 +2,7 @@ import type { Metadata } from "next"; import { ScaffoldStarkAppWithProviders } from "~~/components/ScaffoldStarkAppWithProviders"; import "~~/styles/globals.css"; import { ThemeProvider } from "~~/components/ThemeProvider"; +import { Toaster } from "sonner"; export const metadata: Metadata = { title: "Scaffold-Stark", @@ -15,6 +16,8 @@ const ScaffoldStarkApp = ({ children }: { children: React.ReactNode }) => { + + {children} diff --git a/packages/nextjs/components/auth/investor-signup-form.tsx b/packages/nextjs/components/auth/investor-signup-form.tsx index f485ec1..a97e25c 100644 --- a/packages/nextjs/components/auth/investor-signup-form.tsx +++ b/packages/nextjs/components/auth/investor-signup-form.tsx @@ -10,15 +10,13 @@ import { useRouter } from "next/navigation" import { Button } from "~~/components/ui/button" import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "~~/components/ui/form" import { Input } from "~~/components/ui/input" +import { toast } from "sonner" const investorSignupSchema = z.object({ - fullName: z.string().min(2, { message: "Full name must be at least 2 characters" }), - nin: z - .string() - .length(11, { message: "NIN must be exactly 11 digits" }) - .regex(/^\d+$/, { message: "NIN must contain only numbers" }), - email: z.string().email({ message: "Please enter a valid email address" }), - password: z.string().min(8, { message: "Password must be at least 8 characters" }), + fullName: z.string().min(2, "Full name must be at least 2 characters"), + nin: z.string().length(11, "NIN must be exactly 11 digits").regex(/^\d+$/, "NIN must contain only numbers"), + email: z.string().email("Please enter a valid email address"), + password: z.string().min(8, "Password must be at least 8 characters"), phone: z.string().optional(), }) @@ -43,9 +41,16 @@ export function InvestorSignupForm() { async function onSubmit(data: InvestorSignupFormValues) { setIsLoading(true) console.log("[v0] Investor signup form submitted:", data) - await new Promise((resolve) => setTimeout(resolve, 1000)) + await new Promise((resolve) => setTimeout(resolve, 1500)) setIsLoading(false) - router.push("/dashboard") + + toast.success("Registration successful! Welcome to reAI", { + description: "Redirecting to your dashboard...", + }) + + setTimeout(() => { + router.push("/dashboard/investor") + }, 1000) } return ( @@ -170,4 +175,4 @@ export function InvestorSignupForm() { ) -} \ No newline at end of file +} diff --git a/packages/nextjs/components/auth/realtor-signup-form.tsx b/packages/nextjs/components/auth/realtor-signup-form.tsx index fded500..af46b33 100644 --- a/packages/nextjs/components/auth/realtor-signup-form.tsx +++ b/packages/nextjs/components/auth/realtor-signup-form.tsx @@ -10,6 +10,7 @@ import { useRouter } from "next/navigation" import { Button } from "~~/components/ui/button" import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "~~/components/ui/form" import { Input } from "~~/components/ui/input" +import { toast } from "sonner" const MAX_FILE_SIZE = 5 * 1024 * 1024 const ACCEPTED_FILE_TYPES = ["image/png", "image/jpeg", "image/jpg", "application/pdf"] @@ -55,7 +56,8 @@ export function RealtorSignupForm() { console.log("[v0] Realtor signup form submitted:", data) await new Promise((resolve) => setTimeout(resolve, 1000)) setIsLoading(false) - router.push("/dashboard") + toast.success("Registration successful! Welcome to reAI") + router.push("/dashboard/realtor") } return ( diff --git a/packages/nextjs/components/investor-dashboard/business-metrics.tsx b/packages/nextjs/components/investor-dashboard/business-metrics.tsx new file mode 100644 index 0000000..2bf4d27 --- /dev/null +++ b/packages/nextjs/components/investor-dashboard/business-metrics.tsx @@ -0,0 +1,41 @@ +"use client" + +import { Card } from "~~/components/ui/card" +import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip } from "recharts" + +const data = [ + { name: "Lagos Properties", value: 60, color: "#10b981" }, + { name: "Abuja Properties", value: 30, color: "#3b82f6" }, + { name: "Other Cities", value: 10, color: "#a855f7" }, +] + +export function BusinessMetrics() { + return ( + +

Business Metrics

+
+ {data.map((item) => ( +
+
+
+ {item.name} +
+ {item.value}% +
+ ))} +
+
+ + + + {data.map((entry, index) => ( + + ))} + + `${value}%`} /> + + +
+ + ) +} diff --git a/packages/nextjs/components/investor-dashboard/dashboard-stats.tsx b/packages/nextjs/components/investor-dashboard/dashboard-stats.tsx new file mode 100644 index 0000000..e141236 --- /dev/null +++ b/packages/nextjs/components/investor-dashboard/dashboard-stats.tsx @@ -0,0 +1,66 @@ +"use client" + +import { TrendingUp, Wallet, DollarSign, ArrowUpRight } from "lucide-react" +import { Card } from "~~/components/ui/card" + +const stats = [ + { + title: "Total Portfolio Value", + value: "1,250 STRK", + change: "+16.67%", + icon: TrendingUp, + iconBg: "bg-emerald-100", + iconColor: "text-emerald-600", + }, + { + title: "Total Invested", + value: "1,070 STRK", + subtitle: "Across 3 Properties", + icon: Wallet, + iconBg: "bg-blue-100", + iconColor: "text-blue-600", + }, + { + title: "Monthly Income", + value: "45 STRK", + subtitle: "From rental yields", + icon: DollarSign, + iconBg: "bg-purple-100", + iconColor: "text-purple-600", + }, + { + title: "Total Returns", + value: "180 STRK", + change: "+16.67%", + icon: ArrowUpRight, + iconBg: "bg-orange-100", + iconColor: "text-orange-600", + }, +] + +export function DashboardStats() { + return ( +
+ {stats.map((stat) => ( + +
+
+

{stat.title}

+

{stat.value}

+ {stat.change && ( +

+ + {stat.change} +

+ )} + {stat.subtitle &&

{stat.subtitle}

} +
+
+ +
+
+
+ ))} +
+ ) +} diff --git a/packages/nextjs/components/investor-dashboard/my-properties.tsx b/packages/nextjs/components/investor-dashboard/my-properties.tsx new file mode 100644 index 0000000..d683ce7 --- /dev/null +++ b/packages/nextjs/components/investor-dashboard/my-properties.tsx @@ -0,0 +1,123 @@ +"use client" + +import { Card } from "~~/components/ui/card" +import { Badge } from "~~/components/ui/badge" +import { Progress } from "~~/components/ui/progress" +import { MapPin, TrendingUp } from "lucide-react" +import { Button } from "~~/components/ui/button" +import Link from "next/link" + +const properties = [ + { + id: 1, + name: "Lekki Pearl Towers", + location: "Lekki Phase 1, Lagos", + image: "/estate-img-4.jpg", + + invested: "500 STRK", + currentValue: "580 STRK", + monthlyIncome: "25 STRK", + change: "+16.0%", + ownership: 168000, + ownershipPercent: 70, + status: "Active", + }, + { + id: 2, + name: "Banana Island Mansion", + location: "Banana Island, Lagos", + image: "/estate-img-3.jpg", + + invested: "450 STRK", + currentValue: "520 STRK", + monthlyIncome: "18 STRK", + change: "+15.6%", + ownership: 150000, + ownershipPercent: 65, + status: "Active", + }, + { + id: 3, + name: "Ikeja GRA Residence", + + location: "Ikeja GRA, Lagos", + + image: "/estate-img-2.jpg", + + invested: "120 STRK", + currentValue: "150 STRK", + monthlyIncome: "2 STRK", + change: "+25.0%", + ownership: 40000, + ownershipPercent: 40, + status: "Active", + }, +] + +export function MyProperties() { + return ( + +
+

My Properties

+ + + +
+
+ {properties.map((property) => ( + +
+ {property.name} +
+
+
+

{property.name}

+

+ + {property.location} +

+
+ {property.status} +
+ +
+
+

Invested

+

{property.invested}

+
+
+

Current Value

+

{property.currentValue}

+
+
+

Monthly Income

+

{property.monthlyIncome}

+
+
+

Change

+

+ + {property.change} +

+
+
+ +
+
+ Ownership: {property.ownership.toLocaleString()} + {property.ownershipPercent}% +
+ +
+
+
+
+ ))} +
+
+ ) +} diff --git a/packages/nextjs/components/investor-dashboard/portfolio-chart.tsx b/packages/nextjs/components/investor-dashboard/portfolio-chart.tsx new file mode 100644 index 0000000..9d580ad --- /dev/null +++ b/packages/nextjs/components/investor-dashboard/portfolio-chart.tsx @@ -0,0 +1,78 @@ +"use client" + +import { useState } from "react" +import { Card } from "~~/components/ui/card" +import { Button } from "~~/components/ui/button" +import { Area, AreaChart, CartesianGrid, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts" + +const data7d = [ + { date: "Mon", value: 1050 }, + { date: "Tue", value: 1100 }, + { date: "Wed", value: 1080 }, + { date: "Thu", value: 1150 }, + { date: "Fri", value: 1200 }, + { date: "Sat", value: 1220 }, + { date: "Sun", value: 1250 }, +] + +const data30d = [ + { date: "Week 1", value: 950 }, + { date: "Week 2", value: 1050 }, + { date: "Week 3", value: 1150 }, + { date: "Week 4", value: 1250 }, +] + +const data90d = [ + { date: "Month 1", value: 800 }, + { date: "Month 2", value: 1000 }, + { date: "Month 3", value: 1250 }, +] + +export function PortfolioChart() { + const [period, setPeriod] = useState<"7d" | "30d" | "90d">("7d") + + const data = period === "7d" ? data7d : period === "30d" ? data30d : data90d + + return ( + +
+

Investment Growth

+
+ {(["7d", "30d", "90d"] as const).map((p) => ( + + ))} +
+
+ + + + + + + + + + + + [`${value} STRK`, "Portfolio Value"]} + /> + + + +
+ ) +} diff --git a/packages/nextjs/components/investor-dashboard/quick-actions.tsx b/packages/nextjs/components/investor-dashboard/quick-actions.tsx new file mode 100644 index 0000000..f1c75ff --- /dev/null +++ b/packages/nextjs/components/investor-dashboard/quick-actions.tsx @@ -0,0 +1,50 @@ + +import { Card } from "~~/components/ui/card" +import { Button } from "~~/components/ui/button" +import { Search, Target, FileText, Users } from "lucide-react" +import Link from "next/link" + +const actions = [ + { + label: "Explore New Properties", + icon: Search, + href: "/properties", + variant: "default" as const, + }, + { + label: "Set Investment Goal", + icon: Target, + href: "#", + variant: "outline" as const, + }, + { + label: "Download Documents", + icon: FileText, + href: "#", + variant: "outline" as const, + }, + +] + +export function QuickActions() { + return ( + +

Quick Actions

+
+ {actions.map((action) => ( + + + + ))} +
+
+ ) +} diff --git a/packages/nextjs/components/investor-dashboard/recent-transactions.tsx b/packages/nextjs/components/investor-dashboard/recent-transactions.tsx new file mode 100644 index 0000000..cf1a944 --- /dev/null +++ b/packages/nextjs/components/investor-dashboard/recent-transactions.tsx @@ -0,0 +1,61 @@ + +import { Card } from "~~/components/ui/card" +import { ArrowUpRight, ArrowDownLeft } from "lucide-react" + +const transactions = [ + { + id: 1, + type: "Investment", + property: "Lagos Duplex", + amount: "100 STRK", + date: "2024-01-15", + icon: ArrowUpRight, + iconBg: "bg-blue-100", + iconColor: "text-blue-600", + }, + { + id: 2, + type: "Dividend", + property: "Abuja Apartments", + amount: "18.5 STRK", + date: "2024-01-10", + icon: ArrowDownLeft, + iconBg: "bg-emerald-100", + iconColor: "text-emerald-600", + }, + { + id: 3, + type: "Investment", + property: "Lekki Terrace", + amount: "50 STRK", + date: "2024-01-08", + icon: ArrowUpRight, + iconBg: "bg-blue-100", + iconColor: "text-blue-600", + }, +] + +export function RecentTransactions() { + return ( + +

Recent Transactions

+
+ {transactions.map((transaction) => ( +
+
+ +
+
+

{transaction.type}

+

{transaction.property}

+
+
+

{transaction.amount}

+

{transaction.date}

+
+
+ ))} +
+
+ ) +} diff --git a/packages/nextjs/components/landing/hero.tsx b/packages/nextjs/components/landing/hero.tsx index b16201b..d7e82cd 100644 --- a/packages/nextjs/components/landing/hero.tsx +++ b/packages/nextjs/components/landing/hero.tsx @@ -20,7 +20,7 @@ export function Hero() {

- Invest in Lagos, Abuja, Enugu and Anambra Properties with as little as{" "} + Invest in Anambra, Lagos, Abuja, Enugu and Many more.. Properties with as little as{" "} 50 STRK, tokenize, trade, and earn yield all on blockchain

diff --git a/packages/nextjs/components/landing/listings.tsx b/packages/nextjs/components/landing/listings.tsx index 62fe386..a6f9c7c 100644 --- a/packages/nextjs/components/landing/listings.tsx +++ b/packages/nextjs/components/landing/listings.tsx @@ -8,8 +8,11 @@ import { Progress } from "~~/components/ui/progress" import { MapPin, TrendingUp } from "lucide-react" import { properties, Property } from "~~/data/properties-data" import { PropertyAnalysisModal } from "./property-analysis-modal" +import { useRouter } from "next/navigation" + export function Listings() { + const router = useRouter() const [selectedProperty, setSelectedProperty] = useState(null) const [isAnalysisOpen, setIsAnalysisOpen] = useState(false) @@ -18,6 +21,10 @@ export function Listings() { setIsAnalysisOpen(true) } + const handleInvest = () => { + router.push("/signup?type=investor") + } + return ( <>
@@ -101,7 +108,10 @@ export function Listings() { > Analyse -
diff --git a/packages/nextjs/components/landing/properties-list.tsx b/packages/nextjs/components/landing/properties-list.tsx index 966e9d9..1a5893c 100644 --- a/packages/nextjs/components/landing/properties-list.tsx +++ b/packages/nextjs/components/landing/properties-list.tsx @@ -9,9 +9,11 @@ import { Input } from "~~/components/ui/input" import { MapPin, TrendingUp, Search } from "lucide-react" import { properties, Property } from "~~/data/properties-data" import { PropertyAnalysisModal } from "./property-analysis-modal" +import { useRouter } from "next/navigation" export function PropertiesList() { + const router = useRouter() const [searchQuery, setSearchQuery] = useState("") const [selectedProperty, setSelectedProperty] = useState(null) const [isAnalysisOpen, setIsAnalysisOpen] = useState(false) @@ -27,6 +29,10 @@ export function PropertiesList() { setIsAnalysisOpen(true) } + const handleInvest = () => { + router.push("/signup?type=investor") + } + return ( <> {/* Search Bar */} @@ -124,7 +130,10 @@ export function PropertiesList() { > Analyse - diff --git a/packages/nextjs/components/realtor-dashboard/commission-chart.tsx b/packages/nextjs/components/realtor-dashboard/commission-chart.tsx new file mode 100644 index 0000000..6f1b1a3 --- /dev/null +++ b/packages/nextjs/components/realtor-dashboard/commission-chart.tsx @@ -0,0 +1,91 @@ +"use client" + +import { useState } from "react" +import { Card } from "~~/components/ui/card" +import { Button } from "~~/components/ui/button" +import { Area, AreaChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts" + +const data7d = [ + { day: "Mon", earnings: 2.5 }, + { day: "Tue", earnings: 3.2 }, + { day: "Wed", earnings: 2.8 }, + { day: "Thu", earnings: 4.1 }, + { day: "Fri", earnings: 3.6 }, + { day: "Sat", earnings: 5.2 }, + { day: "Sun", earnings: 4.8 }, +] + +const data30d = [ + { day: "Week 1", earnings: 18.5 }, + { day: "Week 2", earnings: 22.3 }, + { day: "Week 3", earnings: 19.8 }, + { day: "Week 4", earnings: 25.6 }, +] + +const data90d = [ + { day: "Month 1", earnings: 65.2 }, + { day: "Month 2", earnings: 72.8 }, + { day: "Month 3", earnings: 78.5 }, +] + +export function CommissionChart() { + const [period, setPeriod] = useState<"7d" | "30d" | "90d">("7d") + + const data = period === "7d" ? data7d : period === "30d" ? data30d : data90d + + return ( + +
+

Commission Performance

+
+ + + +
+
+ +
+ + + + + + + + + + + [`${value} STRK`, "Earnings"]} + /> + + + +
+
+ ) +} diff --git a/packages/nextjs/components/realtor-dashboard/monthly-stats.tsx b/packages/nextjs/components/realtor-dashboard/monthly-stats.tsx new file mode 100644 index 0000000..695534d --- /dev/null +++ b/packages/nextjs/components/realtor-dashboard/monthly-stats.tsx @@ -0,0 +1,24 @@ +import { Card } from "~~/components/ui/card" + +export function MonthlyStats() { + const stats = [ + { label: "Properties Listed", value: "5" }, + { label: "Total Investments", value: "485 STRK" }, + { label: "Commission Earned", value: "38.8 STRK" }, + { label: "New Investors", value: "38" }, + ] + + return ( + +

This Month

+
+ {stats.map((stat, index) => ( +
+

{stat.label}

+

{stat.value}

+
+ ))} +
+
+ ) +} diff --git a/packages/nextjs/components/realtor-dashboard/quick-actions-realtor.tsx b/packages/nextjs/components/realtor-dashboard/quick-actions-realtor.tsx new file mode 100644 index 0000000..d54d0dd --- /dev/null +++ b/packages/nextjs/components/realtor-dashboard/quick-actions-realtor.tsx @@ -0,0 +1,57 @@ +import { Plus, BarChart3, FileText, Users } from "lucide-react" +import { Card } from "~~/components/ui/card" +import { Button } from "~~/components/ui/button" + +export function QuickActionsRealtor() { + const actions = [ + { + icon: Plus, + iconBg: "bg-emerald-600", + title: "Add Property", + description: "List a new property for tokenization", + }, + { + icon: BarChart3, + iconBg: "bg-blue-600", + title: "View Analytics", + description: "Check detailed performance metrics", + }, + { + icon: FileText, + iconBg: "bg-purple-600", + title: "Manage Documents", + description: "Upload and organize property documents", + }, + { + icon: Users, + iconBg: "bg-orange-600", + title: "Investor Relations", + description: "Communicate with your investors", + }, + ] + + return ( + +

Quick Actions

+
+ {actions.map((action, index) => ( + + ))} +
+
+ ) +} diff --git a/packages/nextjs/components/realtor-dashboard/realtor-stats.tsx b/packages/nextjs/components/realtor-dashboard/realtor-stats.tsx new file mode 100644 index 0000000..a858de6 --- /dev/null +++ b/packages/nextjs/components/realtor-dashboard/realtor-stats.tsx @@ -0,0 +1,58 @@ +import { Building2, DollarSign, Users, TrendingUp } from "lucide-react" +import { Card } from "~~/components/ui/card" + +export function RealtorStats() { + const stats = [ + { + title: "Total Properties", + value: "5", + change: "+2 this month", + icon: Building2, + iconBg: "bg-blue-100", + iconColor: "text-blue-600", + }, + { + title: "Total Value", + value: "485 STRK", + change: "+15% this quarter", + icon: DollarSign, + iconBg: "bg-emerald-100", + iconColor: "text-emerald-600", + }, + { + title: "Active Investors", + value: "225", + change: "+38 this week", + icon: Users, + iconBg: "bg-purple-100", + iconColor: "text-purple-600", + }, + { + title: "Avg. ROI", + value: "8.2%", + change: "+0.3% vs last month", + icon: TrendingUp, + iconBg: "bg-orange-100", + iconColor: "text-orange-600", + }, + ] + + return ( +
+ {stats.map((stat) => ( + +
+
+

{stat.title}

+

{stat.value}

+

{stat.change}

+
+
+ +
+
+
+ ))} +
+ ) +} diff --git a/packages/nextjs/components/realtor-dashboard/recent-activity.tsx b/packages/nextjs/components/realtor-dashboard/recent-activity.tsx new file mode 100644 index 0000000..a53ce5d --- /dev/null +++ b/packages/nextjs/components/realtor-dashboard/recent-activity.tsx @@ -0,0 +1,54 @@ +import { DollarSign, Target, Users, FileText } from "lucide-react" +import { Card } from "~~/components/ui/card" + +export function RecentActivity() { + const activities = [ + { + icon: DollarSign, + iconBg: "bg-emerald-100", + iconColor: "text-emerald-600", + title: "New investment of 50 STRK in Lagos Luxury Duplex", + time: "2 hours ago", + }, + { + icon: Target, + iconBg: "bg-blue-100", + iconColor: "text-blue-600", + title: "Abuja Executive Apartments reached 92% funding", + time: "5 hours ago", + }, + { + icon: Users, + iconBg: "bg-purple-100", + iconColor: "text-purple-600", + title: "15 new investors joined your properties today", + time: "1 day ago", + }, + { + icon: FileText, + iconBg: "bg-orange-100", + iconColor: "text-orange-600", + title: "Property documents updated for Lekki Modern Terrace", + time: "2 days ago", + }, + ] + + return ( + +

Recent Activity

+
+ {activities.map((activity, index) => ( +
+
+ +
+
+

{activity.title}

+

{activity.time}

+
+
+ ))} +
+
+ ) +} diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index b497d96..fc2aa72 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -56,6 +56,8 @@ "react-dom": "19.0.0", "react-hook-form": "^7.65.0", "react-hot-toast": "^2.4.1", + "recharts": "^3.3.0", + "sonner": "^2.0.7", "starknet": "8.5.3", "tailwind-merge": "^3.3.1", "tailwindcss-animate": "^1.0.7", diff --git a/packages/nextjs/pnpm-lock.yaml b/packages/nextjs/pnpm-lock.yaml index 2672629..18fc858 100644 --- a/packages/nextjs/pnpm-lock.yaml +++ b/packages/nextjs/pnpm-lock.yaml @@ -128,6 +128,12 @@ importers: react-hot-toast: specifier: ^2.4.1 version: 2.6.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + recharts: + specifier: ^3.3.0 + version: 3.3.0(@types/react@19.0.12)(react-dom@19.0.0(react@19.0.0))(react-is@17.0.2)(react@19.0.0)(redux@5.0.1) + sonner: + specifier: ^2.0.7 + version: 2.0.7(react-dom@19.0.0(react@19.0.0))(react@19.0.0) starknet: specifier: 8.5.3 version: 8.5.3 @@ -148,7 +154,7 @@ importers: version: 4.1.12 zustand: specifier: ^4.1.2 - version: 4.5.7(@types/react@19.0.12)(react@19.0.0) + version: 4.5.7(@types/react@19.0.12)(immer@10.2.0)(react@19.0.0) devDependencies: '@tailwindcss/postcss': specifier: ^4 @@ -2094,6 +2100,17 @@ packages: '@types/react-dom': optional: true + '@reduxjs/toolkit@2.9.2': + resolution: {integrity: sha512-ZAYu/NXkl/OhqTz7rfPaAhY0+e8Fr15jqNxte/2exKUxvHyQ/hcqmdekiN1f+Lcw3pE+34FCgX+26zcUE3duCg==} + peerDependencies: + react: ^16.9.0 || ^17.0.0 || ^18 || ^19 + react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 + peerDependenciesMeta: + react: + optional: true + react-redux: + optional: true + '@rolldown/pluginutils@1.0.0-beta.27': resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} @@ -2271,6 +2288,9 @@ packages: '@sinclair/typebox@0.25.24': resolution: {integrity: sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==} + '@standard-schema/spec@1.0.0': + resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + '@standard-schema/utils@0.3.0': resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} @@ -2467,6 +2487,33 @@ packages: '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + + '@types/d3-shape@3.1.7': + resolution: {integrity: sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} @@ -2533,6 +2580,9 @@ packages: '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@types/use-sync-external-store@0.0.6': + resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} + '@typescript-eslint/eslint-plugin@8.46.2': resolution: {integrity: sha512-ZGBMToy857/NIPaaCucIUQgqueOiq7HeAKkhlvqVV4lm089zUFW6ikRySx2v+cAhKeUCPuWVHeimyk6Dw1iY3w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3413,6 +3463,50 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-format@3.1.0: + resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + daisyui@5.3.10: resolution: {integrity: sha512-vmjyPmm0hvFhA95KB6uiGmWakziB2pBv6CUcs5Ka/3iMBMn9S+C3SZYx9G9l2JrgTZ1EFn61F/HrPcwaUm2kLQ==} @@ -3461,6 +3555,9 @@ packages: supports-color: optional: true + decimal.js-light@2.5.1: + resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} + decimal.js@10.6.0: resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} @@ -3625,6 +3722,9 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} + es-toolkit@1.41.0: + resolution: {integrity: sha512-bDd3oRmbVgqZCJS6WmeQieOrzpl3URcWBUVDXxOELlUW2FuW+0glPOz1n0KnRie+PdyvUZcXz2sOn00c6pPRIA==} + esbuild-android-64@0.14.47: resolution: {integrity: sha512-R13Bd9+tqLVFndncMHssZrPWe6/0Kpv2/dt4aA69soX4PRxlzsVpCvoJeFE8sOEoeVEiBkI0myjlkDodXlHa0g==} engines: {node: '>=12'} @@ -4255,6 +4355,9 @@ packages: resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} engines: {node: '>= 4'} + immer@10.2.0: + resolution: {integrity: sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==} + import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} @@ -4286,6 +4389,10 @@ packages: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + interpret@1.4.0: resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==} engines: {node: '>= 0.10'} @@ -5294,6 +5401,18 @@ packages: react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + react-redux@9.2.0: + resolution: {integrity: sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==} + peerDependencies: + '@types/react': ^18.2.25 || ^19 + react: ^18.0 || ^19 + redux: ^5.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + redux: + optional: true + react-refresh@0.17.0: resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} @@ -5343,6 +5462,14 @@ packages: resolution: {integrity: sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ==} engines: {node: '>=8.10.0'} + recharts@3.3.0: + resolution: {integrity: sha512-Vi0qmTB0iz1+/Cz9o5B7irVyUjX2ynvEgImbgMt/3sKRREcUM07QiYjS1QpAVrkmVlXqy5gykq4nGWMz9AS4Rg==} + engines: {node: '>=18'} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-is: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + rechoir@0.6.2: resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} engines: {node: '>= 0.10'} @@ -5358,6 +5485,14 @@ packages: redeyed@2.1.1: resolution: {integrity: sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==} + redux-thunk@3.1.0: + resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==} + peerDependencies: + redux: ^5.0.0 + + redux@5.0.1: + resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} + reflect.getprototypeof@1.0.10: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} @@ -5392,6 +5527,9 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + reselect@5.1.1: + resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} + resolve-cwd@3.0.0: resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} engines: {node: '>=8'} @@ -5610,6 +5748,12 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} + sonner@2.0.7: + resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + source-list-map@2.0.1: resolution: {integrity: sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==} @@ -5847,6 +5991,9 @@ packages: resolution: {integrity: sha512-MyqZCTGLDZ77u4k+jqg4UlrzPTPZ49NDlaekU6uuFaJLzPIN1woaRXCbGeqOfxwc3Y37ZROGAJ614Rdv7Olt+g==} engines: {node: '>=10'} + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + tiny-secp256k1@1.1.7: resolution: {integrity: sha512-eb+F6NabSnjbLwNoC+2o5ItbmP1kg7HliWue71JgLegQt6A5mTN8YbvTLCazdlg6e5SV6A+r8OGvZYskdlmhqQ==} engines: {node: '>=6.0.0'} @@ -6123,6 +6270,9 @@ packages: engines: {node: '>= 16'} hasBin: true + victory-vendor@37.3.6: + resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==} + viem@2.38.4: resolution: {integrity: sha512-qnyPNg6Lz1EEC86si/1dq7GlOyZVFHSgAW+p8Q31R5idnAYCOdTM2q5KLE4/ykMeMXzY0bnp5MWTtR/wjCtWmQ==} peerDependencies: @@ -8424,6 +8574,18 @@ snapshots: '@types/react': 19.0.12 '@types/react-dom': 19.0.4(@types/react@19.0.12) + '@reduxjs/toolkit@2.9.2(react-redux@9.2.0(@types/react@19.0.12)(react@19.0.0)(redux@5.0.1))(react@19.0.0)': + dependencies: + '@standard-schema/spec': 1.0.0 + '@standard-schema/utils': 0.3.0 + immer: 10.2.0 + redux: 5.0.1 + redux-thunk: 3.1.0(redux@5.0.1) + reselect: 5.1.1 + optionalDependencies: + react: 19.0.0 + react-redux: 9.2.0(@types/react@19.0.12)(react@19.0.0)(redux@5.0.1) + '@rolldown/pluginutils@1.0.0-beta.27': {} '@rollup/plugin-babel@5.3.1(@babel/core@7.28.5)(@types/babel__core@7.20.5)(rollup@2.79.2)': @@ -8564,6 +8726,8 @@ snapshots: '@sinclair/typebox@0.25.24': {} + '@standard-schema/spec@1.0.0': {} + '@standard-schema/utils@0.3.0': {} '@starknet-io/types-js@0.7.10': {} @@ -8766,6 +8930,30 @@ snapshots: '@types/deep-eql': 4.0.2 assertion-error: 2.0.1 + '@types/d3-array@3.2.2': {} + + '@types/d3-color@3.1.3': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.1': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-shape@3.1.7': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + '@types/deep-eql@4.0.2': {} '@types/eslint-scope@3.7.7': @@ -8846,6 +9034,8 @@ snapshots: '@types/trusted-types@2.0.7': {} + '@types/use-sync-external-store@0.0.6': {} + '@typescript-eslint/eslint-plugin@8.46.2(@typescript-eslint/parser@8.46.2(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 @@ -9890,6 +10080,44 @@ snapshots: csstype@3.1.3: {} + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-color@3.1.0: {} + + d3-ease@3.0.1: {} + + d3-format@3.1.0: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@3.1.0: {} + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.0 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + daisyui@5.3.10: {} damerau-levenshtein@1.0.8: {} @@ -9929,6 +10157,8 @@ snapshots: dependencies: ms: 2.1.3 + decimal.js-light@2.5.1: {} + decimal.js@10.6.0: {} deep-eql@5.0.2: {} @@ -10159,6 +10389,8 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 + es-toolkit@1.41.0: {} + esbuild-android-64@0.14.47: optional: true @@ -10905,6 +11137,8 @@ snapshots: ignore@7.0.5: {} + immer@10.2.0: {} + import-fresh@3.3.1: dependencies: parent-module: 1.0.1 @@ -10934,6 +11168,8 @@ snapshots: hasown: 2.0.2 side-channel: 1.1.0 + internmap@2.0.3: {} + interpret@1.4.0: {} interpret@3.1.1: {} @@ -11900,6 +12136,15 @@ snapshots: react-is@17.0.2: {} + react-redux@9.2.0(@types/react@19.0.12)(react@19.0.0)(redux@5.0.1): + dependencies: + '@types/use-sync-external-store': 0.0.6 + react: 19.0.0 + use-sync-external-store: 1.6.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.12 + redux: 5.0.1 + react-refresh@0.17.0: {} react-remove-scroll-bar@2.3.8(@types/react@19.0.12)(react@19.0.0): @@ -11951,6 +12196,26 @@ snapshots: dependencies: picomatch: 2.3.1 + recharts@3.3.0(@types/react@19.0.12)(react-dom@19.0.0(react@19.0.0))(react-is@17.0.2)(react@19.0.0)(redux@5.0.1): + dependencies: + '@reduxjs/toolkit': 2.9.2(react-redux@9.2.0(@types/react@19.0.12)(react@19.0.0)(redux@5.0.1))(react@19.0.0) + clsx: 2.1.1 + decimal.js-light: 2.5.1 + es-toolkit: 1.41.0 + eventemitter3: 5.0.1 + immer: 10.2.0 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + react-is: 17.0.2 + react-redux: 9.2.0(@types/react@19.0.12)(react@19.0.0)(redux@5.0.1) + reselect: 5.1.1 + tiny-invariant: 1.3.3 + use-sync-external-store: 1.6.0(react@19.0.0) + victory-vendor: 37.3.6 + transitivePeerDependencies: + - '@types/react' + - redux + rechoir@0.6.2: dependencies: resolve: 1.22.11 @@ -11968,6 +12233,12 @@ snapshots: dependencies: esprima: 4.0.1 + redux-thunk@3.1.0(redux@5.0.1): + dependencies: + redux: 5.0.1 + + redux@5.0.1: {} + reflect.getprototypeof@1.0.10: dependencies: call-bind: 1.0.8 @@ -12013,6 +12284,8 @@ snapshots: require-from-string@2.0.2: {} + reselect@5.1.1: {} + resolve-cwd@3.0.0: dependencies: resolve-from: 5.0.0 @@ -12288,6 +12561,11 @@ snapshots: slash@3.0.0: {} + sonner@2.0.7(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + source-list-map@2.0.1: {} source-map-js@1.2.1: {} @@ -12539,6 +12817,8 @@ snapshots: dependencies: convert-hrtime: 3.0.0 + tiny-invariant@1.3.3: {} + tiny-secp256k1@1.1.7: dependencies: bindings: 1.5.0 @@ -12823,6 +13103,23 @@ snapshots: - encoding - supports-color + victory-vendor@37.3.6: + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-ease': 3.0.2 + '@types/d3-interpolate': 3.0.4 + '@types/d3-scale': 4.0.9 + '@types/d3-shape': 3.1.7 + '@types/d3-time': 3.0.4 + '@types/d3-timer': 3.0.2 + d3-array: 3.2.4 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-scale: 4.0.2 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-timer: 3.0.1 + viem@2.38.4(typescript@5.9.3)(zod@3.24.1): dependencies: '@noble/curves': 1.9.1 @@ -13288,9 +13585,10 @@ snapshots: zod@4.1.12: {} - zustand@4.5.7(@types/react@19.0.12)(react@19.0.0): + zustand@4.5.7(@types/react@19.0.12)(immer@10.2.0)(react@19.0.0): dependencies: use-sync-external-store: 1.6.0(react@19.0.0) optionalDependencies: '@types/react': 19.0.12 + immer: 10.2.0 react: 19.0.0 From b4983076fdc41c6c0bd8f74e363400bab9072017 Mon Sep 17 00:00:00 2001 From: legend4tech Date: Fri, 31 Oct 2025 10:36:58 +0100 Subject: [PATCH 2/6] feat: made the login functional --- .../nextjs/app/dashboard/realtor/page.tsx | 19 +- packages/nextjs/app/login/page.tsx | 8 +- .../components/auth/investor-signup-form.tsx | 35 +- .../nextjs/components/auth/login-form.tsx | 68 ++- .../components/auth/realtor-signup-form.tsx | 63 +-- .../realtor-dashboard/add-property-modal.tsx | 404 ++++++++++++++++++ .../quick-actions-realtor.tsx | 13 +- packages/nextjs/components/ui/radio-group.tsx | 44 ++ packages/nextjs/components/ui/select.tsx | 159 +++++++ packages/nextjs/components/ui/textarea.tsx | 22 + packages/nextjs/package.json | 2 + packages/nextjs/pnpm-lock.yaml | 6 + 12 files changed, 779 insertions(+), 64 deletions(-) create mode 100644 packages/nextjs/components/realtor-dashboard/add-property-modal.tsx create mode 100644 packages/nextjs/components/ui/radio-group.tsx create mode 100644 packages/nextjs/components/ui/select.tsx create mode 100644 packages/nextjs/components/ui/textarea.tsx diff --git a/packages/nextjs/app/dashboard/realtor/page.tsx b/packages/nextjs/app/dashboard/realtor/page.tsx index 29827c4..7528431 100644 --- a/packages/nextjs/app/dashboard/realtor/page.tsx +++ b/packages/nextjs/app/dashboard/realtor/page.tsx @@ -13,15 +13,16 @@ import { CommissionChart } from "~~/components/realtor-dashboard/commission-char import { RecentActivity } from "~~/components/realtor-dashboard/recent-activity" import { QuickActionsRealtor } from "~~/components/realtor-dashboard/quick-actions-realtor" import { MonthlyStats } from "~~/components/realtor-dashboard/monthly-stats" +import { AddPropertyModal } from "~~/components/realtor-dashboard/add-property-modal" export default function RealtorDashboard() { const [searchQuery, setSearchQuery] = useState("") + const [isAddPropertyOpen, setIsAddPropertyOpen] = useState(false) - // Use our existing properties data - const realtorProperties = properties.map((property, index) => ({ +const realtorProperties = properties.map((property, index) => ({ ...property, - title: property.name, // Add this line to create a title field from name + title: property.name, investors: [45, 67, 23, 38, 52][index] || 30, fundingProgress: [85, 92, 68, 75, 88][index] || 70, addedDate: ["2024-01-15", "2024-01-10", "2024-01-20", "2024-01-08", "2024-01-12"][index] || "2024-01-15", @@ -56,7 +57,10 @@ export default function RealtorDashboard() {

My Property Listings

- @@ -148,12 +152,15 @@ export default function RealtorDashboard() { {/* Right Column - Recent Activity, Quick Actions, Monthly Stats */}
- +
+ + {/* Add Property Modal */} + ) -} +} \ No newline at end of file diff --git a/packages/nextjs/app/login/page.tsx b/packages/nextjs/app/login/page.tsx index 8edfe45..41c2d25 100644 --- a/packages/nextjs/app/login/page.tsx +++ b/packages/nextjs/app/login/page.tsx @@ -10,9 +10,9 @@ export const metadata: Metadata = { export default function LoginPage() { return ( -
+
-
+
{/* Close button */} @@ -20,8 +20,8 @@ export default function LoginPage() { {/* Header */}
-

Welcome Back

-

Sign in to your reAI account

+

Welcome Back

+

Sign in to your reAI account

{/* Form */} diff --git a/packages/nextjs/components/auth/investor-signup-form.tsx b/packages/nextjs/components/auth/investor-signup-form.tsx index a97e25c..723db5c 100644 --- a/packages/nextjs/components/auth/investor-signup-form.tsx +++ b/packages/nextjs/components/auth/investor-signup-form.tsx @@ -55,17 +55,17 @@ export function InvestorSignupForm() { return (
- + ( - + Full Name * - + @@ -77,11 +77,11 @@ export function InvestorSignupForm() { name="nin" render={({ field }) => ( - + NIN (National Identification Number) * - + @@ -93,11 +93,11 @@ export function InvestorSignupForm() { name="email" render={({ field }) => ( - + Email Address * - + @@ -109,7 +109,7 @@ export function InvestorSignupForm() { name="password" render={({ field }) => ( - + Password * @@ -117,7 +117,7 @@ export function InvestorSignupForm() {
-

+

Already have an account?{" "} Login diff --git a/packages/nextjs/components/auth/login-form.tsx b/packages/nextjs/components/auth/login-form.tsx index 91a2e11..9da621a 100644 --- a/packages/nextjs/components/auth/login-form.tsx +++ b/packages/nextjs/components/auth/login-form.tsx @@ -6,15 +6,21 @@ import { zodResolver } from "@hookform/resolvers/zod" import * as z from "zod" import { Eye, EyeOff } from "lucide-react" import Link from "next/link" +import { useRouter } from "next/navigation" import { Button } from "~~/components/ui/button" import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "~~/components/ui/form" import { Input } from "~~/components/ui/input" import { Checkbox } from "~~/components/ui/checkbox" +import { RadioGroup, RadioGroupItem } from "~~/components/ui/radio-group" +import { toast } from "sonner" const loginSchema = z.object({ email: z.string().email("Please enter a valid email address"), password: z.string().min(8, "Password must be at least 8 characters"), - rememberMe: z.boolean().optional(), + userType: z.enum(["investor", "realtor"], { + required_error: "Please select your account type", + }), + rememberMe: z.boolean().optional().default(false), }) type LoginFormValues = z.infer @@ -22,6 +28,7 @@ type LoginFormValues = z.infer export function LoginForm() { const [showPassword, setShowPassword] = useState(false) const [isLoading, setIsLoading] = useState(false) + const router = useRouter() const form = useForm({ resolver: zodResolver(loginSchema), @@ -35,9 +42,20 @@ export function LoginForm() { async function onSubmit(data: LoginFormValues) { setIsLoading(true) console.log("[v0] Login form submitted:", data) - // TODO: Implement login logic - await new Promise((resolve) => setTimeout(resolve, 1000)) + await new Promise((resolve) => setTimeout(resolve, 1500)) setIsLoading(false) + + toast.success("Login successful! Welcome back", { + description: "Redirecting to your dashboard...", + }) + + setTimeout(() => { + if (data.userType === "investor") { + router.push("/dashboard/investor") + } else { + router.push("/dashboard/realtor") + } + }, 1000) } return ( @@ -85,7 +103,42 @@ export function LoginForm() { )} /> -

+ ( + + + I am signing in as * + + + +
+ + +
+
+ + +
+
+
+ +
+ )} + /> + +
{isLoading ? "Signing in..." : "Sign In"} - -

- Don't have an account?{" "} - - Sign up - -

) diff --git a/packages/nextjs/components/auth/realtor-signup-form.tsx b/packages/nextjs/components/auth/realtor-signup-form.tsx index af46b33..0066b5f 100644 --- a/packages/nextjs/components/auth/realtor-signup-form.tsx +++ b/packages/nextjs/components/auth/realtor-signup-form.tsx @@ -62,17 +62,17 @@ export function RealtorSignupForm() { return (
- + ( - + Full Name * - + @@ -84,11 +84,11 @@ export function RealtorSignupForm() { name="email" render={({ field }) => ( - + Email Address * - + @@ -100,7 +100,7 @@ export function RealtorSignupForm() { name="password" render={({ field }) => ( - + Password * @@ -108,7 +108,7 @@ export function RealtorSignupForm() {
-

+

Already have an account?{" "} Login diff --git a/packages/nextjs/components/realtor-dashboard/add-property-modal.tsx b/packages/nextjs/components/realtor-dashboard/add-property-modal.tsx new file mode 100644 index 0000000..7ce7bec --- /dev/null +++ b/packages/nextjs/components/realtor-dashboard/add-property-modal.tsx @@ -0,0 +1,404 @@ +"use client" + +import { useState } from "react" +import { Building2, ArrowLeft, ArrowRight } from "lucide-react" +import { Dialog, DialogContent, DialogHeader, DialogTitle } from "~~/components/ui/dialog" +import { Button } from "~~/components/ui/button" +import { Input } from "~~/components/ui/input" +import { Label } from "~~/components/ui/label" +import { Textarea } from "~~/components/ui/textarea" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~~/components/ui/select" +import { toast } from "sonner" + +interface AddPropertyModalProps { + open: boolean + onOpenChange: (open: boolean) => void +} + +export function AddPropertyModal({ open, onOpenChange }: AddPropertyModalProps) { + const [currentStep, setCurrentStep] = useState(1) + const [formData, setFormData] = useState({ + // Step 1: Basic Info + propertyName: "", + propertyType: "", + description: "", + constructionStatus: "", + address: "", + city: "", + state: "", + // Step 2: Property Details + size: "", + bedrooms: "", + bathrooms: "", + amenities: "", + // Step 3: Financial Info + totalValue: "", + minInvestment: "", + apy: "", + rentalYield: "", + // Step 4: Geolocation + latitude: "", + longitude: "", + // Step 5: Documents + documents: "", + }) + + const steps = [ + { number: 1, title: "Basic Info" }, + { number: 2, title: "Property Details" }, + { number: 3, title: "Financial Info" }, + { number: 4, title: "Geolocation" }, + { number: 5, title: "Documents" }, + ] + + const handleNext = () => { + if (currentStep < 5) { + setCurrentStep(currentStep + 1) + } + } + + const handlePrevious = () => { + if (currentStep > 1) { + setCurrentStep(currentStep - 1) + } + } + + const handleSubmit = () => { + toast.success("Property added successfully!", { + description: "Your property has been listed for tokenization.", + }) + onOpenChange(false) + setCurrentStep(1) + setFormData({ + propertyName: "", + propertyType: "", + description: "", + constructionStatus: "", + address: "", + city: "", + state: "", + size: "", + bedrooms: "", + bathrooms: "", + amenities: "", + totalValue: "", + minInvestment: "", + apy: "", + rentalYield: "", + latitude: "", + longitude: "", + documents: "", + }) + } + + const updateFormData = (field: string, value: string) => { + setFormData((prev) => ({ ...prev, [field]: value })) + } + + return ( +

+ + + + + Add New Property + + + + {/* Step Indicator */} +
+ {steps.map((step, index) => ( +
+
+
step.number + ? "bg-emerald-100 text-emerald-700" + : "bg-gray-200 text-gray-600" + }`} + > + {step.number} +
+ {index < steps.length - 1 && ( +
step.number ? "bg-emerald-600" : "bg-gray-200" + }`} + /> + )} +
+

+ {step.title} +

+
+ ))} +
+ + {/* Step 1: Basic Info */} + {currentStep === 1 && ( +
+
+
+ + updateFormData("propertyName", e.target.value)} + /> +
+
+ + +
+
+ +
+ +