diff --git a/packages/nextjs/app/analyze/page.tsx b/packages/nextjs/app/analyze/page.tsx deleted file mode 100644 index a9d9ef8..0000000 --- a/packages/nextjs/app/analyze/page.tsx +++ /dev/null @@ -1,43 +0,0 @@ -"use client" - -import { useState } from "react" -import { AnalysisInput } from "~~/components/veri-ai/analysis-input" -import { AnalysisResults } from "~~/components/veri-ai/analysis-results" -import { Footer } from "~~/components/veri-ai/footer" -import { Header } from "~~/components/veri-ai/header" -import { AnalysisData, mockAnalysisData } from "~~/data/mock-data" - - - -export default function AnalyzePage() { - const [tokenId, setTokenId] = useState("") - const [analysisData, setAnalysisData] = useState(null) - const [loading, setLoading] = useState(false) - - const handleAnalyze = async (id: string) => { - setTokenId(id) - setLoading(true) - - // Simulate API call delay - setTimeout(() => { - setAnalysisData(mockAnalysisData) - setLoading(false) - }, 1500) - } - - return ( -
-
-
-
- {!analysisData ? ( - - ) : ( - setAnalysisData(null)} /> - )} -
-
-
-
- ) -} diff --git a/packages/nextjs/app/login/page.tsx b/packages/nextjs/app/login/page.tsx new file mode 100644 index 0000000..9982e3a --- /dev/null +++ b/packages/nextjs/app/login/page.tsx @@ -0,0 +1,38 @@ +import type { Metadata } from "next" +import Link from "next/link" +import { X } from "lucide-react" +import { LoginForm } from "~~/components/auth/login-form" + +export const metadata: Metadata = { + title: "Login | BrickChain", + description: "Sign in to your BrickChain account", +} + +export default function LoginPage() { + return ( +
+
+
+ + + {/* Header */} +
+

Welcome Back

+

Sign in to your BrickChain account

+
+ + {/* Form */} + + + {/* Sign up link */} +

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

+
+
+
+ ) +} diff --git a/packages/nextjs/app/onboarding/page.tsx b/packages/nextjs/app/onboarding/page.tsx new file mode 100644 index 0000000..dbb4ffb --- /dev/null +++ b/packages/nextjs/app/onboarding/page.tsx @@ -0,0 +1,122 @@ +import type { Metadata } from "next" +import Link from "next/link" +import { Users, Building2, CheckCircle2, Briefcase, Building } from "lucide-react" + +export const metadata: Metadata = { + title: "Choose Your Path | BrickChain", + description: "Select how you'd like to join the BrickChain ecosystem", +} + +export default function OnboardingPage() { + return ( +
+
+ {/* Header */} +
+

Choose Your Path

+

Select how you'd like to join the BrickChain ecosystem

+
+ + {/* Cards */} +
+ {/* Investor Card */} + + {/* Icon */} +
+
+ +
+
+ + {/* Title */} +
+
+ +

I'm an Investor

+
+ + Most Popular + +
+ + {/* Description */} +

+ Start investing in premium real estate with as little as 10,000 STRK +

+ + {/* Features */} +
+
+ + Low minimum investment +
+
+ + Instant liquidity +
+
+ + Passive income +
+
+ + + {/* Realtor Card */} + + {/* Icon */} +
+
+ +
+
+ + {/* Title */} +
+
+ +

I'm a Realtor

+
+ + Professional + +
+ + {/* Description */} +

+ List and tokenize your properties on our blockchain platform +

+ + {/* Features */} +
+
+ + List properties +
+
+ + Earn commissions +
+
+ + Manage listings +
+
+ +
+ + {/* Back to home */} +
+ + ← Back to home + +
+
+
+ ) +} diff --git a/packages/nextjs/app/page.tsx b/packages/nextjs/app/page.tsx index 7ec0f62..d7d4686 100644 --- a/packages/nextjs/app/page.tsx +++ b/packages/nextjs/app/page.tsx @@ -1,18 +1,24 @@ -import { FeaturesSection } from "~~/components/veri-ai/features-section"; -import { Footer } from "~~/components/veri-ai/footer"; -import { Header } from "~~/components/veri-ai/header"; -import { HeroSection } from "~~/components/veri-ai/hero-section"; +import { BuiltForNigeria } from "~~/components/landing/built-for-nigeria"; +import { CTA } from "~~/components/landing/cta"; +import { Features } from "~~/components/landing/features"; +import { Footer } from "~~/components/landing/footer"; +import { Header } from "~~/components/landing/header"; +import { Hero } from "~~/components/landing/hero"; +import { Listings } from "~~/components/landing/listings"; +import { Process } from "~~/components/landing/process"; export default function Home() { return ( -
+
-
- - -
+ + + + + +
-
+ ) } diff --git a/packages/nextjs/app/properties/page.tsx b/packages/nextjs/app/properties/page.tsx new file mode 100644 index 0000000..3bb6a61 --- /dev/null +++ b/packages/nextjs/app/properties/page.tsx @@ -0,0 +1,31 @@ + +import { Footer } from "~~/components/landing/footer" +import { Header } from "~~/components/landing/header" +import { PropertiesList } from "~~/components/landing/properties-list" + +export default function PropertiesPage() { + return ( +
+
+ + {/* Page Header */} +
+
+

Browse All Properties

+

+ Discover premium real estate investment opportunities on Starknet +

+
+
+ + {/* Properties Section */} +
+
+ +
+
+ +
+
+ ) +} diff --git a/packages/nextjs/app/signup/page.tsx b/packages/nextjs/app/signup/page.tsx new file mode 100644 index 0000000..2926486 --- /dev/null +++ b/packages/nextjs/app/signup/page.tsx @@ -0,0 +1,42 @@ +import type { Metadata } from "next" +import Link from "next/link" +import { X } from "lucide-react" +import { RealtorSignupForm } from "~~/components/auth/realtor-signup-form" +import { InvestorSignupForm } from "~~/components/auth/investor-signup-form" + + +export const metadata: Metadata = { + title: "Sign Up | BrickChain", + description: "Create your BrickChain account", +} + +type SearchParams = Promise<{ [key: string]: string | string[] | undefined }> + +export default async function SignupPage(props: { searchParams: SearchParams }) { + const searchParams = await props.searchParams + const type = searchParams.type as string | undefined + const isRealtor = type === "realtor" + + return ( +
+
+
+ + + {/* Header */} +
+

+ {isRealtor ? "Register as realtor" : "Create Account"} +

+

+ {isRealtor ? "Fill in your details to get started" : "Join BrickChain and start investing"} +

+
+ + {/* Form */} + {isRealtor ? : } +
+
+
+ ) +} diff --git a/packages/nextjs/components/auth/investor-signup-form.tsx b/packages/nextjs/components/auth/investor-signup-form.tsx new file mode 100644 index 0000000..f485ec1 --- /dev/null +++ b/packages/nextjs/components/auth/investor-signup-form.tsx @@ -0,0 +1,173 @@ +"use client" + +import { useState } from "react" +import { useForm } from "react-hook-form" +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" + +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" }), + phone: z.string().optional(), +}) + +type InvestorSignupFormValues = z.infer + +export function InvestorSignupForm() { + const [showPassword, setShowPassword] = useState(false) + const [isLoading, setIsLoading] = useState(false) + const router = useRouter() + + const form = useForm({ + resolver: zodResolver(investorSignupSchema), + defaultValues: { + fullName: "", + nin: "", + email: "", + password: "", + phone: "", + }, + }) + + async function onSubmit(data: InvestorSignupFormValues) { + setIsLoading(true) + console.log("[v0] Investor signup form submitted:", data) + await new Promise((resolve) => setTimeout(resolve, 1000)) + setIsLoading(false) + router.push("/dashboard") + } + + return ( +
+ + ( + + + Full Name * + + + + + + + )} + /> + + ( + + + NIN (National Identification Number) * + + + + + + + )} + /> + + ( + + + Email Address * + + + + + + + )} + /> + + ( + + + Password * + + +
+ + +
+
+ +
+ )} + /> + + ( + + Phone Number + + + + + + )} + /> + +
+ + +
+ +

+ Already have an account?{" "} + + Login + +

+ + + ) +} \ No newline at end of file diff --git a/packages/nextjs/components/auth/login-form.tsx b/packages/nextjs/components/auth/login-form.tsx new file mode 100644 index 0000000..91a2e11 --- /dev/null +++ b/packages/nextjs/components/auth/login-form.tsx @@ -0,0 +1,126 @@ +"use client" + +import { useState } from "react" +import { useForm } from "react-hook-form" +import { zodResolver } from "@hookform/resolvers/zod" +import * as z from "zod" +import { Eye, EyeOff } from "lucide-react" +import Link from "next/link" +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" + +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(), +}) + +type LoginFormValues = z.infer + +export function LoginForm() { + const [showPassword, setShowPassword] = useState(false) + const [isLoading, setIsLoading] = useState(false) + + const form = useForm({ + resolver: zodResolver(loginSchema), + defaultValues: { + email: "", + password: "", + rememberMe: false, + }, + }) + + 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)) + setIsLoading(false) + } + + return ( +
+ + ( + + Email Address + + + + + + )} + /> + + ( + + Password + +
+ + +
+
+ +
+ )} + /> + +
+ ( + + + + + Remember me + + )} + /> + + Forgot password? + +
+ + + +

+ 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 new file mode 100644 index 0000000..fded500 --- /dev/null +++ b/packages/nextjs/components/auth/realtor-signup-form.tsx @@ -0,0 +1,247 @@ +"use client" + +import { useState } from "react" +import { useForm } from "react-hook-form" +import { zodResolver } from "@hookform/resolvers/zod" +import * as z from "zod" +import { Eye, EyeOff, Upload } 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" + +const MAX_FILE_SIZE = 5 * 1024 * 1024 +const ACCEPTED_FILE_TYPES = ["image/png", "image/jpeg", "image/jpg", "application/pdf"] + +const realtorSignupSchema = z.object({ + fullName: z.string().min(2, "Full name must be at least 2 characters"), + 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(), + businessName: z.string().min(2, "Business name must be at least 2 characters"), + governmentId: z + .instanceof(FileList) + .refine((files) => files.length > 0, "Government ID is required") + .refine((files) => files[0]?.size <= MAX_FILE_SIZE, "File size must be less than 5MB") + .refine((files) => ACCEPTED_FILE_TYPES.includes(files[0]?.type), "Only PNG, JPG, or PDF files are accepted"), + companyDocument: z + .instanceof(FileList) + .refine((files) => files.length > 0, "Company document is required") + .refine((files) => files[0]?.size <= MAX_FILE_SIZE, "File size must be less than 5MB") + .refine((files) => ACCEPTED_FILE_TYPES.includes(files[0]?.type), "Only PNG, JPG, or PDF files are accepted"), +}) + +type RealtorSignupFormValues = z.infer + +export function RealtorSignupForm() { + const [showPassword, setShowPassword] = useState(false) + const [isLoading, setIsLoading] = useState(false) + const router = useRouter() + + const form = useForm({ + resolver: zodResolver(realtorSignupSchema), + defaultValues: { + fullName: "", + email: "", + password: "", + phone: "", + businessName: "", + }, + }) + + async function onSubmit(data: RealtorSignupFormValues) { + setIsLoading(true) + console.log("[v0] Realtor signup form submitted:", data) + await new Promise((resolve) => setTimeout(resolve, 1000)) + setIsLoading(false) + router.push("/dashboard") + } + + return ( +
+ + ( + + + Full Name * + + + + + + + )} + /> + + ( + + + Email Address * + + + + + + + )} + /> + + ( + + + Password * + + +
+ + +
+
+ +
+ )} + /> + + ( + + Phone Number + + + + + + )} + /> + + ( + + Business Name or Company + + + + + + )} + /> + + ( + + + Upload Government-issued ID * + + +
+ onChange(e.target.files)} + {...fieldProps} + /> + +
+
+ +
+ )} + /> + + ( + + + Upload Company Document (CAC or License) * + + +
+ onChange(e.target.files)} + {...fieldProps} + /> + +
+
+ +
+ )} + /> + +
+ + +
+ +

+ Already have an account?{" "} + + Login + +

+ + + ) +} diff --git a/packages/nextjs/components/landing/built-for-nigeria.tsx b/packages/nextjs/components/landing/built-for-nigeria.tsx new file mode 100644 index 0000000..3a63eab --- /dev/null +++ b/packages/nextjs/components/landing/built-for-nigeria.tsx @@ -0,0 +1,86 @@ +"use client" + +import { CheckCircle2, Shield, Users, Building2, TrendingUp } from "lucide-react" + +export function BuiltForNigeria() { + const features = [ + { + title: "Audited Smart Contract", + description: "No rug pull. All contracts are audited by leading security firm", + icon: CheckCircle2, + }, + { + title: "Diasporal-Friendly Investment", + description: "Invest from anywhere with stablecoins and international payment method", + icon: CheckCircle2, + }, + { + title: "Legal Compliance", + description: "Fully compliant with nigerian regulation and international standard", + icon: CheckCircle2, + }, + { + title: "Instant Liquidity via P2PDX Trading", + description: "Trade your token 24/7 on our Decentralized exchange", + icon: CheckCircle2, + }, + ] + + const securityMetrics = [ + { label: "100%\nsecured", icon: Shield }, + { label: "100%\nsecured", icon: Users }, + { label: "100%\nsecured", icon: Building2 }, + { label: "100%\nsecured", icon: TrendingUp }, + ] + + return ( +
+
+
+ {/* Left side - Features */} +
+
+

Built for Nigeria,

+

Powered by Blockchain

+
+ +

+ We built the most secure and compliant platform for real estate tokenization in Nigeria. +

+ +
+ {features.map((feature, index) => { + const Icon = feature.icon + return ( +
+ +
+

{feature.title}

+

{feature.description}

+
+
+ ) + })} +
+
+ + {/* Right side - Security Metrics */} +
+ {securityMetrics.map((metric, index) => { + const Icon = metric.icon + return ( +
+ +

{metric.label}

+
+ ) + })} +
+
+
+
+ ) +} diff --git a/packages/nextjs/components/landing/cta.tsx b/packages/nextjs/components/landing/cta.tsx new file mode 100644 index 0000000..a9ae12e --- /dev/null +++ b/packages/nextjs/components/landing/cta.tsx @@ -0,0 +1,40 @@ +"use client" + +import { Button } from "../ui/button" +import { ArrowRight } from "lucide-react" + +export function CTA() { + return ( +
+
+
+ {/* Background Gradient */} +
+
+
+
+ +
+

Ready to Start Investing?

+

+ Join thousands of investors building wealth through tokenized real estate. Start with just 50 STRK today. +

+
+ + +
+
+
+
+
+ ) +} diff --git a/packages/nextjs/components/landing/features.tsx b/packages/nextjs/components/landing/features.tsx new file mode 100644 index 0000000..f14a4e9 --- /dev/null +++ b/packages/nextjs/components/landing/features.tsx @@ -0,0 +1,60 @@ +"use client" + +import { DollarSign, Zap, Shield } from "lucide-react" + +export function Features() { + const features = [ + { + icon: DollarSign, + title: "No High Costs", + description: "Start investing with just 50 STRK. No need for millions to own premium real estate.", + color: "bg-red-100", + iconColor: "text-red-500", + }, + { + icon: Zap, + title: "Instant Liquidity", + description: "Trade your property token anytime on our marketplace. No waiting months to sell.", + color: "bg-blue-100", + iconColor: "text-blue-500", + }, + { + icon: Shield, + title: "Complete Trust", + description: "Smart contracts ensures Transparency. All transaction are Secure and verifiable on the blockchain.", + color: "bg-emerald-100", + iconColor: "text-emerald-500", + }, + ] + + return ( +
+
+
+

Real Estate Reinvented For Everyone

+

+ Traditional Real Estate barriers eliminated through blockchain innovation +

+
+ +
+ {features.map((feature, index) => { + const Icon = feature.icon + return ( +
+
+ +
+

{feature.title}

+

{feature.description}

+
+ ) + })} +
+
+
+ ) +} diff --git a/packages/nextjs/components/landing/footer.tsx b/packages/nextjs/components/landing/footer.tsx new file mode 100644 index 0000000..41bd6b9 --- /dev/null +++ b/packages/nextjs/components/landing/footer.tsx @@ -0,0 +1,99 @@ +"use client" + +export function Footer() { + return ( + + ) +} diff --git a/packages/nextjs/components/landing/header.tsx b/packages/nextjs/components/landing/header.tsx new file mode 100644 index 0000000..56b9880 --- /dev/null +++ b/packages/nextjs/components/landing/header.tsx @@ -0,0 +1,100 @@ +"use client" + +import { useState } from "react" +import { Menu, X } from "lucide-react" +import { Button } from "../ui/button" +import Link from "next/link" +import { WelcomeModal } from "./welcome-modal" + +export function Header() { + const [isOpen, setIsOpen] = useState(false) + const [showWelcomeModal, setShowWelcomeModal] = useState(false) + + return ( + <> +
+
+
+ +
+ BC +
+ BrickChain + + + + +
+ + +
+ + +
+ + {isOpen && ( + + )} +
+
+ + + + ) +} diff --git a/packages/nextjs/components/landing/hero.tsx b/packages/nextjs/components/landing/hero.tsx new file mode 100644 index 0000000..b16201b --- /dev/null +++ b/packages/nextjs/components/landing/hero.tsx @@ -0,0 +1,68 @@ +"use client" + +import { Button } from "../ui/button" +import { ArrowRight } from "lucide-react" +import { useState } from "react" +import { WelcomeModal } from "./welcome-modal" + +export function Hero() { + const [showWelcomeModal, setShowWelcomeModal] = useState(false) + + return ( + <> +
+
+
+

+ Own a Piece Of Nigerian's +
+ Premium Real Estate +

+ +

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

+ +
+ + +
+ +
+
+

2.5M+

+

Total STRK Locked

+
+
+

15,000+

+

Active Investors

+
+
+

8.5%

+

Average APY

+
+
+
+
+
+ + + + ) +} diff --git a/packages/nextjs/components/landing/listings.tsx b/packages/nextjs/components/landing/listings.tsx new file mode 100644 index 0000000..fdc31fe --- /dev/null +++ b/packages/nextjs/components/landing/listings.tsx @@ -0,0 +1,137 @@ +"use client" + +import { Card } from "../ui/card" +import { Badge } from "../ui/badge" +import { Button } from "../ui/button" +import { Progress } from "../ui/progress" +import { MapPin, TrendingUp } from "lucide-react" + +export function Listings() { + const properties = [ + { + id: 1, + name: "Lekki Pearl Towers", + location: "Sub location", + totalValue: "120,000 STRK", + image: "/modern-luxury-apartment-building-in-lagos.jpg", + availableTokens: "240000", + minimumInvestment: "50,000 STRK", + apy: "9.2%", + funded: 70, + rating: 4.3, + status: "Active", + }, + { + id: 2, + name: "Banana Island Mansion", + location: "Sub location", + totalValue: "250,000 STRK", + image: "/luxury-waterfront-mansion-property.jpg", + availableTokens: "500000", + minimumInvestment: "100,000 STRK", + apy: "8.8%", + funded: 70, + rating: 4.0, + status: "Active", + }, + { + id: 3, + name: "Victoria Island Complex", + location: "Sub location", + totalValue: "180,000 STRK", + image: "/premium-commercial-real-estate-complex.jpg", + availableTokens: "360000", + minimumInvestment: "75,000 STRK", + apy: "9.5%", + funded: 65, + rating: 4.5, + status: "Active", + }, + ] + + return ( +
+
+
+

+ Invest in Iconic Nigerian Properties +

+

Listings are illustrative. Actual properties require verification.

+
+ +
+ {properties.map((property) => ( + + {/* Property Image */} +
+ {property.name} +
+ {property.status} +
+
+ + ★ {property.rating} + +
+
+ + {/* Card Content */} +
+ {/* Property Name and Value */} +

{property.name}

+
+ + {property.location} +
+ + {/* Total Value */} +
+

Total Value

+
+

{property.totalValue}

+
+
+ + {/* Investment Details */} +
+
+

Available Tokens

+

{property.availableTokens}

+
+
+

Minimum Investment

+

{property.minimumInvestment}

+
+
+ + {/* APY and Funding */} +
+
+ + {property.apy} APY +
+ {property.funded}% Funded +
+ + {/* Progress Bar */} + + + {/* Invest Button */} + +
+
+ ))} +
+
+
+ ) +} diff --git a/packages/nextjs/components/landing/process.tsx b/packages/nextjs/components/landing/process.tsx new file mode 100644 index 0000000..72c3158 --- /dev/null +++ b/packages/nextjs/components/landing/process.tsx @@ -0,0 +1,67 @@ +"use client" + +import { Gem, Briefcase, TrendingUp } from "lucide-react" + +export function Process() { + const steps = [ + { + icon: Gem, + title: "Tokenize", + subtitle: "Minimum investment 50 STRK", + description: "Property owners mint tokens (1 token = 5 STRK of equity).", + color: "bg-purple-100", + iconColor: "text-purple-600", + highlightColor: "bg-purple-200", + }, + { + icon: Briefcase, + title: "Invest", + subtitle: "Instant ownership verification", + description: "Buy tokens with USDT/USDC or cNGN. Own fractions hassle-free.", + color: "bg-blue-100", + iconColor: "text-blue-600", + highlightColor: "bg-blue-200", + }, + { + icon: TrendingUp, + title: "Earn", + subtitle: "Up to 12% annual return", + description: "Stake tokens to earn 5% annual yield or trade on our P2P market.", + color: "bg-red-100", + iconColor: "text-red-600", + highlightColor: "bg-red-200", + }, + ] + + return ( +
+
+
+

Own Real Estate in 3 Simple Steps

+

Get started in minutes, not months

+
+ +
+ {steps.map((step, index) => { + const Icon = step.icon + return ( +
+
+ +
+

{step.title}

+
+ {step.subtitle} +
+

{step.description}

+
+ ) + })} +
+
+
+ ) +} diff --git a/packages/nextjs/components/landing/properties-list.tsx b/packages/nextjs/components/landing/properties-list.tsx new file mode 100644 index 0000000..ab17a8f --- /dev/null +++ b/packages/nextjs/components/landing/properties-list.tsx @@ -0,0 +1,122 @@ +"use client" + +import { useState } from "react" +import { Card } from "../ui/card" +import { Badge } from "../ui/badge" +import { Button } from "../ui/button" +import { Progress } from "../ui/progress" +import { Input } from "../ui/input" +import { MapPin, TrendingUp, Search } from "lucide-react" +import { properties } from "~~/data/properties-data" + +export function PropertiesList() { + const [searchQuery, setSearchQuery] = useState("") + + const filteredProperties = properties.filter( + (property) => + property.name.toLowerCase().includes(searchQuery.toLowerCase()) || + property.location.toLowerCase().includes(searchQuery.toLowerCase()), + ) + + return ( + <> + {/* Search Bar */} +
+ + setSearchQuery(e.target.value)} + className="pl-12 py-6 text-base border-gray-300 focus:border-emerald-600 focus:ring-emerald-600" + /> +
+ + {/* Properties Grid */} + {filteredProperties.length === 0 ? ( +
+

No properties found matching your search.

+
+ ) : ( + <> +

+ Showing {filteredProperties.length} {filteredProperties.length === 1 ? "property" : "properties"} +

+ +
+ {filteredProperties.map((property) => ( + + {/* Property Image */} +
+ {property.name} +
+ {property.status} +
+
+ + ★ {property.rating} + +
+
+ + {/* Card Content */} +
+ {/* Property Name and Value */} +

{property.name}

+
+ + {property.location} +
+ + {/* Total Value */} +
+

Total Value

+
+

{property.totalValue}

+
+
+ + {/* Investment Details */} +
+
+

Available Tokens

+

{property.availableTokens}

+
+
+

Minimum Investment

+

{property.minimumInvestment}

+
+
+ + {/* APY and Funding */} +
+
+ + {property.apy} APY +
+ {property.funded}% Funded +
+ + {/* Progress Bar */} + + + {/* Invest Button */} + +
+
+ ))} +
+ + )} + + ) +} diff --git a/packages/nextjs/components/landing/stats.tsx b/packages/nextjs/components/landing/stats.tsx new file mode 100644 index 0000000..066918b --- /dev/null +++ b/packages/nextjs/components/landing/stats.tsx @@ -0,0 +1,47 @@ +"use client" + +export function Stats() { + const stats = [ + { + value: "2.5M+ STRK", + label: "Total Value Locked", + company: "On-Chain", + }, + { + value: "15,000+", + label: "Active Investors", + company: "Community", + }, + { + value: "8.5%", + label: "Average APY", + company: "Returns", + }, + { + value: "50+", + label: "Premium Properties", + company: "Portfolio", + }, + ] + + return ( +
+
+
+ {stats.map((stat, index) => ( +
+
+

{stat.value}

+

{stat.label}

+
+

{stat.company}

+
+ ))} +
+
+
+ ) +} diff --git a/packages/nextjs/components/landing/welcome-modal.tsx b/packages/nextjs/components/landing/welcome-modal.tsx new file mode 100644 index 0000000..dd801d5 --- /dev/null +++ b/packages/nextjs/components/landing/welcome-modal.tsx @@ -0,0 +1,86 @@ +"use client" + +import { Dialog, DialogContent, DialogTitle } from "../ui/dialog" +import { ArrowRight, UserPlus } from "lucide-react" +import Link from "next/link" +import { useRouter } from "next/navigation" + +interface WelcomeModalProps { + open: boolean + onOpenChange: (open: boolean) => void +} + +export function WelcomeModal({ open, onOpenChange }: WelcomeModalProps) { + const router = useRouter() + + const handleSignIn = () => { + onOpenChange(false) + router.push("/login") + } + + const handleSignUp = () => { + onOpenChange(false) + router.push("/onboarding") + } + + return ( + + +
+ {/* Title */} +
+ Welcome to BrickChain +

Choose how you'd like to continue

+
+ + {/* Options */} +
+ {/* I have an account */} + + + {/* I'm new here */} + +
+ + {/* Footer */} +

+ By continuing, you agree to our{" "} + + Terms of Service + {" "} + and{" "} + + Privacy Policy + +

+
+
+
+ ) +} diff --git a/packages/nextjs/components/ui/badge.tsx b/packages/nextjs/components/ui/badge.tsx new file mode 100644 index 0000000..12bc72e --- /dev/null +++ b/packages/nextjs/components/ui/badge.tsx @@ -0,0 +1,36 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "~~/lib/utils" + +const badgeVariants = cva( + "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: + "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", + outline: "text-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ) +} + +export { Badge, badgeVariants } diff --git a/packages/nextjs/components/ui/card.tsx b/packages/nextjs/components/ui/card.tsx new file mode 100644 index 0000000..cbaef92 --- /dev/null +++ b/packages/nextjs/components/ui/card.tsx @@ -0,0 +1,76 @@ +import * as React from "react" + +import { cn } from "~~/lib/utils" + +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +Card.displayName = "Card" + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardHeader.displayName = "CardHeader" + +const CardTitle = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardTitle.displayName = "CardTitle" + +const CardDescription = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardDescription.displayName = "CardDescription" + +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardContent.displayName = "CardContent" + +const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardFooter.displayName = "CardFooter" + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } diff --git a/packages/nextjs/components/ui/checkbox.tsx b/packages/nextjs/components/ui/checkbox.tsx new file mode 100644 index 0000000..4c7d1e3 --- /dev/null +++ b/packages/nextjs/components/ui/checkbox.tsx @@ -0,0 +1,30 @@ +"use client" + +import * as React from "react" +import * as CheckboxPrimitive from "@radix-ui/react-checkbox" +import { Check } from "lucide-react" + +import { cn } from "~~/lib/utils" + +const Checkbox = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + +)) +Checkbox.displayName = CheckboxPrimitive.Root.displayName + +export { Checkbox } diff --git a/packages/nextjs/components/ui/dialog.tsx b/packages/nextjs/components/ui/dialog.tsx new file mode 100644 index 0000000..f7192c0 --- /dev/null +++ b/packages/nextjs/components/ui/dialog.tsx @@ -0,0 +1,122 @@ +"use client" + +import * as React from "react" +import * as DialogPrimitive from "@radix-ui/react-dialog" +import { X } from "lucide-react" + +import { cn } from "~~/lib/utils" + +const Dialog = DialogPrimitive.Root + +const DialogTrigger = DialogPrimitive.Trigger + +const DialogPortal = DialogPrimitive.Portal + +const DialogClose = DialogPrimitive.Close + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)) +DialogContent.displayName = DialogPrimitive.Content.displayName + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogHeader.displayName = "DialogHeader" + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogFooter.displayName = "DialogFooter" + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogTitle.displayName = DialogPrimitive.Title.displayName + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogDescription.displayName = DialogPrimitive.Description.displayName + +export { + Dialog, + DialogPortal, + DialogOverlay, + DialogTrigger, + DialogClose, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +} diff --git a/packages/nextjs/components/ui/form.tsx b/packages/nextjs/components/ui/form.tsx new file mode 100644 index 0000000..d008357 --- /dev/null +++ b/packages/nextjs/components/ui/form.tsx @@ -0,0 +1,178 @@ +"use client" + +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" +import { Slot } from "@radix-ui/react-slot" +import { + Controller, + FormProvider, + useFormContext, + type ControllerProps, + type FieldPath, + type FieldValues, +} from "react-hook-form" + +import { cn } from "~~/lib/utils" +import { Label } from "~~/components/ui/label" + +const Form = FormProvider + +type FormFieldContextValue< + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath +> = { + name: TName +} + +const FormFieldContext = React.createContext(null) + +const FormField = < + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath +>({ + ...props +}: ControllerProps) => { + return ( + + + + ) +} + +const useFormField = () => { + const fieldContext = React.useContext(FormFieldContext) + const itemContext = React.useContext(FormItemContext) + const { getFieldState, formState } = useFormContext() + + if (!fieldContext) { + throw new Error("useFormField should be used within ") + } + + if (!itemContext) { + throw new Error("useFormField should be used within ") + } + + const fieldState = getFieldState(fieldContext.name, formState) + + const { id } = itemContext + + return { + id, + name: fieldContext.name, + formItemId: `${id}-form-item`, + formDescriptionId: `${id}-form-item-description`, + formMessageId: `${id}-form-item-message`, + ...fieldState, + } +} + +type FormItemContextValue = { + id: string +} + +const FormItemContext = React.createContext(null) + +const FormItem = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const id = React.useId() + + return ( + +
+ + ) +}) +FormItem.displayName = "FormItem" + +const FormLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + const { error, formItemId } = useFormField() + + return ( +