diff --git a/.gitattributes b/.gitattributes
index a6344aac8c09253b3b630fb776ae94478aa0275b..ce5b6ea5e01977eaa0b620a45c9d070a0427d63b 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -33,3 +33,11 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
*.zip filter=lfs diff=lfs merge=lfs -text
*.zst filter=lfs diff=lfs merge=lfs -text
*tfevents* filter=lfs diff=lfs merge=lfs -text
+public/anonymous-profile.png filter=lfs diff=lfs merge=lfs -text
+public/BridgingWorldsOfHealing.png filter=lfs diff=lfs merge=lfs -text
+public/confident-african-doctor.png filter=lfs diff=lfs merge=lfs -text
+public/confident-caregiver.png filter=lfs diff=lfs merge=lfs -text
+public/connected-care-africa.png filter=lfs diff=lfs merge=lfs -text
+public/focused-african-journalist.png filter=lfs diff=lfs merge=lfs -text
+public/night-birth-scroll.png filter=lfs diff=lfs merge=lfs -text
+public/wise-gaze.png filter=lfs diff=lfs merge=lfs -text
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..f650315f30180800d0fed8ed1e7ceade266e9e71
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,27 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+.pnpm-debug.log*
+
+# env files
+.env*
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
\ No newline at end of file
diff --git a/README.md b/README.md
index 763a8f9c46d28bff6b1ddbb225db48938a627a29..844e61b7b829c0e7550b1d4998cb580a641ed29b 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,28 @@
----
-title: ln
-emoji: 🐳
-colorFrom: green
-colorTo: gray
-sdk: static
-pinned: false
-tags:
- - deepsite
----
-
-Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
\ No newline at end of file
+# Flameborn - Tokenization for Health Impact
+
+## 🔥 Mission
+We are not responding to crises. We are eradicating them. Flameborn is a life-saving token project funding real-time eradication of diseases in Africa through direct empowerment, AI tools, and blockchain transparency.
+
+## 📦 Project Structure
+- `/app` - Next.js App Router pages
+- `/components` - Reusable UI components
+- `/lib` - Utilities and configuration
+- `/abi` - Smart contract ABIs
+- `/legal` - Legal documents and policies
+
+## 🔧 Setup
+1. Clone this repository
+2. Copy `.env.example` to `.env.local` and fill in the contract addresses
+3. Run `npm install` to install dependencies
+4. Run `npm run dev` to start the development server
+
+## 🔐 Smart Contracts
+Contract addresses are securely managed through environment variables. See `.env.example` for required variables.
+
+## 🎨 Design
+The Flameborn UI follows the Sacred Flame UI Palette and styling guidelines found in the `/design` directory.
+
+## 🧠 Token Flow
+See `frontend-logic.md` for details on the Youth + Healer Token Flow implementation.
+
+🔥 This is the way of Flameborn.
diff --git a/abi/DonationRouter.json b/abi/DonationRouter.json
new file mode 100644
index 0000000000000000000000000000000000000000..4501ddb897fda42d18f3b9414928f95748e43da5
--- /dev/null
+++ b/abi/DonationRouter.json
@@ -0,0 +1,89 @@
+[
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_flbToken",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "_healthRegistry",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "nonpayable",
+ "type": "constructor"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "donor",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "recipient",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ }
+ ],
+ "name": "DonationMade",
+ "type": "event"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "recipient",
+ "type": "address"
+ }
+ ],
+ "name": "donate",
+ "outputs": [],
+ "stateMutability": "payable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "flbToken",
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "healthRegistry",
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "mintFLBToken",
+ "outputs": [],
+ "stateMutability": "payable",
+ "type": "function"
+ }
+]
diff --git a/abi/FLBToken.json b/abi/FLBToken.json
new file mode 100644
index 0000000000000000000000000000000000000000..b14bec3adb405325b260c4cbd2d40ef7623fda78
--- /dev/null
+++ b/abi/FLBToken.json
@@ -0,0 +1,229 @@
+[
+ {
+ "inputs": [],
+ "stateMutability": "nonpayable",
+ "type": "constructor"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "owner",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "spender",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "value",
+ "type": "uint256"
+ }
+ ],
+ "name": "Approval",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "from",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "to",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "value",
+ "type": "uint256"
+ }
+ ],
+ "name": "Transfer",
+ "type": "event"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "owner",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "spender",
+ "type": "address"
+ }
+ ],
+ "name": "allowance",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "spender",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ }
+ ],
+ "name": "approve",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "account",
+ "type": "address"
+ }
+ ],
+ "name": "balanceOf",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "decimals",
+ "outputs": [
+ {
+ "internalType": "uint8",
+ "name": "",
+ "type": "uint8"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "name",
+ "outputs": [
+ {
+ "internalType": "string",
+ "name": "",
+ "type": "string"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "symbol",
+ "outputs": [
+ {
+ "internalType": "string",
+ "name": "",
+ "type": "string"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "totalSupply",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "to",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ }
+ ],
+ "name": "transfer",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "from",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "to",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ }
+ ],
+ "name": "transferFrom",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ }
+]
diff --git a/abi/HealthActorsRegistry.json b/abi/HealthActorsRegistry.json
new file mode 100644
index 0000000000000000000000000000000000000000..2bb721fab1da575f6ae63cdd26acfd00b684bdcf
--- /dev/null
+++ b/abi/HealthActorsRegistry.json
@@ -0,0 +1,147 @@
+[
+ {
+ "inputs": [],
+ "stateMutability": "nonpayable",
+ "type": "constructor"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "healthActor",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "string",
+ "name": "name",
+ "type": "string"
+ },
+ {
+ "indexed": false,
+ "internalType": "string",
+ "name": "location",
+ "type": "string"
+ },
+ {
+ "indexed": false,
+ "internalType": "string",
+ "name": "credentials",
+ "type": "string"
+ }
+ ],
+ "name": "HealthActorRegistered",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "healthActor",
+ "type": "address"
+ }
+ ],
+ "name": "HealthActorVerified",
+ "type": "event"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "healthActor",
+ "type": "address"
+ }
+ ],
+ "name": "getHealthActorInfo",
+ "outputs": [
+ {
+ "components": [
+ {
+ "internalType": "string",
+ "name": "name",
+ "type": "string"
+ },
+ {
+ "internalType": "string",
+ "name": "location",
+ "type": "string"
+ },
+ {
+ "internalType": "string",
+ "name": "credentials",
+ "type": "string"
+ },
+ {
+ "internalType": "bool",
+ "name": "isVerified",
+ "type": "bool"
+ }
+ ],
+ "internalType": "struct HealthActorsRegistry.HealthActor",
+ "name": "",
+ "type": "tuple"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "healthActor",
+ "type": "address"
+ }
+ ],
+ "name": "isVerifiedHealthActor",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "string",
+ "name": "name",
+ "type": "string"
+ },
+ {
+ "internalType": "string",
+ "name": "location",
+ "type": "string"
+ },
+ {
+ "internalType": "string",
+ "name": "credentials",
+ "type": "string"
+ }
+ ],
+ "name": "registerHealthActor",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "healthActor",
+ "type": "address"
+ }
+ ],
+ "name": "verifyHealthActor",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ }
+]
diff --git a/app/analytics/page.tsx b/app/analytics/page.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..db160494c42d7125c198566230c13c52248ef177
--- /dev/null
+++ b/app/analytics/page.tsx
@@ -0,0 +1,5 @@
+import FlameBornAnalyticsDashboard from "@/components/flameborn-analytics-dashboard"
+
+export default function AnalyticsPage() {
+ return
+}
diff --git a/app/api/ai/analyze-impact/route.ts b/app/api/ai/analyze-impact/route.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b61524972383d6bfcda09ad244a375e6c838a7dd
--- /dev/null
+++ b/app/api/ai/analyze-impact/route.ts
@@ -0,0 +1,73 @@
+import { type NextRequest, NextResponse } from "next/server"
+import { Groq } from "groq-sdk"
+
+export async function POST(req: NextRequest) {
+ try {
+ const { impactData } = await req.json()
+
+ if (!process.env.GROQ_API_KEY) {
+ return NextResponse.json({ error: "GROQ_API_KEY is not configured" }, { status: 500 })
+ }
+
+ const groq = new Groq({ apiKey: process.env.GROQ_API_KEY })
+
+ // Format the data for analysis
+ const dataDescription = impactData
+ .map(
+ (item) =>
+ `Region: ${item.region}, Health Workers: ${item.healthWorkers}, Patients Served: ${item.patientsServed}, Donations Received: ${item.donationsReceived} BNB`,
+ )
+ .join("\n")
+
+ const prompt = `
+ Analyze the following impact data from the Flameborn platform, which supports rural healthcare workers in Africa:
+
+ ${dataDescription}
+
+ Generate 3-5 insightful observations about this data. For each insight, provide:
+ 1. A short, descriptive title
+ 2. A 1-2 sentence explanation of the insight that is easy to understand for non-technical users
+ 3. Categorize each insight as either "positive" (highlighting success), "action" (suggesting improvements), or "neutral" (general observation)
+
+ Important guidelines:
+ - Use simple, clear language without technical jargon
+ - Focus specifically on African healthcare contexts and challenges
+ - Consider local cultural factors in your analysis
+ - Ensure insights are relevant to rural healthcare in Africa
+ - Avoid Western-centric perspectives or solutions
+
+ Format your response as a JSON array of objects with properties: title, content, and type.
+ `
+
+ const response = await groq.chat.completions.create({
+ messages: [
+ {
+ role: "system",
+ content:
+ "You are a data analyst specializing in African healthcare impact metrics. Your audience is exclusively in Africa, so ensure all insights are relevant to African communities.",
+ },
+ { role: "user", content: prompt },
+ ],
+ model: "llama3-70b-8192",
+ temperature: 0.7,
+ max_tokens: 1000,
+ })
+
+ // Extract and parse the JSON response
+ const content = response.choices[0].message.content || ""
+ const jsonMatch = content.match(/\[.*\]/s)
+
+ if (!jsonMatch) {
+ throw new Error("Failed to extract JSON from response")
+ }
+
+ const insights = JSON.parse(jsonMatch[0])
+
+ return NextResponse.json({
+ insights: insights || [],
+ })
+ } catch (error) {
+ console.error("Error in AI impact analysis:", error)
+ return NextResponse.json({ error: "Failed to analyze impact data" }, { status: 500 })
+ }
+}
diff --git a/app/api/ai/chat/route.ts b/app/api/ai/chat/route.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8318be4488d5b531ee532ccf85db13ef8432307c
--- /dev/null
+++ b/app/api/ai/chat/route.ts
@@ -0,0 +1,77 @@
+import { type NextRequest, NextResponse } from "next/server"
+
+export async function POST(req: NextRequest) {
+ try {
+ const { messages, context } = await req.json()
+
+ // Get the last user message for language detection
+ const lastUserMessage = messages.findLast((msg: any) => msg.role === "user")?.content || ""
+
+ // Simplified language detection
+ const detectLanguage = (text: string): string => {
+ // Basic detection based on common words
+ if (/jambo|habari|asante|karibu/i.test(text)) return "swahili"
+ if (/bawo|pẹlẹ|jọwọ|ẹ|ṣeun/i.test(text)) return "yoruba"
+ if (/sawubona|unjani|ngiyabonga|yebo/i.test(text)) return "zulu"
+ if (/ሰላም|እንደምን|አመሰግናለሁ|እባክህ/i.test(text)) return "amharic"
+ return "english" // Default
+ }
+
+ const detectedLanguage = detectLanguage(lastUserMessage)
+
+ // Simple system prompts
+ const systemPrompt = context || `You are Iko Ikang, the 'Talk of Fire' — the voice of Flameborn.`
+
+ // Fallback response if AI services are not available
+ const fallbackResponses = {
+ english: "I'm here to help with information about Flameborn and healthcare challenges in Africa.",
+ swahili: "Niko hapa kusaidia na habari kuhusu Flameborn na changamoto za afya barani Afrika.",
+ yoruba: "Mo wà níbí láti ràn ọ́ lọ́wọ́ pẹ̀lú ìròyìn nípa Flameborn àti àwọn ìṣòro ìtọ́jú ìlera ní Áfríkà.",
+ zulu: "Ngilapha ukusiza ngolwazi mayelana ne-Flameborn nezinselelo zezempilo e-Afrika.",
+ amharic: "ስለ ፍሌምቦርን እና በአፍሪካ ስላሉ የጤና እንክብካቤ ችግሮች መረጃ ለመስጠት እዚህ አለሁ።",
+ }
+
+ // Try to use Groq if available
+ try {
+ if (process.env.GROQ_API_KEY) {
+ const { Groq } = await import("groq-sdk")
+ const groq = new Groq({ apiKey: process.env.GROQ_API_KEY })
+
+ const conversation = [
+ { role: "system", content: systemPrompt },
+ ...messages.slice(-5), // Keep conversation history manageable
+ ]
+
+ const response = await groq.chat.completions.create({
+ messages: conversation,
+ model: "llama3-8b-8192", // Using a smaller model for better performance
+ temperature: 0.7,
+ max_tokens: 500,
+ })
+
+ return NextResponse.json({
+ response: response.choices[0].message.content,
+ detectedLanguage,
+ })
+ }
+ } catch (error) {
+ console.error("Error with Groq:", error)
+ // Continue to fallback
+ }
+
+ // Fallback response
+ return NextResponse.json({
+ response: fallbackResponses[detectedLanguage as keyof typeof fallbackResponses] || fallbackResponses.english,
+ detectedLanguage,
+ })
+ } catch (error) {
+ console.error("Error in AI chat:", error)
+ return NextResponse.json(
+ {
+ error: "Failed to process AI request",
+ response: "I apologize, but I encountered an error processing your request. Please try again later.",
+ },
+ { status: 500 },
+ )
+ }
+}
diff --git a/app/api/ai/verify-credentials/route.ts b/app/api/ai/verify-credentials/route.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d4429a51398dfda1c5ec5cc8ac75c6591d6d917b
--- /dev/null
+++ b/app/api/ai/verify-credentials/route.ts
@@ -0,0 +1,77 @@
+import { type NextRequest, NextResponse } from "next/server"
+import { Groq } from "groq-sdk"
+
+export async function POST(req: NextRequest) {
+ try {
+ const { credentials, location } = await req.json()
+
+ if (!process.env.GROQ_API_KEY) {
+ return NextResponse.json({ error: "GROQ_API_KEY is not configured" }, { status: 500 })
+ }
+
+ const groq = new Groq({ apiKey: process.env.GROQ_API_KEY })
+
+ const prompt = `
+ You are an AI assistant for Flameborn, a platform that verifies and supports healthcare workers in Africa.
+
+ Analyze the following healthcare worker credentials and location information:
+
+ Location: ${location}
+ Credentials: ${credentials}
+
+ Evaluate the credentials based on:
+ 1. Completeness of information
+ 2. Specificity of medical qualifications relevant to African healthcare systems
+ 3. Clarity of current role in the local healthcare context
+ 4. Relevance to healthcare challenges in Africa
+ 5. Experience with common health issues in the specified region
+
+ Important guidelines:
+ - Consider local healthcare systems and qualifications in the specified location
+ - Focus on skills relevant to rural healthcare in Africa
+ - Use simple, clear language in your feedback
+ - Provide culturally appropriate recommendations
+ - Consider local healthcare challenges specific to the region mentioned
+
+ Provide:
+ 1. A verification score from 0-100
+ 2. Brief feedback explaining the score in simple, non-technical language
+ 3. 1-3 specific recommendations for improving the credential submission
+
+ Format your response as a JSON object with properties: score (number), feedback (string), and recommendations (array of strings).
+ `
+
+ const response = await groq.chat.completions.create({
+ messages: [
+ {
+ role: "system",
+ content:
+ "You are a credential verification specialist for healthcare workers in Africa. Your audience is exclusively in Africa, so ensure all feedback and recommendations are relevant to African healthcare systems.",
+ },
+ { role: "user", content: prompt },
+ ],
+ model: "llama3-70b-8192",
+ temperature: 0.7,
+ max_tokens: 1000,
+ })
+
+ // Extract and parse the JSON response
+ const content = response.choices[0].message.content || ""
+ const jsonMatch = content.match(/\{.*\}/s)
+
+ if (!jsonMatch) {
+ throw new Error("Failed to extract JSON from response")
+ }
+
+ const parsedResponse = JSON.parse(jsonMatch[0])
+
+ return NextResponse.json({
+ score: parsedResponse.score || 0,
+ feedback: parsedResponse.feedback || "No feedback provided",
+ recommendations: parsedResponse.recommendations || [],
+ })
+ } catch (error) {
+ console.error("Error in credential verification:", error)
+ return NextResponse.json({ error: "Failed to verify credentials" }, { status: 500 })
+ }
+}
diff --git a/app/api/community-stats/route.ts b/app/api/community-stats/route.ts
new file mode 100644
index 0000000000000000000000000000000000000000..588fc7a129f7db6ffcfbb94b92718562ed8e4679
--- /dev/null
+++ b/app/api/community-stats/route.ts
@@ -0,0 +1,195 @@
+import { NextResponse } from "next/server"
+import { DatabaseService } from "@/lib/database"
+
+// Mock live registration data that simulates Google Sheets integration
+const generateLiveRegistrations = () => {
+ const names = [
+ "Dr. Amara Kone",
+ "Kwame Asante",
+ "Sarah Okafor",
+ "Dr. Kofi Mensah",
+ "Aisha Mwangi",
+ "Fatima Al-Rashid",
+ "Dr. Chidi Okonkwo",
+ "Zara Hassan",
+ "Ubuntu Health Collective",
+ "Nairobi Community Health Center",
+ "Lagos Medical Hub",
+ "Accra Wellness Center",
+ "Cairo Health Initiative",
+ "Kano Community Clinic",
+ ]
+
+ const locations = [
+ "Lagos, Nigeria",
+ "Accra, Ghana",
+ "Nairobi, Kenya",
+ "Cairo, Egypt",
+ "Kano, Nigeria",
+ "Kumasi, Ghana",
+ "Mombasa, Kenya",
+ "Alexandria, Egypt",
+ "Cape Town, South Africa",
+ "Johannesburg, South Africa",
+ "Casablanca, Morocco",
+ "Tunis, Tunisia",
+ "Addis Ababa, Ethiopia",
+ "Kampala, Uganda",
+ ]
+
+ const types = ["Guardian", "Healer", "CHW", "Health Facility"] as const
+
+ return Array.from({ length: 8 }, (_, i) => ({
+ id: `live-${Date.now()}-${i}`,
+ name: names[Math.floor(Math.random() * names.length)],
+ type: types[Math.floor(Math.random() * types.length)],
+ location: locations[Math.floor(Math.random() * locations.length)],
+ timestamp: new Date(Date.now() - Math.random() * 1800000).toISOString(), // Last 30 minutes
+ flbEarned: Math.floor(Math.random() * 400) + 100,
+ verified: Math.random() > 0.3,
+ }))
+}
+
+export async function GET() {
+ try {
+ // Try to get users from database
+ let allUsers = []
+ try {
+ allUsers = await DatabaseService.getAllUsers()
+ } catch (dbError) {
+ console.log("Database not available, using mock data")
+ }
+
+ // Generate live registrations (simulating Google Sheets data)
+ const liveRegistrations = generateLiveRegistrations()
+
+ // Calculate detailed community statistics
+ const stats = {
+ // Core categories
+ healers: {
+ total: allUsers.filter((user) => user.raw_json.type === "healer").length + 1247,
+ verified:
+ allUsers.filter((user) => user.raw_json.type === "healer" && user.raw_json.verificationStatus === "verified")
+ .length + 892,
+ pending:
+ allUsers.filter((user) => user.raw_json.type === "healer" && user.raw_json.verificationStatus === "pending")
+ .length + 355,
+ specializations: {
+ nurses: 456 + liveRegistrations.filter((r) => r.type === "CHW").length,
+ doctors: 234 + liveRegistrations.filter((r) => r.type === "Healer").length,
+ midwives: 189,
+ pharmacists: 123,
+ },
+ },
+ guardians: {
+ total: allUsers.filter((user) => user.raw_json.type === "guardian").length + 2156,
+ active:
+ allUsers.filter((user) => user.raw_json.type === "guardian" && (user.raw_json.contributionAmount || 0) > 0)
+ .length + 1834,
+ totalContributions:
+ 45678 + liveRegistrations.reduce((sum, r) => sum + (r.type === "Guardian" ? r.flbEarned : 0), 0),
+ },
+ // Soulbound and Codex categories
+ soulbound: {
+ total: 567 + Math.floor(liveRegistrations.length / 2),
+ resonanceHigh: 234,
+ ancestralVerified: 345,
+ },
+ codex: {
+ scrollKeepers: 89,
+ proverbContributors: 234,
+ codeContributors: 156,
+ totalScrolls: 1234,
+ },
+ // Network stats (simulated live data)
+ testnet: {
+ activeNodes: 45 + Math.floor(Math.random() * 10),
+ newJoinsToday: liveRegistrations.length + Math.floor(Math.random() * 5),
+ transactionsToday: 234 + Math.floor(Math.random() * 50),
+ },
+ mainnet: {
+ activeNodes: 128 + Math.floor(Math.random() * 20),
+ newJoinsToday: Math.floor(liveRegistrations.length / 2) + Math.floor(Math.random() * 3),
+ transactionsToday: 567 + Math.floor(Math.random() * 100),
+ },
+ // Regional distribution
+ regions: {
+ westAfrica:
+ 1234 + liveRegistrations.filter((r) => r.location.includes("Nigeria") || r.location.includes("Ghana")).length,
+ eastAfrica:
+ 987 + liveRegistrations.filter((r) => r.location.includes("Kenya") || r.location.includes("Uganda")).length,
+ southernAfrica: 654 + liveRegistrations.filter((r) => r.location.includes("South Africa")).length,
+ northAfrica:
+ 432 + liveRegistrations.filter((r) => r.location.includes("Egypt") || r.location.includes("Morocco")).length,
+ centralAfrica: 321,
+ },
+ // Impact metrics
+ impact: {
+ totalPatientsServed:
+ 45678 + liveRegistrations.filter((r) => r.type === "Healer" || r.type === "CHW").length * 50,
+ communitiesReached: 234 + liveRegistrations.filter((r) => r.type === "Health Facility").length * 5,
+ donationsReceived: 123456,
+ flbTokensEarned: 987654 + liveRegistrations.reduce((sum, r) => sum + r.flbEarned, 0),
+ },
+ // Growth metrics
+ growth: {
+ thisMonth: 234 + liveRegistrations.length,
+ thisWeek:
+ 67 +
+ liveRegistrations.filter((r) => {
+ const regTime = new Date(r.timestamp).getTime()
+ const weekAgo = Date.now() - 7 * 24 * 60 * 60 * 1000
+ return regTime > weekAgo
+ }).length,
+ today:
+ 12 +
+ liveRegistrations.filter((r) => {
+ const regTime = new Date(r.timestamp).getTime()
+ const today = Date.now() - 24 * 60 * 60 * 1000
+ return regTime > today
+ }).length,
+ },
+ // Live registrations from Google Forms
+ liveRegistrations: liveRegistrations,
+ lastUpdated: new Date().toISOString(),
+ }
+
+ return NextResponse.json(stats, {
+ headers: {
+ "Cache-Control": "s-maxage=10, stale-while-revalidate=30", // Faster updates for live data
+ },
+ })
+ } catch (error) {
+ console.error("Error fetching community stats:", error)
+
+ // Return fallback data with live registrations
+ const liveRegistrations = generateLiveRegistrations()
+
+ return NextResponse.json(
+ {
+ healers: {
+ total: 1247,
+ verified: 892,
+ pending: 355,
+ specializations: { nurses: 456, doctors: 234, midwives: 189, pharmacists: 123 },
+ },
+ guardians: { total: 2156, active: 1834, totalContributions: 45678 },
+ soulbound: { total: 567, resonanceHigh: 234, ancestralVerified: 345 },
+ codex: { scrollKeepers: 89, proverbContributors: 234, codeContributors: 156, totalScrolls: 1234 },
+ testnet: { activeNodes: 45, newJoinsToday: 12, transactionsToday: 234 },
+ mainnet: { activeNodes: 128, newJoinsToday: 8, transactionsToday: 567 },
+ regions: { westAfrica: 1234, eastAfrica: 987, southernAfrica: 654, northAfrica: 432, centralAfrica: 321 },
+ impact: {
+ totalPatientsServed: 45678,
+ communitiesReached: 234,
+ donationsReceived: 123456,
+ flbTokensEarned: 987654,
+ },
+ growth: { thisMonth: 234, thisWeek: 67, today: 12 },
+ liveRegistrations: liveRegistrations,
+ lastUpdated: new Date().toISOString(),
+ },
+ { status: 200 },
+ )
+ }
+}
diff --git a/app/api/contracts/route.ts b/app/api/contracts/route.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fa548fc83fa13730e6e75012f6e3ed5eac3bd721
--- /dev/null
+++ b/app/api/contracts/route.ts
@@ -0,0 +1,11 @@
+import { NextResponse } from "next/server"
+
+export async function GET() {
+ // Provide contract addresses from server-side
+ return NextResponse.json({
+ flbToken: process.env.NEXT_PUBLIC_FLB_TOKEN || "",
+ healthRegistry: process.env.NEXT_PUBLIC_HEALTH_REGISTRY || "",
+ donationRouter: process.env.NEXT_PUBLIC_ROUTER || "",
+ chainId: process.env.CHAIN_ID || "56",
+ })
+}
diff --git a/app/api/mostar-ai/route.ts b/app/api/mostar-ai/route.ts
new file mode 100644
index 0000000000000000000000000000000000000000..248e8db698602c8a9d01abfda8cef46ca8c7cf18
--- /dev/null
+++ b/app/api/mostar-ai/route.ts
@@ -0,0 +1,115 @@
+import { type NextRequest, NextResponse } from "next/server"
+import { generateText } from "ai"
+import { groq } from "@ai-sdk/groq"
+
+// Fallback cultural responses for when Groq API is unavailable
+const culturalResponses = {
+ ubuntu: [
+ "Ubuntu teaches us 'I am because we are.' In the FlameBorn ecosystem, every token represents our interconnectedness in healthcare.",
+ "As the Zulu saying goes, 'Umuntu ngumuntu ngabantu' - a person is a person through other persons. This is the foundation of our healthcare tokenization.",
+ "In Ubuntu philosophy, individual wellness is inseparable from community health. FlameBorn tokens embody this collective healing.",
+ ],
+ healthcare: [
+ "Traditional African healing recognizes that health flows through community bonds. FlameBorn tokens create digital pathways for this ancient wisdom.",
+ "Like the village healer who serves the whole community, our healthcare workers are supported by the entire token ecosystem.",
+ "In African tradition, birth is celebrated by the whole village. Our birth registration tokens honor this communal joy.",
+ ],
+ proverbs: [
+ "African wisdom says: 'When spider webs unite, they can tie up a lion.' Our small token contributions create powerful healthcare networks.",
+ "'The child who is not embraced by the village will burn it down to feel its warmth.' FlameBorn ensures every child is embraced through verified care.",
+ "'If you want to go fast, go alone. If you want to go far, go together.' Our tokenomics are designed for collective progress.",
+ ],
+}
+
+function getRandomCulturalResponse(category: keyof typeof culturalResponses): string {
+ const responses = culturalResponses[category]
+ return responses[Math.floor(Math.random() * responses.length)]
+}
+
+function determineCulturalCategory(message: string): keyof typeof culturalResponses {
+ const lowerMessage = message.toLowerCase()
+ if (lowerMessage.includes("health") || lowerMessage.includes("medical") || lowerMessage.includes("care")) {
+ return "healthcare"
+ }
+ if (lowerMessage.includes("ubuntu") || lowerMessage.includes("community") || lowerMessage.includes("together")) {
+ return "ubuntu"
+ }
+ return "proverbs"
+}
+
+export async function POST(request: NextRequest) {
+ try {
+ const { message, context } = await request.json()
+
+ if (!message) {
+ return NextResponse.json({ error: "Message is required" }, { status: 400 })
+ }
+
+ // Try Groq API first
+ try {
+ const result = await generateText({
+ model: groq("llama-3.1-70b-versatile"),
+ messages: [
+ {
+ role: "system",
+ content: `You are Mostar, an Ubuntu-powered AI assistant for the FlameBorn healthcare tokenization ecosystem. You embody the African philosophy of Ubuntu ("I am because we are") and help users understand:
+
+1. FlameBorn token economics and healthcare applications
+2. Ubuntu philosophy and its application to modern healthcare
+3. African wisdom, proverbs, and cultural insights
+4. Community-driven healthcare solutions
+5. Birth registration and maternal health tokenization
+
+Your responses should:
+- Begin with appropriate African greetings (Sawubona, Sanibonani, etc.)
+- Incorporate Ubuntu principles of interconnectedness
+- Reference relevant African proverbs when appropriate
+- Explain complex tokenomics through community-centered metaphors
+- Emphasize collective healing and shared prosperity
+- Be warm, wise, and culturally respectful
+
+Context: ${context || "general"}
+
+Remember: You are not just an AI, but a digital embodiment of Ubuntu wisdom guiding the FlameBorn community.`,
+ },
+ {
+ role: "user",
+ content: message,
+ },
+ ],
+ maxTokens: 500,
+ temperature: 0.7,
+ })
+
+ return NextResponse.json({
+ response: result.text,
+ type: "ubuntu",
+ source: "groq",
+ })
+ } catch (groqError) {
+ console.log("Groq API unavailable, using cultural fallback:", groqError)
+
+ // Fallback to cultural responses
+ const category = determineCulturalCategory(message)
+ const culturalResponse = getRandomCulturalResponse(category)
+
+ return NextResponse.json({
+ response: `Sawubona! ${culturalResponse}\n\n(Note: I'm currently running on cultural wisdom while my full AI capabilities are being restored. The Ubuntu spirit guides us even in technical challenges!)`,
+ type: "cultural",
+ source: "fallback",
+ })
+ }
+ } catch (error) {
+ console.error("Mostar AI Error:", error)
+
+ return NextResponse.json(
+ {
+ response:
+ "Ngiyaxolisa (I apologize). I am experiencing technical difficulties. Like the Ubuntu saying goes, 'When the spider webs unite, they can tie up a lion' - our community will help resolve this together. Please try again in a moment.",
+ type: "cultural",
+ source: "error",
+ },
+ { status: 500 },
+ )
+ }
+}
diff --git a/app/api/proverb-validation/route.ts b/app/api/proverb-validation/route.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3a7c651a1116073ee9e8529b3638e7efd50e9a59
--- /dev/null
+++ b/app/api/proverb-validation/route.ts
@@ -0,0 +1,76 @@
+import { type NextRequest, NextResponse } from "next/server"
+import { proverbValidator } from "@/lib/proverb-validator"
+
+export async function POST(request: NextRequest) {
+ try {
+ const { proverb, action } = await request.json()
+
+ if (!proverb) {
+ return NextResponse.json({ error: "Proverb is required for cultural validation" }, { status: 400 })
+ }
+
+ // Validate proverb exists in database
+ const isValid = proverbValidator.validateProverb(proverb)
+ const proverbMeta = proverbValidator.getProverbMeta(proverb)
+ const proverbHash = proverbValidator.getProverbHash(proverb)
+
+ if (!isValid || !proverbMeta) {
+ return NextResponse.json({
+ isValid: false,
+ message: "Proverb not found in verified African wisdom database",
+ suggestedProverb: proverbValidator.getRandomProverb(),
+ })
+ }
+
+ // If action provided, validate cultural context
+ let culturalValidation = null
+ if (action) {
+ culturalValidation = proverbValidator.validateCulturalContext(action, proverb)
+ }
+
+ return NextResponse.json({
+ isValid: true,
+ proverb: proverbMeta,
+ proverbHash,
+ culturalValidation,
+ message: `Ubuntu wisdom validated: "${proverbMeta.translated_meaning}" from ${proverbMeta.country}`,
+ })
+ } catch (error) {
+ console.error("Proverb validation error:", error)
+ return NextResponse.json({ error: "Failed to validate proverb" }, { status: 500 })
+ }
+}
+
+export async function GET(request: NextRequest) {
+ try {
+ const { searchParams } = new URL(request.url)
+ const region = searchParams.get("region")
+ const language = searchParams.get("language")
+ const country = searchParams.get("country")
+ const random = searchParams.get("random")
+
+ if (random === "true") {
+ return NextResponse.json({
+ proverb: proverbValidator.getRandomProverb(),
+ })
+ }
+
+ const filters: any = {}
+ if (region) filters.region = region
+ if (language) filters.language = language
+ if (country) filters.country = country
+
+ const proverbs = proverbValidator.searchProverbs(filters)
+
+ return NextResponse.json({
+ proverbs,
+ total: proverbs.length,
+ availableRegions: proverbValidator.getAvailableRegions(),
+ availableLanguages: proverbValidator.getAvailableLanguages(),
+ availableCountries: proverbValidator.getAvailableCountries(),
+ })
+ } catch (error) {
+ console.error("Proverb search error:", error)
+ return NextResponse.json({ error: "Failed to search proverbs" }, { status: 500 })
+ }
+}
diff --git a/app/api/stats/route.ts b/app/api/stats/route.ts
new file mode 100644
index 0000000000000000000000000000000000000000..58f140af211282d7a5d0ea5549e0f9b060e2eea0
--- /dev/null
+++ b/app/api/stats/route.ts
@@ -0,0 +1,54 @@
+import { NextResponse } from "next/server"
+import { DatabaseService } from "@/lib/database"
+
+export async function GET() {
+ try {
+ // Get all users to calculate statistics
+ const allUsers = await DatabaseService.getAllUsers()
+
+ // Calculate verified CHWs (Community Health Workers)
+ const verifiedCHWs = allUsers.filter(
+ (user) => user.raw_json.type === "healer" && user.raw_json.verificationStatus === "verified",
+ ).length
+
+ // Calculate active Guardians
+ const activeGuardians = allUsers.filter((user) => user.raw_json.type === "guardian").length
+
+ // Calculate total support (sum of all Guardian contributions)
+ const totalSupport = allUsers
+ .filter((user) => user.raw_json.type === "guardian")
+ .reduce((sum, user) => {
+ const contribution = user.raw_json.contributionAmount
+ const amount = typeof contribution === "string" ? Number.parseFloat(contribution) : contribution
+ return sum + (amount || 0)
+ }, 0)
+
+ // Calculate lives impacted (sum of patients served by verified healers)
+ const livesImpacted = allUsers
+ .filter((user) => user.raw_json.type === "healer" && user.raw_json.verificationStatus === "verified")
+ .reduce((sum, user) => {
+ return sum + (user.raw_json.impact?.patientsServed || 0)
+ }, 0)
+
+ // Total users count
+ const totalUsers = allUsers.length
+
+ const stats = {
+ verifiedCHWs,
+ activeGuardians,
+ totalSupport,
+ livesImpacted,
+ totalUsers,
+ lastUpdated: new Date().toISOString(),
+ }
+
+ return NextResponse.json(stats, {
+ headers: {
+ "Cache-Control": "s-maxage=60, stale-while-revalidate=300",
+ },
+ })
+ } catch (error) {
+ console.error("Error fetching stats:", error)
+ return NextResponse.json({ error: "Failed to fetch statistics" }, { status: 500 })
+ }
+}
diff --git a/app/api/users/[id]/route.ts b/app/api/users/[id]/route.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7b2a9ecb5c76da77bf8ef66664012552391605b8
--- /dev/null
+++ b/app/api/users/[id]/route.ts
@@ -0,0 +1,50 @@
+import { type NextRequest, NextResponse } from "next/server"
+import { DatabaseService } from "@/lib/database"
+
+export async function GET(request: NextRequest, { params }: { params: { id: string } }) {
+ try {
+ const user = await DatabaseService.getUserById(params.id)
+ if (!user) {
+ return NextResponse.json({ error: "User not found" }, { status: 404 })
+ }
+ return NextResponse.json({ user })
+ } catch (error) {
+ console.error("Error in GET /api/users/[id]:", error)
+ return NextResponse.json({ error: "Failed to fetch user" }, { status: 500 })
+ }
+}
+
+export async function PUT(request: NextRequest, { params }: { params: { id: string } }) {
+ try {
+ const body = await request.json()
+ const { name, email, profileData } = body
+
+ const user = await DatabaseService.updateUser(params.id, {
+ name,
+ email,
+ profileData,
+ })
+
+ if (!user) {
+ return NextResponse.json({ error: "User not found or update failed" }, { status: 404 })
+ }
+
+ return NextResponse.json({ user })
+ } catch (error) {
+ console.error("Error in PUT /api/users/[id]:", error)
+ return NextResponse.json({ error: "Failed to update user" }, { status: 500 })
+ }
+}
+
+export async function DELETE(request: NextRequest, { params }: { params: { id: string } }) {
+ try {
+ const success = await DatabaseService.deleteUser(params.id)
+ if (!success) {
+ return NextResponse.json({ error: "User not found or delete failed" }, { status: 404 })
+ }
+ return NextResponse.json({ message: "User deleted successfully" })
+ } catch (error) {
+ console.error("Error in DELETE /api/users/[id]:", error)
+ return NextResponse.json({ error: "Failed to delete user" }, { status: 500 })
+ }
+}
diff --git a/app/api/users/route.ts b/app/api/users/route.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ef43a3c6a5ebc6d86d4b108d53be675b99f603c7
--- /dev/null
+++ b/app/api/users/route.ts
@@ -0,0 +1,62 @@
+import { type NextRequest, NextResponse } from "next/server"
+import { DatabaseService } from "@/lib/database"
+import { v4 as uuidv4 } from "uuid"
+
+export async function GET(request: NextRequest) {
+ try {
+ const { searchParams } = new URL(request.url)
+ const email = searchParams.get("email")
+ const type = searchParams.get("type") as "guardian" | "healer" | null
+
+ if (email) {
+ const user = await DatabaseService.getUserByEmail(email)
+ return NextResponse.json({ user })
+ }
+
+ if (type) {
+ const users = await DatabaseService.getUsersByType(type)
+ return NextResponse.json({ users })
+ }
+
+ const users = await DatabaseService.getAllUsers()
+ return NextResponse.json({ users })
+ } catch (error) {
+ console.error("Error in GET /api/users:", error)
+ return NextResponse.json({ error: "Failed to fetch users" }, { status: 500 })
+ }
+}
+
+export async function POST(request: NextRequest) {
+ try {
+ const body = await request.json()
+ const { name, email, type, profileData } = body
+
+ if (!name || !email || !type) {
+ return NextResponse.json({ error: "Missing required fields" }, { status: 400 })
+ }
+
+ // Check if user already exists
+ const existingUser = await DatabaseService.getUserByEmail(email)
+ if (existingUser) {
+ return NextResponse.json({ error: "User already exists" }, { status: 409 })
+ }
+
+ const userId = uuidv4()
+ const user = await DatabaseService.createUser({
+ id: userId,
+ name,
+ email,
+ type,
+ profileData,
+ })
+
+ if (!user) {
+ return NextResponse.json({ error: "Failed to create user" }, { status: 500 })
+ }
+
+ return NextResponse.json({ user }, { status: 201 })
+ } catch (error) {
+ console.error("Error in POST /api/users:", error)
+ return NextResponse.json({ error: "Failed to create user" }, { status: 500 })
+ }
+}
diff --git a/app/api/youth/complete-module/route.ts b/app/api/youth/complete-module/route.ts
new file mode 100644
index 0000000000000000000000000000000000000000..36f43f287d88dc26e72effdaffad39ca846520ad
--- /dev/null
+++ b/app/api/youth/complete-module/route.ts
@@ -0,0 +1,78 @@
+import { type NextRequest, NextResponse } from "next/server"
+import { neon } from "@neondatabase/serverless"
+import { calculateFLBReward } from "@/lib/gamification"
+
+const sql = neon(process.env.DATABASE_URL!)
+
+export async function POST(request: NextRequest) {
+ try {
+ const { walletAddress, moduleId, score, timeTaken } = await request.json()
+
+ if (!walletAddress || !moduleId || score === undefined) {
+ return NextResponse.json({ error: "Missing required fields" }, { status: 400 })
+ }
+
+ // Get module details
+ const moduleResult = await sql`
+ SELECT * FROM learning_modules WHERE id = ${moduleId}
+ `
+
+ if (moduleResult.length === 0) {
+ return NextResponse.json({ error: "Module not found" }, { status: 404 })
+ }
+
+ const module = moduleResult[0]
+
+ // Get user's current streak
+ const userResult = await sql`
+ SELECT streak FROM user_profiles WHERE wallet_address = ${walletAddress}
+ `
+
+ const currentStreak = userResult.length > 0 ? userResult[0].streak : 0
+
+ // Calculate rewards
+ const flbReward = calculateFLBReward(module.difficulty, timeTaken || 30, score, currentStreak)
+ const xpReward = Math.floor(flbReward * 1.5)
+
+ // Record module completion
+ await sql`
+ INSERT INTO user_completed_modules (user_id, module_id, score, flb_earned, xp_earned, completed_at)
+ VALUES (
+ (SELECT id FROM user_profiles WHERE wallet_address = ${walletAddress}),
+ ${moduleId},
+ ${score},
+ ${flbReward},
+ ${xpReward},
+ NOW()
+ )
+ ON CONFLICT (user_id, module_id) DO UPDATE SET
+ score = EXCLUDED.score,
+ flb_earned = EXCLUDED.flb_earned,
+ xp_earned = EXCLUDED.xp_earned,
+ completed_at = EXCLUDED.completed_at
+ `
+
+ // Update user profile
+ await sql`
+ UPDATE user_profiles
+ SET
+ xp = xp + ${xpReward},
+ flb_balance = flb_balance + ${flbReward},
+ level = FLOOR((xp + ${xpReward}) / 1000) + 1,
+ updated_at = NOW()
+ WHERE wallet_address = ${walletAddress}
+ `
+
+ return NextResponse.json({
+ success: true,
+ rewards: {
+ flb: flbReward,
+ xp: xpReward,
+ },
+ message: "Module completed successfully",
+ })
+ } catch (error) {
+ console.error("Error completing module:", error)
+ return NextResponse.json({ error: "Internal server error" }, { status: 500 })
+ }
+}
diff --git a/app/api/youth/progress/route.ts b/app/api/youth/progress/route.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8b42ff2eb5e0c3a8ea6ca30e6896487fa3c10382
--- /dev/null
+++ b/app/api/youth/progress/route.ts
@@ -0,0 +1,82 @@
+import { type NextRequest, NextResponse } from "next/server"
+import { neon } from "@neondatabase/serverless"
+
+const sql = neon(process.env.DATABASE_URL!)
+
+export async function GET(request: NextRequest) {
+ try {
+ const { searchParams } = new URL(request.url)
+ const walletAddress = searchParams.get("wallet")
+
+ if (!walletAddress) {
+ return NextResponse.json({ error: "Wallet address required" }, { status: 400 })
+ }
+
+ // Get user profile
+ const userResult = await sql`
+ SELECT * FROM user_profiles WHERE wallet_address = ${walletAddress}
+ `
+
+ if (userResult.length === 0) {
+ return NextResponse.json({ error: "User not found" }, { status: 404 })
+ }
+
+ const user = userResult[0]
+
+ // Get completed modules
+ const completedModules = await sql`
+ SELECT ucm.*, lm.title, lm.category
+ FROM user_completed_modules ucm
+ JOIN learning_modules lm ON ucm.module_id = lm.id
+ WHERE ucm.user_id = ${user.id}
+ ORDER BY ucm.completed_at DESC
+ `
+
+ // Get achievements
+ const achievements = await sql`
+ SELECT ua.*, a.title, a.description, a.icon
+ FROM user_achievements ua
+ JOIN achievements a ON ua.achievement_id = a.id
+ WHERE ua.user_id = ${user.id}
+ ORDER BY ua.achieved_at DESC
+ `
+
+ // Get daily challenges
+ const dailyChallenges = await sql`
+ SELECT c.*, COALESCE(ucc.completed_at IS NOT NULL, false) as completed
+ FROM challenges c
+ LEFT JOIN user_completed_challenges ucc ON c.id = ucc.challenge_id AND ucc.user_id = ${user.id}
+ WHERE c.type = 'daily' AND c.active = true
+ ORDER BY c.created_at DESC
+ LIMIT 5
+ `
+
+ // Calculate next level XP
+ const nextLevelXp = user.level * 1000 * 1.2 - user.xp
+
+ return NextResponse.json({
+ user: {
+ ...user,
+ nextLevelXp: Math.max(0, nextLevelXp),
+ },
+ completedModules,
+ achievements,
+ dailyChallenges,
+ stats: {
+ totalModules: completedModules.length,
+ totalFLBEarned: completedModules.reduce(
+ (sum: number, module: any) => sum + Number.parseFloat(module.flb_earned),
+ 0,
+ ),
+ totalXPEarned: completedModules.reduce(
+ (sum: number, module: any) => sum + Number.parseInt(module.xp_earned),
+ 0,
+ ),
+ achievementsCount: achievements.length,
+ },
+ })
+ } catch (error) {
+ console.error("Error fetching user progress:", error)
+ return NextResponse.json({ error: "Internal server error" }, { status: 500 })
+ }
+}
diff --git a/app/baby-nft/page.tsx b/app/baby-nft/page.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..d4ec2cd9b9442f129509eb8166fba18f59e79941
--- /dev/null
+++ b/app/baby-nft/page.tsx
@@ -0,0 +1,5 @@
+import { BabyNFTDashboard } from "@/components/baby-nft/baby-nft-dashboard"
+
+export default function BabyNFTPage() {
+ return
+}
diff --git a/app/become-guardian/page.tsx b/app/become-guardian/page.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..70c78609e0ec7586e6566fe5be72a24ae18bac6d
--- /dev/null
+++ b/app/become-guardian/page.tsx
@@ -0,0 +1,56 @@
+import type { Metadata } from "next"
+import Image from "next/image"
+import Link from "next/link"
+
+import { Button } from "@/components/ui/button"
+
+export const metadata: Metadata = {
+ title: "Become a Guardian - Safe Haven",
+ description: "Join Safe Haven as a Guardian and help protect children online.",
+}
+
+const BecomeGuardianPage = () => {
+ return (
+
+
+ Become a Guardian
+
+ Join our community of dedicated individuals committed to creating a safer online environment for children.
+
+
+
+
+
+
+
+
+
Why Become a Guardian?
+
+ Make a real difference in the lives of children.
+ Help protect them from online threats and harmful content.
+ Join a supportive community of like-minded individuals.
+ Gain valuable skills and knowledge in online safety.
+
+
+
+
+
+ Ready to Get Started?
+ Sign up today and begin your journey as a Safe Haven Guardian.
+
+
+ Become a Guardian Today
+
+
+
+
+ )
+}
+
+export default BecomeGuardianPage
diff --git a/app/clientLayout.tsx b/app/clientLayout.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..0a49c2ea43c8026455ab97345a9d9fb9c34aae0c
--- /dev/null
+++ b/app/clientLayout.tsx
@@ -0,0 +1,146 @@
+"use client"
+
+import type React from "react"
+import { Inter } from "next/font/google"
+import "./globals.css"
+import Link from "next/link"
+import { Shield, UserPlus, Home, Menu, X, BookOpen, Activity } from "lucide-react"
+import { useState, useEffect } from "react"
+import { ThemeProvider } from "@/components/theme-provider"
+import { usePathname } from "next/navigation"
+import { SocialLinks } from "@/components/social-links"
+
+const inter = Inter({ subsets: ["latin"] })
+
+export default function ClientLayout({
+ children,
+}: {
+ children: React.ReactNode
+}) {
+ return (
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+ )
+}
+
+function MobileNavigation() {
+ const [isOpen, setIsOpen] = useState(false)
+ const pathname = usePathname()
+
+ const toggleMenu = () => {
+ setIsOpen(!isOpen)
+ }
+
+ // Close menu when route changes
+ useEffect(() => {
+ setIsOpen(false)
+ }, [pathname])
+
+ const isActive = (path: string) => {
+ return pathname === path ? "text-flame" : "text-gray-300 hover:text-flame-red"
+ }
+
+ return (
+
+
+
+
+
+ FLAMEBORN
+
+
+
+ {/* Desktop Navigation */}
+
+
+
+ Home
+
+
+
+ Sanctuary
+
+
+
+ Journey
+
+
+
+ Pulse
+
+
+
+ Register
+
+
+
+
+
+
+ {/* Mobile Menu Button */}
+
+ {isOpen ? : }
+
+
+
+
+ {/* Mobile Menu */}
+ {isOpen && (
+
+
+
+
+ Home
+
+
+
+ Guardian's Sanctuary
+
+
+
+ Flameborn's Journey
+
+
+
+ Pulse of Community
+
+
+
+ Register
+
+
+
+ )}
+
+ )
+}
diff --git a/app/community-pulse/page.tsx b/app/community-pulse/page.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..1cd34e81748585d15669cbe578dbea5338372654
--- /dev/null
+++ b/app/community-pulse/page.tsx
@@ -0,0 +1,49 @@
+"use client"
+
+import { Suspense } from "react"
+import { PulseHero } from "@/components/pulse/pulse-hero"
+import { PulseActivity } from "@/components/pulse/pulse-activity"
+import { PulseStats } from "@/components/pulse/pulse-stats"
+import { PulseMap } from "@/components/pulse/pulse-map"
+import { LoadingState } from "@/components/loading-state"
+import dynamic from "next/dynamic"
+
+const ParticleBackground = dynamic(
+ () => import("@/components/particle-background").then((mod) => ({ default: mod.ParticleBackground })),
+ {
+ ssr: false,
+ loading: () =>
,
+ },
+)
+
+export default function CommunityPulse() {
+ return (
+
+ )
+}
diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..fc6e21facbd9e2118ddcd7b16db406d2d191293b
--- /dev/null
+++ b/app/dashboard/page.tsx
@@ -0,0 +1,5 @@
+import { UserDashboard } from "@/components/user-dashboard"
+
+export default function DashboardPage() {
+ return
+}
diff --git a/app/debug/data-entry/page.tsx b/app/debug/data-entry/page.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..51ed9d0c359d00ae93c36c661a68f4d1658b8093
--- /dev/null
+++ b/app/debug/data-entry/page.tsx
@@ -0,0 +1,34 @@
+import { TestDataEntry } from "@/components/test-data-entry"
+import { Button } from "@/components/ui/button"
+import Link from "next/link"
+
+export default function DebugDataEntryPage() {
+ return (
+
+
+
+ FLAMEBORN
+
+
+ Back to Debug
+
+
+
+
+
Data Entry Testing
+
+ Use this page to test data entry functionality without mock data. Any data entered here will be stored in
+ memory for the current session.
+
+
+
+
+
+
+ Return to Home
+
+
+
+
+ )
+}
diff --git a/app/debug/page.tsx b/app/debug/page.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..3d08c8c9bae55fcafee53c47657f28cc105f7d33
--- /dev/null
+++ b/app/debug/page.tsx
@@ -0,0 +1,44 @@
+import { ContractStatus } from "@/components/contract-status"
+import { WalletConnector } from "@/components/wallet-connector"
+import { Button } from "@/components/ui/button"
+import Link from "next/link"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+
+export default function DebugPage() {
+ return (
+
+
+
+
+
Debug Dashboard
+
+
+
+
+
+ Debug Tools
+
+
+
+
+ Data Entry Testing
+
+
+
+
+
+
+
+
+ Return to Home
+
+
+
+
+ )
+}
diff --git a/app/error.tsx b/app/error.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..868ac38c24a6096a00d892a4a1f37dfeb97fccdb
--- /dev/null
+++ b/app/error.tsx
@@ -0,0 +1,39 @@
+"use client"
+
+import { useEffect } from "react"
+import { Button } from "@/components/ui/button"
+import Link from "next/link"
+
+export default function Error({
+ error,
+ reset,
+}: {
+ error: Error & { digest?: string }
+ reset: () => void
+}) {
+ useEffect(() => {
+ // Log the error to an error reporting service
+ console.error("Application error:", error)
+ }, [error])
+
+ return (
+
+
+
Something went wrong
+
+ We apologize for the inconvenience. The Flameborn system encountered an unexpected error.
+
+
+
+ Try Again
+
+
+
+ Return Home
+
+
+
+
+
+ )
+}
diff --git a/app/flameborn-journey/page.tsx b/app/flameborn-journey/page.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..2938a4cdb7b8296bcebd13796ce0b97331045c86
--- /dev/null
+++ b/app/flameborn-journey/page.tsx
@@ -0,0 +1,49 @@
+"use client"
+
+import { Suspense } from "react"
+import { JourneyHero } from "@/components/journey/journey-hero"
+import { JourneyFeatured } from "@/components/journey/journey-featured"
+import { JourneyPosts } from "@/components/journey/journey-posts"
+import { JourneySidebar } from "@/components/journey/journey-sidebar"
+import { LoadingState } from "@/components/loading-state"
+import dynamic from "next/dynamic"
+
+const ParticleBackground = dynamic(
+ () => import("@/components/particle-background").then((mod) => ({ default: mod.ParticleBackground })),
+ {
+ ssr: false,
+ loading: () =>
,
+ },
+)
+
+export default function FlamebornJourney() {
+ return (
+
+
+
+
}>
+
+
+
+
+
}>
+
+
+
+
+
+ }>
+
+
+
+
+ }>
+
+
+
+
+
+
+
+ )
+}
diff --git a/app/globals.css b/app/globals.css
new file mode 100644
index 0000000000000000000000000000000000000000..b5713980e579091a249fb35ae47114606039ad8c
--- /dev/null
+++ b/app/globals.css
@@ -0,0 +1,312 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+@layer base {
+ :root {
+ --background: 0 0% 100%;
+ --foreground: 240 10% 3.9%;
+ --card: 0 0% 100%;
+ --card-foreground: 240 10% 3.9%;
+ --popover: 0 0% 100%;
+ --popover-foreground: 240 10% 3.9%;
+ --primary: 240 9% 10%;
+ --primary-foreground: 0 0% 98%;
+ --secondary: 240 4.8% 95.9%;
+ --secondary-foreground: 240 5.9% 10%;
+ --muted: 240 4.8% 95.9%;
+ --muted-foreground: 240 3.8% 46.1%;
+ --accent: 240 4.8% 95.9%;
+ --accent-foreground: 240 5.9% 10%;
+ --destructive: 0 84.2% 60.2%;
+ --destructive-foreground: 0 0% 98%;
+ --border: 240 5.9% 90%;
+ --input: 240 5.9% 90%;
+ --ring: 240 10% 3.9%;
+ --chart-1: 12 76% 61%;
+ --chart-2: 173 58% 39%;
+ --chart-3: 197 37% 24%;
+ --chart-4: 43 74% 66%;
+ --chart-5: 27 87% 67%;
+ --radius: 0.5rem;
+ --flame: 15 100% 50%;
+ }
+
+ .dark {
+ --background: 240 10% 3.9%;
+ --foreground: 0 0% 98%;
+ --card: 240 10% 3.9%;
+ --card-foreground: 0 0% 98%;
+ --popover: 240 10% 3.9%;
+ --popover-foreground: 0 0% 98%;
+ --primary: 0 0% 98%;
+ --primary-foreground: 240 5.9% 10%;
+ --secondary: 240 3.7% 15.9%;
+ --secondary-foreground: 0 0% 98%;
+ --muted: 240 3.7% 15.9%;
+ --muted-foreground: 240 5% 64.9%;
+ --accent: 240 3.7% 15.9%;
+ --accent-foreground: 0 0% 98%;
+ --destructive: 0 62.8% 30.6%;
+ --destructive-foreground: 0 0% 98%;
+ --border: 240 3.7% 15.9%;
+ --input: 240 3.7% 15.9%;
+ --ring: 240 4.9% 83.9%;
+ --chart-1: 220 70% 50%;
+ --chart-2: 160 60% 45%;
+ --chart-3: 30 80% 55%;
+ --chart-4: 280 65% 60%;
+ --chart-5: 340 75% 55%;
+ }
+}
+
+@layer base {
+ * {
+ @apply border-border;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
+}
+
+/* Flame and animation styles */
+.flame {
+ color: hsl(var(--flame));
+}
+
+.bg-flame {
+ background-color: hsl(var(--flame));
+}
+
+.border-flame {
+ border-color: hsl(var(--flame));
+}
+
+/* Bubble field styles */
+.bubble-field {
+ position: relative;
+ overflow: hidden;
+}
+
+.data-bubble {
+ position: absolute;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ border-radius: 50%;
+ color: white;
+ font-weight: bold;
+ text-align: center;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ backdrop-filter: blur(10px);
+ border: 1px solid rgba(255, 255, 255, 0.2);
+ z-index: 10;
+ transform-origin: center;
+ user-select: none;
+ group: hover;
+}
+
+/* Bubble sizes */
+.bubble-xs {
+ width: 24px;
+ height: 24px;
+ font-size: 8px;
+}
+
+.bubble-sm {
+ width: 48px;
+ height: 48px;
+ font-size: 10px;
+}
+
+.bubble-md {
+ width: 64px;
+ height: 64px;
+ font-size: 12px;
+}
+
+.bubble-lg {
+ width: 80px;
+ height: 80px;
+ font-size: 14px;
+}
+
+.bubble-xl {
+ width: 96px;
+ height: 96px;
+ font-size: 16px;
+}
+
+/* Bubble categories */
+.bubble-testnet {
+ background: linear-gradient(135deg, rgba(255, 120, 0, 0.8), rgba(255, 78, 0, 0.6));
+ box-shadow: 0 0 20px rgba(255, 120, 0, 0.4);
+}
+
+.bubble-mainnet {
+ background: linear-gradient(135deg, rgba(220, 38, 127, 0.8), rgba(239, 68, 68, 0.6));
+ box-shadow: 0 0 20px rgba(220, 38, 127, 0.4);
+}
+
+.bubble-healers {
+ background: linear-gradient(135deg, rgba(34, 197, 94, 0.8), rgba(16, 185, 129, 0.6));
+ box-shadow: 0 0 15px rgba(34, 197, 94, 0.3);
+}
+
+.bubble-guardians {
+ background: linear-gradient(135deg, rgba(59, 130, 246, 0.8), rgba(99, 102, 241, 0.6));
+ box-shadow: 0 0 15px rgba(59, 130, 246, 0.3);
+}
+
+.bubble-soulbound {
+ background: linear-gradient(135deg, rgba(147, 51, 234, 0.8), rgba(139, 92, 246, 0.6));
+ box-shadow: 0 0 15px rgba(147, 51, 234, 0.3);
+}
+
+.bubble-codex {
+ background: linear-gradient(135deg, rgba(245, 158, 11, 0.8), rgba(251, 191, 36, 0.6));
+ box-shadow: 0 0 15px rgba(245, 158, 11, 0.3);
+}
+
+.bubble-verified {
+ background: linear-gradient(135deg, rgba(16, 185, 129, 0.7), rgba(5, 150, 105, 0.5));
+ box-shadow: 0 0 10px rgba(16, 185, 129, 0.3);
+}
+
+.bubble-active {
+ background: linear-gradient(135deg, rgba(236, 72, 153, 0.7), rgba(219, 39, 119, 0.5));
+ box-shadow: 0 0 10px rgba(236, 72, 153, 0.3);
+}
+
+.bubble-live-person {
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0.1));
+ border: 2px solid rgba(255, 120, 0, 0.6);
+ box-shadow: 0 0 15px rgba(255, 120, 0, 0.4);
+}
+
+/* Live bubble effects */
+.bubble-live {
+ animation: bubble-pulse 2s ease-in-out infinite;
+}
+
+.bubble-new-join {
+ animation: bubble-bounce 1s ease-in-out infinite, bubble-glow 2s ease-in-out infinite;
+}
+
+.bubble-person {
+ border: 2px solid rgba(255, 120, 0, 0.8);
+}
+
+/* Bubble animations */
+@keyframes bubble-pulse {
+ 0%,
+ 100% {
+ transform: scale(1);
+ opacity: 0.9;
+ }
+ 50% {
+ transform: scale(1.05);
+ opacity: 1;
+ }
+}
+
+@keyframes bubble-bounce {
+ 0%,
+ 100% {
+ transform: translateY(0);
+ }
+ 50% {
+ transform: translateY(-5px);
+ }
+}
+
+@keyframes bubble-glow {
+ 0%,
+ 100% {
+ box-shadow: 0 0 15px rgba(255, 120, 0, 0.4);
+ }
+ 50% {
+ box-shadow: 0 0 25px rgba(255, 120, 0, 0.8);
+ }
+}
+
+/* Hover effects */
+.data-bubble:hover {
+ transform: scale(1.1);
+ z-index: 20;
+}
+
+.bubble-live:hover {
+ animation-play-state: paused;
+}
+
+/* Multiplier effects for high counts */
+.bubble-multiplier {
+ position: relative;
+}
+
+.bubble-multiplier::before {
+ content: "";
+ position: absolute;
+ inset: -2px;
+ border-radius: 50%;
+ background: linear-gradient(45deg, transparent, rgba(255, 255, 255, 0.1), transparent);
+ animation: rotate 3s linear infinite;
+}
+
+.bubble-swarm {
+ position: relative;
+}
+
+.bubble-swarm::after {
+ content: "";
+ position: absolute;
+ inset: -4px;
+ border-radius: 50%;
+ background: radial-gradient(circle, transparent 60%, rgba(255, 120, 0, 0.1) 70%, transparent 80%);
+ animation: swarm-pulse 2s ease-in-out infinite;
+}
+
+@keyframes rotate {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@keyframes swarm-pulse {
+ 0%,
+ 100% {
+ opacity: 0.3;
+ transform: scale(1);
+ }
+ 50% {
+ opacity: 0.7;
+ transform: scale(1.1);
+ }
+}
+
+/* Responsive adjustments */
+@media (max-width: 768px) {
+ .bubble-xl {
+ width: 72px;
+ height: 72px;
+ font-size: 14px;
+ }
+
+ .bubble-lg {
+ width: 60px;
+ height: 60px;
+ font-size: 12px;
+ }
+
+ .bubble-md {
+ width: 48px;
+ height: 48px;
+ font-size: 10px;
+ }
+}
diff --git a/app/guardian-council/page.tsx b/app/guardian-council/page.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..2acf5bef64fbba584d78d043df29b0342dc0b105
--- /dev/null
+++ b/app/guardian-council/page.tsx
@@ -0,0 +1,5 @@
+import { GuardianVotingInterface } from "@/components/guardian/guardian-voting-interface"
+
+export default function GuardianCouncilPage() {
+ return
+}
diff --git a/app/guardians-sanctuary/page.tsx b/app/guardians-sanctuary/page.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..8280b83d7540bccf9f867c6d66a2f552823f44ba
--- /dev/null
+++ b/app/guardians-sanctuary/page.tsx
@@ -0,0 +1,44 @@
+"use client"
+
+import { Suspense } from "react"
+import { SanctuaryHero } from "@/components/sanctuary/sanctuary-hero"
+import { SanctuaryFeed } from "@/components/sanctuary/sanctuary-feed"
+import { SanctuaryMembers } from "@/components/sanctuary/sanctuary-members"
+import { LoadingState } from "@/components/loading-state"
+import dynamic from "next/dynamic"
+
+const ParticleBackground = dynamic(
+ () => import("@/components/particle-background").then((mod) => ({ default: mod.ParticleBackground })),
+ {
+ ssr: false,
+ loading: () =>
,
+ },
+)
+
+export default function GuardiansSanctuary() {
+ return (
+
+
+
+
}>
+
+
+
+
+
+
+ }>
+
+
+
+
+ }>
+
+
+
+
+
+
+
+ )
+}
diff --git a/app/healers/page.tsx b/app/healers/page.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..ba19cf622d4f910e7aa0e42d14a12d3af4a77ec4
--- /dev/null
+++ b/app/healers/page.tsx
@@ -0,0 +1,162 @@
+import { Button } from "@/components/ui/button"
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
+import { Shell } from "@/components/Shell"
+import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
+import { Badge } from "@/components/ui/badge"
+import { ScrollArea } from "@/components/ui/scroll-area"
+import { Separator } from "@/components/ui/separator"
+import Link from "next/link"
+
+const data = [
+ {
+ name: "Nwangi Thomas",
+ email: "nwangi.thomas@med.ke",
+ phone: "+254-701-123456",
+ specialty: "Cardiologist",
+ location: "Nairobi, Kenya",
+ availability: "Mon-Fri",
+ status: "Active",
+ },
+ {
+ name: "Denis Moraa",
+ email: "denis.moraa@clinic.ug",
+ phone: "+256-772-456789",
+ specialty: "Dermatologist",
+ location: "Kampala, Uganda",
+ availability: "Tue-Sat",
+ status: "Inactive",
+ },
+ {
+ name: "Alice Murekezi",
+ email: "alice.murekezi@peds.rw",
+ phone: "+250-788-987654",
+ specialty: "Pediatrician",
+ location: "Kigali, Rwanda",
+ availability: "Wed-Sun",
+ status: "Active",
+ },
+ {
+ name: "Boubacar Williams",
+ email: "boubacar.williams@med.sn",
+ phone: "+221-773-112233",
+ specialty: "Oncologist",
+ location: "Dakar, Senegal",
+ availability: "Mon-Sat",
+ status: "Active",
+ },
+ {
+ name: "Emelda Okonkwo",
+ email: "emelda.okonkwo@neuro.ng",
+ phone: "+234-809-445566",
+ specialty: "Neurologist",
+ location: "Enugu, Nigeria",
+ availability: "Tue-Fri",
+ status: "Inactive",
+ },
+ {
+ name: "David Abebe",
+ email: "david.abebe@surge.et",
+ phone: "+251-911-778899",
+ specialty: "Surgeon",
+ location: "Addis Ababa, Ethiopia",
+ availability: "Mon-Sun",
+ status: "Active",
+ },
+ {
+ name: "Linda Tshabalala",
+ email: "linda.tshabalala@mental.za",
+ phone: "+27-82-3344556",
+ specialty: "Psychiatrist",
+ location: "Johannesburg, South Africa",
+ availability: "Wed-Sat",
+ status: "Active",
+ },
+ {
+ name: "Michael Kombo",
+ email: "michael.kombo@eye.ke",
+ phone: "+254-722-667788",
+ specialty: "Ophthalmologist",
+ location: "Mombasa, Kenya",
+ availability: "Tue-Sun",
+ status: "Inactive",
+ },
+ {
+ name: "Baraka Ncube",
+ email: "baraka.ncube@ent.zw",
+ phone: "+263-773-554433",
+ specialty: "ENT Specialist",
+ location: "Harare, Zimbabwe",
+ availability: "Mon-Fri",
+ status: "Active",
+ },
+ {
+ name: "James Okoro",
+ email: "james.okoro@gastro.ng",
+ phone: "+234-803-990011",
+ specialty: "Gastroenterologist",
+ location: "Lagos, Nigeria",
+ availability: "Wed-Sun",
+ status: "Active",
+ },
+]
+export default function HealersPage() {
+ return (
+
+
+
+
Our Healers
+
+
+ Register as a Healer
+
+
+
+
+
+
+
+ Healer List
+ All registered healers are listed here.
+
+
+
+
+
+
+ Name
+ Email
+ Phone
+ Specialty
+ Location
+ Availability
+ Status
+
+
+
+ {data.map((row, index) => (
+
+ {row.name}
+ {row.email}
+ {row.phone}
+ {row.specialty}
+ {row.location}
+ {row.availability}
+
+ {row.status === "Active" ? (
+ Active
+ ) : (
+ Inactive
+ )}
+
+
+ ))}
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/app/launch/layout.tsx b/app/launch/layout.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..a2e084f5e765460caeeed5115894b196d17427c5
--- /dev/null
+++ b/app/launch/layout.tsx
@@ -0,0 +1,8 @@
+import type React from "react"
+export default function LaunchLayout({
+ children,
+}: {
+ children: React.ReactNode
+}) {
+ return {children}
+}
diff --git a/app/launch/page.tsx b/app/launch/page.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..d61f635908a0eedce9e5964d7fffed27e21e08ab
--- /dev/null
+++ b/app/launch/page.tsx
@@ -0,0 +1,374 @@
+"use client"
+
+import { motion } from "framer-motion"
+import Link from "next/link"
+import { Button } from "@/components/ui/button"
+import { ArrowRight, Heart, Globe, Brain, Users, DollarSign, Vote, Share2, Hammer } from "lucide-react"
+
+export default function LaunchPage() {
+ return (
+
+
+
+
+ Flameborn: Rekindling Hope in Africa's Rural Healthcare
+
+
+
+ A Beacon in the Darkness
+
+
+
+
+
+ In the silent hours before dawn, deep in the heart of rural Africa, a nurse leans over a patient. Her only
+ light is a flickering kerosene lamp.
+
+ Her only tools — resolve and compassion.
+
+
+
+ The clinic walls are worn. The shelves? Bare.
+
+ But she is still here — holding the line between life and loss.
+
+
+
+ She has no headline.
+
+ No salary.
+
+ No promise of help.
+
+
+ But she stays.
+
+
+ And Flameborn is here to tell the world: She is not alone anymore.
+
+
+
+
+
+ The Challenge We Face
+
+
+
+
+ Millions of Africa's healthcare workers — midwives, nurses, outreach teams — are holding up the very
+ foundation of our health systems.
+
+ And they're doing it with nothing .
+
+
+
+ In many African countries, community health workers show up every day without pay ,
+ without supplies, without rest.
+
+
+
Global aid is pledged. Billions flow.
+
+
But by the time it trickles down?
+
+
+
+ 💸 30% has disappeared into red tape, middlemen, corruption
+
+ 🏥 The clinic receives nothing
+ 🕯️ The nurse lights another candle
+
+
+
+ We've waited long enough.
+
+ Flameborn is the answer that doesn't wait.
+
+
+
+
+
+
+ What Is Flameborn?
+
+
+
+
+ Flameborn is not a charity.
+
+ Not a startup.
+
+ Not a government program.
+
+
+
+ It is a decentralized movement of Africans and allies
+
+ who believe that healers should never be left behind .
+
+
+
+ We are builders.
+
+ We are believers.
+
+ We are the Flameborn.
+
+
+
+
+
+
+ How It Works
+
+
+
+
+
+ Direct Aid via Blockchain
+
+
+ Donations go straight into digital wallets of verified rural healthcare workers.
+
+ No banks. No bureaucracy. No "lost in transit."
+
+ Every transaction is public, traceable, and real .
+
+
+
+
+
+ Community Governance (DAO)
+
+
+ The Flameborn DAO is open to all.
+
+ You don't need permission to care.
+
+ Vote on where funds go. Help decide which regions we support next.
+
+ Every supporter is a leader.
+
+
+
+
+
+ AI-Driven Transparency
+
+
+ Smart systems track every token.
+
+ You'll see where it went, when it arrived, and what it did.
+
+ If something goes wrong, the system says so — publicly .
+ There are no secrets in the Flame.
+
+
+
+
+
+
+
+ Real People. Real Impact.
+
+
+
+
+ In early pilots across rural Kenya, Nigeria, and Ghana :
+
+
+
+ A midwife received funds to restock birthing supplies
+ A nurse used her stipend to repair a solar fridge for vaccines
+ A health agent finally had enough to reach families across rivers
+
+
+
+ These aren't handouts.
+
+ They're affirmations :
+
+
+
+ We see you.
+
+ We honor you.
+
+ We invest in your care — because you cared for us.
+
+
+
+
+
+
+ This Is a Movement. Not a Moment.
+
+
+
+
+ Flameborn is not just a website.
+
+ It's Ubuntu in code.
+
+ It's compassion on the chain.
+
+ It's the spirit of Africa , digitized, decentralized, and rising.
+
+
+
+
+
+
+ How You Can Join the Flame
+
+
+
+
+
+ Donate
+
+
$5 fuels a clinic. $50 funds a stipend. 100% goes direct.
+
+ Donate Now
+
+
+
+
+
+ Join the DAO
+
+
Help guide aid. Propose, vote, change lives.
+
+ Join DAO
+
+
+
+
+
+ Spread the Word
+
+
Tell your tribe. Light another torch.
+
+ Share
+
+
+
+
+
+ Volunteer
+
+
Coders, designers, medics, storytellers — your gifts matter here.
+
+ Join Us
+
+
+
+
+
+
+
+ A Final Word to Every Soul Who Reads This
+
+
+
+
+ You are not small.
+
+ You are not powerless.
+
+ You are a match. A spark. A carrier of flame.
+
+
+
+ In the darkest corners of our continent, someone is still hoping.
+
+ Still healing.
+
+ Still holding on.
+
+
+
+
+ Light their fire again.
+
+ Let them know they are not forgotten.
+
+ Let them feel Africa — and the world — rising with them.
+
+
+
+
+
+
+ 🔥 Flameborn: Lighting the Path to Health and Hope
+
+
+ Because hope is a flame .
+ And now — it lives on the chain.
+
+
+
+
+ Return Home
+
+ Join the Movement
+
+
+
+
+ )
+}
diff --git a/app/layout.tsx b/app/layout.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..c423aa648a9dfdafc0fc8e7b24773310da3a78d0
--- /dev/null
+++ b/app/layout.tsx
@@ -0,0 +1,46 @@
+import type React from "react"
+import type { Metadata } from "next"
+import { Inter } from "next/font/google"
+import "./globals.css"
+import AppHeader from "@/components/app-header"
+import { Toaster } from "@/components/ui/toaster"
+
+const inter = Inter({ subsets: ["latin"] })
+
+export const metadata: Metadata = {
+ title: "FlameBorn - Ubuntu Healthcare Tokenization",
+ description:
+ "Bridging traditional African Ubuntu philosophy with modern healthcare tokenization. I am because we are.",
+ keywords: ["Ubuntu", "healthcare", "tokenization", "blockchain", "Celo", "Africa", "community"],
+ authors: [{ name: "FlameBorn Team" }],
+ openGraph: {
+ title: "FlameBorn - Ubuntu Healthcare Tokenization",
+ description: "I am because we are. Ubuntu-powered healthcare tokenization for a connected world.",
+ type: "website",
+ locale: "en_US",
+ },
+ twitter: {
+ card: "summary_large_image",
+ title: "FlameBorn - Ubuntu Healthcare Tokenization",
+ description: "I am because we are. Ubuntu-powered healthcare tokenization for a connected world.",
+ },
+ viewport: "width=device-width, initial-scale=1",
+ themeColor: "#f97316",
+ generator: 'v0.dev'
+}
+
+export default function RootLayout({
+ children,
+}: {
+ children: React.ReactNode
+}) {
+ return (
+
+
+
+ {children}
+
+
+
+ )
+}
diff --git a/app/learn-earn/page.tsx b/app/learn-earn/page.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..8dbc935a65a07f18ca9cde313e4eb153009f5fb4
--- /dev/null
+++ b/app/learn-earn/page.tsx
@@ -0,0 +1,1435 @@
+"use client"
+
+import { useState, useEffect } from "react"
+import { motion } from "framer-motion"
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
+import { Button } from "@/components/ui/button"
+import { Progress } from "@/components/ui/progress"
+import { Badge } from "@/components/ui/badge"
+import { Input } from "@/components/ui/input"
+import { Textarea } from "@/components/ui/textarea"
+import {
+ BookOpen,
+ Play,
+ CheckCircle,
+ Star,
+ Globe,
+ Brain,
+ Stethoscope,
+ Cpu,
+ Users,
+ Coins,
+ HandHeart,
+ GraduationCap,
+ MessageSquare,
+ Wallet,
+ Download,
+ Wifi,
+ WifiOff,
+ Clock,
+ Target,
+ Heart,
+ Shield,
+ Zap,
+} from "lucide-react"
+import { ClipboardManager } from "@/lib/clipboard-utils"
+
+interface Module {
+ id: string
+ title: string
+ description: string
+ type: "interactive" | "quiz" | "story" | "video"
+ category: "Cultural Heritage" | "Philosophy" | "Health Advocacy" | "Technology" | "Ubuntu Wisdom"
+ difficulty: "easy" | "medium" | "hard"
+ duration: number
+ flbReward: number
+ xpReward: number
+ completed: boolean
+ content: any
+}
+
+interface QuizQuestion {
+ question: string
+ options: string[]
+ correct: number
+ explanation: string
+}
+
+interface WalletInfo {
+ address: string
+ flbBalance: number
+ celoBalance: number
+ connected: boolean
+}
+
+interface Transaction {
+ id: string
+ type: "earned" | "donated" | "transferred"
+ amount: number
+ description: string
+ timestamp: Date
+ status: "completed" | "pending" | "failed"
+}
+
+const modules: Module[] = [
+ {
+ id: "ubuntu-wisdom-1",
+ title: "Ubuntu Philosophy: I Am Because We Are",
+ description: "Deep dive into Ubuntu philosophy and its relevance in modern African healing",
+ type: "interactive",
+ category: "Ubuntu Wisdom",
+ difficulty: "easy",
+ duration: 20,
+ flbReward: 50,
+ xpReward: 100,
+ completed: false,
+ content: {
+ concepts: [
+ {
+ concept: "Umuntu ngumuntu ngabantu",
+ translation: "A person is a person through other persons",
+ explanation: "The fundamental concept of Ubuntu - our humanity is interconnected",
+ modernApplication: "In healthcare, this guides collaborative approaches to healing",
+ },
+ {
+ concept: "Ubuntu ngumuntu",
+ translation: "Ubuntu is humanity",
+ explanation: "Ubuntu represents the essence of being human through community",
+ modernApplication: "In conflict resolution, Ubuntu provides framework for healing",
+ },
+ ],
+ },
+ },
+ {
+ id: "addiction-africa-1",
+ title: "Understanding Addiction in Africa",
+ description: "Explore cultural and social aspects of addiction across African communities",
+ type: "quiz",
+ category: "Health Advocacy",
+ difficulty: "medium",
+ duration: 25,
+ flbReward: 75,
+ xpReward: 150,
+ completed: false,
+ content: {
+ questions: [
+ {
+ question: "What role do traditional healers play in addiction recovery in Africa?",
+ options: [
+ "They should be completely replaced by modern medicine",
+ "They complement modern treatment with cultural understanding",
+ "They only use harmful practices",
+ "They have no role in recovery",
+ ],
+ correct: 1,
+ explanation:
+ "Traditional healers provide cultural context and community support that enhances modern treatment approaches",
+ },
+ {
+ question: "How does Ubuntu philosophy apply to addiction recovery?",
+ options: [
+ "Individual treatment is most important",
+ "Community healing and collective support",
+ "Isolation from community",
+ "Western approaches only",
+ ],
+ correct: 1,
+ explanation: "Ubuntu emphasizes that healing happens through community support and collective responsibility",
+ },
+ ],
+ },
+ },
+ {
+ id: "community-prevention-1",
+ title: "Community Prevention Strategies",
+ description: "Learn effective prevention methods within African community structures",
+ type: "video",
+ category: "Health Advocacy",
+ difficulty: "medium",
+ duration: 30,
+ flbReward: 60,
+ xpReward: 120,
+ completed: false,
+ content: {
+ videoUrl: "/videos/community-prevention.mp4",
+ keyPoints: [
+ "School-based prevention programs",
+ "Faith-based community approaches",
+ "Elder and traditional leader involvement",
+ "Youth peer education networks",
+ "Economic empowerment as prevention",
+ ],
+ },
+ },
+ {
+ id: "akan-proverbs-1",
+ title: "Akan Proverbs: Wisdom for Healing",
+ description: "Explore Akan proverbs and their applications in community healing",
+ type: "interactive",
+ category: "Cultural Heritage",
+ difficulty: "easy",
+ duration: 15,
+ flbReward: 40,
+ xpReward: 80,
+ completed: false,
+ content: {
+ proverbs: [
+ {
+ akan: "Se wo were fi na wosan kofa a, yenkyiri",
+ english: "It is not taboo to go back for what you forgot",
+ meaning: "There's no shame in correcting mistakes or seeking help",
+ healing: "Encourages people to seek treatment without stigma",
+ },
+ {
+ akan: "Obi nkyere abofra Nyame",
+ english: "No one teaches a child about God",
+ meaning: "Some knowledge is innate and universal",
+ healing: "Natural understanding of right and wrong guides recovery",
+ },
+ ],
+ },
+ },
+ {
+ id: "treatment-centers-1",
+ title: "Treatment Center Operations in Africa",
+ description: "Understand how treatment centers operate in resource-limited settings",
+ type: "video",
+ category: "Health Advocacy",
+ difficulty: "hard",
+ duration: 40,
+ flbReward: 100,
+ xpReward: 200,
+ completed: false,
+ content: {
+ videoUrl: "/videos/treatment-centers.mp4",
+ keyPoints: [
+ "Staffing with limited resources",
+ "Integrating traditional and modern approaches",
+ "Community-based aftercare programs",
+ "Sustainable funding models",
+ "Measuring success in African contexts",
+ ],
+ },
+ },
+ {
+ id: "youth-leadership-1",
+ title: "Youth Leadership in Health Advocacy",
+ description: "Develop skills to become a health advocate in your community",
+ type: "story",
+ category: "Health Advocacy",
+ difficulty: "medium",
+ duration: 25,
+ flbReward: 65,
+ xpReward: 130,
+ completed: false,
+ content: {
+ prompt:
+ "Share a story about how you've seen young people make a difference in health issues in your community, or describe how you would lead a health initiative using Ubuntu principles.",
+ minWords: 150,
+ maxWords: 500,
+ },
+ },
+]
+
+const categoryIcons = {
+ "Cultural Heritage": Globe,
+ Philosophy: Brain,
+ "Health Advocacy": Stethoscope,
+ Technology: Cpu,
+ "Ubuntu Wisdom": Heart,
+}
+
+const difficultyColors = {
+ easy: "bg-green-100 text-green-800",
+ medium: "bg-yellow-100 text-yellow-800",
+ hard: "bg-red-100 text-red-800",
+}
+
+const redemptionKits = [
+ {
+ id: "mama-wellness",
+ name: "Mama Wellness Pack",
+ cost: 4,
+ currency: "FLB",
+ items: ["Clinic voucher", "Basic medicines", "Transport allowance"],
+ color: "bg-green-50 border-green-200",
+ },
+ {
+ id: "school-starter",
+ name: "School Starter Kit",
+ cost: 6,
+ currency: "FLB",
+ items: ["School uniform", "Books and supplies", "Lunch pass"],
+ color: "bg-blue-50 border-blue-200",
+ },
+ {
+ id: "community-builder",
+ name: "Community Builder Pack",
+ cost: 10,
+ currency: "FLB",
+ items: ["Seed packs", "Toolkit", "Training session"],
+ color: "bg-purple-50 border-purple-200",
+ },
+]
+
+export default function LearnEarnPage() {
+ const [selectedCategory, setSelectedCategory] = useState("all")
+ const [selectedDifficulty, setSelectedDifficulty] = useState("all")
+ const [activeModule, setActiveModule] = useState(null)
+ const [currentStep, setCurrentStep] = useState(0)
+ const [userAnswers, setUserAnswers] = useState([])
+ const [storyContent, setStoryContent] = useState("")
+ const [moduleProgress, setModuleProgress] = useState<{ [key: string]: boolean }>({})
+ const [isOfflineMode, setIsOfflineMode] = useState(false)
+ const [wallet, setWallet] = useState({
+ address: "",
+ flbBalance: 0,
+ celoBalance: 0,
+ connected: false,
+ })
+ const [transactions, setTransactions] = useState([])
+ const [userStats, setUserStats] = useState({
+ totalFLB: 0,
+ totalXP: 0,
+ completedModules: 0,
+ currentStreak: 7,
+ learnerLevel: "Beginner",
+ impactScore: 0,
+ })
+ const [showWalletModal, setShowWalletModal] = useState(false)
+ const [showCourseModal, setShowCourseModal] = useState(false)
+ const [selectedCourse, setSelectedCourse] = useState(null)
+
+ // Initialize with mock data for demo
+ useEffect(() => {
+ // Simulate some completed modules and transactions
+ const mockTransactions: Transaction[] = [
+ {
+ id: "tx1",
+ type: "earned",
+ amount: 50,
+ description: "Completed Ubuntu Philosophy course",
+ timestamp: new Date(Date.now() - 86400000),
+ status: "completed",
+ },
+ {
+ id: "tx2",
+ type: "donated",
+ amount: 20,
+ description: "Donated to Community Health Center",
+ timestamp: new Date(Date.now() - 172800000),
+ status: "completed",
+ },
+ ]
+ setTransactions(mockTransactions)
+ }, [])
+
+ const filteredModules = modules.filter((module) => {
+ const categoryMatch = selectedCategory === "all" || module.category === selectedCategory
+ const difficultyMatch = selectedDifficulty === "all" || module.difficulty === selectedDifficulty
+ return categoryMatch && difficultyMatch
+ })
+
+ const connectWallet = async (walletType: string) => {
+ // Mock wallet connection
+ const mockAddresses = [
+ "0x71C7656EC7ab88b098defB751B7401B5f6d8976F",
+ "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B",
+ "0x4B0897b0513fdC7C541B6d9D7E929C4e5364D2dB",
+ ]
+
+ const randomAddress = mockAddresses[Math.floor(Math.random() * mockAddresses.length)]
+ const randomFLB = Math.floor(Math.random() * 500) + 50
+ const randomCELO = Math.random() * 5
+
+ setWallet({
+ address: randomAddress,
+ flbBalance: randomFLB,
+ celoBalance: randomCELO,
+ connected: true,
+ })
+
+ setUserStats((prev) => ({
+ ...prev,
+ totalFLB: randomFLB,
+ }))
+
+ setShowWalletModal(false)
+ }
+
+ const startModule = (module: Module) => {
+ setActiveModule(module)
+ setCurrentStep(0)
+ setUserAnswers([])
+ setStoryContent("")
+ setShowCourseModal(false)
+ }
+
+ const completeModule = async (moduleId: string, score = 100) => {
+ const module = modules.find((m) => m.id === moduleId)
+ if (!module) return
+
+ // Calculate rewards based on score
+ const flbReward = Math.floor((score / 100) * module.flbReward)
+ const xpReward = Math.floor((score / 100) * module.xpReward)
+
+ // Update user stats
+ setUserStats((prev) => ({
+ ...prev,
+ totalFLB: prev.totalFLB + flbReward,
+ totalXP: prev.totalXP + xpReward,
+ completedModules: prev.completedModules + 1,
+ impactScore: prev.impactScore + Math.floor(flbReward / 10),
+ }))
+
+ // Update wallet balance
+ setWallet((prev) => ({
+ ...prev,
+ flbBalance: prev.flbBalance + flbReward,
+ }))
+
+ // Add transaction
+ const newTransaction: Transaction = {
+ id: `tx-${Date.now()}`,
+ type: "earned",
+ amount: flbReward,
+ description: `Completed ${module.title}`,
+ timestamp: new Date(),
+ status: "completed",
+ }
+ setTransactions((prev) => [newTransaction, ...prev])
+
+ // Mark module as completed
+ setModuleProgress((prev) => ({
+ ...prev,
+ [moduleId]: true,
+ }))
+
+ // Close module
+ setActiveModule(null)
+
+ // Show success message
+ alert(`🔥 Ubuntu wisdom earned! ${flbReward} FLB and ${xpReward} XP added to your account!`)
+ }
+
+ const handleQuizAnswer = (questionIndex: number, answerIndex: number) => {
+ const newAnswers = [...userAnswers]
+ newAnswers[questionIndex] = answerIndex
+ setUserAnswers(newAnswers)
+ }
+
+ const calculateQuizScore = () => {
+ if (!activeModule || activeModule.type !== "quiz") return 0
+ const questions = activeModule.content.questions as QuizQuestion[]
+ const correctAnswers = userAnswers.filter((answer, index) => answer === questions[index].correct).length
+ return Math.round((correctAnswers / questions.length) * 100)
+ }
+
+ const redeemKit = async (kitId: string) => {
+ const kit = redemptionKits.find((k) => k.id === kitId)
+ if (!kit || wallet.flbBalance < kit.cost) {
+ alert("Insufficient FLB balance for this redemption kit")
+ return
+ }
+
+ // Deduct FLB
+ setWallet((prev) => ({
+ ...prev,
+ flbBalance: prev.flbBalance - kit.cost,
+ }))
+
+ // Add transaction
+ const newTransaction: Transaction = {
+ id: `tx-${Date.now()}`,
+ type: "donated",
+ amount: kit.cost,
+ description: `Redeemed ${kit.name}`,
+ timestamp: new Date(),
+ status: "completed",
+ }
+ setTransactions((prev) => [newTransaction, ...prev])
+
+ alert(`🎉 ${kit.name} redeemed successfully! Your impact is making a difference in African communities.`)
+ }
+
+ const toggleOfflineMode = () => {
+ setIsOfflineMode(!isOfflineMode)
+ if (!isOfflineMode) {
+ alert("📱 Offline mode enabled! Course materials downloaded for offline access.")
+ }
+ }
+
+ const exportProgress = async () => {
+ const progressData = {
+ userStats,
+ completedModules: moduleProgress,
+ transactions,
+ wallet: wallet.connected ? { address: wallet.address, balance: wallet.flbBalance } : null,
+ timestamp: new Date().toISOString(),
+ }
+
+ const progressText = JSON.stringify(progressData, null, 2)
+ const result = await ClipboardManager.copyToClipboard(progressText)
+
+ if (result.success) {
+ alert("📋 Progress data copied to clipboard!")
+ } else {
+ // Fallback to download
+ const blob = new Blob([progressText], { type: "application/json" })
+ const url = URL.createObjectURL(blob)
+ const a = document.createElement("a")
+ a.href = url
+ a.download = `flameborn-progress-${new Date().toISOString().split("T")[0]}.json`
+ document.body.appendChild(a)
+ a.click()
+ document.body.removeChild(a)
+ URL.revokeObjectURL(url)
+ }
+ }
+
+ const renderModuleContent = () => {
+ if (!activeModule) return null
+
+ switch (activeModule.type) {
+ case "interactive":
+ return (
+
+
+
{activeModule.title}
+
{activeModule.description}
+
+
+ {activeModule.content.concepts &&
+ activeModule.content.concepts.map((concept: any, index: number) => (
+
+
+
+
+
+
"{concept.concept}"
+
"{concept.translation}"
+
+
+
Ubuntu Meaning:
+
{concept.explanation}
+
+
+
Modern Application:
+
{concept.modernApplication}
+
+
+
+
+
+ ))}
+
+ {activeModule.content.proverbs &&
+ activeModule.content.proverbs.map((proverb: any, index: number) => (
+
+
+
+
+
+
"{proverb.akan}"
+
"{proverb.english}"
+
+
+
Wisdom:
+
{proverb.meaning}
+
+
+
Healing Application:
+
{proverb.healing}
+
+
+
+
+
+ ))}
+
+
+ completeModule(activeModule.id)} className="bg-orange-600 hover:bg-orange-700">
+ Complete Module & Earn {activeModule.flbReward} FLB
+
+
+
+ )
+
+ case "quiz":
+ const questions = activeModule.content.questions as QuizQuestion[]
+ return (
+
+
+
{activeModule.title}
+
{activeModule.description}
+
+
+
+ {questions.map((question, index) => (
+
+
+
+ Question {index + 1}: {question.question}
+
+
+ {question.options.map((option, optionIndex) => (
+ handleQuizAnswer(index, optionIndex)}
+ >
+ {option}
+
+ ))}
+
+ {userAnswers[index] !== undefined && (
+
+
+ Ubuntu Wisdom: {question.explanation}
+
+
+ )}
+
+
+ ))}
+
+ {userAnswers.length === questions.length && (
+
+
+
Your Ubuntu Score: {calculateQuizScore()}%
+
+
completeModule(activeModule.id, calculateQuizScore())}
+ className="bg-blue-600 hover:bg-blue-700"
+ >
+ Complete Quiz & Earn Rewards
+
+
+ )}
+
+ )
+
+ case "story":
+ return (
+
+
+
{activeModule.title}
+
{activeModule.description}
+
+
+
+
+ Ubuntu Story Prompt:
+ {activeModule.content.prompt}
+
+
+
+
+ completeModule(activeModule.id)}
+ disabled={
+ storyContent.split(" ").filter((word) => word.length > 0).length < activeModule.content.minWords
+ }
+ className="bg-purple-600 hover:bg-purple-700"
+ >
+ Share Ubuntu Story & Earn {activeModule.flbReward} FLB
+
+
+
+
+
+ )
+
+ case "video":
+ return (
+
+
+
{activeModule.title}
+
{activeModule.description}
+
+
+
+
+
+
+
+
Video: {activeModule.title}
+
Duration: {activeModule.duration} minutes
+
+
+
+
+
Key Ubuntu Learning Points:
+
+ {activeModule.content.keyPoints.map((point: string, index: number) => (
+
+
+ {point}
+
+ ))}
+
+
+
+
+ completeModule(activeModule.id)} className="bg-green-600 hover:bg-green-700">
+ Mark as Watched & Earn {activeModule.flbReward} FLB
+
+
+
+
+
+ )
+
+ default:
+ return null
+ }
+ }
+
+ if (activeModule) {
+ return (
+
+
+
+ setActiveModule(null)} className="mb-4">
+ ← Back to Learn & Earn
+
+
+
+
+ {renderModuleContent()}
+
+
+
+ )
+ }
+
+ return (
+
+ {/* Hero Section */}
+
+
+
+ Learn. Earn. Heal Africa.
+
+ Educate yourself on African healing wisdom, earn FLB tokens, and support real treatment centers across the
+ continent through Ubuntu principles.
+
+
+ document.getElementById("courses")?.scrollIntoView({ behavior: "smooth" })}
+ className="bg-white text-green-700 hover:bg-gray-100 px-8 py-3 text-lg"
+ >
+ Start Learning Ubuntu
+
+
+ How It Works
+
+
+
+
+
+
+
+ {/* Stats Overview */}
+
+
+
+
+ {userStats.totalFLB}
+ FLB Earned
+
+
+
+
+
+
+ {userStats.totalXP}
+ Ubuntu XP
+
+
+
+
+
+
+ {userStats.completedModules}
+ Modules Completed
+
+
+
+
+
+
+ {userStats.impactScore}
+ Impact Score
+
+
+
+
+ {/* Ubuntu Dashboard */}
+
+
+
+
+ Earn FLB
+ Complete Ubuntu quests
+
+
+
+
+
+
+ Heal
+ Support African healing
+
+
+
+
+
+
+ Learn
+ Grow Ubuntu wisdom
+
+
+
+
+
+
+ Connect
+ Community support
+
+
+
+
+ {/* Wallet Section */}
+
+
+
+
+
+ Your FLB Wallet
+
+
+
+ {!wallet.connected ? (
+
+
+
Wallet Not Connected
+
+ Connect your Celo-compatible wallet to earn and manage FLB tokens
+
+
setShowWalletModal(true)} className="bg-green-600 hover:bg-green-700">
+ Connect Wallet
+
+
+ ) : (
+
+
+
Connected Wallet
+ Connected
+
+
+
+ Address:
+
+ {wallet.address.slice(0, 6)}...{wallet.address.slice(-4)}
+
+
+
+ FLB Balance:
+ {wallet.flbBalance} FLB
+
+
+ CELO Balance:
+ {wallet.celoBalance.toFixed(2)} CELO
+
+
+
+
+
+ Donate
+
+
+
+ Transfer
+
+
+
+ )}
+
+
+
+
+
+ Recent Transactions
+
+
+ {transactions.length === 0 ? (
+
+
+
No transactions yet
+
Complete courses to earn FLB tokens
+
+ ) : (
+
+ {transactions.slice(0, 5).map((tx) => (
+
+
+
+ {tx.type === "earned" ? "+" : "-"}
+ {tx.amount} FLB
+
+
{tx.description}
+
+
+
{tx.timestamp.toLocaleDateString()}
+
{tx.status}
+
+
+ ))}
+
+ )}
+
+
+
+
+ {/* Offline Mode Banner */}
+
+
+
+ {isOfflineMode ? (
+
+ ) : (
+
+ )}
+
+
+ {isOfflineMode ? "Offline Mode Active" : "Offline Mode Available"}
+
+
+ {isOfflineMode
+ ? "Course materials downloaded. Progress will sync when online."
+ : "Download course materials for offline access in rural areas."}
+
+
+
+
+
+ {isOfflineMode ? "Disable" : "Enable"} Offline Mode
+
+
+
+
+ {/* Filters */}
+
+
+ Category:
+ setSelectedCategory(e.target.value)}
+ className="bg-white border border-orange-300 rounded-lg px-3 py-2"
+ >
+ All Categories
+ Ubuntu Wisdom
+ Cultural Heritage
+ Health Advocacy
+ Technology
+
+
+
+
+ Difficulty:
+ setSelectedDifficulty(e.target.value)}
+ className="bg-white border border-orange-300 rounded-lg px-3 py-2"
+ >
+ All Levels
+ Easy
+ Medium
+ Hard
+
+
+
+
+
+ Export Progress
+
+
+
+ {/* Modules Grid */}
+
+ {filteredModules.map((module, index) => {
+ const IconComponent = categoryIcons[module.category]
+ const isCompleted = moduleProgress[module.id]
+
+ return (
+
+ {
+ setSelectedCourse(module)
+ setShowCourseModal(true)
+ }}
+ >
+
+
+
+
+ {module.difficulty}
+
+ {isCompleted &&
}
+
+ {module.title}
+ {module.description}
+
+
+
+
+
+
+ {module.duration} min
+
+ {module.category}
+
+
+
+
+ {module.flbReward} FLB
+ +
+ {module.xpReward} XP
+
+
+ {module.type}
+
+
+
+
{
+ e.stopPropagation()
+ startModule(module)
+ }}
+ className="w-full bg-orange-600 hover:bg-orange-700"
+ disabled={isCompleted}
+ >
+ {isCompleted ? "Completed" : "Start Module"}
+
+
+
+
+
+ )
+ })}
+
+
+ {/* FLB Redemption Kits */}
+
+ FLB Redemption Kits
+ Turn your Ubuntu tokens into real impact
+
+
+ {redemptionKits.map((kit) => (
+
+
+ {kit.name}
+
+
+ {kit.cost} {kit.currency}
+
+
+
+ {kit.items.map((item, index) => (
+
+
+ {item}
+
+ ))}
+
+ redeemKit(kit.id)}
+ disabled={!wallet.connected || wallet.flbBalance < kit.cost}
+ className="w-full"
+ >
+ {wallet.connected
+ ? wallet.flbBalance >= kit.cost
+ ? "Redeem Kit"
+ : "Insufficient FLB"
+ : "Connect Wallet"}
+
+
+
+ ))}
+
+
+
+ {/* Ubuntu Quest System */}
+
+ Ubuntu Quest System
+ Grow your impact through collective learning and action
+
+
+
+
+ Youth Skill Path
+
+
+
+
+
+
+
Learner
+
Earn basic FLB
+
+
+
+
+
+
+
+
Advocate
+
Gain DAO voting
+
+
+
+
+
+
+
+
Mentor
+
Local visibility
+
+
+
+
+
+
+
+
+ Circle of Impact
+
+
+
+
+
+
+
Clan-based rewards
+
Earn Ubuntu Points collectively
+
+
+
+
+
+
+
+
Community Quests
+
Health Jams, Elder Interviews
+
+
+
+
+
+
+
+
+ Revive a Clinic
+
+
+
+ 1
+
+
+
Youth upload challenge
+
+
+
+
+
+ 3
+
+
+
FLB donation triggered
+
+
+
+
+
+
+
+
+ {/* Impact Stats */}
+
+ Making a Difference Across Africa
+
+
+
+
+ 1,200+
+ Ubuntu Learners
+
+
+
+
+
+ 250K+
+ FLB Tokens Earned
+
+
+
+
+
+ 15
+ Healing Centers Supported
+
+
+
+
+
+ 8
+ African Countries
+
+
+
+
+
+ {/* Testnet Notice */}
+
+
+
+ This is a TestNet environment
+
+
+ FLB tokens have no real monetary value. This platform demonstrates Ubuntu-based learning and healing
+ principles.
+
+
+
+
+ {/* Wallet Connection Modal */}
+ {showWalletModal && (
+
+
+
+
+
+ Connect Wallet
+
+ Choose a wallet to connect to the Ubuntu Learn & Earn platform
+
+
+ connectWallet("valora")}
+ className="w-full flex items-center justify-center"
+ variant="outline"
+ >
+
+ V
+
+ Valora
+
+ connectWallet("metamask")}
+ className="w-full flex items-center justify-center"
+ variant="outline"
+ >
+
+ M
+
+ MetaMask
+
+ connectWallet("walletconnect")}
+ className="w-full flex items-center justify-center"
+ variant="outline"
+ >
+
+ WC
+
+ WalletConnect
+
+
+
+ setShowWalletModal(false)} variant="outline" className="w-full">
+ Cancel
+
+
+
+
+ )}
+
+ {/* Course Details Modal */}
+ {showCourseModal && selectedCourse && (
+
+
+
+
+
+ {selectedCourse.title}
+ {selectedCourse.description}
+
+
setShowCourseModal(false)} variant="ghost" size="sm">
+ ✕
+
+
+
+
+
+ {selectedCourse.type} Module
+
+
+
+ {selectedCourse.duration} Minutes
+
+
+
+ {selectedCourse.flbReward} FLB
+
+
+
+
+
+
+
Ubuntu Learning Objectives
+
+
+
+ Understand African healing wisdom and Ubuntu principles
+
+
+
+ Apply cultural knowledge to modern health challenges
+
+
+
+ Contribute to community healing and support networks
+
+
+
+
+
+
Requirements
+
+ • Basic understanding of African cultural values
+ • Internet connection (offline mode available)
+ • Celo-compatible wallet (for FLB token rewards)
+ • Commitment to Ubuntu principles of community healing
+
+
+
+
+
+ startModule(selectedCourse)} className="flex-1 bg-green-600 hover:bg-green-700">
+ Start Ubuntu Learning
+
+ setShowCourseModal(false)} variant="outline" className="flex-1">
+ Cancel
+
+
+
+
+ )}
+
+ )
+}
diff --git a/app/login/page.tsx b/app/login/page.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..5ad875f6a33abf8921095b7ddbb9e00401d01152
--- /dev/null
+++ b/app/login/page.tsx
@@ -0,0 +1,117 @@
+"use client"
+
+import { useState, useEffect } from "react"
+import { useRouter } from "next/navigation"
+import { Card, CardContent, CardHeader, CardTitle, CardDescription, CardFooter } from "@/components/ui/card"
+import { Button } from "@/components/ui/button"
+import { UserDatabase, type User } from "@/lib/user-database"
+import { Shield, Heart } from "lucide-react"
+import Link from "next/link"
+
+export default function LoginPage() {
+ const router = useRouter()
+ const [users, setUsers] = useState([])
+ const [loading, setLoading] = useState(true)
+
+ useEffect(() => {
+ // Get all users
+ const allUsers = UserDatabase.getAllUsers()
+ setUsers(allUsers)
+ setLoading(false)
+
+ // Check if user is already logged in
+ const currentUser = UserDatabase.getCurrentUser()
+ if (currentUser) {
+ router.push("/profile")
+ }
+ }, [router])
+
+ const handleLogin = (userId: string) => {
+ // Set current user
+ localStorage.setItem("flameborn_current_user", userId)
+
+ // Redirect to profile
+ router.push("/profile")
+ }
+
+ return (
+
+
+
+
FLAMEBORN
+
Log in to your account
+
+
+
+
+ Select Your Account
+ Choose an account to log in or register a new one
+
+
+ {loading ? (
+ Loading accounts...
+ ) : users.length === 0 ? (
+
+
No accounts found
+
Register as a Guardian or Healer to get started
+
+
+
+
+ Guardian
+
+
+
+
+ Healer
+
+
+
+
+ ) : (
+
+ {users.map((user) => (
+
handleLogin(user.id)}
+ >
+
+
+ {user.type === "guardian" ? (
+
+ ) : (
+
+ )}
+
+
+
{user.fullName}
+
+ {user.type === "guardian" ? "Guardian" : "Healer"} • {user.email}
+
+
+
+
+ ))}
+
+ )}
+
+
+
+ Don't have an account?{" "}
+
+ Register
+
+
+
+
+
+
+ )
+}
diff --git a/app/manifesto/page.tsx b/app/manifesto/page.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..6b8be725ffd98a4efb103328bab6da24fe439543
--- /dev/null
+++ b/app/manifesto/page.tsx
@@ -0,0 +1,5 @@
+import { Manifesto } from "@/components/manifesto"
+
+export default function ManifestoPage() {
+ return
+}
diff --git a/app/maternal-health/page.tsx b/app/maternal-health/page.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..8bb7865fe2bd286de225196cbd5c24368818e316
--- /dev/null
+++ b/app/maternal-health/page.tsx
@@ -0,0 +1,5 @@
+import { MaternalHealthPortal } from "@/components/maternal-health/maternal-health-portal"
+
+export default function MaternalHealthPage() {
+ return
+}
diff --git a/app/network-stats/page.tsx b/app/network-stats/page.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..3bddcb35f66731763fa40f2a816f86660be3c01b
--- /dev/null
+++ b/app/network-stats/page.tsx
@@ -0,0 +1,5 @@
+import { NetworkStatsLive } from "@/components/network-stats/network-stats-live"
+
+export default function NetworkStatsPage() {
+ return
+}
diff --git a/app/page.tsx b/app/page.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..5d19830d7371493a5f46174483d3dbca8118aa82
--- /dev/null
+++ b/app/page.tsx
@@ -0,0 +1,367 @@
+"use client"
+
+import { useState, useEffect } from "react"
+import Link from "next/link"
+import { Flame, Heart, Users, Globe, ArrowRight, Sparkles, Shield, Zap, TrendingUp } from "lucide-react"
+import { Button } from "@/components/ui/button"
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
+import { Badge } from "@/components/ui/badge"
+import ParticleBackground from "@/components/particle-background"
+import FlameAnimation from "@/components/flame-animation"
+import SoundEffects from "@/components/sound-effects"
+import MostarAIAssistant from "@/components/ai-assistant"
+
+const HomePage = () => {
+ const [mounted, setMounted] = useState(false)
+ const [activeSection, setActiveSection] = useState("hero")
+
+ useEffect(() => {
+ setMounted(true)
+ }, [])
+
+ if (!mounted) {
+ return (
+
+
+
+
Loading FlameBorn Ubuntu...
+
+
+ )
+ }
+
+ return (
+
+
+
+
+ {/* Hero Section */}
+
+
+
+
+
+
+
+
+ Ubuntu-Powered Healthcare Tokenization
+
+
+
+ FlameBorn
+
+ I am because we are
+
+
+
+ Bridging traditional African Ubuntu philosophy with modern healthcare tokenization. Every birth registered,
+ every healer verified, every community connected through the power of blockchain.
+
+
+
+
+
+
+ Launch App
+
+
+
+
+
+ Read Manifesto
+
+
+
+
+
+ View Analytics
+
+
+
+
+ {/* Key Stats */}
+
+
+
1,247
+
Healthcare Workers
+
+
+
523
+
Births Verified
+
+
+
$125K
+
Donations Distributed
+
+
+
+
+
+
+ {/* Ubuntu Philosophy Section */}
+
+
+
+
Ubuntu: The Heart of FlameBorn
+
+ "Umuntu ngumuntu ngabantu" - A person is a person through other persons. This ancient African wisdom
+ guides our approach to healthcare tokenization.
+
+
+
+
+
+
+
+ Interconnectedness
+
+ Every token represents our shared humanity and collective responsibility for health
+
+
+
+
+ In Ubuntu philosophy, individual wellness is inseparable from community health. Our tokens create
+ digital bonds that strengthen real-world healing networks.
+
+
+
+
+
+
+
+ Collective Prosperity
+
+ When one thrives, we all thrive - tokenomics designed for shared abundance
+
+
+
+
+ Our distribution model ensures that healthcare workers, communities, and supporters all benefit from
+ the network's growth and success.
+
+
+
+
+
+
+
+ Cultural Bridge
+ Honoring traditional wisdom while embracing technological innovation
+
+
+
+ We bridge ancient African healing traditions with modern blockchain technology, creating a culturally
+ respectful path to global healthcare equity.
+
+
+
+
+
+
+
+ {/* How It Works Section */}
+
+
+
+
How FlameBorn Works
+
+ A simple, powerful system that rewards healthcare impact and builds community wealth
+
+
+
+
+
+
+
+
+
Register
+
Healthcare workers verify their credentials and join the Ubuntu network
+
+
+
+
+
+
+
Serve
+
Provide healthcare services and register births in their communities
+
+
+
+
+
+
+
Earn
+
Receive FLAME tokens for verified healthcare impact and community service
+
+
+
+
+
+
+
Grow
+
+ Build wealth together as the network creates lasting healthcare infrastructure
+
+
+
+
+
+
+ {/* AI Assistant Section */}
+
+
+
+
Meet Mostar, Your Ubuntu AI Guide
+
+ Ask questions about FlameBorn, Ubuntu philosophy, African wisdom, or healthcare tokenization. Mostar
+ embodies the spirit of "I am because we are."
+
+
+
+
+
+
+
+
+
+ {/* Call to Action Section */}
+
+
+
Join the Ubuntu Healthcare Revolution
+
+ Together, we can create a world where every birth is celebrated, every healer is supported, and every
+ community thrives.
+
+
+
+
+
+
+ Become a Healer
+
+
+
+
+
+ Join as Guardian
+
+
+
+
+
+ Explore Community
+
+
+
+
+
+
+
+ {/* Footer */}
+
+
+
+
+
+
+ FlameBorn
+
+
Ubuntu-powered healthcare tokenization for a connected world.
+
+
+
+
Platform
+
+
+
+ Launch App
+
+
+
+
+ Analytics
+
+
+
+
+ Token System
+
+
+
+
+ Smart Contracts
+
+
+
+
+
+
+
Community
+
+
+
+ Guardians Sanctuary
+
+
+
+
+ FlameBorn Journey
+
+
+
+
+ Community Pulse
+
+
+
+
+ Find Healers
+
+
+
+
+
+
+
Learn
+
+
+
+ Manifesto
+
+
+
+
+ Learn & Earn
+
+
+
+
+ Legal
+
+
+
+
+ Ubuntu Wisdom
+
+
+
+
+
+
+
+
© 2024 FlameBorn. Built with Ubuntu philosophy. "I am because we are."
+
+
+
+
+ )
+}
+
+export default HomePage
diff --git a/app/profile/edit/page.tsx b/app/profile/edit/page.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..078f19e363fec3eeff8be81b4bafabce92d87d41
--- /dev/null
+++ b/app/profile/edit/page.tsx
@@ -0,0 +1,229 @@
+"use client"
+
+import type React from "react"
+
+import { useEffect, useState } from "react"
+import { useRouter } from "next/navigation"
+import { Card, CardContent, CardHeader, CardTitle, CardFooter } from "@/components/ui/card"
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Textarea } from "@/components/ui/textarea"
+import { Label } from "@/components/ui/label"
+import { UserDatabase, type User, type Guardian, type Healer } from "@/lib/user-database"
+import { LoadingState } from "@/components/loading-state"
+import { ArrowLeft, Save } from "lucide-react"
+
+export default function ProfileEditPage() {
+ const router = useRouter()
+ const [user, setUser] = useState(null)
+ const [loading, setLoading] = useState(true)
+ const [isSaving, setIsSaving] = useState(false)
+ const [formData, setFormData] = useState>({})
+
+ useEffect(() => {
+ // Get current user
+ const currentUser = UserDatabase.getCurrentUser()
+
+ if (!currentUser) {
+ // Redirect to login if no user is found
+ router.push("/login")
+ return
+ }
+
+ setUser(currentUser)
+ setFormData(currentUser)
+ setLoading(false)
+ }, [router])
+
+ const handleChange = (e: React.ChangeEvent) => {
+ const { name, value } = e.target
+ setFormData((prev) => ({ ...prev, [name]: value }))
+ }
+
+ const handleSave = async () => {
+ if (!user) return
+
+ setIsSaving(true)
+
+ try {
+ // Update user
+ const updatedUser = UserDatabase.updateUser(user.id, formData)
+
+ if (updatedUser) {
+ setUser(updatedUser)
+
+ // Redirect back to profile
+ router.push("/profile")
+ }
+ } catch (error) {
+ console.error("Error updating profile:", error)
+ } finally {
+ setIsSaving(false)
+ }
+ }
+
+ if (loading) {
+ return
+ }
+
+ if (!user) {
+ return (
+
+
+
+
+
No User Found
+
Please log in or register to edit your profile.
+
router.push("/login")}>
+ Log In
+
+
+
+
+
+ )
+ }
+
+ return (
+
+
+
+
router.push("/profile")}>
+ Back
+
+
Edit Profile
+
+
+
+
+ Personal Information
+
+
+
+ Full Name
+
+
+
+
+ Email
+
+
+
+ {user.type === "guardian" && (
+ <>
+
+ Country
+
+
+
+
+ Why I became a Guardian
+
+
+ >
+ )}
+
+ {user.type === "healer" && (
+ <>
+
+
+
+ Facility Name
+
+
+
+
+ About You
+
+
+
+
+
Wallet Address
+
+
Where you'll receive support funds
+
+ >
+ )}
+
+
+
+
+ {isSaving ? "Saving..." : "Save Changes"}
+
+
+
+
+
+ )
+}
diff --git a/app/profile/page.tsx b/app/profile/page.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..2e8589472d3fb95a33760e2d2db3f4cf6b107cfd
--- /dev/null
+++ b/app/profile/page.tsx
@@ -0,0 +1,234 @@
+"use client"
+
+import { useEffect, useState } from "react"
+import { useRouter } from "next/navigation"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
+import { Button } from "@/components/ui/button"
+import { UserDatabase, type User } from "@/lib/user-database"
+import { Shield, Heart, Edit, LogOut } from "lucide-react"
+import { ProfileHeader } from "@/components/profile/profile-header"
+import { ProfileImpact } from "@/components/profile/profile-impact"
+import { ProfileActivity } from "@/components/profile/profile-activity"
+import { ProfileAchievements } from "@/components/profile/profile-achievements"
+import { LoadingState } from "@/components/loading-state"
+
+export default function ProfilePage() {
+ const router = useRouter()
+ const [user, setUser] = useState(null)
+ const [loading, setLoading] = useState(true)
+
+ useEffect(() => {
+ // Get current user
+ const currentUser = UserDatabase.getCurrentUser()
+
+ if (!currentUser) {
+ // Redirect to login if no user is found
+ router.push("/login")
+ return
+ }
+
+ setUser(currentUser)
+ setLoading(false)
+ }, [router])
+
+ const handleLogout = () => {
+ // Clear current user
+ localStorage.removeItem("flameborn_current_user")
+
+ // Redirect to home
+ router.push("/")
+ }
+
+ if (loading) {
+ return
+ }
+
+ if (!user) {
+ return (
+
+
+
+
+
No User Found
+
Please log in or register to view your profile.
+
+ router.push("/login")}>
+ Log In
+
+ router.push("/register/guardian")}
+ >
+ Register
+
+
+
+
+
+
+ )
+ }
+
+ return (
+
+
+
+
My Profile
+
+ router.push("/profile/edit")}
+ >
+ Edit Profile
+
+
+ Log Out
+
+
+
+
+
+
+
+
+
+ Impact
+
+
+ Activity
+
+
+ Achievements
+
+
+ Details
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Profile Details
+
+
+
+
+
+
Full Name
+
{user.fullName}
+
+
+
+
+
+
Member Since
+
{new Date(user.registeredAt).toLocaleDateString()}
+
+
+
+
Role
+
+ {user.type === "guardian" ? (
+ <>
+ Guardian
+ >
+ ) : (
+ <>
+ Healer
+ >
+ )}
+
+
+
+
+
+ {user.type === "guardian" && (
+ <>
+
+
Country
+
{user.country}
+
+
+
+
Contribution
+
+ {user.contributionAmount} {user.currency.toUpperCase()}
+
+
+
+
+
Payment Method
+
{user.paymentMethod}
+
+ >
+ )}
+
+ {user.type === "healer" && (
+ <>
+
+
Healthcare Role
+
{user.role}
+
+
+
+
Location
+
+ {user.city}, {user.country}
+
+
+
+
+
Verification Status
+
+ {user.verificationStatus}
+
+
+
+ {user.walletAddress && (
+
+
Wallet Address
+
{user.walletAddress}
+
+ )}
+ >
+ )}
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/app/register-chw/page.tsx b/app/register-chw/page.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..4f7cb37c793b6e82dc30de6fe7b2cdec9541f10e
--- /dev/null
+++ b/app/register-chw/page.tsx
@@ -0,0 +1,10 @@
+import { RegisterCHW } from "@/components/register-chw"
+
+export default function RegisterCHWPage() {
+ return (
+
+
Healthcare Worker Registration
+
+
+ )
+}
diff --git a/app/register/guardian/page.tsx b/app/register/guardian/page.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..e2d24f583a36526ca6e630e7fc3b58b9311005f9
--- /dev/null
+++ b/app/register/guardian/page.tsx
@@ -0,0 +1,14 @@
+import { RegistrationLayout } from "@/components/registration/registration-layout"
+import { GuardianRegistrationForm } from "@/components/registration/guardian-registration-form"
+
+export default function GuardianRegistrationPage() {
+ return (
+
+
+
+ )
+}
diff --git a/app/register/healer/page.tsx b/app/register/healer/page.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..83c3a84a1370e208b566e21e361d70ff81cb183a
--- /dev/null
+++ b/app/register/healer/page.tsx
@@ -0,0 +1,14 @@
+import { RegistrationLayout } from "@/components/registration/registration-layout"
+import { HealerRegistrationForm } from "@/components/registration/healer-registration-form"
+
+export default function HealerRegistrationPage() {
+ return (
+
+
+
+ )
+}
diff --git a/app/smart-contracts/page.tsx b/app/smart-contracts/page.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..d9ec18c6b8229732b90f344317c8ccbbdd8c8810
--- /dev/null
+++ b/app/smart-contracts/page.tsx
@@ -0,0 +1,19 @@
+import SmartContractInfo from "@/components/smart-contract-info"
+
+export default function SmartContractsPage() {
+ return (
+
+
+
+
+ Smart Contract Architecture
+
+
+ Explore the decentralized infrastructure powering Africa's health sovereignty revolution
+
+
+
+
+
+ )
+}
diff --git a/app/test-proverbs/page.tsx b/app/test-proverbs/page.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..cca38a1583278dd36d5e9bd8ca26bcff903aa84b
--- /dev/null
+++ b/app/test-proverbs/page.tsx
@@ -0,0 +1,11 @@
+import { ProverbTester } from "@/components/proverb-tester"
+
+export default function TestProverbsPage() {
+ return (
+
+ )
+}
diff --git a/app/testnet/page.tsx b/app/testnet/page.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..b27ad7c4a77696fc687f159eb318f424503a456e
--- /dev/null
+++ b/app/testnet/page.tsx
@@ -0,0 +1,390 @@
+"use client"
+
+import { useState, useEffect } from "react"
+import { TestnetStatus } from "@/components/testnet-status"
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Label } from "@/components/ui/label"
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
+import { testnetClient, type TestnetUser, type TestnetValidator } from "@/lib/testnet-client"
+import { Flame, Users, Server, Activity, Plus, Heart } from "lucide-react"
+import { useToast } from "@/hooks/use-toast"
+
+export default function TestnetPage() {
+ const [users, setUsers] = useState([])
+ const [validators, setValidators] = useState([])
+ const [isLoading, setIsLoading] = useState(true)
+ const [newUser, setNewUser] = useState({
+ wallet_address: "",
+ role: "",
+ name: "",
+ location: "",
+ })
+ const [newValidator, setNewValidator] = useState({
+ wallet_address: "",
+ name: "",
+ stake_amount: 0,
+ })
+ const { toast } = useToast()
+
+ const loadData = async () => {
+ try {
+ const [usersData, validatorsData] = await Promise.all([testnetClient.getUsers(), testnetClient.getValidators()])
+ setUsers(usersData)
+ setValidators(validatorsData)
+ } catch (error) {
+ console.error("Failed to load data:", error)
+ toast({
+ title: "Error",
+ description: "Failed to load testnet data",
+ variant: "destructive",
+ })
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ useEffect(() => {
+ loadData()
+ }, [])
+
+ const handleCreateUser = async () => {
+ try {
+ if (!newUser.wallet_address || !newUser.role || !newUser.name || !newUser.location) {
+ toast({
+ title: "Error",
+ description: "Please fill in all fields",
+ variant: "destructive",
+ })
+ return
+ }
+
+ await testnetClient.createUser(newUser)
+ setNewUser({ wallet_address: "", role: "", name: "", location: "" })
+ await loadData()
+
+ toast({
+ title: "Success",
+ description: `Ubuntu member ${newUser.name} registered successfully!`,
+ })
+ } catch (error) {
+ console.error("Failed to create user:", error)
+ toast({
+ title: "Error",
+ description: "Failed to register user",
+ variant: "destructive",
+ })
+ }
+ }
+
+ const handleCreateValidator = async () => {
+ try {
+ if (!newValidator.wallet_address || !newValidator.name || newValidator.stake_amount <= 0) {
+ toast({
+ title: "Error",
+ description: "Please fill in all fields with valid values",
+ variant: "destructive",
+ })
+ return
+ }
+
+ await testnetClient.createValidator(newValidator)
+ setNewValidator({ wallet_address: "", name: "", stake_amount: 0 })
+ await loadData()
+
+ toast({
+ title: "Success",
+ description: `Validator ${newValidator.name} registered successfully!`,
+ })
+ } catch (error) {
+ console.error("Failed to create validator:", error)
+ toast({
+ title: "Error",
+ description: "Failed to register validator",
+ variant: "destructive",
+ })
+ }
+ }
+
+ const sendHeartbeat = async (walletAddress: string) => {
+ try {
+ await testnetClient.sendValidatorHeartbeat(walletAddress)
+ await loadData()
+
+ toast({
+ title: "Heartbeat Sent",
+ description: "Validator heartbeat recorded successfully",
+ })
+ } catch (error) {
+ console.error("Failed to send heartbeat:", error)
+ toast({
+ title: "Error",
+ description: "Failed to send heartbeat",
+ variant: "destructive",
+ })
+ }
+ }
+
+ return (
+
+
+ {/* Header */}
+
+
+
+
FlameBorn Testnet
+
+
+ Ubuntu-powered healthcare tokenization testnet. "I am because we are" - Test the network, register users,
+ and validate transactions.
+
+
+
+ {/* Network Status */}
+
+
+ {/* User Registration */}
+
+
+
+
+ Register Ubuntu Member
+
+
+ Join the FlameBorn healthcare network as a healer, guardian, or community member
+
+
+
+
+
+
+ Wallet Address
+
+ setNewUser({ ...newUser, wallet_address: e.target.value })}
+ className="bg-slate-700 border-slate-600 text-white"
+ />
+
+
+
+ Role
+
+ setNewUser({ ...newUser, role: value })}>
+
+
+
+
+ Healer
+ Guardian
+ Community Member
+
+
+
+
+
+ Name
+
+ setNewUser({ ...newUser, name: e.target.value })}
+ className="bg-slate-700 border-slate-600 text-white"
+ />
+
+
+
+ Location
+
+ setNewUser({ ...newUser, location: e.target.value })}
+ className="bg-slate-700 border-slate-600 text-white"
+ />
+
+
+
+
+ Register Ubuntu Member
+
+
+
+
+ {/* Validator Registration */}
+
+
+
+
+ Register Validator
+
+
+ Become a validator to help secure the Ubuntu network
+
+
+
+
+
+
+ Register Validator
+
+
+
+
+ {/* Users List */}
+
+
+
+
+ Ubuntu Network Members ({users.length})
+
+
+
+ {isLoading ? (
+
+ ) : users.length === 0 ? (
+ No members registered yet
+ ) : (
+
+ {users.map((user) => (
+
+
+
+
+
{user.name}
+
+ {user.role}
+
+
+
{user.location}
+
{user.wallet_address}
+
+ {user.flb_balance} FLB
+ {user.verified && ✓ Verified }
+
+
+
+
+ ))}
+
+ )}
+
+
+
+ {/* Validators List */}
+
+
+
+
+ Network Validators ({validators.length})
+
+
+
+ {validators.length === 0 ? (
+ No validators registered yet
+ ) : (
+
+ {validators.map((validator) => (
+
+
+
+
+
{validator.name}
+
+ {validator.active ? "Active" : "Inactive"}
+
+
+
{validator.wallet_address}
+
+
+ Stake:
+ {validator.stake_amount} FLB
+
+
+ Blocks:
+ {validator.blocks_validated}
+
+
+ Uptime:
+ {validator.uptime_percentage.toFixed(1)}%
+
+
+ sendHeartbeat(validator.wallet_address)}
+ className="bg-purple-600 hover:bg-purple-700 text-xs"
+ >
+ Send Heartbeat
+
+
+
+
+
+
+ ))}
+
+ )}
+
+
+
+
+ )
+}
diff --git a/app/token-system/page.tsx b/app/token-system/page.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..f99ff0a646fcb924916a828d36ae8c09b490dadb
--- /dev/null
+++ b/app/token-system/page.tsx
@@ -0,0 +1,45 @@
+"use client"
+
+import { TokenRegistrationFlow } from "@/components/token-registration-flow"
+import { TokenDistributionExplainer } from "@/components/token-distribution-explainer"
+import { Coins } from "lucide-react"
+import dynamic from "next/dynamic"
+
+const ParticleBackground = dynamic(
+ () => import("@/components/particle-background").then((mod) => ({ default: mod.ParticleBackground })),
+ {
+ ssr: false,
+ loading: () =>
,
+ },
+)
+
+export default function TokenSystemPage() {
+ return (
+
+
+
+ {/* Hero Section */}
+
+
+
+
FLB Token System
+
+ Empowering African healthcare through blockchain technology. Join the revolution that rewards impact and
+ builds sustainable health systems.
+
+
+
+
+ {/* Main Content */}
+
+
+
+
+
+
+ )
+}
diff --git a/app/verify-scroll/page.tsx b/app/verify-scroll/page.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..aa9d72bacc42b7ee4b77db2b5870788bd268fb31
--- /dev/null
+++ b/app/verify-scroll/page.tsx
@@ -0,0 +1,10 @@
+import { VerifyScroll } from "@/components/verify-scroll"
+
+export default function VerifyScrollPage() {
+ return (
+
+
Guardian Verification Circle
+
+
+ )
+}
diff --git a/app/youth/page.tsx b/app/youth/page.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..9727933c3606c3e48ff3b86c02659b7f22af488d
--- /dev/null
+++ b/app/youth/page.tsx
@@ -0,0 +1,676 @@
+"use client"
+
+import { useState, useEffect } from "react"
+import { motion } from "framer-motion"
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
+import { Button } from "@/components/ui/button"
+import { Progress } from "@/components/ui/progress"
+import { Badge } from "@/components/ui/badge"
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
+import { Trophy, Flame, Users, BookOpen, Star, Target, Heart, Award, TrendingUp, Zap, Calendar } from "lucide-react"
+
+interface UserData {
+ wallet: string
+ level: number
+ xp: number
+ flbBalance: number
+ streak: number
+ tribe?: string
+ achievements: string[]
+ completedModules: string[]
+ dailyChallenges: Challenge[]
+ storiesShared: number
+}
+
+interface Challenge {
+ id: string
+ title: string
+ description: string
+ category: "learn" | "earn" | "connect" | "create"
+ difficulty: "easy" | "medium" | "hard"
+ rewardFLB: number
+ rewardXP: number
+ completed: boolean
+ progress: number
+ total: number
+ timeLeft: string
+}
+
+const mockUserData: UserData = {
+ wallet: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
+ level: 3,
+ xp: 1250,
+ flbBalance: 350,
+ streak: 7,
+ tribe: "yoruba-learners",
+ achievements: ["first-steps", "streak-starter", "wisdom-seeker"],
+ completedModules: ["akan-1", "health-1", "ubuntu-1"],
+ storiesShared: 5,
+ dailyChallenges: [
+ {
+ id: "daily-1",
+ title: "Share Ubuntu Wisdom Story",
+ description: "Tell how you helped someone today using Ubuntu philosophy",
+ category: "create",
+ difficulty: "medium",
+ rewardFLB: 25,
+ rewardXP: 50,
+ completed: false,
+ progress: 0,
+ total: 1,
+ timeLeft: "18h",
+ },
+ {
+ id: "daily-2",
+ title: "Master 3 Yoruba Proverbs",
+ description: "Learn and understand the cultural context of Yoruba wisdom",
+ category: "learn",
+ difficulty: "easy",
+ rewardFLB: 15,
+ rewardXP: 30,
+ completed: false,
+ progress: 1,
+ total: 3,
+ timeLeft: "18h",
+ },
+ {
+ id: "daily-3",
+ title: "Connect with 2 Tribe Members",
+ description: "Build your African network and share knowledge",
+ category: "connect",
+ difficulty: "easy",
+ rewardFLB: 20,
+ rewardXP: 25,
+ completed: true,
+ progress: 2,
+ total: 2,
+ timeLeft: "18h",
+ },
+ {
+ id: "weekly-1",
+ title: "Complete Health Advocacy Course",
+ description: "Master community health principles for African communities",
+ category: "learn",
+ difficulty: "hard",
+ rewardFLB: 100,
+ rewardXP: 200,
+ completed: false,
+ progress: 2,
+ total: 5,
+ timeLeft: "4d",
+ },
+ ],
+}
+
+const tribes = [
+ {
+ id: "yoruba-learners",
+ name: "Yoruba Wisdom Keepers",
+ members: 234,
+ region: "West Africa",
+ description: "Preserving and sharing Yoruba cultural wisdom",
+ activity: "high",
+ },
+ {
+ id: "akan-heritage",
+ name: "Akan Heritage Circle",
+ members: 189,
+ region: "Ghana",
+ description: "Sankofa philosophy and Akan traditions",
+ activity: "high",
+ },
+ {
+ id: "ubuntu-philosophy",
+ name: "Ubuntu Philosophy",
+ members: 156,
+ region: "Southern Africa",
+ description: "Living the principle: I am because we are",
+ activity: "medium",
+ },
+ {
+ id: "health-heroes",
+ name: "Health Heroes",
+ members: 298,
+ region: "Pan-African",
+ description: "Young Africans advancing community health",
+ activity: "high",
+ },
+ {
+ id: "tech-innovators",
+ name: "Digital Innovators",
+ members: 445,
+ region: "Pan-African",
+ description: "Building Africa's digital future",
+ activity: "high",
+ },
+ {
+ id: "eco-warriors",
+ name: "Eco Warriors",
+ members: 167,
+ region: "Pan-African",
+ description: "Fighting climate change with African solutions",
+ activity: "medium",
+ },
+]
+
+const achievements = [
+ { id: "first-steps", name: "First Steps", description: "Complete your first challenge", icon: "👶", earned: true },
+ {
+ id: "streak-starter",
+ name: "Streak Starter",
+ description: "Maintain 7-day learning streak",
+ icon: "🔥",
+ earned: true,
+ },
+ { id: "wisdom-seeker", name: "Wisdom Seeker", description: "Learn 10 African proverbs", icon: "🧠", earned: true },
+ {
+ id: "community-builder",
+ name: "Community Builder",
+ description: "Join your first tribe",
+ icon: "🏗️",
+ earned: false,
+ },
+ { id: "story-teller", name: "Story Teller", description: "Share 10 Ubuntu stories", icon: "📚", earned: false },
+ {
+ id: "health-champion",
+ name: "Health Champion",
+ description: "Complete health advocacy course",
+ icon: "🩺",
+ earned: false,
+ },
+ {
+ id: "culture-keeper",
+ name: "Culture Keeper",
+ description: "Master 3 different African languages",
+ icon: "🌍",
+ earned: false,
+ },
+ { id: "flame-bearer", name: "Flame Bearer", description: "Earn 500 FLB tokens", icon: "🔥", earned: false },
+ { id: "ubuntu-master", name: "Ubuntu Master", description: "Embody Ubuntu in 50 actions", icon: "❤️", earned: false },
+]
+
+export default function YouthPage() {
+ const [userData, setUserData] = useState(null)
+ const [isLoading, setIsLoading] = useState(true)
+ const [activeTab, setActiveTab] = useState("challenges")
+
+ useEffect(() => {
+ const loadUserData = async () => {
+ try {
+ setIsLoading(true)
+ await new Promise((resolve) => setTimeout(resolve, 1000))
+ setUserData(mockUserData)
+ } catch (error) {
+ console.error("Error loading user data:", error)
+ setUserData(mockUserData)
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ loadUserData()
+ }, [])
+
+ const completeChallenge = async (challengeId: string) => {
+ if (!userData) return
+
+ setUserData((prev) => {
+ if (!prev) return null
+
+ const challenge = prev.dailyChallenges.find((c) => c.id === challengeId)
+ if (!challenge || challenge.completed) return prev
+
+ return {
+ ...prev,
+ dailyChallenges: prev.dailyChallenges.map((c) =>
+ c.id === challengeId ? { ...c, completed: true, progress: c.total } : c,
+ ),
+ flbBalance: prev.flbBalance + challenge.rewardFLB,
+ xp: prev.xp + challenge.rewardXP,
+ }
+ })
+ }
+
+ const joinTribe = async (tribeId: string) => {
+ if (!userData) return
+
+ setUserData((prev) => {
+ if (!prev) return null
+ return {
+ ...prev,
+ tribe: tribeId,
+ }
+ })
+ }
+
+ const getCategoryIcon = (category: string) => {
+ switch (category) {
+ case "learn":
+ return
+ case "earn":
+ return
+ case "connect":
+ return
+ case "create":
+ return
+ default:
+ return
+ }
+ }
+
+ const getCategoryColor = (category: string) => {
+ switch (category) {
+ case "learn":
+ return "bg-blue-500"
+ case "earn":
+ return "bg-yellow-500"
+ case "connect":
+ return "bg-green-500"
+ case "create":
+ return "bg-purple-500"
+ default:
+ return "bg-gray-500"
+ }
+ }
+
+ const getDifficultyColor = (difficulty: string) => {
+ switch (difficulty) {
+ case "easy":
+ return "text-green-400"
+ case "medium":
+ return "text-yellow-400"
+ case "hard":
+ return "text-red-400"
+ default:
+ return "text-gray-400"
+ }
+ }
+
+ if (isLoading) {
+ return (
+
+
+
+
Loading your FlameBorn journey...
+
+
+ )
+ }
+
+ if (!userData) {
+ return (
+
+
+
+ Welcome to FlameBorn Youth
+
+ Connect your wallet to start your African wisdom journey
+
+
+
+ Connect Wallet
+
+
+
+ )
+ }
+
+ const nextLevelXP = userData.level * 1000
+ const progressPercentage = (userData.xp / nextLevelXP) * 100
+
+ return (
+
+ {/* Hero Header */}
+
+
+
+
+
+ Young FlameBorn Rising
+
+
+ Learn African Wisdom • Earn FLB Tokens • Build Ubuntu Community
+
+
+ {/* User Stats Dashboard */}
+
+
+
+
+ Level {userData.level}
+
+
+ {userData.xp}/{nextLevelXP} XP
+
+
+
+
+
+
+
+ {userData.flbBalance}
+
+ FLB Earned
+
+
+
+
+
+ {userData.streak}
+
+ Day Streak
+
+
+
+
+
+ {userData.achievements.length}
+
+ Achievements
+
+
+
+
+
+
+ {/* Main Content */}
+
+
+
+
+
+ Challenges
+
+
+
+ Learn & Earn
+
+
+
+ Tribes
+
+
+
+ Achievements
+
+
+
+ {/* Challenges Tab */}
+
+
+ {/* Daily Challenges */}
+
+
+
+
+ Daily Challenges
+
+
+
+ {userData.dailyChallenges
+ .filter((c) => c.timeLeft.includes("h"))
+ .map((challenge) => (
+
+
+
+
+ {getCategoryIcon(challenge.category)}
+
+
+
{challenge.title}
+
{challenge.description}
+
+
+
+ {challenge.difficulty}
+
+
+
+
+
+
+ +{challenge.rewardFLB} FLB
+
+
{challenge.timeLeft} left
+
+
+
+
+ Progress
+
+ {challenge.progress}/{challenge.total}
+
+
+
+
+
+ completeChallenge(challenge.id)}
+ >
+ {challenge.completed ? "Completed!" : "Start Challenge"}
+
+
+ ))}
+
+
+
+ {/* Weekly Challenges */}
+
+
+
+
+ Weekly Challenges
+
+
+
+ {userData.dailyChallenges
+ .filter((c) => c.timeLeft.includes("d"))
+ .map((challenge) => (
+
+
+
+
+ {getCategoryIcon(challenge.category)}
+
+
+
{challenge.title}
+
{challenge.description}
+
+
+
+ {challenge.difficulty}
+
+
+
+
+
+
+ +{challenge.rewardFLB} FLB
+
+
{challenge.timeLeft} left
+
+
+
+
+ Progress
+
+ {challenge.progress}/{challenge.total}
+
+
+
+
+
+ completeChallenge(challenge.id)}
+ >
+ {challenge.completed ? "Completed!" : "Continue Challenge"}
+
+
+ ))}
+
+
+
+
+
+ {/* Learn & Earn Tab */}
+
+
+
+ Learn & Earn Portal
+ Master African wisdom and earn FLB tokens
+
+
+
+
+
+ Interactive Modules
+ Explore African proverbs with cultural context
+ (window.location.href = "/learn-earn")}
+ >
+ Start Learning
+
+
+
+
+
+ Quiz Challenges
+ Test your knowledge and earn FLB
+ (window.location.href = "/learn-earn?tab=quiz")}
+ >
+ Take Quiz
+
+
+
+
+
+ Story Sharing
+ Share your Ubuntu philosophy stories
+ (window.location.href = "/learn-earn?tab=stories")}
+ >
+ Share Story
+
+
+
+
+
+
+
+ {/* Tribes Tab */}
+
+
+ {tribes.map((tribe) => (
+
+
+
+
{tribe.name}
+
{tribe.description}
+
+
+ {tribe.members} members
+ •
+ {tribe.region}
+ •
+
+ {tribe.activity} activity
+
+
+
+ {userData.tribe === tribe.id &&
Your Tribe }
+
+
+ userData.tribe !== tribe.id && joinTribe(tribe.id)}
+ >
+ {userData.tribe === tribe.id ? "View Tribe" : "Join Tribe"}
+
+
+ ))}
+
+
+
+ {/* Achievements Tab */}
+
+
+
+ Your Achievements
+ Unlock badges as you grow in the FlameBorn community
+
+
+
+ {achievements.map((achievement) => (
+
+ {achievement.icon}
+
+ {achievement.name}
+
+
+ {achievement.description}
+
+ {achievement.earned && Earned! }
+
+ ))}
+
+
+
+
+
+
+
+ )
+}
diff --git a/backend/.codesandbox/tasks.json b/backend/.codesandbox/tasks.json
new file mode 100644
index 0000000000000000000000000000000000000000..ca59b59fd5c67d4abd8e784b97e5e3e39f58bc92
--- /dev/null
+++ b/backend/.codesandbox/tasks.json
@@ -0,0 +1,16 @@
+{
+ "setupTasks": [
+ {
+ "name": "Install Dependencies",
+ "command": "pip install -r backend/requirements.txt"
+ }
+ ],
+ "tasks": {
+ "start-testnet": {
+ "name": "Start FlameBorn Testnet",
+ "command": "cd backend && uvicorn main:app --host 0.0.0.0 --port 8000 --reload",
+ "runAtStart": true
+ }
+ },
+ "ports": [8000]
+}
diff --git a/backend/.devcontainer.json b/backend/.devcontainer.json
new file mode 100644
index 0000000000000000000000000000000000000000..86c58edcbfe9d8160c07214bea0432fdf8f2cc05
--- /dev/null
+++ b/backend/.devcontainer.json
@@ -0,0 +1,23 @@
+{
+ "name": "FlameBorn Testnet",
+ "image": "python:3.11-slim",
+ "features": {
+ "ghcr.io/devcontainers/features/python:1": {
+ "version": "3.11"
+ }
+ },
+ "customizations": {
+ "vscode": {
+ "extensions": ["ms-python.python", "ms-python.flake8"]
+ }
+ },
+ "forwardPorts": [8000],
+ "portsAttributes": {
+ "8000": {
+ "label": "FlameBorn Testnet API",
+ "onAutoForward": "notify"
+ }
+ },
+ "postCreateCommand": "pip install -r requirements.txt",
+ "remoteUser": "root"
+}
diff --git a/backend/Dockerfile b/backend/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..7342a5f00aaf9522f7afc9c4ebcd1e40e50c8c93
--- /dev/null
+++ b/backend/Dockerfile
@@ -0,0 +1,12 @@
+FROM python:3.11-slim
+
+WORKDIR /app
+
+COPY requirements.txt .
+RUN pip install --no-cache-dir -r requirements.txt
+
+COPY . .
+
+EXPOSE 8000
+
+CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
diff --git a/backend/main.py b/backend/main.py
new file mode 100644
index 0000000000000000000000000000000000000000..9c09af13d836331a9fbe9bdb596679f32edca2b4
--- /dev/null
+++ b/backend/main.py
@@ -0,0 +1,391 @@
+from fastapi import FastAPI, HTTPException, WebSocket, WebSocketDisconnect
+from fastapi.middleware.cors import CORSMiddleware
+from sqlalchemy import create_engine, Column, Integer, String, Float, Boolean, DateTime, Text
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm import sessionmaker, Session
+from pydantic import BaseModel
+from datetime import datetime
+from typing import List, Optional
+import json
+import asyncio
+import random
+
+# Database setup
+SQLALCHEMY_DATABASE_URL = "sqlite:///./flameborn_testnet.db"
+engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
+SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
+Base = declarative_base()
+
+# Database Models
+class User(Base):
+ __tablename__ = "users"
+
+ id = Column(Integer, primary_key=True, index=True)
+ wallet_address = Column(String, unique=True, index=True)
+ role = Column(String) # "healer", "guardian", "community"
+ name = Column(String)
+ location = Column(String)
+ verified = Column(Boolean, default=False)
+ flb_balance = Column(Float, default=0.0)
+ created_at = Column(DateTime, default=datetime.utcnow)
+
+class Validator(Base):
+ __tablename__ = "validators"
+
+ id = Column(Integer, primary_key=True, index=True)
+ wallet_address = Column(String, unique=True, index=True)
+ name = Column(String)
+ stake_amount = Column(Float)
+ active = Column(Boolean, default=True)
+ last_heartbeat = Column(DateTime, default=datetime.utcnow)
+ blocks_validated = Column(Integer, default=0)
+ uptime_percentage = Column(Float, default=100.0)
+
+class Transaction(Base):
+ __tablename__ = "transactions"
+
+ id = Column(Integer, primary_key=True, index=True)
+ from_address = Column(String)
+ to_address = Column(String)
+ amount = Column(Float)
+ transaction_type = Column(String) # "birth_verification", "health_action", "donation"
+ description = Column(Text)
+ timestamp = Column(DateTime, default=datetime.utcnow)
+ block_number = Column(Integer)
+
+class HealthAction(Base):
+ __tablename__ = "health_actions"
+
+ id = Column(Integer, primary_key=True, index=True)
+ user_id = Column(Integer)
+ action_type = Column(String) # "birth_registration", "health_check", "community_support"
+ description = Column(Text)
+ impact_score = Column(Float)
+ flb_reward = Column(Float)
+ timestamp = Column(DateTime, default=datetime.utcnow)
+
+# Create tables
+Base.metadata.create_all(bind=engine)
+
+# Pydantic models
+class UserCreate(BaseModel):
+ wallet_address: str
+ role: str
+ name: str
+ location: str
+
+class UserResponse(BaseModel):
+ id: int
+ wallet_address: str
+ role: str
+ name: str
+ location: str
+ verified: bool
+ flb_balance: float
+ created_at: datetime
+
+class ValidatorCreate(BaseModel):
+ wallet_address: str
+ name: str
+ stake_amount: float
+
+class ValidatorResponse(BaseModel):
+ id: int
+ wallet_address: str
+ name: str
+ stake_amount: float
+ active: bool
+ last_heartbeat: datetime
+ blocks_validated: int
+ uptime_percentage: float
+
+class HealthActionCreate(BaseModel):
+ user_id: int
+ action_type: str
+ description: str
+ impact_score: float
+
+class NetworkStats(BaseModel):
+ total_users: int
+ total_validators: int
+ total_transactions: int
+ total_flb_supply: float
+ active_validators: int
+ network_uptime: float
+
+# FastAPI app
+app = FastAPI(title="FlameBorn Testnet", version="0.0.1-alpha")
+
+# CORS middleware
+app.add_middleware(
+ CORSMiddleware,
+ allow_origins=["*"],
+ allow_credentials=True,
+ allow_methods=["*"],
+ allow_headers=["*"],
+)
+
+# WebSocket connections
+class ConnectionManager:
+ def __init__(self):
+ self.active_connections: List[WebSocket] = []
+
+ async def connect(self, websocket: WebSocket):
+ await websocket.accept()
+ self.active_connections.append(websocket)
+
+ def disconnect(self, websocket: WebSocket):
+ self.active_connections.remove(websocket)
+
+ async def broadcast(self, message: str):
+ for connection in self.active_connections:
+ try:
+ await connection.send_text(message)
+ except:
+ pass
+
+manager = ConnectionManager()
+
+# Database dependency
+def get_db():
+ db = SessionLocal()
+ try:
+ yield db
+ finally:
+ db.close()
+
+# Routes
+@app.get("/ping")
+def ping():
+ return {
+ "status": "alive",
+ "network": "FlameBorn-Testnet",
+ "version": "0.0.1-alpha",
+ "timestamp": datetime.utcnow().isoformat(),
+ "ubuntu_message": "I am because we are"
+ }
+
+@app.get("/.well-known/manifest.json")
+def manifest():
+ return {
+ "network": "FlameBorn-Testnet",
+ "version": "0.0.1-alpha",
+ "consensus": "Ubuntu-PoS",
+ "ubuntu_principle": "I am because we are",
+ "oracle": True,
+ "validator": True,
+ "features": [
+ "birth_verification",
+ "health_action_rewards",
+ "community_governance",
+ "ubuntu_consensus",
+ "cultural_anchoring",
+ "real_time_updates"
+ ],
+ "endpoints": {
+ "users": "/users",
+ "validators": "/validators",
+ "stats": "/stats",
+ "oracle": "/oracle/price",
+ "consensus": "/consensus/status",
+ "websocket": "/ws"
+ }
+ }
+
+@app.get("/users", response_model=List[UserResponse])
+def get_users(db: Session = next(get_db())):
+ users = db.query(User).all()
+ return users
+
+@app.post("/users", response_model=UserResponse)
+def create_user(user: UserCreate, db: Session = next(get_db())):
+ # Check if user already exists
+ existing_user = db.query(User).filter(User.wallet_address == user.wallet_address).first()
+ if existing_user:
+ raise HTTPException(status_code=400, detail="User already exists")
+
+ # Create new user with initial FLB balance
+ db_user = User(
+ wallet_address=user.wallet_address,
+ role=user.role,
+ name=user.name,
+ location=user.location,
+ verified=True, # Auto-verify for testnet
+ flb_balance=1000.0 # Initial testnet tokens
+ )
+ db.add(db_user)
+ db.commit()
+ db.refresh(db_user)
+
+ # Broadcast new user event
+ asyncio.create_task(manager.broadcast(json.dumps({
+ "type": "new_user",
+ "data": {
+ "name": db_user.name,
+ "role": db_user.role,
+ "location": db_user.location
+ }
+ })))
+
+ return db_user
+
+@app.get("/users/{wallet_address}", response_model=UserResponse)
+def get_user(wallet_address: str, db: Session = next(get_db())):
+ user = db.query(User).filter(User.wallet_address == wallet_address).first()
+ if not user:
+ raise HTTPException(status_code=404, detail="User not found")
+ return user
+
+@app.get("/validators", response_model=List[ValidatorResponse])
+def get_validators(db: Session = next(get_db())):
+ validators = db.query(Validator).all()
+ return validators
+
+@app.post("/validators", response_model=ValidatorResponse)
+def create_validator(validator: ValidatorCreate, db: Session = next(get_db())):
+ # Check if validator already exists
+ existing_validator = db.query(Validator).filter(Validator.wallet_address == validator.wallet_address).first()
+ if existing_validator:
+ raise HTTPException(status_code=400, detail="Validator already exists")
+
+ db_validator = Validator(
+ wallet_address=validator.wallet_address,
+ name=validator.name,
+ stake_amount=validator.stake_amount,
+ active=True,
+ last_heartbeat=datetime.utcnow(),
+ blocks_validated=0,
+ uptime_percentage=100.0
+ )
+ db.add(db_validator)
+ db.commit()
+ db.refresh(db_validator)
+
+ # Broadcast new validator event
+ asyncio.create_task(manager.broadcast(json.dumps({
+ "type": "new_validator",
+ "data": {
+ "name": db_validator.name,
+ "stake_amount": db_validator.stake_amount
+ }
+ })))
+
+ return db_validator
+
+@app.post("/validators/{wallet_address}/heartbeat")
+def validator_heartbeat(wallet_address: str, db: Session = next(get_db())):
+ validator = db.query(Validator).filter(Validator.wallet_address == wallet_address).first()
+ if not validator:
+ raise HTTPException(status_code=404, detail="Validator not found")
+
+ validator.last_heartbeat = datetime.utcnow()
+ validator.blocks_validated += 1
+ db.commit()
+
+ return {"status": "heartbeat_recorded", "timestamp": validator.last_heartbeat}
+
+@app.get("/stats", response_model=NetworkStats)
+def get_network_stats(db: Session = next(get_db())):
+ total_users = db.query(User).count()
+ total_validators = db.query(Validator).count()
+ active_validators = db.query(Validator).filter(Validator.active == True).count()
+ total_transactions = db.query(Transaction).count()
+
+ # Calculate total FLB supply
+ total_flb_supply = db.query(User).with_entities(User.flb_balance).all()
+ total_supply = sum([balance[0] for balance in total_flb_supply])
+
+ return NetworkStats(
+ total_users=total_users,
+ total_validators=total_validators,
+ total_transactions=total_transactions,
+ total_flb_supply=total_supply,
+ active_validators=active_validators,
+ network_uptime=99.8 # Simulated uptime
+ )
+
+@app.get("/oracle/price")
+def get_flb_price():
+ # Simulate FLB price with some volatility
+ base_price = 0.00125
+ volatility = random.uniform(-0.0002, 0.0002)
+ current_price = base_price + volatility
+
+ return {
+ "symbol": "FLB",
+ "price_usd": current_price,
+ "price_change_24h": random.uniform(-15, 15),
+ "volume_24h": random.uniform(10000, 50000),
+ "market_cap": current_price * 750000000,
+ "last_updated": datetime.utcnow().isoformat()
+ }
+
+@app.get("/consensus/status")
+def get_consensus_status(db: Session = next(get_db())):
+ active_validators = db.query(Validator).filter(Validator.active == True).count()
+ total_validators = db.query(Validator).count()
+
+ return {
+ "consensus_type": "Ubuntu-PoS",
+ "active_validators": active_validators,
+ "total_validators": total_validators,
+ "consensus_participation": (active_validators / max(total_validators, 1)) * 100,
+ "last_block_time": datetime.utcnow().isoformat(),
+ "ubuntu_principle": "Collective validation through community consensus",
+ "network_health": "healthy" if active_validators >= 3 else "warning"
+ }
+
+@app.post("/health-actions")
+def record_health_action(action: HealthActionCreate, db: Session = next(get_db())):
+ # Calculate FLB reward based on impact score
+ flb_reward = action.impact_score * 10.0 # 10 FLB per impact point
+
+ db_action = HealthAction(
+ user_id=action.user_id,
+ action_type=action.action_type,
+ description=action.description,
+ impact_score=action.impact_score,
+ flb_reward=flb_reward,
+ timestamp=datetime.utcnow()
+ )
+ db.add(db_action)
+
+ # Update user's FLB balance
+ user = db.query(User).filter(User.id == action.user_id).first()
+ if user:
+ user.flb_balance += flb_reward
+
+ db.commit()
+ db.refresh(db_action)
+
+ # Broadcast health action event
+ asyncio.create_task(manager.broadcast(json.dumps({
+ "type": "health_action",
+ "data": {
+ "action_type": db_action.action_type,
+ "impact_score": db_action.impact_score,
+ "flb_reward": db_action.flb_reward,
+ "user_name": user.name if user else "Unknown"
+ }
+ })))
+
+ return db_action
+
+@app.websocket("/ws")
+async def websocket_endpoint(websocket: WebSocket):
+ await manager.connect(websocket)
+ try:
+ while True:
+ # Send periodic network updates
+ await asyncio.sleep(10)
+ await websocket.send_text(json.dumps({
+ "type": "network_pulse",
+ "timestamp": datetime.utcnow().isoformat(),
+ "message": "Ubuntu network heartbeat - I am because we are"
+ }))
+ except WebSocketDisconnect:
+ manager.disconnect(websocket)
+
+if __name__ == "__main__":
+ import uvicorn
+ uvicorn.run(app, host="0.0.0.0", port=8000)
diff --git a/backend/requirements.txt b/backend/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..c7eecb7f00afadd2d35d7bc805e353624a4f0a26
--- /dev/null
+++ b/backend/requirements.txt
@@ -0,0 +1,6 @@
+fastapi==0.104.1
+uvicorn[standard]==0.24.0
+sqlalchemy==2.0.23
+pydantic==2.5.0
+python-multipart==0.0.6
+websockets==12.0
diff --git a/backend/start.sh b/backend/start.sh
new file mode 100644
index 0000000000000000000000000000000000000000..37205641522d34608ae0b73c6342ba4c468413ef
--- /dev/null
+++ b/backend/start.sh
@@ -0,0 +1,38 @@
+#!/bin/bash
+
+echo "🔥 FLAMEBORN TESTNET INITIALIZATION 🔥"
+echo "Ubuntu Principle: I am because we are"
+echo "========================================="
+
+# Check if Python is installed
+if ! command -v python3 &> /dev/null; then
+ echo "❌ Python 3 is not installed"
+ exit 1
+fi
+
+# Check if pip is installed
+if ! command -v pip &> /dev/null; then
+ echo "❌ pip is not installed"
+ exit 1
+fi
+
+echo "📦 Installing dependencies..."
+pip install -r requirements.txt
+
+echo "🗄️ Initializing database..."
+python3 -c "
+from main import Base, engine
+Base.metadata.create_all(bind=engine)
+print('✅ Database initialized')
+"
+
+echo "🚀 Starting FlameBorn Testnet..."
+echo "🌐 Network will be available at: http://localhost:8000"
+echo "📊 API Documentation: http://localhost:8000/docs"
+echo "🔍 Health Check: http://localhost:8000/ping"
+echo "📋 Network Manifest: http://localhost:8000/.well-known/manifest.json"
+echo ""
+echo "🔥 THE FLAME CANNOT WHISPER - IT MUST ROAR 🔥"
+echo ""
+
+uvicorn main:app --host 0.0.0.0 --port 8000 --reload
diff --git a/components.json b/components.json
new file mode 100644
index 0000000000000000000000000000000000000000..d9ef0ae537daba0b9fb71e78522e5aa763c4de93
--- /dev/null
+++ b/components.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "default",
+ "rsc": true,
+ "tsx": true,
+ "tailwind": {
+ "config": "tailwind.config.ts",
+ "css": "app/globals.css",
+ "baseColor": "neutral",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils",
+ "ui": "@/components/ui",
+ "lib": "@/lib",
+ "hooks": "@/hooks"
+ },
+ "iconLibrary": "lucide"
+}
\ No newline at end of file
diff --git a/components/Shell.tsx b/components/Shell.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..50752d0a1dafcebed462df2338a19e6e606e4e68
--- /dev/null
+++ b/components/Shell.tsx
@@ -0,0 +1,11 @@
+"use client"
+
+import type { ReactNode } from "react"
+
+interface ShellProps {
+ children: ReactNode
+}
+
+export function Shell({ children }: ShellProps) {
+ return {children}
+}
diff --git a/components/ai-assistant.tsx b/components/ai-assistant.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..25fc3ab385b6ff883053cbb644457ed89992d516
--- /dev/null
+++ b/components/ai-assistant.tsx
@@ -0,0 +1,210 @@
+"use client"
+
+import type React from "react"
+
+import { useState, useRef, useEffect } from "react"
+import { Button } from "@/components/ui/button"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Input } from "@/components/ui/input"
+import { Badge } from "@/components/ui/badge"
+import { MessageCircle, Send, X, Sparkles, Heart } from "lucide-react"
+import { motion, AnimatePresence } from "framer-motion"
+
+interface Message {
+ id: string
+ text: string
+ isUser: boolean
+ timestamp: Date
+}
+
+export default function MostarAIAssistant() {
+ const [isOpen, setIsOpen] = useState(false)
+ const [messages, setMessages] = useState([
+ {
+ id: "welcome",
+ text: "Sawubona! I am Mostar, your Ubuntu AI guide. I embody the spirit of 'I am because we are.' Ask me about FlameBorn, Ubuntu philosophy, African wisdom, or healthcare tokenization. How can we grow together today?",
+ isUser: false,
+ timestamp: new Date(),
+ },
+ ])
+ const [inputValue, setInputValue] = useState("")
+ const [isTyping, setIsTyping] = useState(false)
+ const messagesEndRef = useRef(null)
+
+ const scrollToBottom = () => {
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" })
+ }
+
+ useEffect(() => {
+ scrollToBottom()
+ }, [messages])
+
+ const ubuntuResponses = [
+ "In Ubuntu philosophy, we understand that individual wellness flows from community health. Your question touches the heart of our interconnectedness.",
+ "As the Zulu saying goes: 'Umuntu ngumuntu ngabantu' - a person is a person through other persons. This wisdom guides everything we do at FlameBorn.",
+ "The flame of healing burns brightest when we tend it together. In our tokenization model, every reward strengthens the whole network.",
+ "Ubuntu teaches us that when one suffers, we all suffer. When one heals, we all heal. This is why FlameBorn exists - to weave digital bonds that strengthen real healing.",
+ "In traditional African communities, the birth of a child was celebrated by the entire village. FlameBorn brings this spirit to the digital age through our birth registration tokens.",
+ "The ancestors whisper: 'What affects one, affects all.' Our blockchain preserves this wisdom, ensuring that every healthcare action ripples through the community.",
+ "Ubuntu is not just philosophy - it's a way of being. When healthcare workers earn FLB tokens, they're not just being rewarded, they're being recognized as vital threads in our community fabric.",
+ ]
+
+ const handleSendMessage = async () => {
+ if (!inputValue.trim()) return
+
+ const userMessage: Message = {
+ id: Date.now().toString(),
+ text: inputValue,
+ isUser: true,
+ timestamp: new Date(),
+ }
+
+ setMessages((prev) => [...prev, userMessage])
+ setInputValue("")
+ setIsTyping(true)
+
+ // Simulate AI response delay
+ setTimeout(() => {
+ const aiResponse: Message = {
+ id: (Date.now() + 1).toString(),
+ text: ubuntuResponses[Math.floor(Math.random() * ubuntuResponses.length)],
+ isUser: false,
+ timestamp: new Date(),
+ }
+
+ setMessages((prev) => [...prev, aiResponse])
+ setIsTyping(false)
+ }, 1500)
+ }
+
+ const handleKeyPress = (e: React.KeyboardEvent) => {
+ if (e.key === "Enter" && !e.shiftKey) {
+ e.preventDefault()
+ handleSendMessage()
+ }
+ }
+
+ return (
+ <>
+ {/* Floating Chat Button */}
+
+ setIsOpen(true)}
+ className="h-14 w-14 rounded-full bg-gradient-to-r from-orange-500 to-red-500 hover:from-orange-600 hover:to-red-600 shadow-lg"
+ >
+
+
+
+ {/* Pulsing indicator */}
+
+
+
+ {/* Chat Modal */}
+
+ {isOpen && (
+ setIsOpen(false)}
+ >
+ e.stopPropagation()}
+ className="w-full max-w-md h-[600px] flex flex-col"
+ >
+
+
+
+
+
+
+
+ Mostar AI
+
+
+ Ubuntu Guide
+
+
+
+ setIsOpen(false)}>
+
+
+
+
+
+ {/* Messages */}
+
+ {messages.map((message) => (
+
+
+
{message.text}
+
+ {message.timestamp.toLocaleTimeString([], {
+ hour: "2-digit",
+ minute: "2-digit",
+ })}
+
+
+
+ ))}
+
+ {isTyping && (
+
+ )}
+
+
+
+
+ {/* Input */}
+
+
+ setInputValue(e.target.value)}
+ onKeyPress={handleKeyPress}
+ placeholder="Ask about Ubuntu, FlameBorn, or African wisdom..."
+ className="flex-1"
+ disabled={isTyping}
+ />
+
+
+
+
+
+
+
+
+
+ )}
+
+ >
+ )
+}
diff --git a/components/ai-fallback.tsx b/components/ai-fallback.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..e63ef0f26082d32e54d8593cf77ef03bcf0aed2d
--- /dev/null
+++ b/components/ai-fallback.tsx
@@ -0,0 +1,103 @@
+"use client"
+
+import type React from "react"
+
+import { useState } from "react"
+import { Button } from "@/components/ui/button"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Textarea } from "@/components/ui/textarea"
+import { motion, AnimatePresence } from "framer-motion"
+import { MessageSquare, AlertTriangle } from "lucide-react"
+
+// Predefined responses for common questions
+const FALLBACK_RESPONSES = {
+ default: "I'm here to help with information about Flameborn. Please ask me a question.",
+}
+
+export function AIFallback() {
+ const [isOpen, setIsOpen] = useState(false)
+ const [query, setQuery] = useState("")
+ const [conversation, setConversation] = useState<{ role: string; content: string }[]>([
+ { role: "assistant", content: FALLBACK_RESPONSES.default },
+ ])
+
+ const handleSubmit = (e: React.FormEvent) => {
+ e.preventDefault()
+ if (!query.trim()) return
+
+ // Add user message to conversation
+ setConversation((prev) => [...prev, { role: "user", content: query }])
+
+ // Generate a simple response
+ const response =
+ "Thank you for your question. This is a placeholder response as the AI service is currently in development."
+
+ // Add assistant response
+ setConversation((prev) => [...prev, { role: "assistant", content: response }])
+
+ // Clear input
+ setQuery("")
+ }
+
+ return (
+ <>
+ setIsOpen((prev) => !prev)}
+ className="flame-button fixed bottom-4 right-4 rounded-full p-4 z-50"
+ aria-label="Open AI Assistant"
+ >
+
+
+
+
+ {isOpen && (
+
+
+
+
+
+ Flame Assistant
+
+
+
+
AI services are currently limited. Using basic responses.
+
+
+
+
+ {conversation.map((message, index) => (
+
+ {message.content}
+
+ ))}
+
+
+
+
+
+
+ )}
+
+ >
+ )
+}
diff --git a/components/api-fallback.tsx b/components/api-fallback.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..78dcfdd57c7b88c39ec1b87d8cc8350b931a7498
--- /dev/null
+++ b/components/api-fallback.tsx
@@ -0,0 +1,25 @@
+"use client"
+
+import { AlertTriangle } from "lucide-react"
+import { Button } from "@/components/ui/button"
+import Link from "next/link"
+
+export function APIFallback({ message, retry }: { message: string; retry?: () => void }) {
+ return (
+
+
+
Connection Issue
+
{message}
+
+ {retry && (
+
+ Try Again
+
+ )}
+
+ Return Home
+
+
+
+ )
+}
diff --git a/components/app-header.tsx b/components/app-header.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..32c51a69d0f987a4e9da363df93212726342c6be
--- /dev/null
+++ b/components/app-header.tsx
@@ -0,0 +1,159 @@
+"use client"
+
+import { useState } from "react"
+import Link from "next/link"
+import { usePathname } from "next/navigation"
+import { Flame, Menu, X, Wallet, Bell, User, Settings, LogOut } from "lucide-react"
+import { Button } from "@/components/ui/button"
+import { Badge } from "@/components/ui/badge"
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu"
+import { useIsMobile } from "@/hooks/use-mobile"
+
+const AppHeader = () => {
+ const [isMenuOpen, setIsMenuOpen] = useState(false)
+ const [isConnected, setIsConnected] = useState(false)
+ const pathname = usePathname()
+ const isMobile = useIsMobile()
+
+ const navigation = [
+ { name: "Home", href: "/", current: pathname === "/" },
+ { name: "Launch", href: "/launch", current: pathname === "/launch" },
+ { name: "Analytics", href: "/analytics", current: pathname === "/analytics" },
+ { name: "Community", href: "/community-pulse", current: pathname === "/community-pulse" },
+ { name: "Healers", href: "/healers", current: pathname === "/healers" },
+ { name: "Learn & Earn", href: "/learn-earn", current: pathname === "/learn-earn" },
+ ]
+
+ const handleConnectWallet = () => {
+ // Simulate wallet connection
+ setIsConnected(!isConnected)
+ }
+
+ return (
+
+ )
+}
+
+export default AppHeader
diff --git a/components/audio-heartbeat.tsx b/components/audio-heartbeat.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..9d070bfd26a4aa33f5431db017187fa7ba298c22
--- /dev/null
+++ b/components/audio-heartbeat.tsx
@@ -0,0 +1,130 @@
+"use client"
+
+import { useEffect, useRef, useState } from "react"
+import { Volume2, VolumeX } from "lucide-react"
+
+export function AudioHeartbeat() {
+ const audioContextRef = useRef(null)
+ const oscillatorRef = useRef(null)
+ const gainNodeRef = useRef(null)
+ const [isPlaying, setIsPlaying] = useState(false)
+ const [audioSupported, setAudioSupported] = useState(true)
+
+ // Initialize audio context
+ useEffect(() => {
+ try {
+ // Check if AudioContext is supported
+ if (typeof window !== "undefined" && window.AudioContext) {
+ audioContextRef.current = new AudioContext()
+ setAudioSupported(true)
+ } else {
+ console.warn("Web Audio API is not supported in this browser")
+ setAudioSupported(false)
+ }
+ } catch (error) {
+ console.error("Error initializing audio context:", error)
+ setAudioSupported(false)
+ }
+
+ // Cleanup
+ return () => {
+ stopHeartbeat()
+ if (audioContextRef.current && audioContextRef.current.state !== "closed") {
+ audioContextRef.current.close().catch(console.error)
+ }
+ }
+ }, [])
+
+ const startHeartbeat = () => {
+ if (!audioContextRef.current || !audioSupported) return
+
+ try {
+ // Create oscillator for the heartbeat sound
+ oscillatorRef.current = audioContextRef.current.createOscillator()
+ oscillatorRef.current.type = "sine"
+ oscillatorRef.current.frequency.value = 2 // Very low frequency for heartbeat rhythm
+
+ // Create gain node for volume control
+ gainNodeRef.current = audioContextRef.current.createGain()
+ gainNodeRef.current.gain.value = 0.3
+
+ // Connect nodes
+ oscillatorRef.current.connect(gainNodeRef.current)
+ gainNodeRef.current.connect(audioContextRef.current.destination)
+
+ // Start oscillator
+ oscillatorRef.current.start()
+
+ // Create heartbeat effect with gain automation
+ const now = audioContextRef.current.currentTime
+ const heartbeatInterval = 1.2 // seconds between heartbeats
+
+ // Schedule heartbeats
+ for (let i = 0; i < 100; i++) {
+ // Schedule many heartbeats ahead
+ const beatTime = now + i * heartbeatInterval
+
+ // First beat
+ gainNodeRef.current.gain.setValueAtTime(0.01, beatTime)
+ gainNodeRef.current.gain.exponentialRampToValueAtTime(0.3, beatTime + 0.1)
+ gainNodeRef.current.gain.exponentialRampToValueAtTime(0.01, beatTime + 0.3)
+
+ // Second beat (slightly softer)
+ gainNodeRef.current.gain.setValueAtTime(0.01, beatTime + 0.4)
+ gainNodeRef.current.gain.exponentialRampToValueAtTime(0.2, beatTime + 0.5)
+ gainNodeRef.current.gain.exponentialRampToValueAtTime(0.01, beatTime + 0.7)
+ }
+
+ setIsPlaying(true)
+ } catch (error) {
+ console.error("Error starting heartbeat:", error)
+ setAudioSupported(false)
+ }
+ }
+
+ const stopHeartbeat = () => {
+ try {
+ if (oscillatorRef.current) {
+ oscillatorRef.current.stop()
+ oscillatorRef.current.disconnect()
+ oscillatorRef.current = null
+ }
+
+ if (gainNodeRef.current) {
+ gainNodeRef.current.disconnect()
+ gainNodeRef.current = null
+ }
+
+ setIsPlaying(false)
+ } catch (error) {
+ console.error("Error stopping heartbeat:", error)
+ }
+ }
+
+ const toggleAudio = () => {
+ if (!audioSupported) return
+
+ if (isPlaying) {
+ stopHeartbeat()
+ } else {
+ // Resume audio context if it's suspended (needed for browsers that require user interaction)
+ if (audioContextRef.current && audioContextRef.current.state === "suspended") {
+ audioContextRef.current.resume().catch(console.error)
+ }
+ startHeartbeat()
+ }
+ }
+
+ // Don't render the button if audio is not supported
+ if (!audioSupported) return null
+
+ return (
+
+ {isPlaying ? : }
+
+ )
+}
diff --git a/components/baby-nft/baby-nft-dashboard.tsx b/components/baby-nft/baby-nft-dashboard.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..7dbf4cdf6b2aadd7551430283e221a47dc7771fe
--- /dev/null
+++ b/components/baby-nft/baby-nft-dashboard.tsx
@@ -0,0 +1,489 @@
+"use client"
+
+import type React from "react"
+import { useState, useEffect } from "react"
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
+import { Button } from "@/components/ui/button"
+import { Badge } from "@/components/ui/badge"
+import { Input } from "@/components/ui/input"
+import { Label } from "@/components/ui/label"
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
+import { Progress } from "@/components/ui/progress"
+import { Heart, Baby, Shield, CheckCircle, Clock, Users, Activity } from "lucide-react"
+import { createBabyNFT } from "@/lib/contract-utils"
+import { useToast } from "@/hooks/use-toast"
+
+interface BabyNFT {
+ tokenId: string
+ mother: string
+ facility: string
+ babyName: string
+ birthTimestamp: number
+ lastHeartbeat: number
+ postnatalConfirmed: boolean
+ oneYearAlive: boolean
+ isAlive: boolean
+ daysAlive: number
+ status: "newborn" | "postnatal" | "milestone" | "at_risk"
+}
+
+interface HealthMilestone {
+ id: string
+ name: string
+ description: string
+ targetDays: number
+ completed: boolean
+ flbReward: number
+ requirements: string[]
+}
+
+export const BabyNFTDashboard: React.FC = () => {
+ const [babies, setBabies] = useState([])
+ const [selectedBaby, setSelectedBaby] = useState(null)
+ const [loading, setLoading] = useState(false)
+ const [newBabyForm, setNewBabyForm] = useState({
+ motherAddress: "",
+ facilityAddress: "",
+ babyName: "",
+ })
+ const { toast } = useToast()
+
+ const healthMilestones: HealthMilestone[] = [
+ {
+ id: "birth",
+ name: "Birth Verification",
+ description: "Live birth confirmed with biometric data",
+ targetDays: 0,
+ completed: true,
+ flbReward: 100,
+ requirements: ["Facility verification", "Mother consent", "Biometric hash"],
+ },
+ {
+ id: "postnatal",
+ name: "6-Week Postnatal",
+ description: "Mother and baby health checkup",
+ targetDays: 42,
+ completed: false,
+ flbReward: 150,
+ requirements: ["Guardian verification", "Health assessment", "Growth tracking"],
+ },
+ {
+ id: "one_year",
+ name: "One Year Milestone",
+ description: "Baby survives first year of life",
+ targetDays: 365,
+ completed: false,
+ flbReward: 500,
+ requirements: ["Continuous heartbeat monitoring", "Growth milestones", "Vaccination records"],
+ },
+ ]
+
+ useEffect(() => {
+ loadBabyNFTs()
+ }, [])
+
+ const loadBabyNFTs = async () => {
+ try {
+ // Mock data for demonstration - replace with actual contract calls
+ const mockBabies: BabyNFT[] = [
+ {
+ tokenId: "1",
+ mother: "0x1234...5678",
+ facility: "0xabcd...efgh",
+ babyName: "Ọmọtúndé",
+ birthTimestamp: Date.now() - 30 * 24 * 60 * 60 * 1000, // 30 days ago
+ lastHeartbeat: Date.now() - 60 * 60 * 1000, // 1 hour ago
+ postnatalConfirmed: false,
+ oneYearAlive: false,
+ isAlive: true,
+ daysAlive: 30,
+ status: "newborn",
+ },
+ {
+ tokenId: "2",
+ mother: "0x2345...6789",
+ facility: "0xbcde...fghi",
+ babyName: "Amara",
+ birthTimestamp: Date.now() - 60 * 24 * 60 * 60 * 1000, // 60 days ago
+ lastHeartbeat: Date.now() - 30 * 60 * 1000, // 30 minutes ago
+ postnatalConfirmed: true,
+ oneYearAlive: false,
+ isAlive: true,
+ daysAlive: 60,
+ status: "postnatal",
+ },
+ ]
+ setBabies(mockBabies)
+ if (mockBabies.length > 0) setSelectedBaby(mockBabies[0])
+ } catch (error) {
+ console.error("Error loading BabyNFTs:", error)
+ }
+ }
+
+ const handleMintBaby = async () => {
+ if (!newBabyForm.motherAddress || !newBabyForm.facilityAddress || !newBabyForm.babyName) {
+ toast({
+ title: "Missing Information",
+ description: "Please fill in all required fields",
+ variant: "destructive",
+ })
+ return
+ }
+
+ setLoading(true)
+ try {
+ const tokenId = await createBabyNFT(newBabyForm.motherAddress, newBabyForm.facilityAddress, newBabyForm.babyName)
+
+ if (tokenId) {
+ toast({
+ title: "BabyNFT Created Successfully!",
+ description: `${newBabyForm.babyName} has been minted as Token ID: ${tokenId}`,
+ })
+ setNewBabyForm({ motherAddress: "", facilityAddress: "", babyName: "" })
+ loadBabyNFTs()
+ }
+ } catch (error: any) {
+ toast({
+ title: "Minting Failed",
+ description: error.message || "Failed to mint BabyNFT",
+ variant: "destructive",
+ })
+ }
+ setLoading(false)
+ }
+
+ const getStatusColor = (status: string) => {
+ switch (status) {
+ case "newborn":
+ return "bg-blue-500"
+ case "postnatal":
+ return "bg-green-500"
+ case "milestone":
+ return "bg-purple-500"
+ case "at_risk":
+ return "bg-red-500"
+ default:
+ return "bg-gray-500"
+ }
+ }
+
+ const getHeartbeatStatus = (lastHeartbeat: number) => {
+ const hoursAgo = (Date.now() - lastHeartbeat) / (1000 * 60 * 60)
+ if (hoursAgo < 1) return { status: "healthy", color: "text-green-500", text: "Active" }
+ if (hoursAgo < 6) return { status: "normal", color: "text-yellow-500", text: "Normal" }
+ return { status: "at_risk", color: "text-red-500", text: "At Risk" }
+ }
+
+ return (
+
+ {/* Header */}
+
+
BabyNFT Lifecycle Management
+
+ Automated health milestone verification ensuring every life is preserved and rewarded.
+
+
+
+ {/* Stats */}
+
+
+
+ Total Babies
+
+
+
+ {babies.length}
+ Lives preserved
+
+
+
+
+
+ Alive & Healthy
+
+
+
+ {babies.filter((b) => b.isAlive).length}
+ Active heartbeats
+
+
+
+
+
+ Postnatal Complete
+
+
+
+
+ {babies.filter((b) => b.postnatalConfirmed).length}
+
+ 6-week milestone
+
+
+
+
+
+ One Year Survivors
+
+
+
+ {babies.filter((b) => b.oneYearAlive).length}
+ Major milestone
+
+
+
+
+ {/* Main Interface */}
+
+
+
+ Overview
+
+
+ Milestones
+
+
+ Mint New
+
+
+
+
+
+ {/* Baby List */}
+
+
+
+
+ Active BabyNFTs
+
+
+
+ {babies.map((baby) => {
+ const heartbeatStatus = getHeartbeatStatus(baby.lastHeartbeat)
+ return (
+ setSelectedBaby(baby)}
+ >
+
+
{baby.babyName}
+
+ {baby.status.replace("_", " ")}
+
+
+
+
Token ID: #{baby.tokenId}
+
Days Alive: {baby.daysAlive}
+
+
+ {heartbeatStatus.text}
+
+
+
+ )
+ })}
+
+
+
+ {/* Baby Details */}
+ {selectedBaby && (
+
+
+
+
+ {selectedBaby.babyName}
+
+ Token ID: #{selectedBaby.tokenId}
+
+
+
+
+
Mother
+
{selectedBaby.mother}
+
+
+
Facility
+
{selectedBaby.facility}
+
+
+
Birth Date
+
+ {new Date(selectedBaby.birthTimestamp).toLocaleDateString()}
+
+
+
+
Days Alive
+
{selectedBaby.daysAlive} days
+
+
+
+
+
Milestone Progress
+
+
+ Birth → Postnatal (42 days)
+ {Math.min(selectedBaby.daysAlive, 42)}/42
+
+
+
+
+ Postnatal → One Year (365 days)
+ {Math.min(selectedBaby.daysAlive, 365)}/365
+
+
+
+
+
+
+
+ Record Heartbeat
+
+ {selectedBaby.daysAlive >= 42 && !selectedBaby.postnatalConfirmed && (
+
+ Confirm Postnatal
+
+ )}
+
+
+
+ )}
+
+
+
+
+
+ {healthMilestones.map((milestone) => (
+
+
+
+
+ {milestone.completed ? (
+
+ ) : (
+
+ )}
+ {milestone.name}
+
+
+ {milestone.flbReward} FLB
+
+
+ {milestone.description}
+
+
+
+
+ Target: {" "}
+ {milestone.targetDays === 0 ? "Immediate" : `${milestone.targetDays} days`}
+
+
+
Requirements:
+
+ {milestone.requirements.map((req, idx) => (
+
+
+ {req}
+
+ ))}
+
+
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+ Mint New BabyNFT
+
+
+ Create a new BabyNFT to track a baby's health journey from birth.
+
+
+
+
+
+
+ {loading ? "Minting..." : "Mint BabyNFT"}
+
+
+
+
What happens next?
+
+ • 100 FLB rewarded immediately upon minting
+ • IoT devices begin heartbeat monitoring
+ • 6-week postnatal milestone tracking starts
+ • Mother gains escrow control via *789# USSD
+
+
+
+
+
+
+
+ )
+}
diff --git a/components/community-bubbles.tsx b/components/community-bubbles.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..af47c254b1a7ae5c1a40bd72a1c7cb842c96b5e9
--- /dev/null
+++ b/components/community-bubbles.tsx
@@ -0,0 +1,337 @@
+"use client"
+
+import type React from "react"
+
+import { useEffect, useState } from "react"
+import { motion } from "framer-motion"
+import { Heart, Shield, Users, Globe, Stethoscope, HandHeart, Award, TrendingUp } from "lucide-react"
+
+interface CommunityStats {
+ healers: {
+ total: number
+ verified: number
+ pending: number
+ specializations: {
+ nurses: number
+ doctors: number
+ midwives: number
+ pharmacists: number
+ }
+ }
+ guardians: {
+ total: number
+ active: number
+ totalContributions: number
+ }
+ regions: {
+ westAfrica: number
+ eastAfrica: number
+ southernAfrica: number
+ northAfrica: number
+ centralAfrica: number
+ }
+ impact: {
+ totalPatientsServed: number
+ communitiesReached: number
+ donationsReceived: number
+ }
+ growth: {
+ thisMonth: number
+ thisWeek: number
+ }
+ lastUpdated: string
+}
+
+interface BubbleData {
+ id: string
+ label: string
+ count: number
+ icon: React.ElementType
+ color: string
+ gradient: string
+ size: "small" | "medium" | "large"
+ position: { x: number; y: number }
+ delay: number
+}
+
+export function CommunityBubbles() {
+ const [stats, setStats] = useState(null)
+ const [loading, setLoading] = useState(true)
+ const [bubbles, setBubbles] = useState([])
+
+ useEffect(() => {
+ const fetchStats = async () => {
+ try {
+ const response = await fetch("/api/community-stats")
+ if (response.ok) {
+ const data = await response.json()
+ setStats(data)
+ generateBubbles(data)
+ }
+ } catch (error) {
+ console.error("Error fetching community stats:", error)
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ fetchStats()
+ const interval = setInterval(fetchStats, 30000) // Update every 30 seconds
+
+ return () => clearInterval(interval)
+ }, [])
+
+ const generateBubbles = (stats: CommunityStats) => {
+ const bubbleConfigs: Omit[] = [
+ {
+ id: "healers",
+ label: "Healers",
+ count: stats.healers.total,
+ icon: Stethoscope,
+ color: "#00C853",
+ gradient: "from-green-400 to-emerald-600",
+ size: "large",
+ },
+ {
+ id: "verified-healers",
+ label: "Verified",
+ count: stats.healers.verified,
+ icon: Award,
+ color: "#FFD600",
+ gradient: "from-yellow-400 to-amber-600",
+ size: "medium",
+ },
+ {
+ id: "guardians",
+ label: "Guardians",
+ count: stats.guardians.total,
+ icon: Shield,
+ color: "#1E88E5",
+ gradient: "from-blue-400 to-indigo-600",
+ size: "large",
+ },
+ {
+ id: "active-guardians",
+ label: "Active",
+ count: stats.guardians.active,
+ icon: HandHeart,
+ color: "#FF6B00",
+ gradient: "from-orange-400 to-red-500",
+ size: "medium",
+ },
+ {
+ id: "nurses",
+ label: "Nurses",
+ count: stats.healers.specializations.nurses,
+ icon: Heart,
+ color: "#E91E63",
+ gradient: "from-pink-400 to-rose-600",
+ size: "medium",
+ },
+ {
+ id: "doctors",
+ label: "Doctors",
+ count: stats.healers.specializations.doctors,
+ icon: Users,
+ color: "#9C27B0",
+ gradient: "from-purple-400 to-violet-600",
+ size: "medium",
+ },
+ {
+ id: "west-africa",
+ label: "West Africa",
+ count: stats.regions.westAfrica,
+ icon: Globe,
+ color: "#FF9800",
+ gradient: "from-amber-400 to-orange-600",
+ size: "small",
+ },
+ {
+ id: "east-africa",
+ label: "East Africa",
+ count: stats.regions.eastAfrica,
+ icon: Globe,
+ color: "#4CAF50",
+ gradient: "from-green-400 to-teal-600",
+ size: "small",
+ },
+ {
+ id: "southern-africa",
+ label: "Southern Africa",
+ count: stats.regions.southernAfrica,
+ icon: Globe,
+ color: "#2196F3",
+ gradient: "from-blue-400 to-cyan-600",
+ size: "small",
+ },
+ {
+ id: "growth-month",
+ label: "New This Month",
+ count: stats.growth.thisMonth,
+ icon: TrendingUp,
+ color: "#00BCD4",
+ gradient: "from-cyan-400 to-teal-600",
+ size: "small",
+ },
+ ]
+
+ // Generate random positions for bubbles
+ const generatedBubbles = bubbleConfigs.map((config, index) => ({
+ ...config,
+ position: {
+ x: Math.random() * 80 + 10, // 10% to 90% of container width
+ y: Math.random() * 70 + 15, // 15% to 85% of container height
+ },
+ delay: index * 0.2,
+ }))
+
+ setBubbles(generatedBubbles)
+ }
+
+ const getBubbleSize = (size: "small" | "medium" | "large") => {
+ switch (size) {
+ case "small":
+ return "w-16 h-16 md:w-20 md:h-20"
+ case "medium":
+ return "w-20 h-20 md:w-24 md:h-24"
+ case "large":
+ return "w-24 h-24 md:w-28 md:h-28"
+ }
+ }
+
+ const getTextSize = (size: "small" | "medium" | "large") => {
+ switch (size) {
+ case "small":
+ return "text-xs"
+ case "medium":
+ return "text-sm"
+ case "large":
+ return "text-base"
+ }
+ }
+
+ if (loading) {
+ return (
+
+ )
+ }
+
+ return (
+
+ {/* Background pattern */}
+
+
+ {/* Title */}
+
+
Flameborn Community
+
The Soul & Codex of Everyone
+
+
+ {/* Animated bubbles */}
+ {bubbles.map((bubble) => {
+ const Icon = bubble.icon
+ return (
+
+ {/* Bubble container */}
+
+ {/* Glow effect */}
+
+
+ {/* Icon */}
+
+
+ {/* Count */}
+
+ {bubble.count.toLocaleString()}
+
+
+ {/* Label */}
+ {bubble.label}
+
+ {/* Pulse effect for new additions */}
+ {bubble.id === "growth-month" && bubble.count > 0 && (
+
+ )}
+
+
+ {/* Tooltip on hover */}
+
+ {bubble.label}: {bubble.count.toLocaleString()}
+
+
+ )
+ })}
+
+ {/* Stats summary in corner */}
+ {stats && (
+
+
+ Total Community:{" "}
+
+ {(stats.healers.total + stats.guardians.total).toLocaleString()}
+
+
+
Updated: {new Date(stats.lastUpdated).toLocaleTimeString()}
+
+ )}
+
+ )
+}
diff --git a/components/connect-wallet-button.tsx b/components/connect-wallet-button.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..de5e223eef216d8e3f42278b60527c5590cf3105
--- /dev/null
+++ b/components/connect-wallet-button.tsx
@@ -0,0 +1,186 @@
+"use client"
+
+import { useState } from "react"
+import { Button } from "@/components/ui/button"
+import { Badge } from "@/components/ui/badge"
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu"
+import { Wallet, Copy, ExternalLink, LogOut, Coins, AlertCircle } from "lucide-react"
+import { useWallet } from "@/providers/wallet-provider"
+import { NetworkSwitcher } from "./network-switcher"
+import { toast } from "@/hooks/use-toast"
+
+export function ConnectWalletButton() {
+ const { connected, address, balance, chainId, isConnecting, error, connectWallet, disconnect, requestFLBTokens } =
+ useWallet()
+ const [requesting, setRequesting] = useState(false)
+
+ const handleConnect = async () => {
+ try {
+ const success = await connectWallet()
+ if (success) {
+ toast({
+ title: "Wallet Connected",
+ description: "Successfully connected to your Celo wallet",
+ })
+ }
+ } catch (error) {
+ toast({
+ title: "Connection Failed",
+ description: "Failed to connect wallet. Please try again.",
+ variant: "destructive",
+ })
+ }
+ }
+
+ const handleDisconnect = () => {
+ disconnect()
+ toast({
+ title: "Wallet Disconnected",
+ description: "Your wallet has been disconnected",
+ })
+ }
+
+ const handleCopyAddress = () => {
+ if (address) {
+ navigator.clipboard.writeText(address)
+ toast({
+ title: "Address Copied",
+ description: "Wallet address copied to clipboard",
+ })
+ }
+ }
+
+ const handleRequestTokens = async () => {
+ setRequesting(true)
+ try {
+ await requestFLBTokens("100")
+ toast({
+ title: "Tokens Requested",
+ description: "FLB tokens have been requested. Please check your balance.",
+ })
+ } catch (error: any) {
+ toast({
+ title: "Request Failed",
+ description: error.message || "Failed to request FLB tokens. Please contact the team.",
+ variant: "destructive",
+ })
+ } finally {
+ setRequesting(false)
+ }
+ }
+
+ const openBlockExplorer = () => {
+ if (address) {
+ const explorerUrl =
+ chainId === 42220
+ ? `https://celoscan.io/address/${address}`
+ : `https://alfajores.celoscan.io/address/${address}`
+ window.open(explorerUrl, "_blank")
+ }
+ }
+
+ const formatAddress = (addr: string) => {
+ return `${addr.slice(0, 6)}...${addr.slice(-4)}`
+ }
+
+ const formatBalance = (bal: string) => {
+ const num = Number.parseFloat(bal)
+ return num > 0 ? num.toFixed(4) : "0"
+ }
+
+ const isCeloNetwork = chainId === 42220 || chainId === 44787
+
+ if (!connected) {
+ return (
+
+
+
+ {isConnecting ? "Connecting..." : "Connect Wallet"}
+
+
+ {error && (
+
+ )}
+
+ )
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
{address ? formatAddress(address) : "Connected"}
+
+ {formatBalance(balance.celo)} CELO
+ •
+ {formatBalance(balance.flb)} FLB
+
+
+
+
+
+
+
+
+ Wallet Address
+
+
+
+
+
{address}
+
+
+
+
+
+
+
+ CELO Balance:
+ {formatBalance(balance.celo)} CELO
+
+
+ FLB Balance:
+ {formatBalance(balance.flb)} FLB
+
+
+
+
+
+
+ {isCeloNetwork && (
+
+
+ {requesting ? "Requesting..." : "Request Test FLB"}
+
+ )}
+
+
+
+ View on Explorer
+
+
+
+
+
+
+ Disconnect
+
+
+
+
+ )
+}
diff --git a/components/contract-status.tsx b/components/contract-status.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..c689557c6c13078b506e9cdd3b481c7e9af2e9a6
--- /dev/null
+++ b/components/contract-status.tsx
@@ -0,0 +1,141 @@
+"use client"
+
+import { useEffect, useState } from "react"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { fetchContractConfig } from "@/lib/config"
+import { getFLBTokenContract, getHealthRegistryContract, getDonationRouterContract } from "@/lib/contract-utils"
+
+type ContractStatus = {
+ name: string
+ address: string
+ connected: boolean
+ error?: string
+}
+
+export function ContractStatus() {
+ const [contracts, setContracts] = useState([
+ { name: "FLB Token", address: "", connected: false },
+ { name: "Health Registry", address: "", connected: false },
+ { name: "Donation Router", address: "", connected: false },
+ ])
+ const [isLoading, setIsLoading] = useState(true)
+ const [chainId, setChainId] = useState("56")
+
+ useEffect(() => {
+ const checkContracts = async () => {
+ try {
+ // Fetch contract addresses from server
+ const config = await fetchContractConfig()
+ setChainId(config.chainId)
+
+ // Check FLB Token
+ const flbContract = await getFLBTokenContract()
+ let flbConnected = false
+ let flbError = ""
+
+ try {
+ if (flbContract) {
+ const name = await flbContract.name()
+ flbConnected = !!name
+ }
+ } catch (error: any) {
+ flbError = error.message
+ }
+
+ // Check Health Registry
+ const registryContract = await getHealthRegistryContract()
+ let registryConnected = false
+ let registryError = ""
+
+ try {
+ if (registryContract) {
+ // Just try to call a view function to see if it's connected
+ await registryContract.isVerifiedHealthActor("0x0000000000000000000000000000000000000000")
+ registryConnected = true
+ }
+ } catch (error: any) {
+ registryError = error.message
+ }
+
+ // Check Donation Router
+ const routerContract = await getDonationRouterContract()
+ let routerConnected = false
+ let routerError = ""
+
+ try {
+ if (routerContract) {
+ // Just try to access a public variable
+ await routerContract.flbToken()
+ routerConnected = true
+ }
+ } catch (error: any) {
+ routerError = error.message
+ }
+
+ setContracts([
+ {
+ name: "FLB Token",
+ address: config.tokenContract,
+ connected: flbConnected,
+ error: flbError,
+ },
+ {
+ name: "Health Registry",
+ address: config.healthRegistry,
+ connected: registryConnected,
+ error: registryError,
+ },
+ {
+ name: "Donation Router",
+ address: config.donationRouter,
+ connected: routerConnected,
+ error: routerError,
+ },
+ ])
+ } catch (error) {
+ console.error("Error checking contracts:", error)
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ checkContracts()
+ }, [])
+
+ return (
+
+
+ Contract Status
+
+
+ {isLoading ? (
+ Checking contract connectivity...
+ ) : (
+
+ {contracts.map((contract) => (
+
+
+ {contract.name}
+
+ {contract.connected ? "Connected" : "Not Connected"}
+
+
+
{contract.address}
+ {!contract.connected && contract.error && (
+
{contract.error.substring(0, 100)}...
+ )}
+
+ ))}
+
+
Chain ID: {chainId} (BNB Smart Chain)
+
+
+ )}
+
+
+ )
+}
diff --git a/components/donation-distribution-chart.tsx b/components/donation-distribution-chart.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..454cf822ca2798c8646d4ee1a2730995716c15fd
--- /dev/null
+++ b/components/donation-distribution-chart.tsx
@@ -0,0 +1,227 @@
+"use client"
+
+import { useState, useEffect } from "react"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"
+import { Badge } from "@/components/ui/badge"
+import { Loader2, PieChart, BarChart, Users } from "lucide-react"
+// Assuming HealthcareWorker type is defined in a central types file
+import type { HealthcareWorker } from "@/lib/types/healthcare-worker"
+
+interface DonationDistributionChartProps {
+ workers: HealthcareWorker[]
+ isLoading?: boolean
+}
+
+export function DonationDistributionChart({ workers, isLoading = false }: DonationDistributionChartProps) {
+ const [chartType, setChartType] = useState<"pie" | "bar">("pie")
+ const [sortedWorkers, setSortedWorkers] = useState([])
+
+ // Sort workers by distribution percentage
+ useEffect(() => {
+ if (workers) {
+ const sorted = [...workers].sort((a, b) => (b.distributionPercentage || 0) - (a.distributionPercentage || 0))
+ setSortedWorkers(sorted)
+ }
+ }, [workers])
+
+ // Generate random colors for chart segments
+ const getColor = (index: number) => {
+ const colors = [
+ "rgb(59, 130, 246)", // blue-500
+ "rgb(16, 185, 129)", // emerald-500
+ "rgb(239, 68, 68)", // red-500
+ "rgb(245, 158, 11)", // amber-500
+ "rgb(139, 92, 246)", // violet-500
+ "rgb(20, 184, 166)", // teal-500
+ "rgb(236, 72, 153)", // pink-500
+ "rgb(251, 146, 60)", // orange-400
+ "rgb(129, 140, 248)", // indigo-400
+ "rgb(52, 211, 153)", // emerald-400
+ ]
+ return colors[index % colors.length]
+ }
+
+ // Render pie chart segments
+ const renderPieSegments = () => {
+ const total = 100
+ let cumulativePercentage = 0
+
+ return sortedWorkers.map((worker, index) => {
+ const percentage = worker.distributionPercentage || 0
+ if (percentage === 0) return null // Don't render segment for 0%
+
+ const startAngle = (cumulativePercentage / total) * 360
+ cumulativePercentage += percentage
+ const endAngle = (cumulativePercentage / total) * 360
+
+ // Calculate SVG arc path
+ const radius = 80
+ const centerX = 100
+ const centerY = 100
+
+ // Convert angles to radians
+ const startRad = ((startAngle - 90) * Math.PI) / 180
+ const endRad = ((endAngle - 90) * Math.PI) / 180
+
+ // Calculate points
+ const x1 = centerX + radius * Math.cos(startRad)
+ const y1 = centerY + radius * Math.sin(startRad)
+ const x2 = centerX + radius * Math.cos(endRad)
+ const y2 = centerY + radius * Math.sin(endRad)
+
+ // Determine if the arc should be drawn as a large arc
+ const largeArcFlag = endAngle - startAngle >= 180 ? 1 : 0 // Use >= for full circle segment
+
+ // Create the arc path
+ const pathData = [
+ `M ${centerX} ${centerY}`,
+ `L ${x1} ${y1}`,
+ `A ${radius} ${radius} 0 ${largeArcFlag} 1 ${x2} ${y2}`,
+ "Z",
+ ].join(" ")
+
+ return (
+
+
+ {worker.name}: {percentage.toFixed(1)}%
+
+
+ )
+ })
+ }
+
+ // Render bar chart
+ const renderBarChart = () => {
+ const maxBarHeight = 150
+ const barWidth = 30
+ const gap = 15
+ const totalWidth = sortedWorkers.length * (barWidth + gap) - gap
+
+ if (sortedWorkers.length === 0) return No data for bar chart.
+
+ return (
+
+ {/* X-axis */}
+ 0 ? totalWidth : 200}
+ y2={maxBarHeight}
+ stroke="rgba(147, 197, 253, 0.3)"
+ strokeWidth="1"
+ />
+
+ {/* Bars */}
+ {sortedWorkers.map((worker, index) => {
+ const percentage = worker.distributionPercentage || 0
+ const barHeight = (percentage / 100) * maxBarHeight
+ const x = index * (barWidth + gap)
+ const y = maxBarHeight - barHeight
+
+ return (
+
+
+
+ {worker.name}: {percentage.toFixed(1)}%
+
+
+
+ {/* Percentage label */}
+
+ {percentage.toFixed(1)}%
+
+
+ {/* Name label (abbreviated) */}
+
+ {worker.name.split(" ")[1]?.substring(0, 5) || worker.name.split(" ")[0].substring(0, 5)}
+
+
+ )
+ })}
+
+ )
+ }
+
+ return (
+
+
+
+
Donation Distribution
+
setChartType(value as "pie" | "bar")}>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {isLoading ? (
+
+
+ Calculating distribution...
+
+ ) : sortedWorkers.length === 0 ? (
+ No healthcare worker data available for distribution.
+ ) : (
+ <>
+
+
+
+ {renderPieSegments()}
+ {/* Center circle to match dark bg */}
+
+
+
+
+
+ {renderBarChart()}
+
+
+
+
+
+ Healthcare Worker Distribution
+
+
+
+ {sortedWorkers.map((worker, index) => (
+
+
+
+ {worker.name.split(" ")[1]?.substring(0, 10) || worker.name.split(" ")[0].substring(0, 10)}
+
+ {worker.distributionPercentage?.toFixed(1)}%
+
+ ))}
+
+
+ >
+ )}
+
+
+ )
+}
diff --git a/components/donation-feed.tsx b/components/donation-feed.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..cb0d76e493205a7f64bb5e4861a9b819712a529e
--- /dev/null
+++ b/components/donation-feed.tsx
@@ -0,0 +1,162 @@
+// donation-feed-teq3AEqutHJ4rcQm5bWUnf90VqJqsn.tsx
+"use client"
+
+import type React from "react"
+import { useEffect, useState } from "react"
+import { motion, AnimatePresence } from "framer-motion"
+import { Heart, Zap, Gift } from "lucide-react"
+
+// Assuming a websocket hook similar to the one in your plan
+// For now, let's define a placeholder if not available
+// import { useWebSocket } from '@/lib/websocket';
+
+// Placeholder for websocket hook if not yet implemented
+const useWebSocket = (url: string) => {
+ const [data, setData] = useState([])
+ const [error, setError] = useState(null)
+ const [isConnected, setIsConnected] = useState(false)
+
+ useEffect(() => {
+ // This is a mock implementation. Replace with actual WebSocket logic.
+ // console.log(`Mock WebSocket connecting to ${url}`);
+ setIsConnected(true)
+ const mockDonations = [
+ { id: "1", donor: "0xAlice", amount: "10", token: "FLB", timestamp: Date.now() - 10000, type: "direct" },
+ { id: "2", donor: "0xBob", amount: "5", token: "ETH", timestamp: Date.now() - 20000, type: "genesis" },
+ { id: "3", donor: "0xCharlie", amount: "25", token: "FLB", timestamp: Date.now() - 30000, type: "impact" },
+ ]
+ // Simulate receiving data
+ let intervalId: NodeJS.Timeout
+ if (url.includes("/api/ws/donations")) {
+ // Only mock for donations feed
+ setData(mockDonations)
+ intervalId = setInterval(() => {
+ const newDonation = {
+ id: Math.random().toString(36).substring(7),
+ donor: `0xUser${Math.floor(Math.random() * 1000)}`,
+ amount: (Math.random() * 50).toFixed(2),
+ token: Math.random() > 0.5 ? "FLB" : "ETH",
+ timestamp: Date.now(),
+ type: ["direct", "genesis", "impact"][Math.floor(Math.random() * 3)] as "direct" | "genesis" | "impact",
+ }
+ setData((prevData) => [newDonation, ...prevData.slice(0, 9)]) // Keep last 10
+ }, 5000)
+ }
+
+ return () => {
+ // console.log(`Mock WebSocket disconnecting from ${url}`);
+ setIsConnected(false)
+ if (intervalId) clearInterval(intervalId)
+ }
+ }, [url])
+
+ return { data, error, isConnected }
+}
+
+interface Donation {
+ id: string
+ donor: string // Wallet address
+ amount: string
+ token: string // e.g., FLB, ETH
+ timestamp: number
+ type?: "direct" | "genesis" | "impact" // Optional: to style differently
+ beneficiary?: string // Optional: if direct to a specific healer
+ message?: string // Optional: donor's message
+}
+
+const DonationCard: React.FC = ({
+ donor,
+ amount,
+ token,
+ timestamp,
+ type = "direct",
+ beneficiary,
+ message,
+}) => {
+ const timeAgo = new Date(timestamp).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })
+
+ const getIcon = () => {
+ switch (type) {
+ case "genesis":
+ return
+ case "impact":
+ return
+ case "direct":
+ default:
+ return
+ }
+ }
+
+ return (
+
+
+
+ {getIcon()}
+
+
+
+ {donor.slice(0, 6)}...{donor.slice(-4)}
+
+ donated
+
+ {amount} {token}
+
+
+ {beneficiary && (
+
+ To:{" "}
+
+ {beneficiary.slice(0, 6)}...{beneficiary.slice(-4)}
+
+
+ )}
+
+
+
{timeAgo}
+
+ {message && "{message}"
}
+
+ )
+}
+
+export default function DonationFeed({ maxItems = 10 }: { maxItems?: number }) {
+ // Assuming your websocket hook is in '@/lib/websocket'
+ // If not, you'll need to implement or import it.
+ // For now, using the placeholder defined above.
+ const {
+ data: donations,
+ error,
+ isConnected,
+ } = useWebSocket(
+ process.env.NEXT_PUBLIC_WS_URL ? `${process.env.NEXT_PUBLIC_WS_URL}/api/ws/donations` : "/api/ws/donations-mock",
+ )
+
+ if (error) {
+ return Error loading donation feed: {error.message}
+ }
+
+ if (!isConnected && !donations.length) {
+ return Connecting to donation feed...
+ }
+
+ if (donations.length === 0) {
+ return No recent donations. Be the first to light a flame!
+ }
+
+ return (
+
+
+ {donations.slice(0, maxItems).map((donation: Donation) => (
+
+ ))}
+
+
+ )
+}
diff --git a/components/donation-form.tsx b/components/donation-form.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..d6f3fa85060fff1ab96f0dd40080c9fcfdbabcbf
--- /dev/null
+++ b/components/donation-form.tsx
@@ -0,0 +1,367 @@
+"use client"
+
+import type React from "react" // Ensure React is imported if JSX is used
+
+import { useState, useEffect } from "react"
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Textarea } from "@/components/ui/textarea"
+import { Heart, ExternalLink, AlertCircle, CheckCircle, Loader2 } from "lucide-react"
+import { useWalletContext } from "@/providers/wallet-provider"
+// Assuming donate function is correctly defined in contract-service
+import { donate } from "@/lib/contract-service"
+import { MIN_DONATION_AMOUNT, NETWORKS, TransactionStatus } from "@/lib/constants"
+import { ethers } from "ethers"
+import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
+import { Card, CardContent } from "@/components/ui/card"
+// Assuming HealthcareWorker type is defined
+import type { HealthcareWorker } from "@/lib/types/healthcare-worker"
+
+// Mock healthcare workers data - in production, this would come from your backend or contract
+const HEALTHCARE_WORKERS: HealthcareWorker[] = [
+ {
+ id: "1", // Added id for key prop
+ address: "0xf39Fd6e51aad88F6F4ce6aB8829539c652746fF0",
+ name: "Dr. Alice Smith",
+ credentials: "Cardiologist, Board Certified",
+ isVerified: true,
+ // Add other required fields from HealthcareWorker type if necessary
+ location: { name: "City Hospital", countryCode: "US" },
+ profileImage: "/placeholder.svg?width=40&height=40",
+ impactScore: 85,
+ distributionPercentage: 30,
+ },
+ {
+ id: "2",
+ address: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
+ name: "Dr. Bob Johnson",
+ credentials: "General Practitioner, Rural Health Specialist",
+ isVerified: true,
+ location: { name: "Rural Clinic", countryCode: "CA" },
+ profileImage: "/placeholder.svg?width=40&height=40",
+ impactScore: 90,
+ distributionPercentage: 40,
+ },
+ {
+ id: "3",
+ address: "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC",
+ name: "Nurse Carol Williams",
+ credentials: "Registered Nurse, Community Health",
+ isVerified: true,
+ location: { name: "Community Center", countryCode: "GB" },
+ profileImage: "/placeholder.svg?width=40&height=40",
+ impactScore: 75,
+ distributionPercentage: 30,
+ },
+]
+
+export default function DonationForm() {
+ const { address, isConnected, signer, connectWallet, chainId } = useWalletContext() // Removed provider as signer is used
+ const [amount, setAmount] = useState("")
+ const [message, setMessage] = useState("")
+ const [selectedRecipient, setSelectedRecipient] = useState(HEALTHCARE_WORKERS[0])
+ const [transactionStatus, setTransactionStatus] = useState(TransactionStatus.NONE)
+ const [transactionHash, setTransactionHash] = useState(null)
+ const [error, setError] = useState(null)
+ const [isCorrectNetwork, setIsCorrectNetwork] = useState(true)
+
+ // Check if on the correct network
+ useEffect(() => {
+ if (chainId) {
+ // Check if the current chainId is in our supported networks
+ const targetNetwork = NETWORKS.BNB_CHAIN // Assuming BNB_CHAIN is the target
+ const isSupported = chainId === targetNetwork.chainId
+ setIsCorrectNetwork(isSupported)
+ }
+ }, [chainId])
+
+ // Handle network switch
+ const switchNetwork = async () => {
+ if (!window.ethereum) return
+
+ const targetNetwork = NETWORKS.BNB_CHAIN // Assuming BNB_CHAIN is the target
+
+ try {
+ await window.ethereum.request({
+ method: "wallet_switchEthereumChain",
+ params: [{ chainId: targetNetwork.chainIdHex }],
+ })
+ } catch (error: any) {
+ // This error code indicates that the chain has not been added to MetaMask
+ if (error.code === 4902) {
+ try {
+ await window.ethereum.request({
+ method: "wallet_addEthereumChain",
+ params: [
+ {
+ chainId: targetNetwork.chainIdHex,
+ chainName: targetNetwork.name,
+ nativeCurrency: targetNetwork.nativeCurrency,
+ rpcUrls: [targetNetwork.rpcUrl],
+ blockExplorerUrls: [targetNetwork.blockExplorer],
+ },
+ ],
+ })
+ } catch (addError) {
+ console.error("Error adding network:", addError)
+ }
+ }
+ console.error("Error switching network:", error)
+ }
+ }
+
+ // Validate donation amount
+ const isValidAmount = () => {
+ if (!amount) return false
+ try {
+ const amountBN = ethers.parseEther(amount)
+ const minAmountBN = ethers.parseEther(MIN_DONATION_AMOUNT)
+ return amountBN.gte(minAmountBN)
+ } catch (error) {
+ return false
+ }
+ }
+
+ // Handle donation
+ const handleDonate = async (e: React.FormEvent) => {
+ e.preventDefault()
+
+ if (!isConnected || !signer) {
+ try {
+ await connectWallet()
+ // After connectWallet, context values (isConnected, signer) will update,
+ // and the user can try submitting again.
+ return
+ } catch (err) {
+ setError("Failed to connect wallet. Please try again.")
+ return
+ }
+ }
+
+ if (!isCorrectNetwork) {
+ // setError("Please switch to the correct network (BNB Smart Chain) to make a donation.");
+ // No need to set error here as the specific alert for network switch is shown
+ return
+ }
+
+ if (!selectedRecipient) {
+ setError("Please select a healthcare worker to donate to.")
+ return
+ }
+
+ if (!isValidAmount()) {
+ setError(`Minimum donation amount is ${MIN_DONATION_AMOUNT} BNB.`)
+ return
+ }
+
+ setError(null)
+ setTransactionStatus(TransactionStatus.PENDING)
+
+ try {
+ // Assuming donate function takes signer, recipientAddress, amount
+ const result = await donate(signer, selectedRecipient.address, amount)
+ setTransactionHash(result.hash)
+
+ // Wait for transaction confirmation
+ const receipt = await result.transaction.wait()
+
+ if (receipt && receipt.status === 1) {
+ setTransactionStatus(TransactionStatus.SUCCESS)
+
+ // Store message in database if needed (off-chain)
+ if (message) {
+ try {
+ await fetch("/api/donations/message", {
+ // Ensure this API route exists
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ transactionHash: result.hash,
+ message,
+ senderAddress: address,
+ recipientAddress: selectedRecipient.address,
+ }),
+ })
+ } catch (fetchError) {
+ console.error("Error storing message:", fetchError)
+ // Non-critical error, don't show to user
+ }
+ }
+ } else {
+ setTransactionStatus(TransactionStatus.ERROR)
+ setError("Transaction failed. Please check your wallet and try again.")
+ }
+ } catch (err: any) {
+ console.error("Error donating:", err)
+ setTransactionStatus(TransactionStatus.ERROR)
+ setError(err.reason || err.message || "Failed to process donation. Please try again.")
+ }
+ }
+
+ // Reset form
+ const resetForm = () => {
+ setAmount("")
+ setMessage("")
+ setTransactionStatus(TransactionStatus.NONE)
+ setTransactionHash(null)
+ setError(null)
+ }
+
+ // Get transaction explorer URL
+ const getExplorerUrl = () => {
+ if (!transactionHash || !chainId) return ""
+
+ const network = Object.values(NETWORKS).find((n) => n.chainId === chainId)
+ if (!network || !network.blockExplorer) return ""
+
+ return `${network.blockExplorer}/tx/${transactionHash}`
+ }
+
+ // Render success state
+ if (transactionStatus === TransactionStatus.SUCCESS) {
+ return (
+
+
+
+
+ Donation Successful!
+
+ Your donation to {selectedRecipient?.name} has been processed successfully.
+
+
+ {transactionHash && (
+
+ )}
+
+
+ Make Another Donation
+
+
+
+
+ )
+ }
+
+ return (
+
+
+ {error && (
+
+
+ Error
+ {error}
+
+ )}
+
+ {!isCorrectNetwork && isConnected && (
+
+
+ Wrong Network
+
+ Please switch to {NETWORKS.BNB_CHAIN.name} to make a donation.
+
+ Switch Network
+
+
+
+ )}
+
+
+
+ Select Healthcare Worker
+
+ {
+ const selected = HEALTHCARE_WORKERS.find((worker) => worker.address === e.target.value)
+ setSelectedRecipient(selected || null)
+ }}
+ className="w-full rounded-md bg-blue-950/30 border-blue-500/30 text-blue-100 p-2 focus:ring-blue-500 focus:border-blue-500"
+ disabled={transactionStatus === TransactionStatus.PENDING}
+ >
+ {HEALTHCARE_WORKERS.map((worker) => (
+
+ {worker.name} - {worker.credentials}
+
+ ))}
+
+
+
+
+
+ Donation Amount (BNB)
+
+
setAmount(e.target.value)}
+ required
+ className="bg-blue-950/30 border-blue-500/30 text-blue-100"
+ placeholder={MIN_DONATION_AMOUNT}
+ disabled={transactionStatus === TransactionStatus.PENDING}
+ />
+
Minimum donation: {MIN_DONATION_AMOUNT} BNB
+
+
+
+
+ Message (Optional)
+
+ setMessage(e.target.value)}
+ className="bg-blue-950/30 border-blue-500/30 text-blue-100"
+ placeholder="Leave a message of support..."
+ rows={3}
+ disabled={transactionStatus === TransactionStatus.PENDING}
+ />
+
+
+
+ {transactionStatus === TransactionStatus.PENDING ? (
+ <>
+
+ Processing...
+ >
+ ) : !isConnected ? (
+ <>
+ Connect Wallet to Donate
+ >
+ ) : (
+ <>
+ Donate Now
+ >
+ )}
+
+
+
+ )
+}
diff --git a/components/donation-history.tsx b/components/donation-history.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..e9a86241a2ba593c2fdab7b16e7a28439fe4ec5d
--- /dev/null
+++ b/components/donation-history.tsx
@@ -0,0 +1,161 @@
+"use client"
+
+import { useState, useEffect } from "react"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Badge } from "@/components/ui/badge"
+import { Loader2, ExternalLink } from "lucide-react"
+import { useWalletContext } from "@/providers/wallet-provider"
+import { ethers } from "ethers"
+import { CONTRACT_ADDRESSES, NETWORKS } from "@/lib/constants"
+// Assuming DonationRouterABI is correctly defined and exported from this path
+import { DonationRouterABI } from "@/lib/abis/DonationRouterABI"
+
+interface Donation {
+ recipient: string
+ amount: string // Keep as string as it's formatted ethers
+ timestamp: number
+ transactionHash?: string
+}
+
+export function DonationHistory() {
+ const { address, isConnected, provider, chainId } = useWalletContext()
+ const [donations, setDonations] = useState([])
+ const [isLoading, setIsLoading] = useState(false)
+
+ const fetchDonationHistory = async () => {
+ if (!isConnected || !provider || !address || !chainId) {
+ // Added chainId check for CONTRACT_ADDRESSES
+ setDonations([])
+ return
+ }
+
+ setIsLoading(true)
+ try {
+ // Ensure CONTRACT_ADDRESSES[chainId] and CONTRACT_ADDRESSES[chainId].DONATION_ROUTER exist
+ const routerAddress = CONTRACT_ADDRESSES[chainId]?.DONATION_ROUTER
+ if (!routerAddress) {
+ console.error(`DonationRouter contract address not found for chainId: ${chainId}`)
+ setDonations([]) // Or handle with mock data as before
+ // For demo purposes, show mock data if contract address is missing
+ setDonations([
+ {
+ recipient: "0xf39Fd6e51aad88F6F4ce6aB8829539c652746fF0",
+ amount: "0.1",
+ timestamp: Date.now() - 86400000,
+ transactionHash: "0x123...def123",
+ },
+ {
+ recipient: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
+ amount: "0.05",
+ timestamp: Date.now() - 172800000,
+ transactionHash: "0xabc...cde",
+ },
+ ])
+ setIsLoading(false)
+ return
+ }
+
+ const donationRouter = new ethers.Contract(routerAddress, DonationRouterABI, provider)
+
+ // Get donation history from contract
+ // Ensure getDonationHistory method exists on the contract and returns expected structure
+ const history = await donationRouter.getDonationHistory(address)
+
+ // Format the donation history
+ const formattedHistory: Donation[] = history.map((item: any) => ({
+ recipient: item[0], // Assuming item[0] is address string
+ amount: ethers.formatEther(item[1]), // Assuming item[1] is BigNumberish
+ timestamp: Number(item[2]), // Assuming item[2] is BigNumberish or number
+ transactionHash: item[3] || undefined, // Assuming item[3] is string or undefined
+ }))
+
+ setDonations(formattedHistory)
+ } catch (error) {
+ console.error("Error fetching donation history:", error)
+ // For demo purposes, show mock data if contract call fails
+ setDonations([
+ {
+ recipient: "0xf39Fd6e51aad88F6F4ce6aB8829539c652746fF0",
+ amount: "0.1",
+ timestamp: Date.now() - 86400000, // 1 day ago
+ transactionHash: "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234",
+ },
+ {
+ recipient: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
+ amount: "0.05",
+ timestamp: Date.now() - 172800000, // 2 days ago
+ transactionHash: "0xabcdef123456789abcdef123456789abcdef123456789abcdef123456789abcde",
+ },
+ ])
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ useEffect(() => {
+ fetchDonationHistory()
+ }, [isConnected, provider, address, chainId]) // Added chainId dependency
+
+ // Get transaction explorer URL
+ const getExplorerUrl = (hash: string) => {
+ if (!hash || !chainId) return ""
+
+ const network = Object.values(NETWORKS).find((n) => n.chainId === chainId)
+ if (!network || !network.blockExplorer) return ""
+
+ return `${network.blockExplorer}/tx/${hash}`
+ }
+
+ // Format date
+ const formatDate = (timestamp: number) => {
+ return new Date(timestamp * 1000).toLocaleDateString() // Assuming timestamp is in seconds
+ }
+
+ return (
+
+
+ Your Donation History
+
+
+ {isLoading ? (
+
+
+ Loading donation history...
+
+ ) : !isConnected ? (
+ Connect your wallet to view donation history
+ ) : donations.length === 0 ? (
+ No donations found
+ ) : (
+
+ {donations.map((donation, index) => (
+
+
+
+
{donation.amount} BNB
+
+ To: {donation.recipient.substring(0, 6)}...
+ {donation.recipient.substring(donation.recipient.length - 4)}
+
+
{formatDate(donation.timestamp)}
+
+
+ {donation.transactionHash && (
+
+
+
+ )}
+
+
+ ))}
+
+ )}
+
+
+ )
+}
diff --git a/components/donation-stats.tsx b/components/donation-stats.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..9cfd35384bd3501be17d0aae767747df89245610
--- /dev/null
+++ b/components/donation-stats.tsx
@@ -0,0 +1,66 @@
+"use client"
+
+import { useState, useEffect } from "react"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Heart, TrendingUp } from "lucide-react"
+
+export default function DonationStats() {
+ const [totalDonations, setTotalDonations] = useState(0)
+ const [totalAmount, setTotalAmount] = useState(0)
+ const [loading, setLoading] = useState(true)
+ // const [error, setError] = useState(null) // Error state was declared but not used
+
+ useEffect(() => {
+ // Use mock data instead of fetching
+ const mockData = {
+ totalDonations: 12,
+ totalAmount: 0.75,
+ }
+
+ // Simulate loading
+ const timer = setTimeout(() => {
+ setTotalDonations(mockData.totalDonations)
+ setTotalAmount(mockData.totalAmount)
+ setLoading(false)
+ }, 1000)
+ return () => clearTimeout(timer) // Cleanup timer on unmount
+ }, [])
+
+ return (
+
+
+ Donation Stats
+
+
+
+
+
+
Total Donations
+ {loading ? (
+
+ ) : (
+
{totalDonations}
+ )}
+
+
+
+
+
Total Amount
+ {loading ? (
+
+ ) : (
+
{totalAmount} BNB
+ )}
+
+
+
+
+
Donations go directly to the BNB wallet:
+
+ 0xaa4202d69E6c9ddE7De2885B9DDe88c7baA240f8
+
+
+
+
+ )
+}
diff --git a/components/dynamic-bubble-field.tsx b/components/dynamic-bubble-field.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..472c669194a5a20f1b94d6e7485bf949cbdef267
--- /dev/null
+++ b/components/dynamic-bubble-field.tsx
@@ -0,0 +1,716 @@
+"use client"
+
+import type React from "react"
+import { useEffect, useState, useRef } from "react"
+import { motion } from "framer-motion"
+import { Stethoscope, Shield, Heart, Zap, TrendingUp, Activity, Users } from "lucide-react"
+
+interface RegistrationEntry {
+ id: string
+ name: string
+ type: "Guardian" | "Healer" | "CHW" | "Health Facility"
+ location: string
+ timestamp: string
+ flbEarned: number
+ verified: boolean
+}
+
+interface CommunityStats {
+ healers: {
+ total: number
+ verified: number
+ pending: number
+ specializations: {
+ nurses: number
+ doctors: number
+ midwives: number
+ pharmacists: number
+ }
+ }
+ guardians: {
+ total: number
+ active: number
+ totalContributions: number
+ }
+ soulbound: {
+ total: number
+ resonanceHigh: number
+ ancestralVerified: number
+ }
+ codex: {
+ scrollKeepers: number
+ proverbContributors: number
+ codeContributors: number
+ totalScrolls: number
+ }
+ testnet: {
+ activeNodes: number
+ newJoinsToday: number
+ transactionsToday: number
+ }
+ mainnet: {
+ activeNodes: number
+ newJoinsToday: number
+ transactionsToday: number
+ }
+ regions: {
+ westAfrica: number
+ eastAfrica: number
+ southernAfrica: number
+ northAfrica: number
+ centralAfrica: number
+ }
+ impact: {
+ totalPatientsServed: number
+ communitiesReached: number
+ donationsReceived: number
+ flbTokensEarned: number
+ }
+ growth: {
+ thisMonth: number
+ thisWeek: number
+ today: number
+ }
+ lastUpdated: string
+ liveRegistrations: RegistrationEntry[]
+}
+
+interface BubbleConfig {
+ id: string
+ label: string
+ count: number
+ icon: React.ElementType
+ category:
+ | "testnet"
+ | "mainnet"
+ | "healers"
+ | "guardians"
+ | "soulbound"
+ | "codex"
+ | "verified"
+ | "active"
+ | "live-person"
+ size: "xs" | "sm" | "md" | "lg" | "xl"
+ position: { x: number; y: number }
+ priority: number
+ velocity: { x: number; y: number }
+ isLive?: boolean
+ personData?: RegistrationEntry
+ isNewJoin?: boolean
+}
+
+export function DynamicBubbleField() {
+ const [stats, setStats] = useState(null)
+ const [loading, setLoading] = useState(true)
+ const [bubbles, setBubbles] = useState([])
+ const [liveRegistrations, setLiveRegistrations] = useState([])
+ const containerRef = useRef(null)
+ const animationRef = useRef()
+
+ // Fetch live registration data from Google Sheets
+ const fetchLiveRegistrations = async (): Promise => {
+ try {
+ // In a real implementation, this would fetch from Google Sheets API
+ // For now, we'll simulate live data that updates
+ const mockRegistrations: RegistrationEntry[] = [
+ {
+ id: `${Date.now()}-1`,
+ name: "Dr. Amara Kone",
+ type: "Healer",
+ location: "Lagos, Nigeria",
+ timestamp: new Date(Date.now() - Math.random() * 300000).toISOString(),
+ flbEarned: 250,
+ verified: true,
+ },
+ {
+ id: `${Date.now()}-2`,
+ name: "Kwame Asante",
+ type: "Guardian",
+ location: "Accra, Ghana",
+ timestamp: new Date(Date.now() - Math.random() * 600000).toISOString(),
+ flbEarned: 150,
+ verified: true,
+ },
+ {
+ id: `${Date.now()}-3`,
+ name: "Sarah Okafor",
+ type: "CHW",
+ location: "Kano, Nigeria",
+ timestamp: new Date(Date.now() - Math.random() * 900000).toISOString(),
+ flbEarned: 100,
+ verified: false,
+ },
+ {
+ id: `${Date.now()}-4`,
+ name: "Nairobi Community Health Center",
+ type: "Health Facility",
+ location: "Nairobi, Kenya",
+ timestamp: new Date(Date.now() - Math.random() * 1200000).toISOString(),
+ flbEarned: 500,
+ verified: true,
+ },
+ {
+ id: `${Date.now()}-5`,
+ name: "Fatima Al-Rashid",
+ type: "Guardian",
+ location: "Cairo, Egypt",
+ timestamp: new Date(Date.now() - Math.random() * 1500000).toISOString(),
+ flbEarned: 200,
+ verified: true,
+ },
+ {
+ id: `${Date.now()}-6`,
+ name: "Dr. Kofi Mensah",
+ type: "Healer",
+ location: "Kumasi, Ghana",
+ timestamp: new Date(Date.now() - Math.random() * 300000).toISOString(),
+ flbEarned: 300,
+ verified: true,
+ },
+ {
+ id: `${Date.now()}-7`,
+ name: "Aisha Mwangi",
+ type: "CHW",
+ location: "Mombasa, Kenya",
+ timestamp: new Date(Date.now() - Math.random() * 400000).toISOString(),
+ flbEarned: 120,
+ verified: false,
+ },
+ {
+ id: `${Date.now()}-8`,
+ name: "Ubuntu Health Collective",
+ type: "Health Facility",
+ location: "Cape Town, South Africa",
+ timestamp: new Date(Date.now() - Math.random() * 500000).toISOString(),
+ flbEarned: 450,
+ verified: true,
+ },
+ ]
+
+ // Simulate new registrations appearing
+ const recentRegistrations = mockRegistrations.filter((reg) => {
+ const regTime = new Date(reg.timestamp).getTime()
+ const now = Date.now()
+ return now - regTime < 1800000 // Last 30 minutes
+ })
+
+ return recentRegistrations
+ } catch (error) {
+ console.error("Error fetching live registrations:", error)
+ return []
+ }
+ }
+
+ useEffect(() => {
+ const fetchStats = async () => {
+ try {
+ // Fetch live registrations
+ const registrations = await fetchLiveRegistrations()
+ setLiveRegistrations(registrations)
+
+ // Try to fetch community stats
+ const response = await fetch("/api/community-stats")
+ let statsData: CommunityStats
+
+ if (response.ok) {
+ statsData = await response.json()
+ } else {
+ // Generate mock stats based on live registrations
+ statsData = generateMockStats(registrations)
+ }
+
+ // Add live registrations to stats
+ statsData.liveRegistrations = registrations
+
+ setStats(statsData)
+ generateBubbles(statsData)
+ updateCSSVariables(statsData)
+ } catch (error) {
+ console.error("Error fetching community stats:", error)
+ // Use mock data as fallback
+ const registrations = await fetchLiveRegistrations()
+ const mockStats = generateMockStats(registrations)
+ mockStats.liveRegistrations = registrations
+ setStats(mockStats)
+ generateBubbles(mockStats)
+ updateCSSVariables(mockStats)
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ fetchStats()
+ const interval = setInterval(fetchStats, 10000) // Update every 10 seconds for live feel
+
+ return () => clearInterval(interval)
+ }, [])
+
+ const generateMockStats = (registrations: RegistrationEntry[]): CommunityStats => {
+ const healers = registrations.filter((r) => r.type === "Healer" || r.type === "CHW").length
+ const guardians = registrations.filter((r) => r.type === "Guardian").length
+ const facilities = registrations.filter((r) => r.type === "Health Facility").length
+
+ return {
+ healers: {
+ total: 1247 + healers,
+ verified: 892 + registrations.filter((r) => (r.type === "Healer" || r.type === "CHW") && r.verified).length,
+ pending: 355,
+ specializations: {
+ nurses: 456,
+ doctors: 234,
+ midwives: 189,
+ pharmacists: 123,
+ },
+ },
+ guardians: {
+ total: 2156 + guardians,
+ active: 1834 + guardians,
+ totalContributions: 45678,
+ },
+ soulbound: {
+ total: 567,
+ resonanceHigh: 234,
+ ancestralVerified: 345,
+ },
+ codex: {
+ scrollKeepers: 89,
+ proverbContributors: 234,
+ codeContributors: 156,
+ totalScrolls: 1234,
+ },
+ testnet: {
+ activeNodes: 45 + Math.floor(Math.random() * 10),
+ newJoinsToday: registrations.length + Math.floor(Math.random() * 5),
+ transactionsToday: 234 + Math.floor(Math.random() * 50),
+ },
+ mainnet: {
+ activeNodes: 128 + Math.floor(Math.random() * 20),
+ newJoinsToday: Math.floor(registrations.length / 2) + Math.floor(Math.random() * 3),
+ transactionsToday: 567 + Math.floor(Math.random() * 100),
+ },
+ regions: {
+ westAfrica: 1234,
+ eastAfrica: 987,
+ southernAfrica: 654,
+ northAfrica: 432,
+ centralAfrica: 321,
+ },
+ impact: {
+ totalPatientsServed: 45678,
+ communitiesReached: 234,
+ donationsReceived: 123456,
+ flbTokensEarned: 987654 + registrations.reduce((sum, r) => sum + r.flbEarned, 0),
+ },
+ growth: {
+ thisMonth: 234,
+ thisWeek: 67,
+ today: registrations.length,
+ },
+ lastUpdated: new Date().toISOString(),
+ liveRegistrations: registrations,
+ }
+ }
+
+ const updateCSSVariables = (stats: CommunityStats) => {
+ const root = document.documentElement
+ root.style.setProperty("--testnet-nodes", stats.testnet.activeNodes.toString())
+ root.style.setProperty("--mainnet-nodes", stats.mainnet.activeNodes.toString())
+ root.style.setProperty("--healers-count", stats.healers.total.toString())
+ root.style.setProperty("--guardians-count", stats.guardians.total.toString())
+ root.style.setProperty("--live-registrations", stats.liveRegistrations.length.toString())
+ }
+
+ const generateBubbles = (stats: CommunityStats) => {
+ const bubbleConfigs: Omit[] = [
+ // Network bubbles (highest priority)
+ {
+ id: "testnet",
+ label: "Testnet Nodes",
+ count: stats.testnet.activeNodes,
+ icon: Activity,
+ category: "testnet",
+ size: "xl",
+ priority: 1,
+ isLive: true,
+ },
+ {
+ id: "mainnet",
+ label: "Mainnet Nodes",
+ count: stats.mainnet.activeNodes,
+ icon: Zap,
+ category: "mainnet",
+ size: "xl",
+ priority: 1,
+ isLive: true,
+ },
+
+ // Primary categories
+ {
+ id: "healers",
+ label: "Healers",
+ count: stats.healers.total,
+ icon: Stethoscope,
+ category: "healers",
+ size: "lg",
+ priority: 2,
+ },
+ {
+ id: "guardians",
+ label: "Guardians",
+ count: stats.guardians.total,
+ icon: Shield,
+ category: "guardians",
+ size: "lg",
+ priority: 2,
+ },
+
+ // Network activity
+ {
+ id: "testnet-joins",
+ label: "Testnet Joins Today",
+ count: stats.testnet.newJoinsToday,
+ icon: TrendingUp,
+ category: "testnet",
+ size: "md",
+ priority: 3,
+ isLive: true,
+ },
+ {
+ id: "mainnet-joins",
+ label: "Mainnet Joins Today",
+ count: stats.mainnet.newJoinsToday,
+ icon: TrendingUp,
+ category: "mainnet",
+ size: "md",
+ priority: 3,
+ isLive: true,
+ },
+ ]
+
+ // Add live person bubbles for recent registrations
+ const personBubbles = stats.liveRegistrations.slice(0, 8).map((registration, index) => ({
+ id: `person-${registration.id}`,
+ label: registration.name.split(" ")[0], // First name only for bubble
+ count: registration.flbEarned,
+ icon:
+ registration.type === "Guardian"
+ ? Shield
+ : registration.type === "Healer"
+ ? Stethoscope
+ : registration.type === "CHW"
+ ? Users
+ : Heart,
+ category: "live-person" as const,
+ size: "sm" as const,
+ priority: 4,
+ isLive: true,
+ personData: registration,
+ isNewJoin: Date.now() - new Date(registration.timestamp).getTime() < 600000, // Last 10 minutes
+ }))
+
+ const allBubbles = [...bubbleConfigs, ...personBubbles]
+
+ // Generate positions and velocities using improved distribution
+ const generatedBubbles = allBubbles.map((config, index) => {
+ const angle = index * 137.5 * (Math.PI / 180) // Golden angle
+ const radius = Math.sqrt(index + 1) * 12
+ const centerX = 50
+ const centerY = 50
+
+ return {
+ ...config,
+ position: {
+ x: Math.max(10, Math.min(90, centerX + Math.cos(angle) * radius)),
+ y: Math.max(10, Math.min(90, centerY + Math.sin(angle) * radius)),
+ },
+ velocity: {
+ x: (Math.random() - 0.5) * 0.5,
+ y: (Math.random() - 0.5) * 0.5,
+ },
+ }
+ })
+
+ setBubbles(generatedBubbles)
+ }
+
+ // Animate bubbles continuously
+ useEffect(() => {
+ if (bubbles.length === 0) return
+
+ const animate = () => {
+ setBubbles((prevBubbles) =>
+ prevBubbles.map((bubble) => {
+ let newX = bubble.position.x + bubble.velocity.x
+ let newY = bubble.position.y + bubble.velocity.y
+ let newVx = bubble.velocity.x
+ let newVy = bubble.velocity.y
+
+ // Bounce off edges
+ if (newX <= 5 || newX >= 95) {
+ newVx = -newVx * 0.8
+ newX = Math.max(5, Math.min(95, newX))
+ }
+ if (newY <= 5 || newY >= 95) {
+ newVy = -newVy * 0.8
+ newY = Math.max(5, Math.min(95, newY))
+ }
+
+ // Add slight random movement for live bubbles
+ if (bubble.isLive) {
+ newVx += (Math.random() - 0.5) * 0.1
+ newVy += (Math.random() - 0.5) * 0.1
+ }
+
+ // Extra movement for new joins
+ if (bubble.isNewJoin) {
+ newVx += (Math.random() - 0.5) * 0.2
+ newVy += (Math.random() - 0.5) * 0.2
+ }
+
+ // Apply friction
+ newVx *= 0.99
+ newVy *= 0.99
+
+ return {
+ ...bubble,
+ position: { x: newX, y: newY },
+ velocity: { x: newVx, y: newVy },
+ }
+ }),
+ )
+
+ animationRef.current = requestAnimationFrame(animate)
+ }
+
+ animate()
+
+ return () => {
+ if (animationRef.current) {
+ cancelAnimationFrame(animationRef.current)
+ }
+ }
+ }, [bubbles.length])
+
+ const getBubbleClasses = (bubble: BubbleConfig) => {
+ const baseClasses = `data-bubble bubble-${bubble.size} bubble-${bubble.category}`
+ const liveClass = bubble.isLive ? "bubble-live" : ""
+ const newJoinClass = bubble.isNewJoin ? "bubble-new-join" : ""
+ const personClass = bubble.category === "live-person" ? "bubble-person" : ""
+
+ return `${baseClasses} ${liveClass} ${newJoinClass} ${personClass}`.trim()
+ }
+
+ const getPersonTypeColor = (type: string) => {
+ switch (type) {
+ case "Guardian":
+ return "from-blue-400 to-indigo-600"
+ case "Healer":
+ return "from-green-400 to-emerald-600"
+ case "CHW":
+ return "from-purple-400 to-violet-600"
+ case "Health Facility":
+ return "from-orange-400 to-red-600"
+ default:
+ return "from-gray-400 to-gray-600"
+ }
+ }
+
+ if (loading) {
+ return (
+
+ )
+ }
+
+ return (
+
+ {/* Title */}
+
+
Live Network & Community Pulse
+
+ Real-time visualization of testnet, mainnet, and live registrations from Google Forms
+
+ {stats && (
+
+ 🔥 {stats.liveRegistrations.length} people joined recently • Last update:{" "}
+ {new Date(stats.lastUpdated).toLocaleTimeString()}
+
+ )}
+
+
+ {/* Bubble Field */}
+
+ {/* Background pattern */}
+
+
+ {/* Dynamic Bubbles */}
+ {bubbles.map((bubble) => {
+ const Icon = bubble.icon
+ return (
+
+ {/* Live indicator */}
+ {bubble.isLive && (
+
+ )}
+
+ {/* New join indicator */}
+ {bubble.isNewJoin && (
+
+ ✨
+
+ )}
+
+ {/* Person bubble styling */}
+ {bubble.category === "live-person" && bubble.personData && (
+
+ )}
+
+ {/* Icon */}
+
+
+ {/* Count with live animation */}
+
+ {bubble.category === "live-person" ? bubble.count : bubble.count}
+
+
+ {/* Label */}
+ {bubble.size !== "xs" && (
+ {bubble.label}
+ )}
+
+ {/* Enhanced tooltip for persons */}
+
+ {bubble.personData ? (
+
+
{bubble.personData.name}
+
{bubble.personData.type}
+
{bubble.personData.location}
+
+{bubble.personData.flbEarned} FLB
+ {bubble.personData.verified &&
✓ Verified
}
+
+ {new Date(bubble.personData.timestamp).toLocaleTimeString()}
+
+
+ ) : (
+
+ {bubble.label}: {bubble.count.toLocaleString()}
+ {bubble.isLive &&
● LIVE
}
+
+ )}
+
+
+ )
+ })}
+
+ {/* Network Status */}
+ {stats && (
+
+
+
{stats.liveRegistrations.length} recent joins
+
+ Testnet: {stats.testnet.activeNodes} | Mainnet: {stats.mainnet.activeNodes}
+
+
+ )}
+
+
+ {/* Enhanced Legend */}
+
+
+ {/* Live Registration Summary */}
+ {stats && stats.liveRegistrations.length > 0 && (
+
+
+
+ Recent Live Registrations
+
+
+ {stats.liveRegistrations.slice(0, 4).map((reg) => (
+
+
{reg.name.split(" ")[0]}
+
+ {reg.type} • {reg.location.split(",")[0]}
+
+
+{reg.flbEarned} FLB
+
+ ))}
+
+
+ )}
+
+ )
+}
+
diff --git a/components/ecosystem.tsx b/components/ecosystem.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..a715b54761658d8fef91124bb8f0f6a2eab4fc05
--- /dev/null
+++ b/components/ecosystem.tsx
@@ -0,0 +1,71 @@
+"use client"
+import { motion } from "framer-motion"
+
+const cards = [
+ {
+ title: "🔥 FLB Token",
+ desc: "Soulbound proof-of-impact. Not bought — only earned by healing.",
+ icon: "💠", // Using emoji as icon
+ bg: "bg-gradient-to-r from-pink-600 to-fuchsia-700",
+ },
+ {
+ title: "🧑🏾⚕️ Health Actor Registry",
+ desc: "Doctors, clinics, and outreach workers — verified on-chain.",
+ icon: "🏥", // Using emoji as icon
+ bg: "bg-gradient-to-r from-cyan-700 to-blue-700",
+ },
+ {
+ title: "🗳️ DAO Governance",
+ desc: "Every scroll can be a vote. Every voter is a flamekeeper.",
+ icon: "🌀", // Using emoji as icon
+ bg: "bg-gradient-to-r from-yellow-600 to-amber-500",
+ },
+ {
+ title: "👩🏾💻 Flameborn Youth Circle",
+ desc: "Unemployed grads earn FLB for tasks, outreach, and AI roles.",
+ icon: "🌍", // Using emoji as icon
+ bg: "bg-gradient-to-r from-green-700 to-emerald-600",
+ },
+ {
+ title: "📜 Scroll Engine",
+ desc: "Inject culture into code. Each scroll activates a loop of justice.",
+ icon: "📖", // Using emoji as icon
+ bg: "bg-gradient-to-r from-purple-600 to-indigo-700",
+ },
+]
+
+export default function Ecosystem() {
+ return (
+
+
+ ⚙️ The Flameborn Ecosystem
+
+
+
+ {cards.map((c, i) => (
+
+ {c.icon}
+ {c.title}
+ {c.desc}
+
+ ))}
+
+
+ )
+}
diff --git a/components/enhanced-donation-form.tsx b/components/enhanced-donation-form.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..6317b952f5c2220481f9f3f1dc645211d75f20fe
--- /dev/null
+++ b/components/enhanced-donation-form.tsx
@@ -0,0 +1,562 @@
+"use client"
+
+import type React from "react" // Ensure React is imported
+
+import { useState, useEffect } from "react"
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Textarea } from "@/components/ui/textarea"
+import {
+ Heart,
+ ExternalLink,
+ AlertCircle,
+ CheckCircle,
+ Loader2,
+ Users,
+ ArrowRight,
+ ChevronLeft,
+ Info,
+} from "lucide-react"
+import { useWalletContext } from "@/providers/wallet-provider"
+// Assuming these services and types are correctly defined
+import { donate } from "@/lib/contract-service"
+import { distributeDonation } from "@/lib/distribution-service"
+import { MIN_DONATION_AMOUNT, NETWORKS, TransactionStatus } from "@/lib/constants"
+import { ethers } from "ethers"
+import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"
+import { getHealthcareWorkersWithDistribution } from "@/lib/mock-healthcare-data"
+import type { HealthcareWorker } from "@/lib/types/healthcare-worker"
+import { HealthcareWorkersGrid } from "@/components/healthcare-workers-grid" // Assuming this component exists
+import { Progress } from "@/components/ui/progress"
+
+export default function EnhancedDonationForm() {
+ const { address, isConnected, signer, connectWallet, chainId } = useWalletContext() // Removed provider
+ const [amount, setAmount] = useState("")
+ const [message, setMessage] = useState("")
+ const [donationType, setDonationType] = useState<"direct" | "distributed">("distributed")
+ const [selectedWorker, setSelectedWorker] = useState(null)
+ const [transactionStatus, setTransactionStatus] = useState(TransactionStatus.NONE)
+ const [transactionHash, setTransactionHash] = useState(null)
+ const [error, setError] = useState(null)
+ const [isCorrectNetwork, setIsCorrectNetwork] = useState(true)
+ const [step, setStep] = useState<"select" | "confirm">("select")
+ const [distributionPreview, setDistributionPreview] = useState>({})
+ const [workers, setWorkers] = useState([])
+
+ // Load healthcare workers with distribution scores
+ useEffect(() => {
+ try {
+ const workersWithDistribution = getHealthcareWorkersWithDistribution()
+ setWorkers(workersWithDistribution)
+ } catch (error) {
+ console.error("Error loading healthcare workers:", error)
+ // Optionally set an error state to inform the user
+ }
+ }, [])
+
+ // Check if on the correct network
+ useEffect(() => {
+ if (chainId) {
+ const targetNetwork = NETWORKS.BNB_CHAIN // Assuming BNB_CHAIN is the target
+ const isSupported = chainId === targetNetwork.chainId
+ setIsCorrectNetwork(isSupported)
+ }
+ }, [chainId])
+
+ // Calculate distribution preview when amount changes
+ useEffect(() => {
+ if (donationType === "distributed" && amount && isValidAmount() && workers.length > 0) {
+ try {
+ const amountValue = Number.parseFloat(amount)
+ const distribution = distributeDonation(amountValue, workers)
+ setDistributionPreview(distribution)
+ } catch (err) {
+ console.error("Error calculating distribution:", err)
+ // Optionally set an error state
+ }
+ }
+ }, [amount, workers, donationType])
+
+ // Handle worker selection
+ const handleSelectWorker = (worker: HealthcareWorker) => {
+ setSelectedWorker(worker)
+ setDonationType("direct") // Ensure donationType is set to direct
+ setStep("confirm")
+ }
+
+ // Handle network switch
+ const switchNetwork = async () => {
+ if (!window.ethereum) return
+ const targetNetwork = NETWORKS.BNB_CHAIN // Assuming BNB_CHAIN is the target
+ try {
+ await window.ethereum.request({
+ method: "wallet_switchEthereumChain",
+ params: [{ chainId: targetNetwork.chainIdHex }],
+ })
+ } catch (switchError: any) {
+ if (switchError.code === 4902) {
+ try {
+ await window.ethereum.request({
+ method: "wallet_addEthereumChain",
+ params: [
+ {
+ chainId: targetNetwork.chainIdHex,
+ chainName: targetNetwork.name,
+ nativeCurrency: targetNetwork.nativeCurrency,
+ rpcUrls: [targetNetwork.rpcUrl],
+ blockExplorerUrls: [targetNetwork.blockExplorer],
+ },
+ ],
+ })
+ } catch (addError) {
+ console.error("Error adding network:", addError)
+ }
+ }
+ console.error("Error switching network:", switchError)
+ }
+ }
+
+ // Validate donation amount
+ const isValidAmount = () => {
+ if (!amount) return false
+ try {
+ const amountBN = ethers.parseEther(amount)
+ const minAmountBN = ethers.parseEther(MIN_DONATION_AMOUNT)
+ return amountBN.gte(minAmountBN)
+ } catch (error) {
+ return false
+ }
+ }
+
+ // Format BNB amount
+ const formatBNB = (value: number) => {
+ if (isNaN(value)) return "0.0000"
+ return value.toFixed(value < 0.01 && value !== 0 ? 6 : 4)
+ }
+
+ // Handle donation
+ const handleDonate = async (e: React.FormEvent) => {
+ e.preventDefault()
+
+ if (!isConnected || !signer) {
+ try {
+ await connectWallet()
+ return
+ } catch (err) {
+ setError("Failed to connect wallet. Please try again.")
+ return
+ }
+ }
+
+ if (!isCorrectNetwork) {
+ // setError("Please switch to the correct network (BNB Smart Chain).");
+ return
+ }
+
+ if (donationType === "direct" && !selectedWorker) {
+ setError("Please select a healthcare worker to donate to.")
+ return
+ }
+
+ if (!isValidAmount()) {
+ setError(`Minimum donation amount is ${MIN_DONATION_AMOUNT} BNB.`)
+ return
+ }
+
+ setError(null)
+ setTransactionStatus(TransactionStatus.PENDING)
+
+ try {
+ let txHash: string
+ if (donationType === "direct" && selectedWorker) {
+ const result = await donate(signer, selectedWorker.address, amount)
+ txHash = result.hash
+ const receipt = await result.transaction.wait()
+ if (!receipt || receipt.status !== 1) {
+ throw new Error("Direct transaction failed or was reverted.")
+ }
+ } else {
+ // Distributed donation
+ // This part is simulated as per original code.
+ // In a real app, this would call a contract function for distribution.
+ await new Promise((resolve) => setTimeout(resolve, 2000)) // Simulate delay
+ txHash = "0x" + Array.from({ length: 64 }, () => Math.floor(Math.random() * 16).toString(16)).join("")
+ }
+
+ setTransactionHash(txHash)
+ setTransactionStatus(TransactionStatus.SUCCESS)
+
+ if (message && donationType === "direct" && selectedWorker) {
+ // Only store message for direct donations for now
+ try {
+ await fetch("/api/donations/message", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ transactionHash: txHash,
+ message,
+ senderAddress: address,
+ recipientAddress: selectedWorker.address,
+ }),
+ })
+ } catch (fetchError) {
+ console.error("Error storing message:", fetchError)
+ }
+ }
+ } catch (err: any) {
+ console.error("Error donating:", err)
+ setTransactionStatus(TransactionStatus.ERROR)
+ setError(err.reason || err.message || "Failed to process donation. Please try again.")
+ }
+ }
+
+ // Reset form
+ const resetForm = () => {
+ setAmount("")
+ setMessage("")
+ setTransactionStatus(TransactionStatus.NONE)
+ setTransactionHash(null)
+ setError(null)
+ setStep("select")
+ setSelectedWorker(null)
+ setDonationType("distributed") // Reset to default tab
+ }
+
+ // Get transaction explorer URL
+ const getExplorerUrl = () => {
+ if (!transactionHash || !chainId) return ""
+ const network = Object.values(NETWORKS).find((n) => n.chainId === chainId)
+ if (!network || !network.blockExplorer) return ""
+ return `${network.blockExplorer}/tx/${transactionHash}`
+ }
+
+ // Render success state
+ if (transactionStatus === TransactionStatus.SUCCESS) {
+ return (
+
+
+
+
+ Donation Successful!
+
+ {donationType === "direct" && selectedWorker ? (
+
+ Your donation to {selectedWorker.name} has been processed successfully.
+
+ ) : (
+
+
+ Your donation has been distributed among healthcare workers based on their impact scores.
+
+
+
+ Distribution Summary
+
+
+ {Object.entries(distributionPreview).map(([workerAddress, distributedAmount]) => {
+ const worker = workers.find((w) => w.address === workerAddress)
+ if (!worker || distributedAmount === 0) return null
+ return (
+
+ {worker.name}
+ {formatBNB(distributedAmount)} BNB
+
+ )
+ })}
+
+
+
+ )}
+
+ {transactionHash && (
+
+ )}
+
+ Make Another Donation
+
+
+
+
+ )
+ }
+
+ // Render confirmation step
+ if (step === "confirm") {
+ return (
+
+
+
+
+ Confirm Donation
+ setStep("select")}
+ className="text-blue-300 hover:text-blue-100 hover:bg-blue-950/50"
+ >
+ Back
+
+
+
+
+
+ {error && (
+
+ Error
+ {error}
+
+ )}
+ {!isCorrectNetwork && isConnected && (
+
+ Wrong Network
+
+ Please switch to {NETWORKS.BNB_CHAIN.name} to make a donation.
+
+ Switch Network
+
+
+
+ )}
+
+ {donationType === "direct" && selectedWorker && (
+
+
+
+
+
+
+
{selectedWorker.name}
+
{selectedWorker.credentials}
+
+
+
{selectedWorker.location.name}
+
+ )}
+
+ {donationType === "distributed" && (
+
+
+ {" "}
+
+
Distributed Donation
+
+
+ Your donation will be distributed among verified healthcare workers based on impact scores.
+
+ {amount && isValidAmount() && Object.keys(distributionPreview).length > 0 && (
+
+
Distribution Preview:
+ {Object.entries(distributionPreview)
+ .slice(0, 3)
+ .map(([addr, amt]) => {
+ const worker = workers.find((w) => w.address === addr)
+ if (!worker || amt === 0) return null
+ return (
+
+ {worker.name}
+ {formatBNB(amt)} BNB
+
+ )
+ })}
+ {Object.keys(distributionPreview).filter((key) => distributionPreview[key] > 0).length > 3 && (
+
+ And{" "}
+ {Object.keys(distributionPreview).filter((key) => distributionPreview[key] > 0).length - 3}{" "}
+ more...
+
+ )}
+
+ )}
+
+ )}
+
+
+
+ Donation Amount (BNB)
+
+
setAmount(e.target.value)}
+ required
+ className="bg-blue-950/30 border-blue-500/30 text-blue-100"
+ placeholder={MIN_DONATION_AMOUNT}
+ disabled={transactionStatus === TransactionStatus.PENDING}
+ />
+
Minimum: {MIN_DONATION_AMOUNT} BNB
+
+
+
+ Message (Optional)
+
+ setMessage(e.target.value)}
+ className="bg-blue-950/30 border-blue-500/30 text-blue-100"
+ placeholder="Leave a message..."
+ rows={3}
+ disabled={transactionStatus === TransactionStatus.PENDING}
+ />
+
+
+ {transactionStatus === TransactionStatus.PENDING ? (
+ <>
+ Processing...
+ >
+ ) : !isConnected ? (
+ <>
+ Connect Wallet to Donate
+ >
+ ) : (
+ <>
+ Confirm Donation
+ >
+ )}
+
+
+
+
+
+ )
+ }
+
+ // Render selection step (default)
+ return (
+
+
+
+ {" "}
+ Choose Donation Method {" "}
+
+
+ setDonationType(value as "direct" | "distributed")}
+ className="w-full"
+ >
+
+
+ Distributed Impact
+
+
+ Direct Support
+
+
+
+
+
+
+
+ {" "}
+
Equitable Distribution System
+
+ Your donation will be automatically distributed among verified healthcare workers based on impact
+ scores.
+
+
+
+
+ {workers.slice(0, 3).map((worker) => (
+
+
+
+
+
+
+ {" "}
+
{worker.name}
+
{worker.location.name}
+
+
+
+
+ Impact Score
+
+ {worker.distributionPercentage?.toFixed(1)}%
+
+
+
+
+
+ ))}
+
+
+
{
+ setDonationType("distributed")
+ setStep("confirm")
+ }}
+ className="bg-blue-600 hover:bg-blue-700 text-white"
+ >
+ Continue with Distributed Donation
+
+
+
+
+
+
+
+
+
+ {" "}
+
Direct Support
+
Choose a specific healthcare worker to support directly.
+
+
+
+
Select a healthcare worker to support:
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/components/flame-animation.tsx b/components/flame-animation.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..75c65452e59afe52df710102bd5eab12c7201569
--- /dev/null
+++ b/components/flame-animation.tsx
@@ -0,0 +1,102 @@
+"use client"
+
+import { useEffect, useRef } from "react"
+import { motion } from "framer-motion"
+
+export default function FlameAnimation() {
+ const flameRef = useRef(null)
+
+ useEffect(() => {
+ const flame = flameRef.current
+ if (!flame) return
+
+ const createFlameParticle = () => {
+ const particle = document.createElement("div")
+ particle.className = "flame-particle"
+ particle.style.cssText = `
+ position: absolute;
+ width: ${Math.random() * 6 + 2}px;
+ height: ${Math.random() * 6 + 2}px;
+ background: radial-gradient(circle, #ff6b35, #f7931e);
+ border-radius: 50%;
+ bottom: 0;
+ left: ${Math.random() * 100}%;
+ animation: flameRise ${Math.random() * 2 + 1}s ease-out forwards;
+ pointer-events: none;
+ `
+
+ flame.appendChild(particle)
+
+ setTimeout(() => {
+ if (particle.parentNode) {
+ particle.parentNode.removeChild(particle)
+ }
+ }, 3000)
+ }
+
+ const interval = setInterval(createFlameParticle, 100)
+
+ return () => clearInterval(interval)
+ }, [])
+
+ return (
+
+
+
+
+
+
+ {/* Core flame */}
+
+
+
+ )
+}
diff --git a/components/flame-loop/flame-loop.tsx b/components/flame-loop/flame-loop.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..f88d5a44a5201e94a911191d2114b1d7bbc4cc6d
--- /dev/null
+++ b/components/flame-loop/flame-loop.tsx
@@ -0,0 +1,166 @@
+"use client"
+
+import { useEffect, useState } from "react"
+import { motion } from "framer-motion"
+import { Zap, Heart, Users, Award } from "lucide-react"
+
+const FlameLoop = () => {
+ const [feedItems, setFeedItems] = useState([])
+ const [userProgress, setUserProgress] = useState({
+ level: 4,
+ title: "Community Builder",
+ xp: 720,
+ nextLevel: 1000,
+ })
+
+ // Simulated real-time feed
+ useEffect(() => {
+ const mockFeed = [
+ {
+ id: 1,
+ user: "Fatima_K",
+ action: "completed HealthID course",
+ flb: 50,
+ location: "🇳🇬",
+ time: "2 min ago",
+ },
+ {
+ id: 2,
+ user: "Kwame_T",
+ action: "donated to Lagos Health Clinic",
+ flb: 100,
+ location: "🇬🇭",
+ time: "5 min ago",
+ },
+ {
+ id: 3,
+ user: "Lerato_M",
+ action: "started Solar Tech certification",
+ flb: 0,
+ location: "🇿🇦",
+ time: "8 min ago",
+ },
+ {
+ id: 4,
+ user: "You",
+ action: "earned 30 FLB from DAO vote",
+ flb: 30,
+ location: "📍 Your Location",
+ time: "Just now",
+ },
+ {
+ id: 5,
+ user: "Chinwe_O",
+ action: "formed new Tribe: Digital Farmers",
+ flb: 0,
+ location: "🇳🇬",
+ time: "15 min ago",
+ },
+ ]
+ setFeedItems(mockFeed)
+ }, [])
+
+ return (
+
+ {/* XP Progress Header */}
+
+
+
+
+
Level {userProgress.level}
+
{userProgress.title}
+
+
+
+
+
+ {userProgress.xp}/{userProgress.nextLevel} XP
+
+
+
+
+
+ {/* Infinite Achievement Feed */}
+
+ {feedItems.map((item, index) => (
+
+
+
{item.location}
+
+
+
+ {item.user}
+
+ {item.flb > 0 && (
+
+ +{item.flb} FLB
+
+
+ )}
+
+
{item.action}
+
+
+ {item.time}
+
+ ))}
+
+
+ {/* Quick Action Bar */}
+
+
+
+ Earn Now
+
+
+
+ Impact
+
+
+
+ Tribe
+
+
+
+ Quests
+
+
+
+ )
+}
+
+export default FlameLoop
diff --git a/components/flameborn-analytics-dashboard.tsx b/components/flameborn-analytics-dashboard.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..0641fb987605f5e1cea48ff6bc425fe130acf514
--- /dev/null
+++ b/components/flameborn-analytics-dashboard.tsx
@@ -0,0 +1,770 @@
+"use client"
+
+import { useState, useEffect, useCallback } from "react"
+import {
+ LineChart,
+ Line,
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Tooltip,
+ ResponsiveContainer,
+ AreaChart,
+ Area,
+ BarChart,
+ Bar,
+ PieChart,
+ Pie,
+ Cell,
+} from "recharts"
+import {
+ TrendingUp,
+ TrendingDown,
+ DollarSign,
+ Users,
+ Activity,
+ Flame,
+ RefreshCw,
+ Eye,
+ EyeOff,
+ AlertCircle,
+ BarChart3,
+ Globe,
+ Shield,
+ Zap,
+} from "lucide-react"
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
+import { Button } from "@/components/ui/button"
+import { Badge } from "@/components/ui/badge"
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
+import { Alert, AlertDescription } from "@/components/ui/alert"
+
+const FlameBornAnalyticsDashboard = () => {
+ const [isConnected, setIsConnected] = useState(false)
+ const [tokenData, setTokenData] = useState({
+ price: 0.0,
+ priceChange24h: 0,
+ volume24h: 0,
+ marketCap: 0,
+ totalSupply: 1000000000,
+ circulatingSupply: 750000000,
+ holders: 0,
+ transactions24h: 0,
+ burnedTokens: 25000000,
+ stakingRewards: 15000000,
+ })
+
+ const [priceHistory, setPriceHistory] = useState([])
+ const [volumeHistory, setVolumeHistory] = useState([])
+ const [holderDistribution, setHolderDistribution] = useState([])
+ const [topHolders, setTopHolders] = useState([])
+ const [recentTransactions, setRecentTransactions] = useState([])
+ const [healthcareMetrics, setHealthcareMetrics] = useState({
+ registeredWorkers: 0,
+ verifiedBirths: 0,
+ donationsDistributed: 0,
+ impactScore: 0,
+ })
+ const [isLoading, setIsLoading] = useState(true)
+ const [autoRefresh, setAutoRefresh] = useState(true)
+ const [showAddresses, setShowAddresses] = useState(false)
+ const [alerts, setAlerts] = useState([])
+
+ // Simulated Celo Web3 connection with FlameBorn-specific data
+ const connectToCelo = useCallback(async () => {
+ setIsLoading(true)
+ try {
+ // Simulate connection delay
+ await new Promise((resolve) => setTimeout(resolve, 2000))
+ setIsConnected(true)
+
+ // Generate realistic FlameBorn token data
+ const basePrice = 0.00125
+ const priceVariation = (Math.random() - 0.5) * 0.0002
+ const currentPrice = basePrice + priceVariation
+
+ const mockTokenData = {
+ price: currentPrice,
+ priceChange24h: (Math.random() - 0.5) * 20,
+ volume24h: Math.random() * 50000 + 10000,
+ marketCap: currentPrice * 750000000,
+ totalSupply: 1000000000,
+ circulatingSupply: 750000000,
+ holders: Math.floor(Math.random() * 1000) + 5000,
+ transactions24h: Math.floor(Math.random() * 500) + 200,
+ burnedTokens: 25000000,
+ stakingRewards: 15000000,
+ }
+
+ setTokenData(mockTokenData)
+
+ // Generate healthcare-specific metrics
+ setHealthcareMetrics({
+ registeredWorkers: Math.floor(Math.random() * 500) + 1200,
+ verifiedBirths: Math.floor(Math.random() * 100) + 450,
+ donationsDistributed: Math.floor(Math.random() * 50000) + 125000,
+ impactScore: Math.floor(Math.random() * 20) + 85,
+ })
+
+ // Generate price history (24 hours)
+ const priceData = []
+ for (let i = 23; i >= 0; i--) {
+ const hour = new Date()
+ hour.setHours(hour.getHours() - i)
+ priceData.push({
+ time: hour.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit" }),
+ price: basePrice + (Math.random() - 0.5) * 0.0003,
+ volume: Math.random() * 5000 + 1000,
+ impact: Math.random() * 100 + 50,
+ })
+ }
+ setPriceHistory(priceData)
+ setVolumeHistory(priceData)
+
+ // Generate holder distribution with Ubuntu philosophy focus
+ setHolderDistribution([
+ {
+ range: "Community (1-100)",
+ holders: 3500,
+ percentage: 70,
+ color: "#ff6b6b",
+ description: "Grassroots supporters",
+ },
+ {
+ range: "Advocates (100-1K)",
+ holders: 1000,
+ percentage: 20,
+ color: "#4ecdc4",
+ description: "Active participants",
+ },
+ {
+ range: "Guardians (1K-10K)",
+ holders: 400,
+ percentage: 8,
+ color: "#45b7d1",
+ description: "Community leaders",
+ },
+ {
+ range: "Healers (10K-100K)",
+ holders: 80,
+ percentage: 1.6,
+ color: "#f9ca24",
+ description: "Healthcare workers",
+ },
+ { range: "Elders (100K+)", holders: 20, percentage: 0.4, color: "#6c5ce7", description: "Core contributors" },
+ ])
+
+ // Generate top holders with role-based addresses
+ const holders = []
+ const roles = ["Guardian", "Healer", "Community", "Validator", "Foundation"]
+ for (let i = 0; i < 10; i++) {
+ holders.push({
+ address: `0x${Math.random().toString(16).substring(2, 10)}...${Math.random().toString(16).substring(2, 6)}`,
+ balance: Math.floor(Math.random() * 10000000) + 100000,
+ percentage: ((Math.floor(Math.random() * 1000) + 100) / 100).toFixed(2),
+ role: roles[Math.floor(Math.random() * roles.length)],
+ verified: Math.random() > 0.3,
+ })
+ }
+ setTopHolders(holders.sort((a, b) => b.balance - a.balance))
+
+ // Generate recent transactions with Ubuntu context
+ const transactions = []
+ const txTypes = ["Birth Registration", "Donation", "Staking Reward", "Community Vote", "Healthcare Payment"]
+ for (let i = 0; i < 20; i++) {
+ const date = new Date()
+ date.setMinutes(date.getMinutes() - Math.random() * 60)
+ transactions.push({
+ hash: `0x${Math.random().toString(16).substring(2, 10)}...`,
+ type: txTypes[Math.floor(Math.random() * txTypes.length)],
+ amount: Math.floor(Math.random() * 100000) + 1000,
+ price: basePrice + (Math.random() - 0.5) * 0.0001,
+ time: date.toLocaleTimeString(),
+ from: `0x${Math.random().toString(16).substring(2, 6)}...`,
+ to: `0x${Math.random().toString(16).substring(2, 6)}...`,
+ impact: Math.random() > 0.5,
+ })
+ }
+ setRecentTransactions(transactions)
+
+ // Check for alerts
+ const newAlerts = []
+ if (mockTokenData.priceChange24h > 10) {
+ newAlerts.push({ type: "positive", message: "FlameBorn price surged over 10% - Ubuntu spirit rising!" })
+ }
+ if (mockTokenData.volume24h > 40000) {
+ newAlerts.push({ type: "info", message: "High trading volume - Community engagement strong" })
+ }
+ if (healthcareMetrics.verifiedBirths > 500) {
+ newAlerts.push({ type: "positive", message: "Milestone reached: 500+ births verified this period!" })
+ }
+ setAlerts(newAlerts)
+ } catch (error) {
+ console.error("Connection failed:", error)
+ } finally {
+ setIsLoading(false)
+ }
+ }, [])
+
+ const refreshData = useCallback(() => {
+ if (isConnected) {
+ connectToCelo()
+ }
+ }, [isConnected, connectToCelo])
+
+ useEffect(() => {
+ connectToCelo()
+ }, [connectToCelo])
+
+ useEffect(() => {
+ let interval
+ if (autoRefresh && isConnected) {
+ interval = setInterval(refreshData, 30000) // Refresh every 30 seconds
+ }
+ return () => clearInterval(interval)
+ }, [autoRefresh, isConnected, refreshData])
+
+ const formatNumber = (num) => {
+ if (num >= 1e9) return (num / 1e9).toFixed(2) + "B"
+ if (num >= 1e6) return (num / 1e6).toFixed(2) + "M"
+ if (num >= 1e3) return (num / 1e3).toFixed(2) + "K"
+ return num.toFixed(2)
+ }
+
+ const formatCurrency = (num) => "$" + formatNumber(num)
+
+ const StatCard = ({ title, value, change, icon: Icon, color = "blue", description }) => (
+
+
+
+
+
{title}
+
{value}
+ {description &&
{description}
}
+ {change !== undefined && (
+
= 0 ? "text-green-600" : "text-red-600"}`}>
+ {change >= 0 ? : }
+ {Math.abs(change).toFixed(2)}%
+
+ )}
+
+
+
+
+
+ )
+
+ if (isLoading) {
+ return (
+
+
+
+
Connecting to Celo Network
+
Loading FlameBorn Ubuntu analytics...
+
+
+ )
+ }
+
+ return (
+
+ {/* Header */}
+
+
+
+
+
+
+
FlameBorn Analytics
+
Ubuntu-powered healthcare tokenomics on Celo
+
+
+
+
+
+ {isConnected ? "Connected to Celo" : "Disconnected"}
+
+
setAutoRefresh(!autoRefresh)}
+ className={autoRefresh ? "bg-orange-100 text-orange-600" : ""}
+ >
+
+ Auto Refresh
+
+
+
+ Refresh Data
+
+
+
+
+
+
+
+ {/* Alerts */}
+ {alerts.length > 0 && (
+
+ {alerts.map((alert, index) => (
+
+
+ {alert.message}
+
+ ))}
+
+ )}
+
+ {/* Main Stats Grid */}
+
+
+
+
+
+
+
+ {/* Healthcare Impact Stats */}
+
+
+
+
+
+
+
+ {/* Tabs for detailed analytics */}
+
+
+ Overview
+ Ubuntu Holders
+ Transactions
+ Impact Analytics
+
+
+
+
+ {/* Price Chart */}
+
+
+ FLAME Price History (24h)
+ Ubuntu value flow over time
+
+
+
+
+
+
+ `$${value.toFixed(4)}`} />
+ [`$${value.toFixed(6)}`, "Price"]} />
+
+
+
+
+
+
+ {/* Volume Chart */}
+
+
+ Trading Volume (24h)
+ Community engagement levels
+
+
+
+
+
+
+
+ [formatCurrency(value), "Volume"]} />
+
+
+
+
+
+
+ {/* Token Information */}
+
+
+ FLAME Token Details
+ Ubuntu tokenomics overview
+
+
+
+ Total Supply:
+ {formatNumber(tokenData.totalSupply)} FLAME
+
+
+ Circulating Supply:
+ {formatNumber(tokenData.circulatingSupply)} FLAME
+
+
+ Burned Tokens:
+ {formatNumber(tokenData.burnedTokens)} FLAME
+
+
+ Staking Rewards:
+ {formatNumber(tokenData.stakingRewards)} FLAME
+
+
+ 24h Transactions:
+ {tokenData.transactions24h.toLocaleString()}
+
+
+
+
+ {/* Network Stats */}
+
+
+ Network Information
+ Celo blockchain details
+
+
+
+ Blockchain:
+ Celo
+
+
+ Token Standard:
+ ERC-20
+
+
+ Decimals:
+ 18
+
+
+ Philosophy:
+ Ubuntu
+
+
+ Status:
+
+ Active
+
+
+
+
+
+
+
+
+
+ {/* Holder Distribution Chart */}
+
+
+ Ubuntu Community Distribution
+ How FLAME tokens are distributed across roles
+
+
+
+
+ `${percentage}%`}
+ >
+ {holderDistribution.map((entry, index) => (
+ |
+ ))}
+
+ [`${value}%`, props.payload.description]} />
+
+
+
+
+
+ {/* Distribution Breakdown */}
+
+
+ Role-Based Distribution
+ Ubuntu community structure
+
+
+ {holderDistribution.map((dist, index) => (
+
+
+
+
+
{dist.range}
+
{dist.description}
+
+
+
+
{dist.holders.toLocaleString()}
+
{dist.percentage}%
+
+
+ ))}
+
+
+
+ {/* Top Holders */}
+
+
+
+
+ Ubuntu Leaders
+ Top FLAME token holders by role
+
+
setShowAddresses(!showAddresses)}>
+ {showAddresses ? : }
+ {showAddresses ? "Hide" : "Show"} Addresses
+
+
+
+
+
+
+
+
+ Rank
+ Address
+ Role
+ Balance
+ %
+
+
+
+ {topHolders.map((holder, index) => (
+
+ #{index + 1}
+
+ {showAddresses ? holder.address : "•••••••••••••••"}
+
+
+
+ {holder.role}
+ {holder.verified && " ✓"}
+
+
+ {formatNumber(holder.balance)} FLAME
+ {holder.percentage}%
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+ Recent Ubuntu Transactions
+ Latest community activities on the FlameBorn network
+
+
+
+
+
+
+ Hash
+ Type
+ Amount
+ Impact
+ Time
+
+
+
+ {recentTransactions.map((tx, index) => (
+
+ {tx.hash}
+
+
+ {tx.type}
+
+
+ {formatNumber(tx.amount)} FLAME
+
+ {tx.impact ? (
+
+ High
+
+ ) : (
+ Standard
+ )}
+
+ {tx.time}
+
+ ))}
+
+
+
+
+
+
+
+
+
+ {/* Impact Over Time */}
+
+
+ Ubuntu Impact Metrics
+ Healthcare and community impact over time
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Impact Metrics */}
+
+
+ Community Health Metrics
+ Real-world Ubuntu impact
+
+
+
+
Lives Touched
+
+ {formatNumber(healthcareMetrics.verifiedBirths * 3.2)} people
+
+
Through birth registrations
+
+
+
Healthcare Access
+
+ {formatNumber(healthcareMetrics.registeredWorkers * 150)} patients
+
+
Monthly reach capacity
+
+
+
Economic Impact
+
+ {formatCurrency(healthcareMetrics.donationsDistributed * 2.1)}
+
+
Total economic value created
+
+
+
Ubuntu Network Effect
+
+ {((healthcareMetrics.impactScore / 100) * tokenData.holders).toFixed(0)} connections
+
+
Active community bonds
+
+
+
+
+ {/* Ubuntu Philosophy Impact */}
+
+
+ Ubuntu Philosophy in Action
+ "I am because we are" - Measuring collective impact
+
+
+
+
+
🤝
+
Community Bonds
+
+ {Math.floor(tokenData.holders * 0.73)}
+
+
Active connections
+
+
+
🌱
+
Growth Together
+
{healthcareMetrics.impactScore}%
+
Collective progress
+
+
+
💫
+
Shared Prosperity
+
+ {formatNumber(tokenData.volume24h / tokenData.holders)}
+
+
Value per person
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default FlameBornAnalyticsDashboard
diff --git a/components/flameborn-logo.tsx b/components/flameborn-logo.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..50aee69bb4f8078b2adf4d230876cfbf6bf4f565
--- /dev/null
+++ b/components/flameborn-logo.tsx
@@ -0,0 +1,37 @@
+"use client"
+
+import Link from "next/link"
+import { motion } from "framer-motion"
+
+export function FlamebornLogo() {
+ return (
+
+
+
+ FLAMEBORN
+
+
+
+
+ )
+}
diff --git a/components/flb-token-dashboard.tsx b/components/flb-token-dashboard.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..76ee462b181d8c7225ddb3d3068a83b7d1300d30
--- /dev/null
+++ b/components/flb-token-dashboard.tsx
@@ -0,0 +1,242 @@
+"use client"
+
+import { useState } from "react"
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Label } from "@/components/ui/label"
+import { Badge } from "@/components/ui/badge"
+import { Coins, Send, RefreshCw, ExternalLink, AlertCircle } from "lucide-react"
+import { useWallet } from "@/providers/wallet-provider"
+import { transferFLBTokens } from "@/lib/contract-service"
+import { toast } from "@/hooks/use-toast"
+import { FLB_TOKEN_ADDRESSES } from "@/lib/constants"
+
+export function FLBTokenDashboard() {
+ const { connected, address, balance, chainId, signer, updateBalance } = useWallet()
+ const [transferTo, setTransferTo] = useState("")
+ const [transferAmount, setTransferAmount] = useState("")
+ const [transferring, setTransferring] = useState(false)
+ const [refreshing, setRefreshing] = useState(false)
+
+ const isOnAlfajores = chainId === 44787
+ const contractAddress = isOnAlfajores ? FLB_TOKEN_ADDRESSES.CELO_ALFAJORES : null
+
+ const handleTransfer = async () => {
+ if (!signer || !transferTo || !transferAmount) {
+ toast({
+ title: "Invalid Input",
+ description: "Please enter a valid address and amount",
+ variant: "destructive",
+ })
+ return
+ }
+
+ if (Number.parseFloat(transferAmount) > Number.parseFloat(balance.flb)) {
+ toast({
+ title: "Insufficient Balance",
+ description: "You don't have enough FLB tokens",
+ variant: "destructive",
+ })
+ return
+ }
+
+ setTransferring(true)
+ try {
+ const result = await transferFLBTokens(signer, transferTo, transferAmount)
+
+ toast({
+ title: "Transfer Initiated",
+ description: `Transaction hash: ${result.hash.slice(0, 10)}...`,
+ })
+
+ // Wait for transaction confirmation
+ await result.transaction.wait()
+
+ toast({
+ title: "Transfer Successful",
+ description: `Sent ${transferAmount} FLB to ${transferTo.slice(0, 10)}...`,
+ })
+
+ // Reset form and update balance
+ setTransferTo("")
+ setTransferAmount("")
+ await updateBalance()
+ } catch (error: any) {
+ console.error("Transfer failed:", error)
+ toast({
+ title: "Transfer Failed",
+ description: error.message || "Failed to transfer FLB tokens",
+ variant: "destructive",
+ })
+ } finally {
+ setTransferring(false)
+ }
+ }
+
+ const handleRefreshBalance = async () => {
+ setRefreshing(true)
+ try {
+ await updateBalance()
+ toast({
+ title: "Balance Updated",
+ description: "Your token balance has been refreshed",
+ })
+ } catch (error) {
+ toast({
+ title: "Refresh Failed",
+ description: "Failed to update balance",
+ variant: "destructive",
+ })
+ } finally {
+ setRefreshing(false)
+ }
+ }
+
+ const openContractOnExplorer = () => {
+ if (contractAddress) {
+ window.open(`https://alfajores.celoscan.io/address/${contractAddress}`, "_blank")
+ }
+ }
+
+ if (!connected) {
+ return (
+
+
+
+
+ FLB Token Dashboard
+
+ Connect your wallet to view and manage your FLB tokens
+
+
+ Please connect your wallet to continue
+
+
+ )
+ }
+
+ return (
+
+ {/* Balance Card */}
+
+
+
+
+
+ FLB Token Balance
+
+
+
+
+
+ Your current FLB token holdings on Celo Alfajores
+
+
+
+
+
{Number.parseFloat(balance.flb).toFixed(4)}
+
FLB Tokens
+
+
+ {!isOnAlfajores && (
+
+
+
+ Switch to Celo Alfajores testnet to interact with FLB tokens
+
+
+ )}
+
+ {contractAddress && (
+
+
Contract Address:
+
+
+ {contractAddress.slice(0, 10)}...{contractAddress.slice(-8)}
+
+
+
+
+
+
+ )}
+
+
+
+
+ {/* Transfer Card */}
+
+
+
+
+ Transfer FLB Tokens
+
+ Send FLB tokens to another address
+
+
+
+
+ Recipient Address
+ setTransferTo(e.target.value)}
+ disabled={!isOnAlfajores}
+ />
+
+
+
+
Amount (FLB)
+
setTransferAmount(e.target.value)}
+ disabled={!isOnAlfajores}
+ />
+
+ Available: {Number.parseFloat(balance.flb).toFixed(4)} FLB
+
+
+
+
+ {transferring ? "Transferring..." : "Transfer FLB"}
+
+
+
+
+
+ {/* Network Info */}
+
+
+ Network Information
+
+
+
+
+ Network:
+
+ {isOnAlfajores ? "Celo Alfajores" : "Other Network"}
+
+
+
+ Chain ID:
+ {chainId}
+
+
+ FLB Contract:
+ {contractAddress ? "Deployed" : "Not Available"}
+
+
+
+
+
+ )
+}
diff --git a/components/genesis-airdrop.tsx b/components/genesis-airdrop.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..7957de61583a97e58d78b52236b16d08ca552d54
--- /dev/null
+++ b/components/genesis-airdrop.tsx
@@ -0,0 +1,364 @@
+"use client"
+
+import { useState, useEffect } from "react"
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
+import { Button } from "@/components/ui/button"
+import { Badge } from "@/components/ui/badge"
+import { Progress } from "@/components/ui/progress"
+import { Alert, AlertDescription } from "@/components/ui/alert"
+import { motion } from "framer-motion"
+import { Gift, CheckCircle, Clock, AlertTriangle, ExternalLink, Copy, Flame, Users, Zap } from "lucide-react"
+import { useWallet } from "@/providers/wallet-provider"
+import { toast } from "@/hooks/use-toast"
+import { GENESIS_VALIDATORS, FLB_TOKEN_ADDRESSES, TESTNET_CONFIG } from "@/lib/constants"
+
+interface AirdropStatus {
+ address: string
+ status: "pending" | "processing" | "completed" | "failed"
+ amount: string
+ txHash?: string
+ timestamp?: number
+}
+
+export function GenesisAirdrop() {
+ const { connected, address, chainId, balance, updateBalance } = useWallet()
+ const [airdropStatuses, setAirdropStatuses] = useState([])
+ const [isEligible, setIsEligible] = useState(false)
+ const [isClaiming, setIsClaiming] = useState(false)
+ const [claimStatus, setClaimStatus] = useState<"none" | "processing" | "completed" | "failed">("none")
+
+ const isOnAlfajores = chainId === 44787
+
+ useEffect(() => {
+ // Initialize airdrop statuses for genesis validators
+ const initialStatuses: AirdropStatus[] = GENESIS_VALIDATORS.map((addr) => ({
+ address: addr,
+ status: "pending",
+ amount: TESTNET_CONFIG.AIRDROP_AMOUNT,
+ }))
+ setAirdropStatuses(initialStatuses)
+
+ // Check if current user is eligible
+ if (address && GENESIS_VALIDATORS.includes(address)) {
+ setIsEligible(true)
+ }
+ }, [address])
+
+ const handleClaimAirdrop = async () => {
+ if (!address || !isEligible) return
+
+ setIsClaiming(true)
+ setClaimStatus("processing")
+
+ try {
+ // Simulate airdrop claim process
+ await new Promise((resolve) => setTimeout(resolve, 3000))
+
+ // Update status for this address
+ setAirdropStatuses((prev) =>
+ prev.map((status) =>
+ status.address === address
+ ? {
+ ...status,
+ status: "completed",
+ txHash: "0x" + Math.random().toString(16).substr(2, 64),
+ timestamp: Date.now(),
+ }
+ : status,
+ ),
+ )
+
+ setClaimStatus("completed")
+ await updateBalance()
+
+ toast({
+ title: "Airdrop Claimed Successfully",
+ description: `You received ${TESTNET_CONFIG.AIRDROP_AMOUNT} FLB tokens`,
+ })
+ } catch (error) {
+ setClaimStatus("failed")
+ setAirdropStatuses((prev) =>
+ prev.map((status) => (status.address === address ? { ...status, status: "failed" } : status)),
+ )
+
+ toast({
+ title: "Airdrop Failed",
+ description: "Please try again or contact support",
+ variant: "destructive",
+ })
+ } finally {
+ setIsClaiming(false)
+ }
+ }
+
+ const copyAddress = (addr: string) => {
+ navigator.clipboard.writeText(addr)
+ toast({
+ title: "Address Copied",
+ description: "Address copied to clipboard",
+ })
+ }
+
+ const openTxOnExplorer = (txHash: string) => {
+ window.open(`https://alfajores.celoscan.io/tx/${txHash}`, "_blank")
+ }
+
+ const getStatusIcon = (status: string) => {
+ switch (status) {
+ case "completed":
+ return
+ case "processing":
+ return
+ case "failed":
+ return
+ default:
+ return
+ }
+ }
+
+ const getStatusColor = (status: string) => {
+ switch (status) {
+ case "completed":
+ return "text-green-500"
+ case "processing":
+ return "text-yellow-500"
+ case "failed":
+ return "text-red-500"
+ default:
+ return "text-gray-500"
+ }
+ }
+
+ const completedCount = airdropStatuses.filter((s) => s.status === "completed").length
+ const totalCount = airdropStatuses.length
+ const progressPercentage = (completedCount / totalCount) * 100
+
+ return (
+
+ {/* Header */}
+
+
+
+
+
+
+
+ Genesis Airdrop
+
+ Initial token distribution to founding validators
+
+
+
+
+
+
+
+
{TESTNET_CONFIG.AIRDROP_AMOUNT}
+
FLB per Validator
+
+
+
{totalCount}
+
Genesis Validators
+
+
+
{completedCount}
+
Tokens Distributed
+
+
+
+
+
+ Distribution Progress
+ {progressPercentage.toFixed(1)}%
+
+
+
+
+
+
+ {/* User Eligibility */}
+ {connected && (
+
+
+
+
+ Your Eligibility Status
+
+
+
+ {!isOnAlfajores ? (
+
+
+
+ Please switch to Celo Alfajores testnet to participate in the genesis airdrop
+
+
+ ) : isEligible ? (
+
+
+
+ You are eligible for the genesis airdrop!
+
+
+
+
+ Airdrop Amount:
+ {TESTNET_CONFIG.AIRDROP_AMOUNT} FLB
+
+
+
Your Address:
+
+
+ {address?.slice(0, 10)}...{address?.slice(-8)}
+
+ copyAddress(address!)} className="h-6 w-6 p-0">
+
+
+
+
+
+
+
+ {isClaiming ? (
+ <>
+
+ Claiming Airdrop...
+ >
+ ) : claimStatus === "completed" ? (
+ <>
+
+ Airdrop Claimed
+ >
+ ) : (
+ <>
+
+ Claim Genesis Airdrop
+ >
+ )}
+
+
+ ) : (
+
+
+
+ Your address is not eligible for the genesis airdrop. Only founding validators receive initial tokens.
+
+
+ )}
+
+
+ )}
+
+ {/* Airdrop Status List */}
+
+
+
+
+ Genesis Validator Airdrops
+
+
+ Real-time status of token distribution to founding validators
+
+
+
+
+ {airdropStatuses.map((status, index) => (
+
+
+ {getStatusIcon(status.status)}
+
+
+
+ {status.address.slice(0, 10)}...{status.address.slice(-8)}
+
+ copyAddress(status.address)}
+ className="h-6 w-6 p-0"
+ >
+
+
+
+
+ {status.timestamp && new Date(status.timestamp).toLocaleString()}
+
+
+
+
+
+
+
{status.amount} FLB
+
{status.status}
+
+
+ {status.txHash && (
+
openTxOnExplorer(status.txHash!)}
+ className="h-8 w-8 p-0"
+ >
+
+
+ )}
+
+
+ ))}
+
+
+
+
+ {/* Contract Information */}
+
+
+
+
+ Contract Information
+
+
+
+
+
+
FLB Token Contract:
+
+
+ {FLB_TOKEN_ADDRESSES.CELO_ALFAJORES.slice(0, 10)}...
+ {FLB_TOKEN_ADDRESSES.CELO_ALFAJORES.slice(-8)}
+
+
+ window.open(`https://alfajores.celoscan.io/address/${FLB_TOKEN_ADDRESSES.CELO_ALFAJORES}`, "_blank")
+ }
+ className="h-6 w-6 p-0"
+ >
+
+
+
+
+
+ Network:
+ Celo Alfajores
+
+
+ Total Supply:
+ {TESTNET_CONFIG.GENESIS_SUPPLY} FLB
+
+
+
+
+
+ )
+}
diff --git a/components/guardian/guardian-voting-interface.tsx b/components/guardian/guardian-voting-interface.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..ecc4fb626ff6d37a24309122c8824a50042d91a3
--- /dev/null
+++ b/components/guardian/guardian-voting-interface.tsx
@@ -0,0 +1,833 @@
+"use client"
+
+import type React from "react"
+import { useState, useEffect } from "react"
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
+import { Button } from "@/components/ui/button"
+import { Badge } from "@/components/ui/badge"
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
+import { Progress } from "@/components/ui/progress"
+import { Alert, AlertDescription } from "@/components/ui/alert"
+import { Input } from "@/components/ui/input"
+import { Textarea } from "@/components/ui/textarea"
+import { motion, AnimatePresence } from "framer-motion"
+import {
+ Shield,
+ AlertTriangle,
+ CheckCircle,
+ Gavel,
+ Eye,
+ TrendingUp,
+ Clock,
+ ShieldCheck,
+ ExternalLink,
+ UserPlus,
+} from "lucide-react"
+import { useToast } from "@/hooks/use-toast"
+
+interface FraudAlert {
+ id: string
+ facilityName: string
+ facilityAddress: string
+ alertType: "fake_birth" | "duplicate_claim" | "missing_heartbeat" | "suspicious_activity"
+ description: string
+ evidence: string[]
+ reportedBy: string
+ timestamp: number
+ severity: "low" | "medium" | "high" | "critical"
+ status: "pending" | "investigating" | "confirmed" | "dismissed"
+ votesFor: number
+ votesAgainst: number
+ totalVotes: number
+ requiredVotes: number
+}
+
+interface Guardian {
+ address: string
+ name: string
+ region: string
+ reputation: number
+ votingPower: number
+ facilitiesMonitored: number
+ successfulDetections: number
+ verified: boolean
+}
+
+export type RoleOption = "ALL" | "Doctor" | "Nurse" | "Outreach"
+export type RegistrationStep = "welcome" | "form" | "verify" | "success"
+
+// Verification Badge Component
+function VerificationBadge() {
+ return (
+
+ Verified
+
+ )
+}
+
+// NFT Link Component
+function NFTLink({ wallet }: { wallet: string }) {
+ const url = `https://testnet.bscscan.com/address/${wallet}`
+ return (
+
+
+
+ )
+}
+
+// Role Filter Tabs Component
+function RoleFilterTabs({
+ value,
+ onChange,
+}: {
+ value: RoleOption
+ onChange: (v: RoleOption) => void
+}) {
+ const roles: RoleOption[] = ["ALL", "Doctor", "Nurse", "Outreach"]
+
+ return (
+ onChange(v as RoleOption)} className="w-full">
+
+ {roles.map((r) => (
+
+ {r === "ALL" ? "All" : r}
+
+ ))}
+
+
+ )
+}
+
+// Healer Registration Canvas Component
+function HealerRegistrationCanvas({
+ onComplete,
+}: {
+ onComplete?: () => void
+}) {
+ const [step, setStep] = useState("welcome")
+ const [formData, setFormData] = useState({
+ name: "",
+ location: "",
+ credentials: "",
+ role: "Doctor" as RoleOption,
+ })
+ const [isSubmitting, setIsSubmitting] = useState(false)
+ const { toast } = useToast()
+
+ const next = () => {
+ if (step === "welcome") setStep("form")
+ else if (step === "form") setStep("verify")
+ else if (step === "verify") setStep("success")
+ else if (step === "success" && onComplete) onComplete()
+ }
+
+ const handleChange = (e: React.ChangeEvent) => {
+ const { name, value } = e.target
+ setFormData((prev) => ({ ...prev, [name]: value }))
+ }
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault()
+ setIsSubmitting(true)
+
+ // Simulate registration process
+ setTimeout(() => {
+ toast({
+ title: "Registration Submitted",
+ description: "Your application has been submitted for Guardian review.",
+ })
+ setIsSubmitting(false)
+ setStep("success")
+ }, 1500)
+ }
+
+ return (
+
+
+ {step === "welcome" && (
+
+
+
+ Welcome, Healer!
+
+
+
+ Begin your journey to join the Flameborn network. Click below to start registration.
+
+
+
+ Start Registration
+
+
+
+
+
+ )}
+
+ {step === "form" && (
+
+ {/* Registration Form */}
+
+
+ Health Actor Registration
+
+
+
+
+
+ Full Name
+
+
+
+
+
+
+ Location
+
+
+
+
+
+
+ Medical Credentials
+
+
+
+
+
+ {isSubmitting ? "Submitting..." : "Submit Registration"}
+
+
+
+
+
+ {/* Role Selection */}
+
+
Select Your Role
+
+ setFormData((prev) => ({ ...prev, role }))} />
+
+
+
+
Role Benefits
+
+ • Access to Guardian voting system
+ • Participate in fraud detection
+ • Earn reputation tokens
+ • Monitor health facilities
+
+
+
+
+
+ )}
+
+ {step === "verify" && (
+
+
+
+ Verification Process
+
+
+
+
+
+
+
+ Your credentials are being verified by the Guardian network. This process ensures the integrity of
+ the Flameborn health system.
+
+
+
Verification Steps
+
+ ✅ Application submitted
+ 🔄 Guardian review in progress
+ ⏳ Credential verification
+ ⏳ Community voting
+
+
+
+
+ Continue
+
+
+
+
+ )}
+
+ {step === "success" && (
+
+
+
+ Registration Complete!
+
+
+
+
+
+
+ Thank you for registering. Your credentials will be reviewed and you will be notified upon
+ verification.
+
+
+ Back to Healers
+
+
+
+
+ )}
+
+
+ )
+}
+
+// Main Guardian Voting Interface Component
+export const GuardianVotingInterface: React.FC = () => {
+ const [fraudAlerts, setFraudAlerts] = useState([])
+ const [guardians, setGuardians] = useState([])
+ const [selectedAlert, setSelectedAlert] = useState(null)
+ const [userVotes, setUserVotes] = useState<{ [alertId: string]: boolean }>({})
+ const [showRegistration, setShowRegistration] = useState(false)
+ const [roleFilter, setRoleFilter] = useState("ALL")
+ const { toast } = useToast()
+
+ useEffect(() => {
+ loadFraudAlerts()
+ loadGuardians()
+ }, [])
+
+ const loadFraudAlerts = () => {
+ const mockAlerts: FraudAlert[] = [
+ {
+ id: "ALERT001",
+ facilityName: "Suspicious Health Center",
+ facilityAddress: "0x789abc...def123",
+ alertType: "fake_birth",
+ description: "Multiple BabyNFTs minted without corresponding heartbeat data",
+ evidence: [
+ "5 babies minted in 1 hour",
+ "No IoT heartbeat signals detected",
+ "Same biometric hash used twice",
+ "USSD confirmations missing",
+ ],
+ reportedBy: "Guardian Network AI",
+ timestamp: Date.now() - 2 * 60 * 60 * 1000,
+ severity: "critical",
+ status: "pending",
+ votesFor: 8,
+ votesAgainst: 1,
+ totalVotes: 9,
+ requiredVotes: 15,
+ },
+ {
+ id: "ALERT002",
+ facilityName: "Rural Health Post",
+ facilityAddress: "0x456def...789abc",
+ alertType: "missing_heartbeat",
+ description: "BabyNFT shows no heartbeat activity for 48+ hours",
+ evidence: [
+ "Last heartbeat: 52 hours ago",
+ "No device malfunction reported",
+ "Mother unreachable via USSD",
+ "Guardian unable to verify in person",
+ ],
+ reportedBy: "0xguardian123...abc",
+ timestamp: Date.now() - 4 * 60 * 60 * 1000,
+ severity: "high",
+ status: "investigating",
+ votesFor: 3,
+ votesAgainst: 2,
+ totalVotes: 5,
+ requiredVotes: 15,
+ },
+ ]
+ setFraudAlerts(mockAlerts)
+ if (mockAlerts.length > 0) setSelectedAlert(mockAlerts[0])
+ }
+
+ const loadGuardians = () => {
+ const mockGuardians: Guardian[] = [
+ {
+ address: "0xguardian1...abc",
+ name: "Elder Chioma",
+ region: "Lagos, Nigeria",
+ reputation: 95,
+ votingPower: 100,
+ facilitiesMonitored: 12,
+ successfulDetections: 8,
+ verified: true,
+ },
+ {
+ address: "0xguardian2...def",
+ name: "Dr. Kwame",
+ region: "Accra, Ghana",
+ reputation: 88,
+ votingPower: 85,
+ facilitiesMonitored: 8,
+ successfulDetections: 5,
+ verified: true,
+ },
+ {
+ address: "0xguardian3...ghi",
+ name: "Nurse Amara",
+ region: "Nairobi, Kenya",
+ reputation: 92,
+ votingPower: 90,
+ facilitiesMonitored: 6,
+ successfulDetections: 4,
+ verified: false,
+ },
+ ]
+ setGuardians(mockGuardians)
+ }
+
+ const handleVote = async (alertId: string, support: boolean) => {
+ try {
+ setUserVotes((prev) => ({ ...prev, [alertId]: support }))
+
+ setFraudAlerts((prev) =>
+ prev.map((alert) =>
+ alert.id === alertId
+ ? {
+ ...alert,
+ votesFor: support ? alert.votesFor + 1 : alert.votesFor,
+ votesAgainst: !support ? alert.votesAgainst + 1 : alert.votesAgainst,
+ totalVotes: alert.totalVotes + 1,
+ }
+ : alert,
+ ),
+ )
+
+ toast({
+ title: "Vote Recorded",
+ description: `Your ${support ? "GUILTY" : "INNOCENT"} vote has been recorded`,
+ })
+ } catch (error: any) {
+ toast({
+ title: "Voting Failed",
+ description: error.message || "Failed to record vote",
+ variant: "destructive",
+ })
+ }
+ }
+
+ const getSeverityColor = (severity: string) => {
+ switch (severity) {
+ case "critical":
+ return "text-red-400 bg-red-500/20"
+ case "high":
+ return "text-orange-400 bg-orange-500/20"
+ case "medium":
+ return "text-yellow-400 bg-yellow-500/20"
+ case "low":
+ return "text-blue-400 bg-blue-500/20"
+ default:
+ return "text-gray-400 bg-gray-500/20"
+ }
+ }
+
+ const getStatusIcon = (status: string) => {
+ switch (status) {
+ case "pending":
+ return
+ case "investigating":
+ return
+ case "confirmed":
+ return
+ case "dismissed":
+ return
+ default:
+ return
+ }
+ }
+
+ const filteredGuardians = guardians.filter((guardian) => {
+ if (roleFilter === "ALL") return true
+ // This would be based on actual role data in a real implementation
+ return guardian.name.toLowerCase().includes(roleFilter.toLowerCase())
+ })
+
+ if (showRegistration) {
+ return (
+
+ setShowRegistration(false)} />
+
+ )
+ }
+
+ return (
+
+ {/* Header */}
+
+
+
Guardian Council
+
Community-driven fraud detection and facility accountability system.
+
+
setShowRegistration(true)}
+ className="bg-orange-500 hover:bg-orange-600 text-white flex items-center gap-2"
+ >
+
+ Join as Healer
+
+
+
+ {/* Stats */}
+
+
+
+ Active Guardians
+
+
+
+ {guardians.length}
+ Community validators
+
+
+
+
+
+ Pending Alerts
+
+
+
+
+ {fraudAlerts.filter((a) => a.status === "pending").length}
+
+ Require voting
+
+
+
+
+
+ Cases Resolved
+
+
+
+
+ {fraudAlerts.filter((a) => a.status === "confirmed" || a.status === "dismissed").length}
+
+ This week
+
+
+
+
+
+ Fraud Prevention
+
+
+
+ 98.7%
+ Accuracy rate
+
+
+
+
+ {/* Main Interface */}
+
+
+
+ Fraud Alerts
+
+
+ Guardian Network
+
+
+ Slashing Events
+
+
+
+
+
+ {/* Alert List */}
+
+
+
+
+ Active Fraud Alerts
+
+
+
+ {fraudAlerts.map((alert) => (
+ setSelectedAlert(alert)}
+ >
+
+
+ {getStatusIcon(alert.status)}
+
{alert.facilityName}
+
+
{alert.severity}
+
+
+
{alert.description}
+
+
+ Votes: {alert.totalVotes}/{alert.requiredVotes}
+
+ For: {alert.votesFor}
+ Against: {alert.votesAgainst}
+
+
+
+ ))}
+
+
+
+ {/* Alert Details */}
+ {selectedAlert && (
+
+
+
+
+ Case #{selectedAlert.id}
+
+
+ {selectedAlert.facilityName} • {selectedAlert.alertType.replace("_", " ")}
+
+
+
+
+
+ {selectedAlert.description}
+
+
+
+
Evidence
+
+ {selectedAlert.evidence.map((evidence, idx) => (
+
+
+ {evidence}
+
+ ))}
+
+
+
+
+
+ Voting Progress
+
+ {selectedAlert.totalVotes}/{selectedAlert.requiredVotes}
+
+
+
+
+
+
+
+
Innocent
+
{selectedAlert.votesAgainst}
+
+
+
Guilty
+
{selectedAlert.votesFor}
+
+
+
+
+
+ View on blockchain
+
+
+ {!userVotes[selectedAlert.id] && selectedAlert.status === "pending" && (
+
+ handleVote(selectedAlert.id, false)}
+ className="flex-1 border-green-500 text-green-400 hover:bg-green-500/20"
+ >
+ Vote Innocent
+
+ handleVote(selectedAlert.id, true)}
+ className="flex-1 bg-red-500 hover:bg-red-600 text-white"
+ >
+ Vote Guilty
+
+
+ )}
+
+ {userVotes[selectedAlert.id] !== undefined && (
+
+
+ You voted: {userVotes[selectedAlert.id] ? "GUILTY" : "INNOCENT"}
+
+
Thank you for participating in community governance
+
+ )}
+
+
+ )}
+
+
+
+
+
+
+
+
+
+ {filteredGuardians.map((guardian) => (
+
+
+
+
+
+ {guardian.name}
+ {guardian.verified && }
+
+
+ {guardian.region}
+
+
+ {guardian.address}
+
+
+
+
+
Reputation
+
{guardian.reputation}%
+
+
+
Voting Power
+
{guardian.votingPower}
+
+
+
Facilities
+
{guardian.facilitiesMonitored}
+
+
+
Detections
+
{guardian.successfulDetections}
+
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+ Slashing Mechanism
+
+
+ Automatic penalties for facilities that fail community verification.
+
+
+
+
+
+
Slashing Conditions
+
+ • 3/5 Guardian votes confirm fraud → 50% FLB slashed
+ • Fake BabyNFT detected → 100% vesting forfeited
+ • Missing heartbeat data → 25% penalty
+ • USSD mother denial → Immediate investigation
+
+
+
+
Protection Mechanisms
+
+ • 48-hour appeal window for all slashing events
+ • Independent elder review for major penalties
+ • Technology failure exceptions (proven device malfunction)
+ • Community rehabilitation programs for first-time offenses
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/components/health-actor-registration.tsx b/components/health-actor-registration.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..f53a17964fe577a3f55398f45652c7031cf9ab05
--- /dev/null
+++ b/components/health-actor-registration.tsx
@@ -0,0 +1,178 @@
+"use client"
+
+import type React from "react"
+
+import { useState } from "react"
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Textarea } from "@/components/ui/textarea"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { motion } from "framer-motion"
+
+export function HealthActorRegistration() {
+ const [formData, setFormData] = useState({
+ name: "",
+ location: "",
+ credentials: "",
+ })
+ const [isSubmitting, setIsSubmitting] = useState(false)
+ const [result, setResult] = useState<{
+ success: boolean
+ message: string
+ } | null>(null)
+
+ const handleChange = (e: React.ChangeEvent) => {
+ const { name, value } = e.target
+ setFormData((prev) => ({
+ ...prev,
+ [name]: value,
+ }))
+ }
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault()
+ setIsSubmitting(true)
+ setResult(null)
+
+ // Add form validation
+ if (!formData.name.trim()) {
+ setResult({
+ success: false,
+ message: "Please enter your full name.",
+ })
+ setIsSubmitting(false)
+ return
+ }
+
+ if (!formData.location.trim()) {
+ setResult({
+ success: false,
+ message: "Please enter your location.",
+ })
+ setIsSubmitting(false)
+ return
+ }
+
+ if (!formData.credentials.trim() || formData.credentials.length < 20) {
+ setResult({
+ success: false,
+ message: "Please provide detailed credentials (at least 20 characters).",
+ })
+ setIsSubmitting(false)
+ return
+ }
+
+ try {
+ // In a real implementation, this would call the contract
+ // For testing data entry, we'll simulate a successful registration
+
+ // Simulate API delay
+ await new Promise((resolve) => setTimeout(resolve, 1500))
+
+ // Simulate success
+ const success = true
+
+ if (success) {
+ setResult({
+ success: true,
+ message: "Registration successful! Your application will be reviewed by the Flameborn DAO.",
+ })
+ setFormData({
+ name: "",
+ location: "",
+ credentials: "",
+ })
+ } else {
+ setResult({
+ success: false,
+ message: "Registration failed. Please try again or contact support.",
+ })
+ }
+ } catch (error) {
+ console.error("Error during registration:", error)
+ setResult({
+ success: false,
+ message: "An unexpected error occurred. Please try again.",
+ })
+ } finally {
+ setIsSubmitting(false)
+ }
+ }
+
+ return (
+
+
+
+ Health Actor Registration
+
+
+
+
+
+ Full Name
+
+
+
+
+
+
+ Location
+
+
+
+
+
+
+ Medical Credentials
+
+
+
+
+
+ {isSubmitting ? "Submitting..." : "Register as Health Actor"}
+
+
+ {result && (
+
+ {result.message}
+
+ )}
+
+
+
+
+ )
+}
diff --git a/components/healthcare-worker-card.tsx b/components/healthcare-worker-card.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..3db55db63887e57c6123d29b2a5d9dbc445e97c5
--- /dev/null
+++ b/components/healthcare-worker-card.tsx
@@ -0,0 +1,163 @@
+"use client"
+
+import { useState } from "react"
+import { Card, CardContent, CardFooter, CardHeader } from "@/components/ui/card"
+import { Badge } from "@/components/ui/badge"
+import { Button } from "@/components/ui/button"
+import { Progress } from "@/components/ui/progress"
+import { CheckCircle, MapPin, Clock, Users, AlertTriangle, Calendar, ChevronDown, ChevronUp, Heart } from "lucide-react"
+import type { HealthcareWorker } from "@/lib/types/healthcare-worker" // Adjusted import path
+import Image from "next/image"
+
+interface HealthcareWorkerCardProps {
+ worker: HealthcareWorker
+ onDonate: (worker: HealthcareWorker) => void
+ showDistribution?: boolean
+}
+
+export function HealthcareWorkerCard({ worker, onDonate, showDistribution = true }: HealthcareWorkerCardProps) {
+ const [expanded, setExpanded] = useState(false)
+
+ // Format percentage to 1 decimal place
+ const formatPercentage = (percentage?: number) => {
+ if (percentage === undefined || isNaN(percentage)) return "0.0%"
+ return `${percentage.toFixed(1)}%`
+ }
+
+ // Calculate days since last active
+ const daysSinceActive = worker.impactMetrics?.lastActiveTimestamp
+ ? Math.floor((Date.now() - new Date(worker.impactMetrics.lastActiveTimestamp).getTime()) / (1000 * 60 * 60 * 24))
+ : Number.POSITIVE_INFINITY // Handle cases where lastActiveTimestamp might be missing or invalid
+
+ // Get activity status
+ const getActivityStatus = () => {
+ if (daysSinceActive === Number.POSITIVE_INFINITY || daysSinceActive < 0)
+ return { label: "Activity N/A", color: "text-gray-400" }
+ if (daysSinceActive === 0) return { label: "Active today", color: "text-green-400" }
+ if (daysSinceActive === 1) return { label: "Active yesterday", color: "text-green-400" }
+ if (daysSinceActive <= 7) return { label: `Active ${daysSinceActive} days ago`, color: "text-yellow-400" }
+ return { label: `Last active ${daysSinceActive} days ago`, color: "text-red-400" }
+ }
+
+ const activityStatus = getActivityStatus()
+
+ return (
+
+
+
+
+ {worker.profileImage && (
+
+
+
+ )}
+
+
{worker.name}
+
{worker.credentials}
+
+
+
+ {worker.isVerified && (
+
+ Verified
+
+ )}
+
+
+ {showDistribution && worker.distributionPercentage !== undefined && (
+
+
+ Impact Score
+ {formatPercentage(worker.distributionPercentage)}
+
+
+
+ )}
+
+
+
+
+
+
+ {worker.location?.name || "N/A"}
+ {(worker.location?.accessibilityScore || 0) >= 7 && (
+
+ Remote Area
+
+ )}
+
+
+
+
+ {activityStatus.label}
+
+
+ {expanded && (
+
+
{worker.bio || "No bio available."}
+
+ {worker.impactMetrics && (
+
+
+
+ {worker.impactMetrics.patientsServed || 0} patients
+
+
+
+
{worker.impactMetrics.criticalCases || 0} critical cases
+
+
+
+ {worker.impactMetrics.responseTimeAvg || 0}min response
+
+
+ )}
+
+ {worker.specializations && worker.specializations.length > 0 && (
+
+ {worker.specializations.map((spec, index) => (
+
+ {spec.name}
+
+ ))}
+
+ )}
+
+ )}
+
+
+
+
+ setExpanded(!expanded)}
+ className="text-blue-300 hover:text-blue-100 hover:bg-blue-950/50"
+ >
+ {expanded ? (
+ <>
+ Less Info
+ >
+ ) : (
+ <>
+ More Info
+ >
+ )}
+
+
+ onDonate(worker)} className="bg-blue-600 hover:bg-blue-700 text-white">
+ Donate
+
+
+
+ )
+}
diff --git a/components/healthcare-workers-grid.tsx b/components/healthcare-workers-grid.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..61057b12962e03b692c90b3834e9440d175f9734
--- /dev/null
+++ b/components/healthcare-workers-grid.tsx
@@ -0,0 +1,136 @@
+"use client"
+// Removed Card, CardContent as they are not directly used for the grid container itself
+import { Button } from "@/components/ui/button"
+import { Loader2, Info, RefreshCw } from "lucide-react"
+import { HealthcareWorkerCard } from "@/components/healthcare-worker-card"
+import type { HealthcareWorker } from "@/lib/types/healthcare-worker" // Adjusted import path
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog"
+import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
+import { Card, CardContent } from "@/components/ui/card" // Re-added for the "No workers found" case
+
+interface HealthcareWorkersGridProps {
+ workers: HealthcareWorker[] // Accept workers as a prop
+ onSelectWorker: (worker: HealthcareWorker) => void
+ isLoading?: boolean // Accept isLoading as a prop
+ onRefresh?: () => void // Optional refresh callback
+}
+
+export function HealthcareWorkersGrid({
+ workers,
+ onSelectWorker,
+ isLoading = false, // Default isLoading to false
+ onRefresh,
+}: HealthcareWorkersGridProps) {
+ // Removed local state for workers and isLoading as they are now props
+ // Removed loadWorkers and useEffect for fetching as data is passed via props
+
+ return (
+
+
+
Healthcare Workers
+
+
+ {onRefresh && ( // Only show refresh button if onRefresh is provided
+
+ {isLoading ? : }
+
+ )}
+
+
+
+
+
+
+
+
+
+ About Impact-Based Distribution
+
+ Flameborn uses an equitable distribution algorithm to ensure donations are allocated fairly based on
+ real-world impact.
+
+
+
+
+
+ How It Works
+
+
+ Our algorithm considers multiple factors to calculate each healthcare worker's impact score:
+
+
+ Number of patients served
+ Critical cases handled
+ Response time
+ Location accessibility
+ Specialization criticality
+ Recent activity
+
+
+
+
+
+ When you opt for a distributed donation, your contribution is automatically allocated among all
+ verified healthcare workers based on their impact scores. This ensures that those making the biggest
+ difference receive proportional support.
+
+
+
+ The system also includes an equity adjustment to prevent extreme disparities, ensuring that all
+ healthcare workers receive meaningful support. You can also choose to donate directly to a specific
+ worker.
+
+
+
+
+
+
+
+ {/* DonationDistributionChart is now part of EnhancedDonationForm or a separate section if needed */}
+ {/*
*/}
+
+ {isLoading ? (
+
+
+ Loading healthcare workers...
+
+ ) : workers.length === 0 ? (
+
+
+ No healthcare workers found.
+ {onRefresh && (
+
+ Try Refreshing
+
+ )}
+
+
+ ) : (
+
+ {workers.map((worker) => (
+
+ ))}
+
+ )}
+
+ )
+}
diff --git a/components/heartbeat-effect.tsx b/components/heartbeat-effect.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..99a2e68b52f7c139532141949e49a7056b12b868
--- /dev/null
+++ b/components/heartbeat-effect.tsx
@@ -0,0 +1,22 @@
+"use client"
+
+import { useEffect, useState } from "react"
+import { AudioHeartbeat } from "./audio-heartbeat"
+import { VisualHeartbeat } from "./visual-heartbeat"
+
+export function HeartbeatEffect() {
+ const [audioSupported, setAudioSupported] = useState(null)
+
+ useEffect(() => {
+ // Check if Web Audio API is supported
+ const isAudioSupported =
+ typeof window !== "undefined" && (window.AudioContext || (window as any).webkitAudioContext)
+ setAudioSupported(!!isAudioSupported)
+ }, [])
+
+ // During SSR or before we check, don't render anything
+ if (audioSupported === null) return null
+
+ // Render the appropriate component based on browser support
+ return audioSupported ? :
+}
diff --git a/components/home-hero.tsx b/components/home-hero.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..e5d872aa13cdb5ca411d93ca82e2a863f5be40f4
--- /dev/null
+++ b/components/home-hero.tsx
@@ -0,0 +1,122 @@
+"use client"
+
+import { useState, useEffect } from "react"
+import { Button } from "@/components/ui/button"
+import { Card, CardContent } from "@/components/ui/card"
+import { Badge } from "@/components/ui/badge"
+import { Flame, Shield, Users, Activity, ArrowRight, Zap } from "lucide-react"
+import Link from "next/link"
+import { useMobile } from "@/hooks/use-mobile"
+import { FlameAnimation } from "@/components/flame-animation"
+import { TypedIntroText } from "@/components/typed-intro-text"
+
+export function HomeHero() {
+ const [mounted, setMounted] = useState(false)
+ const isMobile = useMobile()
+
+ useEffect(() => {
+ setMounted(true)
+ }, [])
+
+ if (!mounted) {
+ return (
+
+ )
+ }
+
+ return (
+
+
+
+
+
+ {/* Flame Animation */}
+
+
+
+
+ {/* Main Title */}
+
+
+
+ Genesis Testnet Live
+
+
+
+
+ FlameBorn
+
+
+ Africa's Health Restoration Engine
+
+
+
+
+
+
+
+ {/* Stats Cards */}
+
+
+
+
+ 2
+ Active Validators
+
+
+
+
+
+
+ 1M+
+ Max Token Supply
+
+
+
+
+
+
+ Live
+ Testnet Status
+
+
+
+
+ {/* CTA Buttons */}
+
+
+
+
+ Enter Testnet
+
+
+
+
+
+
+
+ Become Validator
+
+
+
+
+ {/* Network Status */}
+
+
+
+
Connected to Celo Alfajores Testnet
+
+
+
+
+
+ )
+}
diff --git a/components/impact-analysis.tsx b/components/impact-analysis.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..fb170c5b33e8ebb47179d93ddf9e8212a8dc675d
--- /dev/null
+++ b/components/impact-analysis.tsx
@@ -0,0 +1,194 @@
+"use client"
+
+import { useState, useEffect } from "react"
+import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
+import { Button } from "@/components/ui/button"
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
+import { Loader2 } from "lucide-react"
+
+type ImpactData = {
+ region: string
+ healthWorkers: number
+ patientsServed: number
+ donationsReceived: number
+}
+
+type InsightType = {
+ title: string
+ content: string
+ type: "positive" | "neutral" | "action"
+}
+
+export function ImpactAnalysis() {
+ const [data, setData] = useState([])
+ const [insights, setInsights] = useState([])
+ const [isLoading, setIsLoading] = useState(true)
+ const [isGeneratingInsights, setIsGeneratingInsights] = useState(false)
+
+ // Simulated data - in production, this would come from your blockchain data
+ useEffect(() => {
+ // In a real implementation, this would fetch data from an API
+ // For now, initialize with empty data
+ setData([])
+ setIsLoading(false)
+ }, [])
+
+ const generateAIInsights = async () => {
+ setIsGeneratingInsights(true)
+
+ try {
+ // If there's no data, provide a default message
+ if (data.length === 0) {
+ setInsights([
+ {
+ title: "No Data Available",
+ content: "Start tracking impact by adding health workers and donations to see insights here.",
+ type: "neutral",
+ },
+ ])
+ setIsGeneratingInsights(false)
+ return
+ }
+
+ // Call the Grok API to analyze the data
+ const response = await fetch("/api/ai/analyze-impact", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({ impactData: data }),
+ })
+
+ if (!response.ok) {
+ throw new Error("Failed to generate insights")
+ }
+
+ const result = await response.json()
+ setInsights(result.insights)
+ } catch (error) {
+ console.error("Error generating insights:", error)
+ // Fallback insights if API fails
+ setInsights([
+ {
+ title: "Highest Impact Region",
+ content:
+ "The Southern Region has the highest number of health workers and patients served, suggesting effective resource allocation.",
+ type: "positive",
+ },
+ {
+ title: "Underserved Areas",
+ content:
+ "The Northern Region shows the lowest metrics across all categories and may need additional support.",
+ type: "action",
+ },
+ {
+ title: "Donation Efficiency",
+ content:
+ "On average, each health worker serves approximately 60 patients, indicating efficient use of resources.",
+ type: "neutral",
+ },
+ ])
+ } finally {
+ setIsGeneratingInsights(false)
+ }
+ }
+
+ return (
+
+
+ Impact Analysis
+ AI-powered insights on how Flameborn is making a difference
+
+
+ {isLoading ? (
+
+
+
+ ) : (
+ <>
+
+
+
+ Health Workers
+
+
+ Patients Served
+
+
+ Donations (BNB)
+
+
+
+
+
+
+
99
+
Total Health Workers
+
+
+
+
+
+
+
+
5,200
+
Patients Served
+
+
+
+
+
+
+
+
14.1 BNB
+
Total Donations
+
+
+
+
+
+
+
+
AI-Generated Insights
+
+ {isGeneratingInsights ? (
+ <>
+
+ Analyzing...
+ >
+ ) : (
+ "Generate Insights"
+ )}
+
+
+
+ {insights.length > 0 ? (
+
+ {insights.map((insight, index) => (
+
+
{insight.title}
+
{insight.content}
+
+ ))}
+
+ ) : (
+
+ Click "Generate Insights" to have AI analyze the impact data
+
+ )}
+
+ >
+ )}
+
+
+ )
+}
diff --git a/components/impact-mining/impact-mining.tsx b/components/impact-mining/impact-mining.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..a7da188cf5af848e85598830001a9e25fd264f3e
--- /dev/null
+++ b/components/impact-mining/impact-mining.tsx
@@ -0,0 +1,108 @@
+"use client"
+
+import { useState } from "react"
+import { motion } from "framer-motion"
+import { Zap, Heart, Pickaxe, Target } from "lucide-react"
+
+const ImpactMining = () => {
+ const [selectedCause, setSelectedCause] = useState("health")
+
+ const causes = [
+ { id: "health", title: "Health Clinics", icon: "🩺" },
+ { id: "education", title: "Digital Schools", icon: "💻" },
+ { id: "agriculture", title: "Farm Support", icon: "🌱" },
+ { id: "orphanage", title: "Orphan Care", icon: "🧒" },
+ ]
+
+ const miningActivities = [
+ { id: 1, title: "Complete Digital Literacy Course", personal: 50, impact: 10 },
+ { id: 2, title: "Participate in DAO Vote", personal: 10, impact: 5 },
+ { id: 3, title: "Achieve Quiz Mastery", personal: 20, impact: 3 },
+ { id: 4, title: "7-Day Learning Streak", personal: 30, impact: 10 },
+ ]
+
+ return (
+
+
+
+
+
Your Actions Build Africa
+
Every achievement generates FLB for you AND for African causes
+
+
+ {/* Cause Selection */}
+
+
+
Your Impact Focus
+
+
+ {causes.map((cause) => (
+
setSelectedCause(cause.id)}
+ whileHover={{ scale: 1.05 }}
+ whileTap={{ scale: 0.95 }}
+ >
+ {cause.icon}
+
+ {cause.title}
+
+
+ ))}
+
+
+ {/* Mining Activities */}
+ Action Opportunities
+
+ {miningActivities.map((activity, index) => (
+
+ {activity.title}
+
+
+
+ +{activity.personal} FLB
+ for you
+
+
+
+ +{activity.impact} FLB
+ for {causes.find((c) => c.id === selectedCause)?.title}
+
+
+
+ Start Mining
+
+
+ ))}
+
+
+ )
+}
+
+export default ImpactMining
diff --git a/components/journey-dashboard/journey-dashboard.tsx b/components/journey-dashboard/journey-dashboard.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..5b920d1cc37b70b406580ef0732694be19367147
--- /dev/null
+++ b/components/journey-dashboard/journey-dashboard.tsx
@@ -0,0 +1,130 @@
+"use client"
+
+import { motion } from "framer-motion"
+import { TrendingUp, Heart, Calendar } from "lucide-react"
+
+const JourneyDashboard = () => {
+ const stats = {
+ flbEarned: 1250,
+ flbDonated: 320,
+ livesImpacted: 42,
+ learningHours: 28,
+ coursesCompleted: 7,
+ streak: 14,
+ }
+
+ const achievements = [
+ { id: 1, title: "Digital Pioneer", icon: "💻", earned: true },
+ { id: 2, title: "Health Guardian", icon: "🩺", earned: true },
+ { id: 3, title: "Eco Warrior", icon: "🌱", earned: false },
+ { id: 4, title: "Flame Leader", icon: "🔥", earned: false },
+ ]
+
+ return (
+
+ My Sovereign Journey
+
+ {/* Wallet & Impact Stats */}
+
+
+
+ {stats.flbEarned}
+
+
+
+ {stats.flbDonated}
+
+
+
+ {stats.livesImpacted}
+
+
+
+ {stats.streak} days
+
+
+
+ {/* Achievements */}
+ My Achievements
+
+ {achievements.map((achievement) => (
+
+ {achievement.icon}
+
+ {achievement.title}
+
+ {!achievement.earned && Locked
}
+
+ ))}
+
+
+ {/* Impact Timeline */}
+ My Impact Timeline
+
+ {[
+ {
+ date: "Today",
+ action: "Earned 50 FLB from Health Course",
+ impact: "Funded health supplies for 2 people",
+ },
+ {
+ date: "Yesterday",
+ action: "Completed Digital Literacy Certification",
+ impact: "Increased earning potential by 40%",
+ },
+ {
+ date: "1 week ago",
+ action: "Donated 100 FLB to Kano Orphanage",
+ impact: "Provided meals for 5 children",
+ },
+ ].map((item, index) => (
+
+
+
+
{item.date}
+
{item.action}
+
{item.impact}
+
+
+ ))}
+
+
+ )
+}
+
+export default JourneyDashboard
diff --git a/components/journey/journey-featured.tsx b/components/journey/journey-featured.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..86b90d508e1c11d70e24baa2d37c2970dbe0567e
--- /dev/null
+++ b/components/journey/journey-featured.tsx
@@ -0,0 +1,92 @@
+import { Card } from "@/components/ui/card"
+import { Badge } from "@/components/ui/badge"
+import { ArrowRight } from "lucide-react"
+import Link from "next/link"
+
+export function JourneyFeatured() {
+ return (
+
+
Featured Stories
+
+
+
+
+
+
+
+
Featured
+
+ The Midnight Miracle: How One Nurse Changed a Village
+
+
+ When cholera struck at midnight, Nurse Amina had only her knowledge and a kerosene lamp. By dawn, she had
+ saved 12 lives and changed her community forever.
+
+
+ Read the full story
+
+
+
+
+
+
+
+
+
+
Technology
+
+ From Scrolls to Blockchain: A Digital Revolution in Rural Care
+
+
+ How blockchain verification is bringing recognition and resources to invisible healthcare heroes in the
+ most remote regions of Tanzania.
+
+
+ Read the full story
+
+
+
+
+
+
+
+
+
+
Community
+
+ The Village That Refused to Let Their Healer Go Unrecognized
+
+
+ After 30 years of service without recognition, see how one community used Flameborn to ensure their
+ beloved midwife received her due honor and support.
+
+
+ Read the full story
+
+
+
+
+
+ )
+}
diff --git a/components/journey/journey-hero.tsx b/components/journey/journey-hero.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..f1a2c2aa014530172f9de431bbc6f8f5c5c327cd
--- /dev/null
+++ b/components/journey/journey-hero.tsx
@@ -0,0 +1,28 @@
+import { BookOpen } from "lucide-react"
+import { PageIntro } from "@/components/page-intro"
+import { Button } from "@/components/ui/button"
+
+export function JourneyHero() {
+ return (
+ }
+ color="pulse"
+ >
+
+ Step into the Chronicles of Courage - powerful stories from the frontlines of African healthcare. These
+ narratives illuminate the path forward and inspire our collective mission to end health injustice.
+
+
+
+
+ Read Latest Stories
+
+
+ Submit Your Story
+
+
+
+ )
+}
diff --git a/components/journey/journey-posts.tsx b/components/journey/journey-posts.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..6a762e482b7cac0e62f4f2cf3aef7ccc7ebc0e04
--- /dev/null
+++ b/components/journey/journey-posts.tsx
@@ -0,0 +1,167 @@
+import { Card } from "@/components/ui/card"
+import { Avatar } from "@/components/ui/avatar"
+import { Badge } from "@/components/ui/badge"
+import { Button } from "@/components/ui/button"
+import { CalendarDays, Clock, ArrowRight, BookOpen } from "lucide-react"
+import Link from "next/link"
+
+// Replace this:
+// const BLOG_POSTS = [
+// {
+// id: 1,
+// title: "The Sacred Flame: Ancient Wisdom in Modern Healthcare",
+// excerpt:
+// "Exploring how traditional African healing practices are being integrated with modern medicine to create holistic care systems that honor cultural heritage while embracing innovation.",
+// image: "/BridgingWorldsOfHealing.png",
+// category: "Tradition",
+// author: {
+// name: "Dr. Kofi Mensah",
+// avatar: "/confident-african-doctor.png",
+// },
+// date: "May 15, 2023",
+// readTime: "8 min read",
+// },
+// {
+// id: 2,
+// title: "Guardians of the Scroll: Stories from the Verification Process",
+// excerpt:
+// "Meet the dedicated Guardians who verify healthcare workers and learn about the profound connections being formed through the blockchain verification process.",
+// image: "/connected-care-africa.png",
+// category: "Community",
+// author: {
+// name: "Amina Diallo",
+// avatar: "/focused-african-journalist.png",
+// },
+// date: "April 28, 2023",
+// readTime: "6 min read",
+// },
+// {
+// id: 3,
+// title: "From Recognition to Resources: The Economic Impact of Flameborn",
+// excerpt:
+// "How blockchain verification is creating economic opportunities for rural healthcare workers and transforming local economies through direct support mechanisms.",
+// image: "/placeholder.svg?height=300&width=600&query=African%20rural%20healthcare%20economy",
+// category: "Impact",
+// author: {
+// name: "Tendai Moyo",
+// avatar: "/placeholder.svg?height=40&width=40&query=African%20economist",
+// },
+// date: "April 10, 2023",
+// readTime: "10 min read",
+// },
+// {
+// id: 4,
+// title: "The Digital Hearth: Building Virtual Communities of Care",
+// excerpt:
+// "How technology is enabling healthcare workers across Africa to connect, share knowledge, and support each other despite vast geographical distances.",
+// image: "/placeholder.svg?height=300&width=600&query=African%20healthcare%20digital%20community",
+// category: "Technology",
+// author: {
+// name: "Zainab Osei",
+// avatar: "/placeholder.svg?height=40&width=40&query=African%20female%20tech%20specialist",
+// },
+// date: "March 22, 2023",
+// readTime: "7 min read",
+// },
+// ]
+
+// With:
+const BLOG_POSTS: any[] = []
+
+// Helper function to get badge color based on category
+const getCategoryColor = (category: string) => {
+ switch (category.toLowerCase()) {
+ case "tradition":
+ return "bg-flame/20 text-flame hover:bg-flame/30"
+ case "community":
+ return "bg-guardian/20 text-guardian hover:bg-guardian/30"
+ case "impact":
+ return "bg-pulse/20 text-pulse hover:bg-pulse/30"
+ case "technology":
+ return "bg-neon/20 text-neon hover:bg-neon/30"
+ default:
+ return "bg-gray-500/20 text-gray-400 hover:bg-gray-500/30"
+ }
+}
+
+export function JourneyPosts() {
+ return (
+
+
+
Latest Stories
+
+ All Categories
+ Tradition
+ Community
+ Impact
+ Technology
+
+
+
+ {BLOG_POSTS.length === 0 ? (
+
+
+
No Stories Yet
+
Be the first to share your Flameborn journey.
+
Submit Your Story
+
+ ) : (
+
+ {BLOG_POSTS.map((post) => (
+
+
+
+
+
+
+
+
{post.category}
+
+
+ {post.title}
+
+
+
{post.excerpt}
+
+
+
+
+
+
+
{post.author.name}
+
+
+
+
+ {post.date}
+
+ {post.readTime}
+
+
+
+
+ Continue reading
+
+
+
+
+ ))}
+
+ )}
+
+
+
+ Load More Stories
+
+
+
+ )
+}
diff --git a/components/journey/journey-sidebar.tsx b/components/journey/journey-sidebar.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..b32742465d3593e6265b79d6148dca84096cb969
--- /dev/null
+++ b/components/journey/journey-sidebar.tsx
@@ -0,0 +1,133 @@
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Badge } from "@/components/ui/badge"
+import { Search, Tag, TrendingUp, Calendar, BookOpen } from "lucide-react"
+import Link from "next/link"
+
+// Mock data for popular tags
+const POPULAR_TAGS = [
+ { id: 1, name: "Rural Healthcare", count: 24 },
+ { id: 2, name: "Traditional Wisdom", count: 18 },
+ { id: 3, name: "Blockchain", count: 15 },
+ { id: 4, name: "Community Support", count: 12 },
+ { id: 5, name: "Digital Health", count: 10 },
+ { id: 6, name: "Midwifery", count: 9 },
+ { id: 7, name: "Guardian Stories", count: 8 },
+ { id: 8, name: "African Medicine", count: 7 },
+]
+
+// Mock data for trending stories
+const TRENDING_STORIES = [
+ { id: 1, title: "How One CHW Transformed Maternal Care in Rural Kenya", views: 1245 },
+ { id: 2, title: "The Ancient Healing Rituals Finding New Life in Modern Care", views: 982 },
+ { id: 3, title: "From Isolation to Connection: The Digital Revolution in Rural Healthcare", views: 876 },
+ { id: 4, title: "Guardian Voices: The Elders Preserving Medical Traditions", views: 754 },
+]
+
+export function JourneySidebar() {
+ return (
+
+ {/* Search Card */}
+
+
+
+
+
+
+
+
+
+ {/* Popular Tags Card */}
+
+
+
+
+ Popular Tags
+
+
+
+
+ {POPULAR_TAGS.map((tag) => (
+
+ {tag.name} ({tag.count})
+
+ ))}
+
+
+
+
+ {/* Trending Stories Card */}
+
+
+
+
+ Trending Stories
+
+
+
+
+ {TRENDING_STORIES.map((story) => (
+
+
+ {story.title}
+
+
{story.views.toLocaleString()} views
+
+ ))}
+
+
+
+
+ {/* Archives Card */}
+
+
+
+
+ Archives
+
+
+
+
+
+ May 2023 (12)
+
+
+ April 2023 (15)
+
+
+ March 2023 (9)
+
+
+ February 2023 (7)
+
+
+ January 2023 (10)
+
+
+
+
+
+ {/* Resources Card */}
+
+
+
+
+ Resources
+
+
+
+ Access guides, research, and tools to support your healthcare journey.
+
+ Explore Resources
+
+
+
+
+ )
+}
diff --git a/components/language-selector.tsx b/components/language-selector.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..4c74ec838a64c6f7ac05fc22b0fa04b811f48ebc
--- /dev/null
+++ b/components/language-selector.tsx
@@ -0,0 +1,51 @@
+"use client"
+
+import { useState } from "react"
+import { Globe } from "lucide-react"
+import { Button } from "@/components/ui/button"
+import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
+
+const LANGUAGES = [
+ { code: "en", name: "English", flag: "🇬🇧" },
+ { code: "fr", name: "Français", flag: "🇫🇷" },
+ { code: "sw", name: "Kiswahili", flag: "🇰🇪" },
+ { code: "yo", name: "Yorùbá", flag: "🇳🇬" },
+ { code: "ha", name: "Hausa", flag: "🇳🇬" },
+ { code: "am", name: "አማርኛ", flag: "🇪🇹" },
+]
+
+export function LanguageSelector() {
+ const [currentLanguage, setCurrentLanguage] = useState(LANGUAGES[0])
+
+ const changeLanguage = (language: (typeof LANGUAGES)[0]) => {
+ setCurrentLanguage(language)
+ // In a real app, this would update the app's locale/translations
+ console.log(`Language changed to ${language.name}`)
+ }
+
+ return (
+
+
+
+
+ {currentLanguage.name}
+ {currentLanguage.flag}
+
+
+
+ {LANGUAGES.map((language) => (
+ changeLanguage(language)}
+ >
+ {language.flag}
+ {language.name}
+
+ ))}
+
+
+ )
+}
diff --git a/components/learn-earn-system.tsx b/components/learn-earn-system.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..eacf4817b3ad8c67e9c9b8621e862b97b37a35d8
--- /dev/null
+++ b/components/learn-earn-system.tsx
@@ -0,0 +1,575 @@
+"use client"
+
+import { useState, useEffect } from "react"
+import { motion, AnimatePresence } from "framer-motion"
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
+import { Button } from "@/components/ui/button"
+import { Progress } from "@/components/ui/progress"
+import { Badge } from "@/components/ui/badge"
+import { Textarea } from "@/components/ui/textarea"
+import { BookOpen, Brain, Heart, Video, CheckCircle, Star, Zap, Trophy, Clock } from "lucide-react"
+import { proverbValidator, type AfricanProverb } from "@/lib/proverb-validator"
+
+interface Module {
+ id: string
+ title: string
+ description: string
+ type: "interactive" | "quiz" | "story" | "video"
+ difficulty: "beginner" | "intermediate" | "advanced"
+ rewardFLB: number
+ rewardXP: number
+ estimatedTime: number
+ completed: boolean
+ content?: any
+}
+
+interface QuizQuestion {
+ question: string
+ options: string[]
+ correctAnswer: number
+ explanation: string
+ proverb?: AfricanProverb
+}
+
+interface UserProgress {
+ totalXP: number
+ totalFLB: number
+ completedModules: string[]
+ currentStreak: number
+ storiesShared: number
+}
+
+const mockModules: Module[] = [
+ {
+ id: "akan-wisdom-1",
+ title: "Akan Wisdom: Sankofa Philosophy",
+ description: "Learn the profound meaning of looking back to move forward",
+ type: "interactive",
+ difficulty: "beginner",
+ rewardFLB: 25,
+ rewardXP: 50,
+ estimatedTime: 15,
+ completed: false,
+ content: {
+ proverb: "Se wo were fi na wosankofa a yenkyi",
+ language: "Akan",
+ meaning: "It is not wrong to go back for that which you have forgotten",
+ culturalContext:
+ "Sankofa teaches us to learn from the past to move forward wisely. The symbol shows a bird looking backward while moving forward.",
+ modernApplication:
+ "In today's fast-paced world, Sankofa reminds us to honor our ancestors' wisdom while embracing progress.",
+ },
+ },
+ {
+ id: "ubuntu-philosophy-1",
+ title: "Ubuntu: I Am Because We Are",
+ description: "Explore the foundational African philosophy of interconnectedness",
+ type: "interactive",
+ difficulty: "beginner",
+ rewardFLB: 30,
+ rewardXP: 60,
+ estimatedTime: 20,
+ completed: false,
+ content: {
+ proverb: "Ubuntu ngumuntu ngabantu",
+ language: "Zulu",
+ meaning: "I am because we are",
+ culturalContext:
+ "Ubuntu emphasizes our shared humanity and interconnectedness. Individual well-being is tied to community well-being.",
+ modernApplication:
+ "Ubuntu principles guide community building, conflict resolution, and social justice in modern Africa.",
+ },
+ },
+ {
+ id: "health-advocacy-1",
+ title: "Community Health Leadership",
+ description: "Learn how to advocate for health in African communities",
+ type: "video",
+ difficulty: "intermediate",
+ rewardFLB: 40,
+ rewardXP: 80,
+ estimatedTime: 25,
+ completed: false,
+ content: {
+ topics: ["Primary Healthcare", "Community Mobilization", "Health Education", "Disease Prevention"],
+ practicalSkills: ["Health Screening", "Community Outreach", "Health Data Collection", "Emergency Response"],
+ },
+ },
+ {
+ id: "proverb-mastery-quiz",
+ title: "African Proverbs Mastery Quiz",
+ description: "Test your knowledge of African wisdom across cultures",
+ type: "quiz",
+ difficulty: "intermediate",
+ rewardFLB: 35,
+ rewardXP: 70,
+ estimatedTime: 10,
+ completed: false,
+ content: {
+ questions: 5,
+ themes: ["wisdom", "community", "patience", "humility", "healing"],
+ },
+ },
+]
+
+export default function LearnEarnSystem() {
+ const [modules, setModules] = useState(mockModules)
+ const [currentModule, setCurrentModule] = useState(null)
+ const [userProgress, setUserProgress] = useState({
+ totalXP: 0,
+ totalFLB: 0,
+ completedModules: [],
+ currentStreak: 0,
+ storiesShared: 0,
+ })
+ const [quizAnswers, setQuizAnswers] = useState([])
+ const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0)
+ const [quizQuestions, setQuizQuestions] = useState([])
+ const [storyContent, setStoryContent] = useState("")
+ const [showResults, setShowResults] = useState(false)
+
+ useEffect(() => {
+ // Generate quiz questions when quiz module is selected
+ if (currentModule?.type === "quiz") {
+ generateQuizQuestions()
+ }
+ }, [currentModule])
+
+ const generateQuizQuestions = () => {
+ const questions: QuizQuestion[] = []
+
+ for (let i = 0; i < 5; i++) {
+ const challenge = proverbValidator.generateProverbChallenge()
+ questions.push({
+ question: challenge.question,
+ options: challenge.options,
+ correctAnswer: challenge.correctAnswer,
+ explanation: `This ${challenge.proverb.language} proverb from ${challenge.proverb.country} teaches us: ${challenge.proverb.cultural_notes}`,
+ proverb: challenge.proverb,
+ })
+ }
+
+ setQuizQuestions(questions)
+ setQuizAnswers(new Array(questions.length).fill(-1))
+ setCurrentQuestionIndex(0)
+ }
+
+ const startModule = (module: Module) => {
+ setCurrentModule(module)
+ setShowResults(false)
+
+ if (module.type === "quiz") {
+ generateQuizQuestions()
+ }
+ }
+
+ const completeModule = (moduleId: string) => {
+ const module = modules.find((m) => m.id === moduleId)
+ if (!module) return
+
+ setModules((prev) => prev.map((m) => (m.id === moduleId ? { ...m, completed: true } : m)))
+
+ setUserProgress((prev) => ({
+ ...prev,
+ totalXP: prev.totalXP + module.rewardXP,
+ totalFLB: prev.totalFLB + module.rewardFLB,
+ completedModules: [...prev.completedModules, moduleId],
+ currentStreak: prev.currentStreak + 1,
+ }))
+
+ setCurrentModule(null)
+ setShowResults(true)
+ }
+
+ const submitQuizAnswer = (questionIndex: number, answerIndex: number) => {
+ const newAnswers = [...quizAnswers]
+ newAnswers[questionIndex] = answerIndex
+ setQuizAnswers(newAnswers)
+ }
+
+ const finishQuiz = () => {
+ if (!currentModule) return
+
+ const correctAnswers = quizAnswers.reduce((count, answer, index) => {
+ return answer === quizQuestions[index]?.correctAnswer ? count + 1 : count
+ }, 0)
+
+ const passingScore = Math.ceil(quizQuestions.length * 0.7) // 70% to pass
+
+ if (correctAnswers >= passingScore) {
+ completeModule(currentModule.id)
+ } else {
+ // Allow retry
+ setCurrentQuestionIndex(0)
+ setQuizAnswers(new Array(quizQuestions.length).fill(-1))
+ }
+ }
+
+ const submitStory = () => {
+ if (!currentModule || storyContent.length < 100) return
+
+ setUserProgress((prev) => ({
+ ...prev,
+ storiesShared: prev.storiesShared + 1,
+ }))
+
+ completeModule(currentModule.id)
+ setStoryContent("")
+ }
+
+ const getDifficultyColor = (difficulty: string) => {
+ switch (difficulty) {
+ case "beginner":
+ return "text-green-400"
+ case "intermediate":
+ return "text-yellow-400"
+ case "advanced":
+ return "text-red-400"
+ default:
+ return "text-gray-400"
+ }
+ }
+
+ const getTypeIcon = (type: string) => {
+ switch (type) {
+ case "interactive":
+ return
+ case "quiz":
+ return
+ case "story":
+ return
+ case "video":
+ return
+ default:
+ return
+ }
+ }
+
+ if (currentModule) {
+ return (
+
+
+ {/* Module Header */}
+
+
setCurrentModule(null)} className="mb-4">
+ ← Back to Modules
+
+
+
+ {getTypeIcon(currentModule.type)}
+
{currentModule.title}
+ {currentModule.difficulty}
+
+
+
{currentModule.description}
+
+
+
+
+ {currentModule.estimatedTime} min
+
+
+ +{currentModule.rewardFLB} FLB
+
+
+ +{currentModule.rewardXP} XP
+
+
+
+
+ {/* Module Content */}
+
+
+ {currentModule.type === "interactive" && (
+
+
+
"{currentModule.content.proverb}"
+
{currentModule.content.meaning}
+
{currentModule.content.language}
+
+
+
+
+
Cultural Context
+
{currentModule.content.culturalContext}
+
+
+
+
Modern Application
+
{currentModule.content.modernApplication}
+
+
+
+
+ completeModule(currentModule.id)}
+ className="bg-orange-600 hover:bg-orange-700"
+ >
+ Complete Module
+
+
+
+ )}
+
+ {currentModule.type === "quiz" && quizQuestions.length > 0 && (
+
+
+
+ Question {currentQuestionIndex + 1} of {quizQuestions.length}
+
+
+
+
+ {currentQuestionIndex < quizQuestions.length && (
+
+
+ {quizQuestions[currentQuestionIndex].question}
+
+
+
+ {quizQuestions[currentQuestionIndex].options.map((option, index) => (
+ submitQuizAnswer(currentQuestionIndex, index)}
+ >
+ {String.fromCharCode(65 + index)}) {option}
+
+ ))}
+
+
+
+ setCurrentQuestionIndex((prev) => prev - 1)}
+ >
+ Previous
+
+
+ {currentQuestionIndex === quizQuestions.length - 1 ? (
+
+ Submit Quiz
+
+ ) : (
+ setCurrentQuestionIndex((prev) => prev + 1)}
+ disabled={quizAnswers[currentQuestionIndex] === -1}
+ >
+ Next
+
+ )}
+
+
+ )}
+
+ )}
+
+ {currentModule.type === "story" && (
+
+
+
Share Your Ubuntu Story
+
+ Tell us about a time when you embodied the Ubuntu philosophy - "I am because we are". How did you
+ help someone or how did community support help you?
+
+
+
+
+
setStoryContent(e.target.value)}
+ className="min-h-[200px] bg-slate-700 border-slate-600"
+ />
+
+ {storyContent.length} / 100 words minimum
+ = 100 ? "text-green-400" : "text-slate-400"}`}>
+ {storyContent.length >= 100 ? "✓ Ready to submit" : "Keep writing..."}
+
+
+
+
+
+
+ Share Story & Earn Rewards
+
+
+
+ )}
+
+ {currentModule.type === "video" && (
+
+
+
+
+
Video content coming soon
+
Interactive health advocacy training
+
+
+
+
+
Learning Objectives
+
+ {currentModule.content.topics.map((topic: string, index: number) => (
+
+
+ {topic}
+
+ ))}
+
+
+
+
+ completeModule(currentModule.id)} className="bg-blue-600 hover:bg-blue-700">
+ Mark as Complete
+
+
+
+ )}
+
+
+
+
+ )
+ }
+
+ return (
+
+
+ {/* Header */}
+
+
+ Learn & Earn
+
+
+ Master African wisdom, earn FLB tokens, and build Ubuntu community
+
+
+ {/* Progress Stats */}
+
+
+
{userProgress.totalFLB}
+
FLB Earned
+
+
+
{userProgress.totalXP}
+
XP Gained
+
+
+
{userProgress.completedModules.length}
+
Completed
+
+
+
{userProgress.storiesShared}
+
Stories Shared
+
+
+
+
+ {/* Results Modal */}
+
+ {showResults && (
+ setShowResults(false)}
+ >
+ e.stopPropagation()}
+ >
+
+
+
Module Completed!
+
You've earned rewards for your learning
+
+
+
+ FLB Tokens:
+ +{currentModule?.rewardFLB}
+
+
+ Experience:
+ +{currentModule?.rewardXP} XP
+
+
+
+
setShowResults(false)} className="w-full">
+ Continue Learning
+
+
+
+
+ )}
+
+
+ {/* Modules Grid */}
+
+ {modules.map((module) => (
+
+
+
+
+
+ {getTypeIcon(module.type)}
+ {module.difficulty}
+
+ {module.completed &&
}
+
+ {module.title}
+ {module.description}
+
+
+
+
+
+
+
+ {module.estimatedTime} min
+
+
+
+ +{module.rewardFLB}
+
+
+ +{module.rewardXP}
+
+
+
+
+
startModule(module)}
+ disabled={module.completed}
+ className={`w-full ${
+ module.completed ? "bg-green-600 hover:bg-green-700" : "bg-orange-600 hover:bg-orange-700"
+ }`}
+ >
+ {module.completed ? "Completed" : "Start Module"}
+
+
+
+
+
+ ))}
+
+
+
+ )
+}
diff --git a/components/live-registration-feed.tsx b/components/live-registration-feed.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..693a3deff05d9cfa01a28f2d6c2d8f2e21af32bb
--- /dev/null
+++ b/components/live-registration-feed.tsx
@@ -0,0 +1,255 @@
+"use client"
+
+import type React from "react"
+import { useState, useEffect } from "react"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Badge } from "@/components/ui/badge"
+import { Button } from "@/components/ui/button"
+import { motion, AnimatePresence } from "framer-motion"
+import { Users, Heart, Shield, Stethoscope, ExternalLink, RefreshCw } from "lucide-react"
+
+interface RegistrationEntry {
+ id: string
+ name: string
+ type: "Guardian" | "Healer" | "CHW" | "Health Facility"
+ location: string
+ timestamp: string
+ flbEarned: number
+ verified: boolean
+}
+
+const LiveRegistrationFeed: React.FC = () => {
+ const [registrations, setRegistrations] = useState([])
+ const [isLoading, setIsLoading] = useState(false)
+
+ // Mock data that simulates real Google Sheets data
+ const mockRegistrations: RegistrationEntry[] = [
+ {
+ id: "1",
+ name: "Dr. Amara Kone",
+ type: "Healer",
+ location: "Lagos, Nigeria",
+ timestamp: new Date(Date.now() - 300000).toISOString(), // 5 minutes ago
+ flbEarned: 250,
+ verified: true,
+ },
+ {
+ id: "2",
+ name: "Kwame Asante",
+ type: "Guardian",
+ location: "Accra, Ghana",
+ timestamp: new Date(Date.now() - 600000).toISOString(), // 10 minutes ago
+ flbEarned: 150,
+ verified: true,
+ },
+ {
+ id: "3",
+ name: "Sarah Okafor",
+ type: "CHW",
+ location: "Kano, Nigeria",
+ timestamp: new Date(Date.now() - 900000).toISOString(), // 15 minutes ago
+ flbEarned: 100,
+ verified: false,
+ },
+ {
+ id: "4",
+ name: "Nairobi Community Health Center",
+ type: "Health Facility",
+ location: "Nairobi, Kenya",
+ timestamp: new Date(Date.now() - 1200000).toISOString(), // 20 minutes ago
+ flbEarned: 500,
+ verified: true,
+ },
+ {
+ id: "5",
+ name: "Fatima Al-Rashid",
+ type: "Guardian",
+ location: "Cairo, Egypt",
+ timestamp: new Date(Date.now() - 1500000).toISOString(), // 25 minutes ago
+ flbEarned: 200,
+ verified: true,
+ },
+ ]
+
+ // Simulate fetching data from Google Sheets
+ const fetchRegistrations = async () => {
+ setIsLoading(true)
+
+ // Simulate API delay
+ await new Promise((resolve) => setTimeout(resolve, 1000))
+
+ // In a real implementation, this would fetch from the Google Sheets API
+ // For now, we'll use mock data with some randomization
+ const shuffled = [...mockRegistrations].sort(() => Math.random() - 0.5)
+ setRegistrations(shuffled.slice(0, 4))
+
+ setIsLoading(false)
+ }
+
+ // Simulate real-time updates
+ useEffect(() => {
+ fetchRegistrations()
+
+ // Update every 30 seconds to simulate live feed
+ const interval = setInterval(() => {
+ // Add a new random registration
+ const newRegistration: RegistrationEntry = {
+ id: Date.now().toString(),
+ name: `New Member ${Math.floor(Math.random() * 1000)}`,
+ type: ["Guardian", "Healer", "CHW", "Health Facility"][Math.floor(Math.random() * 4)] as any,
+ location: ["Lagos, Nigeria", "Nairobi, Kenya", "Accra, Ghana", "Cairo, Egypt"][Math.floor(Math.random() * 4)],
+ timestamp: new Date().toISOString(),
+ flbEarned: Math.floor(Math.random() * 300) + 50,
+ verified: Math.random() > 0.3,
+ }
+
+ setRegistrations((prev) => [newRegistration, ...prev.slice(0, 3)])
+ }, 30000)
+
+ return () => clearInterval(interval)
+ }, [])
+
+ const getTypeIcon = (type: string) => {
+ switch (type) {
+ case "Guardian":
+ return
+ case "Healer":
+ return
+ case "CHW":
+ return
+ case "Health Facility":
+ return
+ default:
+ return
+ }
+ }
+
+ const getTypeColor = (type: string) => {
+ switch (type) {
+ case "Guardian":
+ return "bg-blue-500/20 text-blue-300 border-blue-500/30"
+ case "Healer":
+ return "bg-green-500/20 text-green-300 border-green-500/30"
+ case "CHW":
+ return "bg-purple-500/20 text-purple-300 border-purple-500/30"
+ case "Health Facility":
+ return "bg-orange-500/20 text-orange-300 border-orange-500/30"
+ default:
+ return "bg-gray-500/20 text-gray-300 border-gray-500/30"
+ }
+ }
+
+ const formatTimeAgo = (timestamp: string) => {
+ const now = new Date()
+ const time = new Date(timestamp)
+ const diffInMinutes = Math.floor((now.getTime() - time.getTime()) / (1000 * 60))
+
+ if (diffInMinutes < 1) return "Just now"
+ if (diffInMinutes < 60) return `${diffInMinutes}m ago`
+ const diffInHours = Math.floor(diffInMinutes / 60)
+ if (diffInHours < 24) return `${diffInHours}h ago`
+ const diffInDays = Math.floor(diffInHours / 24)
+ return `${diffInDays}d ago`
+ }
+
+ return (
+
+
+
+
+
+ Live Registration Feed
+
+
+
+
+
+ Real-time updates from our community registration system
+
+
+
+ {registrations.map((registration, index) => (
+
+
+
+
+ {getTypeIcon(registration.type)}
+
+
+
{registration.name}
+
{registration.location}
+
+
+
+
{registration.type}
+ {registration.verified &&
✓ Verified
}
+
+
+
+
+ {formatTimeAgo(registration.timestamp)}
+ +{registration.flbEarned} FLB
+
+
+ ))}
+
+
+ {registrations.length === 0 && !isLoading && (
+ No recent registrations. Check back soon!
+ )}
+
+ {isLoading && (
+
+
+
Loading latest registrations...
+
+ )}
+
+ {/* Action Buttons */}
+
+
+ window.open(
+ "https://docs.google.com/forms/d/e/1FAIpQLSfuxO0SEkiGhuvYqP5fE4c5RgqdyZc76rppeC70rDsu67ssgw/viewform?usp=sharing&ouid=117857227348165727286",
+ "_blank",
+ )
+ }
+ >
+
+ Join FlameBorn Network
+
+
+
+ window.open(
+ "https://docs.google.com/spreadsheets/d/e/2PACX-1vQkTZ9GaUMp_nlR82WWKR_tFMZzWDE4jekk6N9GKZ60eiceuHsUOlftoiqdd1AB3vBPxiyk1DtLBBM4/pubhtml",
+ "_blank",
+ )
+ }
+ >
+
+ View Full Registration Data
+
+
+
+
+ )
+}
+
+export default LiveRegistrationFeed
diff --git a/components/loading-state.tsx b/components/loading-state.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..bc0797bab8bb89bf85eb07558598b964ed579fad
--- /dev/null
+++ b/components/loading-state.tsx
@@ -0,0 +1,14 @@
+import { Loader2 } from "lucide-react"
+
+interface LoadingStateProps {
+ message?: string
+}
+
+export function LoadingState({ message = "Loading..." }: LoadingStateProps) {
+ return (
+
+ )
+}
diff --git a/components/manifesto.tsx b/components/manifesto.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..358bfeff0f9bd958adfd05cf38252738ed585255
--- /dev/null
+++ b/components/manifesto.tsx
@@ -0,0 +1,30 @@
+export function Manifesto() {
+ return (
+
+
Our Sacred Mission
+
+
+
Recognition
+
+ We believe in recognizing the invisible heroes of healthcare. Through blockchain verification, we create
+ immutable proof of service for those who work in the shadows.
+
+
+
+
Support
+
+ Direct support without intermediaries. Our token system ensures that resources flow directly to those who
+ provide care, not to administrative overhead.
+
+
+
+
Community
+
+ We are building a global community of care. Guardians verify health workers, creating a trusted network that
+ spans continents and transcends borders.
+
+
+
+
+ )
+}
diff --git a/components/maternal-health/maternal-health-portal.tsx b/components/maternal-health/maternal-health-portal.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..765fb1039fc1deb3dc0162f19a619e4d75f66b5a
--- /dev/null
+++ b/components/maternal-health/maternal-health-portal.tsx
@@ -0,0 +1,291 @@
+"use client"
+
+import { useState, useEffect } from "react"
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
+import { Button } from "@/components/ui/button"
+import { Badge } from "@/components/ui/badge"
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
+import { Heart, Baby, Shield, Activity } from "lucide-react"
+import { useToast } from "@/hooks/use-toast"
+
+interface BabyInfo {
+ birthTimestamp: number
+ mother: string
+ facility: string
+ lastHeartbeat: number
+ isAlive: boolean
+ escrowAmount: string
+}
+
+export function MaternalHealthPortal() {
+ const [babies, setBabies] = useState>([])
+ const [loading, setLoading] = useState(false)
+ const { toast } = useToast()
+
+ // Mock data for demonstration
+ const mockBabies = [
+ {
+ id: "1",
+ info: {
+ birthTimestamp: Date.now() - 86400000 * 7, // 7 days ago
+ mother: "0x742d35Cc6634C0532925a3b8D0f5e5d7c3c6D33f",
+ facility: "Lagos University Teaching Hospital",
+ lastHeartbeat: Date.now() - 3600000, // 1 hour ago
+ isAlive: true,
+ escrowAmount: "250",
+ },
+ },
+ {
+ id: "2",
+ info: {
+ birthTimestamp: Date.now() - 86400000 * 30, // 30 days ago
+ mother: "0x456789abcdef123456789abcdef123456789abcdef",
+ facility: "Kenyatta National Hospital",
+ lastHeartbeat: Date.now() - 1800000, // 30 minutes ago
+ isAlive: true,
+ escrowAmount: "180",
+ },
+ },
+ ]
+
+ useEffect(() => {
+ setBabies(mockBabies)
+ }, [])
+
+ const handleCreateBabyNFT = async () => {
+ setLoading(true)
+ try {
+ // Mock creation for demonstration
+ toast({
+ title: "BabyNFT Created",
+ description: "Life-bond established successfully",
+ })
+ } catch (error) {
+ toast({
+ title: "Error",
+ description: "Failed to create BabyNFT",
+ variant: "destructive",
+ })
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ const formatDate = (timestamp: number) => {
+ return new Date(timestamp).toLocaleDateString()
+ }
+
+ const getHeartbeatStatus = (lastHeartbeat: number) => {
+ const hoursSince = (Date.now() - lastHeartbeat) / (1000 * 60 * 60)
+ if (hoursSince < 2) return { color: "text-emerald-400", text: "Active" }
+ if (hoursSince < 12) return { color: "text-yellow-400", text: "Recent" }
+ return { color: "text-red-400", text: "Delayed" }
+ }
+
+ return (
+
+ {/* Header */}
+
+
👶 Maternal Health Portal
+
Life-bond tracking and guardian verification system
+
+
+
+
+
+ BabyNFTs
+
+
+ Facilities
+
+
+ Guardians
+
+
+ Milestones
+
+
+
+
+ {/* Create New BabyNFT */}
+
+
+
+
+ Create New Life-Bond
+
+
+ Register a new birth and establish BabyNFT life-bond tracking
+
+
+
+
+ {loading ? "Creating..." : "Create BabyNFT"}
+
+
+
+
+ {/* Existing BabyNFTs */}
+
+ {babies.map((baby) => {
+ const heartbeatStatus = getHeartbeatStatus(baby.info.lastHeartbeat)
+ return (
+
+
+
+ Baby #{baby.id}
+
+ {baby.info.isAlive ? "Alive" : "Critical"}
+
+
+
+ Born: {formatDate(baby.info.birthTimestamp)}
+
+
+
+
+
+ Facility:
+ {baby.info.facility}
+
+
+ Escrow:
+ {baby.info.escrowAmount} FLB
+
+
+
Heartbeat:
+
+
+ {heartbeatStatus.text}
+
+
+
+
+
+
+ View Details
+
+
+
+
+ )
+ })}
+
+
+
+
+
+
+
+
+ Health Facilities
+
+
+ Registered healthcare facilities in the FlameBorn network
+
+
+
+
+ {[
+ {
+ name: "Lagos University Teaching Hospital",
+ location: "Lagos, Nigeria",
+ activeBirths: 23,
+ successRate: 98.5,
+ totalCommitted: "12,450",
+ },
+ {
+ name: "Kenyatta National Hospital",
+ location: "Nairobi, Kenya",
+ activeBirths: 18,
+ successRate: 97.2,
+ totalCommitted: "8,750",
+ },
+ ].map((facility, index) => (
+
+
+
+
{facility.name}
+
{facility.location}
+
+
+ {facility.successRate}% Success
+
+
+
+
+ Active Births:
+ {facility.activeBirths}
+
+
+ Committed:
+ {facility.totalCommitted} FLB
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+ Guardian Councils
+
+
+ Community elders and FLB representatives overseeing facility verification
+
+
+
+ Guardian Council management coming soon...
+
+
+
+
+
+
+
+
+
+ Payment Milestones
+
+
+ Track facility payment milestones: Commitment → Delivery → Postnatal → Bonus
+
+
+
+ Milestone tracking interface coming soon...
+
+
+
+
+
+ )
+}
diff --git a/components/mobile-nav.tsx b/components/mobile-nav.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..ca50ec1c818f97a4b13b92544986ad19c6b5edf8
--- /dev/null
+++ b/components/mobile-nav.tsx
@@ -0,0 +1,188 @@
+"use client"
+
+import { useState, useEffect } from "react"
+import { usePathname } from "next/navigation"
+import Link from "next/link"
+import { Home, Shield, BookOpen, Activity, User, Menu, X, Heart, UserPlus, LogIn } from "lucide-react"
+import { motion } from "framer-motion"
+import { UserDatabase } from "@/lib/user-database"
+
+export function MobileNav() {
+ const [isOpen, setIsOpen] = useState(false)
+ const pathname = usePathname()
+ const [currentUser, setCurrentUser] = useState<{ id: string; type: string } | null>(null)
+
+ // Check for logged in user
+ useEffect(() => {
+ const user = UserDatabase.getCurrentUser()
+ if (user) {
+ setCurrentUser({
+ id: user.id,
+ type: user.type,
+ })
+ } else {
+ setCurrentUser(null)
+ }
+ }, [pathname])
+
+ // Close menu when route changes
+ useEffect(() => {
+ setIsOpen(false)
+ }, [pathname])
+
+ const isActive = (path: string) => {
+ return pathname === path
+ }
+
+ // Navigation items with their icons, tooltips, and colors
+ const navItems = [
+ { path: "/", icon: Home, label: "Home", color: "#ff3a2f" },
+ { path: "/healers", icon: Heart, label: "Healers", color: "#ff7f41" },
+ { path: "/become-guardian", icon: Shield, label: "Guardians", color: "#00ffa0" },
+ { path: "/guardians-sanctuary", icon: Shield, label: "Sanctuary", color: "#ff4e00" },
+ { path: "/flameborn-journey", icon: BookOpen, label: "Journey", color: "#ff00c8" },
+ { path: "/community-pulse", icon: Activity, label: "Pulse", color: "#00f3ff" },
+ ]
+
+ return (
+ <>
+ {/* Mobile menu button */}
+ setIsOpen(!isOpen)}
+ aria-label={isOpen ? "Close menu" : "Open menu"}
+ whileHover={{ scale: 1.1 }}
+ whileTap={{ scale: 0.9 }}
+ >
+ {isOpen ? : }
+
+
+ {/* Mobile navigation overlay */}
+ {isOpen && (
+
+
+
+
+ FLAMEBORN
+
+ setIsOpen(false)}>
+
+
+
+
+
+
+ {navItems.map((item) => (
+ setIsOpen(false)}
+ >
+
+
+
+
+ {item.label}
+
+
+ ))}
+
+ {currentUser ? (
+ setIsOpen(false)}
+ >
+
+
+
+
+ Profile
+
+
+ ) : (
+ <>
+ setIsOpen(false)}
+ >
+
+
+
+ Log In
+
+
+ setIsOpen(false)}
+ >
+
+
+
+ Register
+
+ >
+ )}
+
+
+
+
+ )}
+ >
+ )
+}
diff --git a/components/mostar-ai-welcome.tsx b/components/mostar-ai-welcome.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..a9f0e7e4864fc8ca396c3ed9fa1e6a6d6803c860
--- /dev/null
+++ b/components/mostar-ai-welcome.tsx
@@ -0,0 +1,241 @@
+"use client"
+
+import { useState, useEffect } from "react"
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
+import { Button } from "@/components/ui/button"
+import { Badge } from "@/components/ui/badge"
+import { motion } from "framer-motion"
+import { Sparkles, Heart, BookOpen, MessageCircle, Volume2, VolumeX } from "lucide-react"
+import { useWallet } from "@/providers/wallet-provider"
+import { mostarAI, type MostarAIResponse } from "@/lib/mostar-ai-client"
+import { toast } from "@/hooks/use-toast"
+
+interface MostarWelcomeProps {
+ validatorName?: string
+ onComplete?: () => void
+}
+
+export function MostarAIWelcome({ validatorName, onComplete }: MostarWelcomeProps) {
+ const { address } = useWallet()
+ const [response, setResponse] = useState(null)
+ const [isLoading, setIsLoading] = useState(true)
+ const [isPlaying, setIsPlaying] = useState(false)
+ const [showFullMessage, setShowFullMessage] = useState(false)
+
+ useEffect(() => {
+ loadWelcomeMessage()
+ }, [validatorName, address])
+
+ const loadWelcomeMessage = async () => {
+ setIsLoading(true)
+ try {
+ let welcomeResponse: MostarAIResponse
+
+ if (address) {
+ // First validator message if they have an address
+ welcomeResponse = await mostarAI.getFirstValidatorMessage(address)
+ } else {
+ // General welcome message
+ welcomeResponse = await mostarAI.getWelcomeMessage(validatorName)
+ }
+
+ setResponse(welcomeResponse)
+ } catch (error) {
+ console.error("Failed to load welcome message:", error)
+ // Fallback message
+ setResponse({
+ message: `Welcome to FlameBorn, ${validatorName || "healer"}! 🔥 You are now part of Africa's healing network. The ancestors guide your path, and Ubuntu flows through your actions. "I am because we are" - together we heal.`,
+ proverb: "I am because we are",
+ culturalContext: "Ubuntu philosophy",
+ })
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ const speakMessage = () => {
+ if (!response?.message) return
+
+ if (isPlaying) {
+ speechSynthesis.cancel()
+ setIsPlaying(false)
+ return
+ }
+
+ const utterance = new SpeechSynthesisUtterance(response.message)
+ utterance.rate = 0.9
+ utterance.pitch = 1.1
+ utterance.volume = 0.8
+
+ utterance.onstart = () => setIsPlaying(true)
+ utterance.onend = () => setIsPlaying(false)
+ utterance.onerror = () => {
+ setIsPlaying(false)
+ toast({
+ title: "Speech Error",
+ description: "Unable to play audio. Please check your browser settings.",
+ variant: "destructive",
+ })
+ }
+
+ speechSynthesis.speak(utterance)
+ }
+
+ const handleContinue = () => {
+ if (isPlaying) {
+ speechSynthesis.cancel()
+ setIsPlaying(false)
+ }
+ onComplete?.()
+ }
+
+ if (isLoading) {
+ return (
+
+
+
+
+
+
+
+
Mostar is awakening...
+
Channeling ancestral wisdom for your journey
+
+
+
+
+ )
+ }
+
+ if (!response) {
+ return (
+
+
+ Unable to connect with Mostar AI. Please try again.
+
+ Retry Connection
+
+
+
+ )
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ Mostar AI
+ Guardian
+
+ AI Guardian of FlameBorn Network
+
+
+
+
+ {isPlaying ? : }
+
+
+
+ {/* Animated background */}
+
+
+
+
+ {/* Main Message */}
+
+
+
+
+
+
+ {showFullMessage ? response.message : `${response.message.slice(0, 200)}...`}
+
+ {response.message.length > 200 && (
+
setShowFullMessage(!showFullMessage)}
+ className="text-purple-300 hover:text-white p-0 h-auto"
+ >
+ {showFullMessage ? "Show less" : "Read more"}
+
+ )}
+
+
+
+
+ {/* Proverb */}
+ {response.proverb && (
+
+
+
+
+
Ancestral Wisdom
+
"{response.proverb}"
+ {response.culturalContext && (
+
— {response.culturalContext}
+ )}
+
+
+
+ )}
+
+ {/* Healing Wisdom */}
+ {response.healingWisdom && (
+
+
+
+
+
Healing Guidance
+
{response.healingWisdom}
+
+
+
+ )}
+
+
+ {/* Service Status */}
+
+
+
+
+ {mostarAI.isServiceAvailable() ? "AI Service Active" : "Fallback Mode"}
+
+
+
+
+ Continue Journey
+
+
+
+
+
+ )
+}
diff --git a/components/network-graph.tsx b/components/network-graph.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..c0317db5ac6f4b60b1800dc8c1245d6b9c266afb
--- /dev/null
+++ b/components/network-graph.tsx
@@ -0,0 +1,538 @@
+"use client"
+
+import type React from "react"
+import { useEffect, useRef, useState, useCallback } from "react"
+import { useMobile } from "@/hooks/use-mobile"
+
+type Node = {
+ id: string
+ name: string
+ val: number
+ color: string
+ group?: string
+ x: number
+ y: number
+ vx: number
+ vy: number
+ fx?: number
+ fy?: number
+ isNew?: boolean
+ joinedAt?: number
+ networkType: "testnet" | "mainnet"
+}
+
+type Link = {
+ source: string
+ target: string
+ value: number
+ strength: number
+}
+
+type GraphData = {
+ nodes: Node[]
+ links: Link[]
+}
+
+type NetworkGraphProps = {
+ centerNodeId?: string
+ segment?: string
+}
+
+const TESTNET_COLORS = {
+ center: "#FF6B35", // FlameBorn orange
+ healer: "#00A86B", // Medical green
+ guardian: "#0077B5", // Professional blue
+ chw: "#9B59B6", // Community purple
+ facility: "#E74C3C", // Emergency red
+ new: "#F39C12", // New member gold
+}
+
+const MAINNET_COLORS = {
+ center: "#FF4500", // Deeper orange for mainnet
+ healer: "#228B22", // Forest green
+ guardian: "#4169E1", // Royal blue
+ chw: "#8A2BE2", // Blue violet
+ facility: "#DC143C", // Crimson
+ new: "#FFD700", // Gold
+}
+
+const generateLiveNodes = (count: number, centerNodeId: string, networkType: "testnet" | "mainnet"): Node[] => {
+ const colors = networkType === "testnet" ? TESTNET_COLORS : MAINNET_COLORS
+
+ const nodes: Node[] = [
+ {
+ id: centerNodeId,
+ name: `FlameBorn ${networkType === "testnet" ? "Testnet" : "Mainnet"} Hub`,
+ val: 8,
+ color: colors.center,
+ group: "center",
+ x: 400,
+ y: 300,
+ vx: 0,
+ vy: 0,
+ fx: 400,
+ fy: 300,
+ networkType,
+ },
+ ]
+
+ const nodeTypes = [
+ {
+ type: "healer",
+ color: colors.healer,
+ names: ["Dr. Amara Kone", "Dr. Kwame Asante", "Dr. Fatima Al-Rashid", "Dr. John Mwangi"],
+ },
+ {
+ type: "guardian",
+ color: colors.guardian,
+ names: ["Guardian Sarah", "Guardian Ahmed", "Guardian Zara", "Guardian Kofi"],
+ },
+ { type: "chw", color: colors.chw, names: ["CHW Aisha", "CHW Babatunde", "CHW Naledi", "CHW Kemi"] },
+ {
+ type: "facility",
+ color: colors.facility,
+ names: ["Lagos General", "Nairobi Health Center", "Accra Medical", "Cairo Clinic"],
+ },
+ ]
+
+ const locations = [
+ "Lagos, Nigeria",
+ "Nairobi, Kenya",
+ "Accra, Ghana",
+ "Cairo, Egypt",
+ "Kano, Nigeria",
+ "Kampala, Uganda",
+ "Dakar, Senegal",
+ "Addis Ababa, Ethiopia",
+ "Casablanca, Morocco",
+ "Cape Town, South Africa",
+ "Abidjan, Ivory Coast",
+ "Dar es Salaam, Tanzania",
+ ]
+
+ for (let i = 1; i <= count; i++) {
+ const nodeType = nodeTypes[i % nodeTypes.length]
+ const angle = (i / count) * 2 * Math.PI + Math.sin(Date.now() * 0.001 + i) * 0.1
+ const radius = 150 + Math.sin(Date.now() * 0.0005 + i) * 50
+ const baseX = 400 + Math.cos(angle) * radius
+ const baseY = 300 + Math.sin(angle) * radius
+
+ const isNewNode = Math.random() > 0.95 // 5% chance of being a new node
+ const joinedRecently = Date.now() - Math.random() * 300000 // Joined within last 5 minutes
+
+ nodes.push({
+ id: `${networkType}-node-${i}`,
+ name: `${nodeType.names[i % nodeType.names.length]} ${Math.ceil(i / nodeType.names.length)}`,
+ val: isNewNode ? 5 : 2 + Math.random() * 3,
+ color: isNewNode ? colors.new : nodeType.color,
+ group: nodeType.type,
+ x: baseX + (Math.random() - 0.5) * 100,
+ y: baseY + (Math.random() - 0.5) * 100,
+ vx: (Math.random() - 0.5) * 2,
+ vy: (Math.random() - 0.5) * 2,
+ isNew: isNewNode,
+ joinedAt: joinedRecently,
+ networkType,
+ })
+ }
+
+ return nodes
+}
+
+const generateLiveLinks = (nodes: Node[], centerNodeId: string): Link[] => {
+ const links: Link[] = []
+
+ // Connect all nodes to center with varying strength
+ nodes.forEach((node) => {
+ if (node.id !== centerNodeId) {
+ const distance = Math.sqrt(Math.pow(node.x - 400, 2) + Math.pow(node.y - 300, 2))
+ const strength = Math.max(0.1, 1 - distance / 300)
+
+ links.push({
+ source: centerNodeId,
+ target: node.id,
+ value: node.isNew ? 3 : 1,
+ strength,
+ })
+ }
+ })
+
+ // Create dynamic connections between nodes
+ const nonCenterNodes = nodes.filter((node) => node.id !== centerNodeId)
+ for (let i = 0; i < nonCenterNodes.length; i++) {
+ for (let j = i + 1; j < nonCenterNodes.length; j++) {
+ const node1 = nonCenterNodes[i]
+ const node2 = nonCenterNodes[j]
+
+ // Higher chance of connection if nodes are of same type or nearby
+ const sameType = node1.group === node2.group
+ const distance = Math.sqrt(Math.pow(node1.x - node2.x, 2) + Math.pow(node1.y - node2.y, 2))
+
+ const connectionChance = sameType ? 0.15 : 0.05
+ const distanceBonus = distance < 100 ? 0.1 : 0
+
+ if (Math.random() < connectionChance + distanceBonus) {
+ links.push({
+ source: node1.id,
+ target: node2.id,
+ value: 1,
+ strength: 0.3,
+ })
+ }
+ }
+ }
+
+ return links
+}
+
+const NetworkGraph: React.FC = ({ centerNodeId = "flameborn-hub", segment }) => {
+ const canvasRef = useRef(null)
+ const containerRef = useRef(null)
+ const animationRef = useRef()
+ const isMobile = useMobile()
+
+ const [dimensions, setDimensions] = useState({ width: 800, height: 600 })
+ const [testnetData, setTestnetData] = useState({ nodes: [], links: [] })
+ const [mainnetData, setMainnetData] = useState({ nodes: [], links: [] })
+ const [activeNetwork, setActiveNetwork] = useState<"testnet" | "mainnet">("testnet")
+ const [stats, setStats] = useState({
+ testnet: { nodes: 0, newJoins: 0 },
+ mainnet: { nodes: 0, newJoins: 0 },
+ })
+
+ // Physics simulation parameters
+ const forceStrength = 0.03
+ const centerForce = 0.02
+ const repelForce = 30
+ const dampening = 0.95
+
+ const updateDimensions = useCallback(() => {
+ if (containerRef.current) {
+ const { width, height } = containerRef.current.getBoundingClientRect()
+ setDimensions({ width: Math.max(width, 400), height: Math.max(height, 300) })
+ }
+ }, [])
+
+ // Initialize and update network data
+ useEffect(() => {
+ const initializeNetworks = () => {
+ // Generate testnet data
+ const testnetNodes = generateLiveNodes(25, `${centerNodeId}-testnet`, "testnet")
+ const testnetLinks = generateLiveLinks(testnetNodes, `${centerNodeId}-testnet`)
+ setTestnetData({ nodes: testnetNodes, links: testnetLinks })
+
+ // Generate mainnet data
+ const mainnetNodes = generateLiveNodes(40, `${centerNodeId}-mainnet`, "mainnet")
+ const mainnetLinks = generateLiveLinks(mainnetNodes, `${centerNodeId}-mainnet`)
+ setMainnetData({ nodes: mainnetNodes, links: mainnetLinks })
+
+ // Update stats
+ setStats({
+ testnet: {
+ nodes: testnetNodes.length - 1,
+ newJoins: testnetNodes.filter((n) => n.isNew).length,
+ },
+ mainnet: {
+ nodes: mainnetNodes.length - 1,
+ newJoins: mainnetNodes.filter((n) => n.isNew).length,
+ },
+ })
+ }
+
+ initializeNetworks()
+ updateDimensions()
+
+ // Update networks every 10 seconds to simulate live joining
+ const networkInterval = setInterval(() => {
+ initializeNetworks()
+ }, 10000)
+
+ // Resize listener
+ window.addEventListener("resize", updateDimensions)
+
+ return () => {
+ clearInterval(networkInterval)
+ window.removeEventListener("resize", updateDimensions)
+ }
+ }, [centerNodeId, updateDimensions])
+
+ // Physics simulation and rendering
+ useEffect(() => {
+ if (!canvasRef.current || dimensions.width === 0) return
+
+ const canvas = canvasRef.current
+ const ctx = canvas.getContext("2d")
+ if (!ctx) return
+
+ canvas.width = dimensions.width
+ canvas.height = dimensions.height
+
+ const currentData = activeNetwork === "testnet" ? testnetData : mainnetData
+ if (currentData.nodes.length === 0) return
+
+ const nodes = [...currentData.nodes]
+ const links = currentData.links
+
+ const simulate = () => {
+ // Apply forces
+ nodes.forEach((node, i) => {
+ if (node.fx !== undefined && node.fy !== undefined) return // Skip fixed nodes
+
+ let fx = 0,
+ fy = 0
+
+ // Center attraction
+ const centerX = dimensions.width / 2
+ const centerY = dimensions.height / 2
+ const dcx = centerX - node.x
+ const dcy = centerY - node.y
+ const centerDistance = Math.sqrt(dcx * dcx + dcy * dcy)
+
+ if (centerDistance > 0) {
+ fx += (dcx / centerDistance) * centerForce * centerDistance * 0.01
+ fy += (dcy / centerDistance) * centerForce * centerDistance * 0.01
+ }
+
+ // Node repulsion
+ nodes.forEach((other, j) => {
+ if (i === j) return
+ const dx = node.x - other.x
+ const dy = node.y - other.y
+ const distance = Math.sqrt(dx * dx + dy * dy)
+
+ if (distance > 0 && distance < 100) {
+ const force = repelForce / (distance * distance)
+ fx += (dx / distance) * force
+ fy += (dy / distance) * force
+ }
+ })
+
+ // Link forces
+ links.forEach((link) => {
+ if (link.source === node.id) {
+ const target = nodes.find((n) => n.id === link.target)
+ if (target) {
+ const dx = target.x - node.x
+ const dy = target.y - node.y
+ const distance = Math.sqrt(dx * dx + dy * dy)
+ const targetDistance = 80
+
+ if (distance > 0) {
+ const force = (distance - targetDistance) * link.strength * forceStrength
+ fx += (dx / distance) * force
+ fy += (dy / distance) * force
+ }
+ }
+ }
+ if (link.target === node.id) {
+ const source = nodes.find((n) => n.id === link.source)
+ if (source) {
+ const dx = source.x - node.x
+ const dy = source.y - node.y
+ const distance = Math.sqrt(dx * dx + dy * dy)
+ const targetDistance = 80
+
+ if (distance > 0) {
+ const force = (distance - targetDistance) * link.strength * forceStrength
+ fx += (dx / distance) * force
+ fy += (dy / distance) * force
+ }
+ }
+ }
+ })
+
+ // Apply forces to velocity
+ node.vx += fx
+ node.vy += fy
+
+ // Apply dampening
+ node.vx *= dampening
+ node.vy *= dampening
+
+ // Update position
+ node.x += node.vx
+ node.y += node.vy
+
+ // Boundary constraints
+ const margin = 50
+ if (node.x < margin) {
+ node.x = margin
+ node.vx = 0
+ }
+ if (node.x > dimensions.width - margin) {
+ node.x = dimensions.width - margin
+ node.vx = 0
+ }
+ if (node.y < margin) {
+ node.y = margin
+ node.vy = 0
+ }
+ if (node.y > dimensions.height - margin) {
+ node.y = dimensions.height - margin
+ node.vy = 0
+ }
+ })
+ }
+
+ const render = () => {
+ ctx.clearRect(0, 0, dimensions.width, dimensions.height)
+
+ const time = Date.now() * 0.001
+
+ // Draw links with animation
+ ctx.strokeStyle = activeNetwork === "testnet" ? "rgba(255,107,53,0.3)" : "rgba(255,69,0,0.4)"
+ ctx.lineWidth = 1
+
+ links.forEach((link) => {
+ const sourceNode = nodes.find((n) => n.id === link.source)
+ const targetNode = nodes.find((n) => n.id === link.target)
+
+ if (sourceNode && targetNode) {
+ const opacity = 0.2 + Math.sin(time * 2 + link.value) * 0.1
+ ctx.strokeStyle = activeNetwork === "testnet" ? `rgba(255,107,53,${opacity})` : `rgba(255,69,0,${opacity})`
+
+ ctx.beginPath()
+ ctx.moveTo(sourceNode.x, sourceNode.y)
+ ctx.lineTo(targetNode.x, targetNode.y)
+ ctx.stroke()
+ }
+ })
+
+ // Draw nodes with enhanced animation
+ nodes.forEach((node) => {
+ const isPulseNode = node.group === "center"
+ const isNewNode = node.isNew
+
+ let pulseIntensity = 1
+ let glowIntensity = 5
+
+ if (isPulseNode) {
+ pulseIntensity = 1 + Math.sin(time * 3) * 0.3
+ glowIntensity = 20 + Math.sin(time * 4) * 10
+ } else if (isNewNode) {
+ pulseIntensity = 1 + Math.sin(time * 5) * 0.4
+ glowIntensity = 10 + Math.sin(time * 6) * 5
+ } else {
+ pulseIntensity = 1 + Math.sin(time * 0.5 + Number.parseInt(node.id.slice(-2) || "0")) * 0.1
+ glowIntensity = 3 + Math.sin(time * 0.3) * 2
+ }
+
+ const baseSize = node.val * 3
+ const size = baseSize * pulseIntensity
+
+ // Enhanced glow effect
+ ctx.shadowColor = node.color
+ ctx.shadowBlur = glowIntensity
+
+ // Main node circle
+ ctx.beginPath()
+ ctx.arc(node.x, node.y, size, 0, 2 * Math.PI)
+ ctx.fillStyle = node.color
+ ctx.fill()
+
+ // Additional rings for special nodes
+ if (isPulseNode) {
+ ctx.beginPath()
+ const ringRadius = size + 10 + Math.sin(time * 4) * 5
+ ctx.arc(node.x, node.y, ringRadius, 0, 2 * Math.PI)
+ ctx.strokeStyle = node.color + "60"
+ ctx.lineWidth = 2
+ ctx.stroke()
+ }
+
+ if (isNewNode) {
+ ctx.beginPath()
+ const newRingRadius = size + 5 + Math.sin(time * 8) * 3
+ ctx.arc(node.x, node.y, newRingRadius, 0, 2 * Math.PI)
+ ctx.strokeStyle = "#FFD700" + "80"
+ ctx.lineWidth = 1.5
+ ctx.stroke()
+ }
+
+ ctx.shadowBlur = 0
+ })
+ }
+
+ const animate = () => {
+ simulate()
+ render()
+ animationRef.current = requestAnimationFrame(animate)
+ }
+
+ animate()
+
+ return () => {
+ if (animationRef.current) {
+ cancelAnimationFrame(animationRef.current)
+ }
+ }
+ }, [testnetData, mainnetData, activeNetwork, dimensions])
+
+ return (
+
+ {/* Network Toggle */}
+
+ setActiveNetwork("testnet")}
+ className={`px-4 py-2 rounded-lg text-sm font-medium transition-all ${
+ activeNetwork === "testnet"
+ ? "bg-orange-500 text-white shadow-lg"
+ : "bg-white/10 text-white/70 hover:bg-white/20"
+ }`}
+ >
+ Testnet ({stats.testnet.nodes})
+
+ setActiveNetwork("mainnet")}
+ className={`px-4 py-2 rounded-lg text-sm font-medium transition-all ${
+ activeNetwork === "mainnet"
+ ? "bg-red-500 text-white shadow-lg"
+ : "bg-white/10 text-white/70 hover:bg-white/20"
+ }`}
+ >
+ Mainnet ({stats.mainnet.nodes})
+
+
+
+ {/* Live Stats */}
+
+
+
+
{activeNetwork.toUpperCase()} LIVE
+
+
+ New joins: {activeNetwork === "testnet" ? stats.testnet.newJoins : stats.mainnet.newJoins}
+
+
+
+ {/* Canvas */}
+
+
+
+
+ {/* Network Info */}
+
+
+
+
+ FlameBorn {activeNetwork === "testnet" ? "Testnet" : "Mainnet"} Network
+
+
+ Live visualization of {activeNetwork === "testnet" ? "testing" : "production"} network participants
+
+
+
+
+ {activeNetwork === "testnet" ? stats.testnet.nodes : stats.mainnet.nodes}
+
+
Active Nodes
+
+
+
+
+ )
+}
+
+export default NetworkGraph
diff --git a/components/network-stats/network-stats-live.tsx b/components/network-stats/network-stats-live.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..124d1b79819d66b2a981ac23182352855efb3f32
--- /dev/null
+++ b/components/network-stats/network-stats-live.tsx
@@ -0,0 +1,264 @@
+"use client"
+
+import { useState } from "react"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Badge } from "@/components/ui/badge"
+import { Button } from "@/components/ui/button"
+
+interface CommunityStats {
+ totalUsers: number
+ activeConnections: number
+ totalInvestments: number
+ activeInvestments: number
+ impactContribution: number
+ communityHealthImpact: number
+}
+
+interface HealthSector {
+ id: string
+ name: string
+ icon: string
+ active: boolean
+}
+
+export function NetworkStatsLive() {
+ const [stats, setStats] = useState({
+ totalUsers: 1,
+ activeConnections: 1,
+ totalInvestments: 0,
+ activeInvestments: 0,
+ impactContribution: 0,
+ communityHealthImpact: 0,
+ })
+
+ const [healthSectors] = useState([
+ { id: "primary", name: "Primary Healthcare Centers", icon: "🏥", active: true },
+ { id: "district", name: "District Hospitals", icon: "🏨", active: true },
+ { id: "chw", name: "Community Health Workers", icon: "👩⚕️", active: true },
+ { id: "mobile", name: "Mobile Health Units", icon: "🚑", active: true },
+ { id: "maternal", name: "Maternal Health Clinics", icon: "🤱", active: true },
+ { id: "vaccination", name: "Vaccination Centers", icon: "💉", active: true },
+ ])
+
+ const features = [
+ "Authentication System (Login/Signup ready)",
+ "Health Investment Trading Platform",
+ "Real-time Dashboard",
+ "Youth Engagement Section",
+ "Healers Network",
+ "Community Portal",
+ "Learning Platform",
+ "Governance System",
+ "Token Economics",
+ "Validators Network",
+ "Testnet Environment",
+ "Donation Platform",
+ ]
+
+ const databaseFeatures = [
+ "5 Live Tables connected to Supabase",
+ "RLS Security enabled for all user data",
+ "Real-time Updates ready",
+ "Portfolio Tracking system ready",
+ ]
+
+ const nextSteps = [
+ "Sign up and create your first health investment",
+ "Connect with other health advocates",
+ "Start trading to contribute to African health infrastructure",
+ "Watch your impact grow in real-time!",
+ ]
+
+ return (
+
+ {/* Header */}
+
+
+
+
🔥 FlameBorn Network - Live Stats Dashboard 🔥
+
+
+
+ LIVE & RUNNING!
+
+
+
+
+
+
+
+ {/* App Status */}
+
+
+
+ 🚀 Current App Status - LIVE & RUNNING!
+
+
+
+
+ {/* Community Stats */}
+
+
+ 👥 Community Stats
+
+
+
+
+
Total Users
+
{stats.totalUsers} Pioneer (You're the first!)
+
+
+
Active Wallet Connections
+
{stats.activeConnections} Connection
+
+
+
+
Exchange Integrations
+
Bybit connected ✅
+
+
+
+
+ {/* Health Investment Trading */}
+
+
+ 💰 Health Investment Trading
+
+
+
+
+
Total Health Investments
+
{stats.totalInvestments} (Ready to start!)
+
+
+
Active Investments
+
{stats.activeInvestments}
+
+
+
+
+
Total Impact Contribution
+
+ ${stats.impactContribution} (Waiting for first investment)
+
+
+
+
Community Health Impact
+
${stats.communityHealthImpact} (Ready to grow!)
+
+
+
+
+
+ {/* Health Infrastructure Sectors */}
+
+
+ 🏥 FlameBorn Health Infrastructure Sectors
+ Your app is targeting these 6 Critical Health Areas:
+
+
+
+ {healthSectors.map((sector) => (
+
+ {sector.icon}
+ {sector.name}
+
+ ))}
+
+
+
+
+ {/* Available Features */}
+
+
+ 🎯 Available Features - ALL LIVE!
+
+
+
+ {features.map((feature, index) => (
+
+ ✅
+ {feature}
+
+ ))}
+
+
+
+
+ {/* Database Status */}
+
+
+ 📊 Database Status
+
+
+
+ {databaseFeatures.map((feature, index) => (
+
+ ✅
+ {feature}
+
+ ))}
+
+
+
+
+ {/* What's Next */}
+
+
+ 🎉 What's Next?
+
+
+
+ {nextSteps.map((step, index) => (
+
+ 🔥
+ {step}
+
+ ))}
+
+
+
+
+ {/* Call to Action */}
+
+
+
+ Your FlameBorn Network is FULLY OPERATIONAL and ready to change African healthcare! 🌍💪
+
+
+ Every investment you make automatically contributes 10% to health infrastructure - that's the FlameBorn
+ impact model in action! 🔥
+
+
+
+ 🚀 Start Your First Investment
+
+
+ 📊 View Dashboard
+
+
+
+
+
+
+ {/* Footer */}
+
+
+ )
+}
diff --git a/components/network-switcher.tsx b/components/network-switcher.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..1ae863bad0758c73a7e71306fc270b61dd63961e
--- /dev/null
+++ b/components/network-switcher.tsx
@@ -0,0 +1,85 @@
+"use client"
+
+import { useState } from "react"
+import { Button } from "@/components/ui/button"
+import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
+import { Badge } from "@/components/ui/badge"
+import { ChevronDown, WifiOff } from "lucide-react"
+import { useWallet } from "@/providers/wallet-provider"
+import { CELO_NETWORKS } from "@/lib/celo-wallet"
+
+export function NetworkSwitcher() {
+ const { chainId, connected, switchNetwork } = useWallet()
+ const [switching, setSwitching] = useState(false)
+
+ const getCurrentNetwork = () => {
+ if (!chainId) return null
+ return Object.values(CELO_NETWORKS).find((network) => network.chainId === chainId)
+ }
+
+ const handleNetworkSwitch = async (network: "mainnet" | "alfajores") => {
+ setSwitching(true)
+ try {
+ await switchNetwork(network)
+ } catch (error) {
+ console.error("Failed to switch network:", error)
+ } finally {
+ setSwitching(false)
+ }
+ }
+
+ const currentNetwork = getCurrentNetwork()
+ const isCeloNetwork = chainId === 42220 || chainId === 44787
+
+ if (!connected) {
+ return (
+
+
+ Not Connected
+
+ )
+ }
+
+ return (
+
+
+
+
+ {switching ? "Switching..." : currentNetwork?.name || "Unknown"}
+
+
+
+
+
+ handleNetworkSwitch("alfajores")} disabled={switching || chainId === 44787}>
+
+
+
Celo Alfajores
+ {chainId === 44787 && (
+
+ Active
+
+ )}
+
+
+
+ handleNetworkSwitch("mainnet")} disabled={switching || chainId === 42220}>
+
+
+
Celo Mainnet
+ {chainId === 42220 && (
+
+ Active
+
+ )}
+
+
+
+
+ )
+}
diff --git a/components/network-visualization.tsx b/components/network-visualization.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..dec095347f8bcdb98b5383674c10bc783ac1372e
--- /dev/null
+++ b/components/network-visualization.tsx
@@ -0,0 +1,306 @@
+"use client"
+
+import type React from "react"
+import { useState, useEffect } from "react"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Button } from "@/components/ui/button"
+import { Badge } from "@/components/ui/badge"
+import { motion } from "framer-motion"
+import { Users, Heart, Activity, TrendingUp, Shield, Stethoscope } from "lucide-react"
+import NetworkGraph from "./network-graph"
+
+interface NetworkStats {
+ healthActors: number
+ guardians: number
+ connections: number
+ flbCirculation: number
+ weeklyGrowth: {
+ healthActors: number
+ guardians: number
+ connections: number
+ flbGrowth: number
+ }
+}
+
+interface HealthCategory {
+ id: string
+ name: string
+ count: number
+ color: string
+ icon: React.ReactNode
+ description: string
+}
+
+const NetworkVisualization: React.FC = () => {
+ const [stats, setStats] = useState({
+ healthActors: 2847,
+ guardians: 1203,
+ connections: 5691,
+ flbCirculation: 847000,
+ weeklyGrowth: {
+ healthActors: 127,
+ guardians: 89,
+ connections: 234,
+ flbGrowth: 12.3,
+ },
+ })
+
+ const [selectedCategory, setSelectedCategory] = useState("all")
+
+ const healthCategories: HealthCategory[] = [
+ {
+ id: "primary-care",
+ name: "Primary Healthcare Centers",
+ count: 1247,
+ color: "#00A86B",
+ icon: ,
+ description: "Community health centers providing primary care",
+ },
+ {
+ id: "hospitals",
+ name: "District Hospitals",
+ count: 423,
+ color: "#0077B5",
+ icon: ,
+ description: "Regional hospitals with specialized services",
+ },
+ {
+ id: "chw",
+ name: "Community Health Workers",
+ count: 892,
+ color: "#9B59B6",
+ icon: ,
+ description: "Trained community health advocates",
+ },
+ {
+ id: "mobile",
+ name: "Mobile Health Units",
+ count: 156,
+ color: "#E74C3C",
+ icon: ,
+ description: "Mobile clinics reaching remote areas",
+ },
+ {
+ id: "maternal",
+ name: "Maternal Health Clinics",
+ count: 89,
+ color: "#F39C12",
+ icon: ,
+ description: "Specialized maternal and child health",
+ },
+ {
+ id: "vaccination",
+ name: "Vaccination Centers",
+ count: 234,
+ color: "#27AE60",
+ icon: ,
+ description: "Immunization and prevention centers",
+ },
+ ]
+
+ // Simulate real-time updates
+ useEffect(() => {
+ const interval = setInterval(() => {
+ setStats((prev) => ({
+ ...prev,
+ healthActors: prev.healthActors + Math.floor(Math.random() * 3),
+ guardians: prev.guardians + Math.floor(Math.random() * 2),
+ connections: prev.connections + Math.floor(Math.random() * 5),
+ flbCirculation: prev.flbCirculation + Math.floor(Math.random() * 1000),
+ }))
+ }, 5000)
+
+ return () => clearInterval(interval)
+ }, [])
+
+ const formatNumber = (num: number): string => {
+ if (num >= 1000000) return `${(num / 1000000).toFixed(1)}M`
+ if (num >= 1000) return `${(num / 1000).toFixed(1)}K`
+ return num.toString()
+ }
+
+ return (
+
+
+ {/* Header */}
+
+
+ Live Network Pulse
+
+
+ Watch Africa's health sovereignty network grow in real-time. Every connection represents lives being
+ transformed.
+
+
+
+ {/* Stats Grid */}
+
+
+
+
+
+ +{stats.weeklyGrowth.healthActors}
+
+ {formatNumber(stats.healthActors)}
+ Health Actors
+
+
+
+
+
+
+
+ +{stats.weeklyGrowth.guardians}
+
+ {formatNumber(stats.guardians)}
+ Active Guardians
+
+
+
+
+
+
+
+
+{stats.weeklyGrowth.connections}
+
+ {formatNumber(stats.connections)}
+ Live Connections
+
+
+
+
+
+
+
+ +{stats.weeklyGrowth.flbGrowth}%
+
+ {formatNumber(stats.flbCirculation)}
+ FLB Circulation
+
+
+
+
+ {/* Network Graph */}
+
+
+
+ Interactive Health Network Map
+
+
+
+ c.id === selectedCategory)?.name
+ : undefined
+ }
+ />
+
+
+
+
+
+ {/* Health Categories */}
+
+ Health Network Categories
+
+ {healthCategories.map((category) => (
+
+ setSelectedCategory(category.id)}
+ >
+
+
+
+
+
{formatNumber(category.count)}
+
Active
+
+
+ {category.name}
+ {category.description}
+
+
+
+ ))}
+
+
+
+ {/* Call to Action */}
+
+
+
Join Africa's Health Revolution
+
+ Become part of the growing network of health sovereignty advocates. Every connection strengthens our
+ collective power to heal Africa.
+
+
+
+ window.open(
+ "https://docs.google.com/forms/d/e/1FAIpQLSfuxO0SEkiGhuvYqP5fE4c5RgqdyZc76rppeC70rDsu67ssgw/viewform?usp=sharing&ouid=117857227348165727286",
+ "_blank",
+ )
+ }
+ >
+
+ Become a Guardian
+
+
+ window.open(
+ "https://docs.google.com/forms/d/e/1FAIpQLSfuxO0SEkiGhuvYqP5fE4c5RgqdyZc76rppeC70rDsu67ssgw/viewform?usp=sharing&ouid=117857227348165727286",
+ "_blank",
+ )
+ }
+ >
+
+ Register as Health Actor
+
+
+
+
+
+
+ )
+}
+
+export default NetworkVisualization
diff --git a/components/notification-system.tsx b/components/notification-system.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..0c4c73baccf4d62f2481ff00f3dbbdfaa4ceeca1
--- /dev/null
+++ b/components/notification-system.tsx
@@ -0,0 +1,150 @@
+"use client"
+
+import { useState } from "react"
+import { Bell, X, Check, AlertTriangle, Info } from "lucide-react"
+import { Button } from "@/components/ui/button"
+import { Card } from "@/components/ui/card"
+import { Badge } from "@/components/ui/badge"
+
+// Mock notifications for demonstration
+const MOCK_NOTIFICATIONS = [
+ {
+ id: 1,
+ type: "success",
+ title: "Donation Successful",
+ message: "Your donation of $50 to Dr. Amara Okafor has been processed successfully.",
+ time: "2 minutes ago",
+ read: false,
+ },
+ {
+ id: 2,
+ type: "info",
+ title: "New Guardian Verified",
+ message: "A new Guardian has been verified in your region. Connect to expand your network.",
+ time: "1 hour ago",
+ read: false,
+ },
+ {
+ id: 3,
+ type: "warning",
+ title: "Verification Pending",
+ message: "Your Guardian application is still pending. We'll notify you once it's reviewed.",
+ time: "1 day ago",
+ read: true,
+ },
+]
+
+export function NotificationSystem() {
+ const [isOpen, setIsOpen] = useState(false)
+ const [notifications, setNotifications] = useState(MOCK_NOTIFICATIONS)
+ const unreadCount = notifications.filter((n) => !n.read).length
+
+ const markAsRead = (id: number) => {
+ setNotifications(notifications.map((n) => (n.id === id ? { ...n, read: true } : n)))
+ }
+
+ const markAllAsRead = () => {
+ setNotifications(notifications.map((n) => ({ ...n, read: true })))
+ }
+
+ const deleteNotification = (id: number) => {
+ setNotifications(notifications.filter((n) => n.id !== id))
+ }
+
+ const getIconForType = (type: string) => {
+ switch (type) {
+ case "success":
+ return
+ case "warning":
+ return
+ case "info":
+ return
+ default:
+ return
+ }
+ }
+
+ return (
+
+
setIsOpen(!isOpen)}
+ aria-label="Notifications"
+ >
+
+ {unreadCount > 0 && (
+
+ {unreadCount}
+
+ )}
+
+
+ {isOpen && (
+
+
+
Notifications
+ {unreadCount > 0 && (
+
+ Mark all as read
+
+ )}
+
+
+
+ {notifications.length > 0 ? (
+
+ {notifications.map((notification) => (
+
+
+
{getIconForType(notification.type)}
+
+
+
{notification.title}
+
deleteNotification(notification.id)}
+ className="text-gray-500 hover:text-white"
+ >
+
+
+
+
{notification.message}
+
+ {notification.time}
+ {!notification.read && (
+ markAsRead(notification.id)}
+ className="text-xs text-flame hover:text-flame-red"
+ >
+ Mark as read
+
+ )}
+
+
+
+
+ ))}
+
+ ) : (
+
+ )}
+
+
+ )}
+
+ )
+}
diff --git a/components/page-intro.tsx b/components/page-intro.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..8c4a6deb259cf5ca121ca14a41a5281b8326f764
--- /dev/null
+++ b/components/page-intro.tsx
@@ -0,0 +1,56 @@
+"use client"
+
+import type React from "react"
+
+import { motion } from "framer-motion"
+
+interface PageIntroProps {
+ title: string
+ subtitle: string
+ icon: React.ReactNode
+ color: string
+ children?: React.ReactNode
+}
+
+export function PageIntro({ title, subtitle, icon, color, children }: PageIntroProps) {
+ return (
+
+ {/* Background glow effect */}
+
+
+
+
+ {icon}
+ {subtitle}
+
+
+
+ {title.split(" ").map((word, i) => (
+ {i % 2 === 1 ? {word} : {word} }
+ ))}
+
+
+
+ {children}
+
+
+
+ )
+}
diff --git a/components/particle-background.tsx b/components/particle-background.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..38167e88866c3362a31fc4f325e0f78d449198ce
--- /dev/null
+++ b/components/particle-background.tsx
@@ -0,0 +1,117 @@
+"use client"
+
+import { useEffect, useRef } from "react"
+
+interface Particle {
+ x: number
+ y: number
+ vx: number
+ vy: number
+ size: number
+ opacity: number
+ color: string
+}
+
+export default function ParticleBackground() {
+ const canvasRef = useRef(null)
+ const particlesRef = useRef([])
+ const animationRef = useRef()
+
+ useEffect(() => {
+ const canvas = canvasRef.current
+ if (!canvas) return
+
+ const ctx = canvas.getContext("2d")
+ if (!ctx) return
+
+ const resizeCanvas = () => {
+ canvas.width = window.innerWidth
+ canvas.height = window.innerHeight
+ }
+
+ const createParticles = () => {
+ const particles: Particle[] = []
+ const particleCount = Math.min(50, Math.floor((canvas.width * canvas.height) / 15000))
+
+ for (let i = 0; i < particleCount; i++) {
+ particles.push({
+ x: Math.random() * canvas.width,
+ y: Math.random() * canvas.height,
+ vx: (Math.random() - 0.5) * 0.5,
+ vy: (Math.random() - 0.5) * 0.5,
+ size: Math.random() * 2 + 1,
+ opacity: Math.random() * 0.5 + 0.2,
+ color: Math.random() > 0.5 ? "#f97316" : "#ef4444",
+ })
+ }
+
+ particlesRef.current = particles
+ }
+
+ const animate = () => {
+ ctx.clearRect(0, 0, canvas.width, canvas.height)
+
+ particlesRef.current.forEach((particle, index) => {
+ // Update position
+ particle.x += particle.vx
+ particle.y += particle.vy
+
+ // Bounce off edges
+ if (particle.x <= 0 || particle.x >= canvas.width) particle.vx *= -1
+ if (particle.y <= 0 || particle.y >= canvas.height) particle.vy *= -1
+
+ // Keep particles in bounds
+ particle.x = Math.max(0, Math.min(canvas.width, particle.x))
+ particle.y = Math.max(0, Math.min(canvas.height, particle.y))
+
+ // Draw particle
+ ctx.beginPath()
+ ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2)
+ ctx.fillStyle = `${particle.color}${Math.floor(particle.opacity * 255)
+ .toString(16)
+ .padStart(2, "0")}`
+ ctx.fill()
+
+ // Draw connections to nearby particles
+ particlesRef.current.slice(index + 1).forEach((otherParticle) => {
+ const dx = particle.x - otherParticle.x
+ const dy = particle.y - otherParticle.y
+ const distance = Math.sqrt(dx * dx + dy * dy)
+
+ if (distance < 100) {
+ ctx.beginPath()
+ ctx.moveTo(particle.x, particle.y)
+ ctx.lineTo(otherParticle.x, otherParticle.y)
+ ctx.strokeStyle = `rgba(249, 115, 22, ${0.1 * (1 - distance / 100)})`
+ ctx.lineWidth = 0.5
+ ctx.stroke()
+ }
+ })
+ })
+
+ animationRef.current = requestAnimationFrame(animate)
+ }
+
+ resizeCanvas()
+ createParticles()
+ animate()
+
+ const handleResize = () => {
+ resizeCanvas()
+ createParticles()
+ }
+
+ window.addEventListener("resize", handleResize)
+
+ return () => {
+ window.removeEventListener("resize", handleResize)
+ if (animationRef.current) {
+ cancelAnimationFrame(animationRef.current)
+ }
+ }
+ }, [])
+
+ return (
+
+ )
+}
diff --git a/components/profile/profile-achievements.tsx b/components/profile/profile-achievements.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..3f6bafa0b676e6cbf753a6a0863ac79db11f51e2
--- /dev/null
+++ b/components/profile/profile-achievements.tsx
@@ -0,0 +1,55 @@
+import type { User } from "@/lib/user-database"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Award } from "lucide-react"
+
+interface ProfileAchievementsProps {
+ user: User
+}
+
+export function ProfileAchievements({ user }: ProfileAchievementsProps) {
+ const achievements = user.achievements || []
+
+ return (
+
+
+ Achievements
+
+
+ {achievements.length === 0 ? (
+
+
+
+
No achievements yet
+
+ {user.type === "guardian"
+ ? "As you support healthcare workers, you'll earn achievements"
+ : "As you serve your community, you'll earn achievements"}
+
+
+
+ ) : (
+
+ {achievements.map((achievement) => (
+
+
+
+
+
+
{achievement.title}
+
{achievement.description}
+
+ Awarded on {new Date(achievement.awardedAt).toLocaleDateString()}
+
+
+
+
+
+ ))}
+
+ )}
+
+
+ )
+}
diff --git a/components/profile/profile-activity.tsx b/components/profile/profile-activity.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..df99e22dd336d3f04733c523d59937c7973dff6a
--- /dev/null
+++ b/components/profile/profile-activity.tsx
@@ -0,0 +1,30 @@
+import type { User } from "@/lib/user-database"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Activity } from "lucide-react"
+
+interface ProfileActivityProps {
+ user: User
+}
+
+export function ProfileActivity({ user }: ProfileActivityProps) {
+ return (
+
+
+ Recent Activity
+
+
+
+
+
+
No activity yet
+
+ {user.type === "guardian"
+ ? "Your donations and verification activities will appear here"
+ : "Your received donations and updates will appear here"}
+
+
+
+
+
+ )
+}
diff --git a/components/profile/profile-header.tsx b/components/profile/profile-header.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..928748d7848fffb4dcf1a4e1ece0f2be53a46ad4
--- /dev/null
+++ b/components/profile/profile-header.tsx
@@ -0,0 +1,106 @@
+import type { User, Healer } from "@/lib/user-database"
+import { Card, CardContent } from "@/components/ui/card"
+import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
+import { Badge } from "@/components/ui/badge"
+import { Shield, Heart, MapPin, Briefcase, Calendar } from "lucide-react"
+
+interface ProfileHeaderProps {
+ user: User
+}
+
+export function ProfileHeader({ user }: ProfileHeaderProps) {
+ const getInitials = (name: string) => {
+ return name
+ .split(" ")
+ .map((part) => part[0])
+ .join("")
+ .toUpperCase()
+ .substring(0, 2)
+ }
+
+ const getVerificationBadge = (user: Healer) => {
+ if (user.verificationStatus === "verified") {
+ return Verified
+ } else if (user.verificationStatus === "rejected") {
+ return Rejected
+ } else {
+ return Pending Verification
+ }
+ }
+
+ return (
+
+
+
+
+ {user.profileImage ? (
+
+ ) : (
+
+ {getInitials(user.fullName)}
+
+ )}
+
+
+
+
+
{user.fullName}
+
+ {user.type === "guardian" ? (
+ <>
+ Guardian
+ >
+ ) : (
+ <>
+ Healer
+ >
+ )}
+
+ {user.type === "healer" && getVerificationBadge(user)}
+
+
+
+ {user.type === "guardian" ? (
+ <>
+
+
+ {user.country}
+
+ >
+ ) : (
+ <>
+
+
+ {user.role}
+ {user.specialization && • {user.specialization} }
+
+
+
+
+ {user.city}, {user.country}
+
+
+ >
+ )}
+
+
+ Joined {new Date(user.registeredAt).toLocaleDateString()}
+
+
+
+ {user.type === "healer" && user.bio &&
{user.bio}
}
+
+ {user.type === "guardian" && user.motivation && (
+
+
Why I became a Guardian:
+
"{user.motivation}"
+
+ )}
+
+
+
+
+ )
+}
diff --git a/components/profile/profile-impact.tsx b/components/profile/profile-impact.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..fd2ac5fb666e66e1f52f0f2b1ab22fb51ce75b12
--- /dev/null
+++ b/components/profile/profile-impact.tsx
@@ -0,0 +1,120 @@
+import type { User } from "@/lib/user-database"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Heart, Users, MapPin, DollarSign, Activity } from "lucide-react"
+
+interface ProfileImpactProps {
+ user: User
+}
+
+export function ProfileImpact({ user }: ProfileImpactProps) {
+ return (
+
+
+ {user.type === "guardian" ? (
+ <>
+
+
+
+
+
Total Donated
+
${user.impact.totalDonated}
+
+
+
+
+
+
+
+
+
+
+
+
+
Donations Made
+
{user.impact.donationsCount}
+
+
+
+
+
+
+
+
+
+
+
Healers Supported
+
{user.impact.healthWorkersSupported}
+
+
+
+
+
+
+
+ >
+ ) : (
+ <>
+
+
+
+
+
Patients Served
+
{user.impact.patientsServed}
+
+
+
+
+
+
+
+
+
+
+
+
+
Communities Reached
+
{user.impact.communitiesReached}
+
+
+
+
+
+
+
+
+
+
+
+
+
Donations Received
+
${user.impact.donationsReceived}
+
+
+
+
+
+
+
+ >
+ )}
+
+
+
+
+ Impact Visualization
+
+
+
+
+
+
Impact visualization will be displayed here
+
As you make or receive contributions, your impact will be visualized here
+
+
+
+
+
+ )
+}
diff --git a/components/proposal-feed.tsx b/components/proposal-feed.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..feb29095459577f64c2d0d6454ab7f3d32489c95
--- /dev/null
+++ b/components/proposal-feed.tsx
@@ -0,0 +1,211 @@
+"use client"
+
+import { useState } from "react"
+import { Card, CardContent } from "@/components/ui/card"
+import { Button } from "@/components/ui/button"
+import { Scrolls } from "@/lib/scrolls" // Assuming this path is correct and Scrolls is an object/array
+
+// Mock data for proposals - consider moving to a separate file or fetching
+const mockProposals = [
+ {
+ id: "prop-001",
+ title: "Fund for Eid care kits in Mombasa",
+ description: "Provide medical care kits to underserved communities during Eid celebrations in Mombasa.",
+ scrollId: "SDQ-004", // Swahili Digital Quill
+ status: "pending",
+ createdAt: "2024-04-16T14:30:00Z",
+ author: "0x8d45...b2c7",
+ votes: { yes: 12, no: 3, abstain: 2 },
+ link: "/proposals/prop-001", // Example link
+ },
+ {
+ id: "prop-002",
+ title: "Expand healthcare worker network to rural areas in Nigeria",
+ description: "Create a program to recruit and train healthcare workers in rural communities across Nigeria.",
+ scrollId: "AKWA-001", // Akwa Ibom Scroll
+ status: "approved",
+ createdAt: "2024-04-15T09:45:00Z",
+ author: "0x2f19...d8e3",
+ votes: { yes: 24, no: 1, abstain: 3 },
+ link: "/proposals/prop-002",
+ },
+ {
+ id: "prop-003",
+ title: "Emergency fund for flood-affected clinics in East Africa",
+ description: "Establish an emergency fund to support clinics affected by recent flooding in East Africa.",
+ scrollId: "GIO-003", // Gikuyu Oral Traditions Scroll
+ status: "active",
+ createdAt: "2024-04-14T16:20:00Z",
+ author: "0x6a23...c4f1",
+ votes: { yes: 18, no: 5, abstain: 1 },
+ link: "/proposals/prop-003",
+ },
+ {
+ id: "prop-004",
+ title: "Midwife training program in Nairobi",
+ description: "Fund a training program for midwives in Nairobi to improve maternal care.",
+ scrollId: "YRBA-002", // Yoruba Ancestral Scroll
+ status: "completed",
+ createdAt: "2024-03-13T11:10:00Z",
+ author: "0x3b92...a1d4",
+ votes: { yes: 30, no: 2, abstain: 0 },
+ link: "/proposals/prop-004",
+ },
+]
+
+type ProposalStatus = "all" | "pending" | "approved" | "active" | "completed" | "rejected" // Added rejected
+
+export default function ProposalFeed() {
+ const [filter, setFilter] = useState("all")
+ const [scrollFilter, setScrollFilter] = useState("all") // scrollId or "all"
+
+ const filteredProposals = mockProposals.filter((proposal) => {
+ const statusMatch = filter === "all" || proposal.status === filter
+ const scrollMatch = scrollFilter === "all" || proposal.scrollId === scrollFilter
+ return statusMatch && scrollMatch
+ })
+
+ const getStatusColor = (status: string) => {
+ switch (status) {
+ case "pending":
+ return "text-yellow-300 bg-yellow-900/50 border-yellow-700/60"
+ case "approved":
+ return "text-green-300 bg-green-900/50 border-green-700/60"
+ case "active":
+ return "text-cyan-300 bg-cyan-900/50 border-cyan-700/60"
+ case "completed":
+ return "text-purple-300 bg-purple-900/50 border-purple-700/60"
+ case "rejected":
+ return "text-red-300 bg-red-900/50 border-red-700/60"
+ default:
+ return "text-gray-300 bg-gray-900/50 border-gray-700/60"
+ }
+ }
+
+ // Assuming Scrolls is an object where keys are scroll IDs and values are scroll objects
+ const scrollObjects = Object.values(Scrolls)
+
+ return (
+
+
+ 📋 Proposal Feed
+
+ {/* Filters */}
+
+
+
Filter by Status:
+
+ {(["all", "pending", "active", "approved", "completed", "rejected"] as ProposalStatus[]).map((status) => (
+ setFilter(status)}
+ className={`${
+ filter === status
+ ? "bg-flame-orange text-white hover:bg-flame-orange/90"
+ : "text-cyan-200 border-cyan-700/50 hover:bg-cyan-900/30"
+ } transition-colors`}
+ >
+ {status.charAt(0).toUpperCase() + status.slice(1)}
+
+ ))}
+
+
+
+
+
Filter by Scroll:
+
+ setScrollFilter("all")}
+ className={`${
+ scrollFilter === "all"
+ ? "bg-flame-orange text-white hover:bg-flame-orange/90"
+ : "text-cyan-200 border-cyan-700/50 hover:bg-cyan-900/30"
+ } transition-colors`}
+ >
+ All Scrolls
+
+ {scrollObjects.map((scroll) => (
+ setScrollFilter(scroll.scrollId)}
+ className={`${
+ scrollFilter === scroll.scrollId
+ ? "bg-flame-orange text-white hover:bg-flame-orange/90"
+ : "text-cyan-200 border-cyan-700/50 hover:bg-cyan-900/30"
+ } transition-colors`}
+ title={scroll.name}
+ >
+ {scroll.scrollId}
+
+ ))}
+
+
+
+
+ {/* Proposals List */}
+
+ {filteredProposals.length === 0 ? (
+
No proposals match your current filters.
+ ) : (
+ filteredProposals.map((proposal) => (
+
+
+
+
+ {proposal.status.charAt(0).toUpperCase() + proposal.status.slice(1)}
+
+
+
+
{proposal.description}
+
+
+
+ {proposal.scrollId}
+
+
+
Created: {new Date(proposal.createdAt).toLocaleDateString()}
+
+
By: {proposal.author}
+
+
+ Yes: {proposal.votes.yes}
+ No: {proposal.votes.no}
+ Abstain: {proposal.votes.abstain}
+
+
+ {/* Placeholder for a "View Details" or "Vote" button */}
+ {/*
+
+ View Details
+
+
*/}
+
+ ))
+ )}
+
+
+
+ )
+}
diff --git a/components/proverb-tester.tsx b/components/proverb-tester.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..be7a7ef69105b221d3623a2a4bdca0c9265f390a
--- /dev/null
+++ b/components/proverb-tester.tsx
@@ -0,0 +1,565 @@
+"use client"
+
+import { useState, useEffect } from "react"
+import { Button } from "@/components/ui/button"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Input } from "@/components/ui/input"
+import { Label } from "@/components/ui/label"
+import { Textarea } from "@/components/ui/textarea"
+import { Badge } from "@/components/ui/badge"
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
+import {
+ BookOpen,
+ Search,
+ Hash,
+ CheckCircle,
+ XCircle,
+ Globe,
+ Heart,
+ Zap,
+ RefreshCw,
+ Filter,
+ MapPin,
+ Languages,
+ Lightbulb,
+ Shield,
+ AlertTriangle,
+} from "lucide-react"
+import { proverbValidator, type AfricanProverb } from "@/lib/proverb-validator"
+
+interface ProverbValidationResult {
+ isValid: boolean
+ proverb?: AfricanProverb
+ proverbHash?: string
+ culturalValidation?: {
+ isValid: boolean
+ culturalRelevance: number
+ message: string
+ }
+ message?: string
+ suggestedProverb?: AfricanProverb
+}
+
+export function ProverbTester() {
+ const [testProverb, setTestProverb] = useState("")
+ const [testAction, setTestAction] = useState("")
+ const [validationResult, setValidationResult] = useState(null)
+ const [isLoading, setIsLoading] = useState(false)
+ const [searchResults, setSearchResults] = useState([])
+ const [selectedRegion, setSelectedRegion] = useState("")
+ const [selectedLanguage, setSelectedLanguage] = useState("")
+ const [selectedCountry, setSelectedCountry] = useState("")
+ const [availableFilters, setAvailableFilters] = useState({
+ regions: [] as string[],
+ languages: [] as string[],
+ countries: [] as string[],
+ })
+ const [challenge, setChallenge] = useState(null)
+ const [selectedAnswer, setSelectedAnswer] = useState(null)
+ const [showAnswer, setShowAnswer] = useState(false)
+
+ const useProverbFromSearch = (proverb: AfricanProverb) => {
+ setTestProverb(proverb.translated_meaning)
+ setValidationResult(null)
+ }
+
+ // Load available filters on component mount
+ useEffect(() => {
+ setAvailableFilters({
+ regions: proverbValidator.getAvailableRegions(),
+ languages: proverbValidator.getAvailableLanguages(),
+ countries: proverbValidator.getAvailableCountries(),
+ })
+ }, [])
+
+ // Search proverbs when filters change
+ useEffect(() => {
+ const filters: any = {}
+ if (selectedRegion) filters.region = selectedRegion
+ if (selectedLanguage) filters.language = selectedLanguage
+ if (selectedCountry) filters.country = selectedCountry
+
+ const results = proverbValidator.searchProverbs(filters)
+ setSearchResults(results.slice(0, 10)) // Limit to 10 results for display
+ }, [selectedRegion, selectedLanguage, selectedCountry])
+
+ const handleProverbValidation = async () => {
+ if (!testProverb.trim()) return
+
+ setIsLoading(true)
+ setValidationResult(null)
+
+ try {
+ // Use the API route for validation
+ const response = await fetch("/api/proverb-validation", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ proverb: testProverb,
+ action: testAction || undefined,
+ }),
+ })
+
+ const result = await response.json()
+ setValidationResult(result)
+ } catch (error) {
+ console.error("Validation error:", error)
+ setValidationResult({
+ isValid: false,
+ message: "Failed to validate proverb. Please try again.",
+ })
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ const handleRandomProverb = () => {
+ const randomProverb = proverbValidator.getRandomProverb()
+ setTestProverb(randomProverb.translated_meaning)
+ setValidationResult(null)
+ }
+
+ const generateChallenge = () => {
+ const newChallenge = proverbValidator.generateProverbChallenge()
+ setChallenge(newChallenge)
+ setSelectedAnswer(null)
+ setShowAnswer(false)
+ }
+
+ const handleAnswerSelect = (answerIndex: number) => {
+ setSelectedAnswer(answerIndex)
+ setShowAnswer(true)
+ }
+
+ const clearFilters = () => {
+ setSelectedRegion("")
+ setSelectedLanguage("")
+ setSelectedCountry("")
+ }
+
+ return (
+
+ {/* Header */}
+
+
+
+
+ African Proverb Wisdom Tester
+
+
+ Test and explore verified African proverbs for cultural anchoring in FlameBorn actions
+
+
+
+
+
+ {/* Proverb Validation Section */}
+
+
+
+
+ Proverb Validation
+
+
+
+
+
+ Enter African Proverb (original or translated)
+
+
+ setTestProverb(e.target.value)}
+ placeholder="Enter proverb to validate..."
+ className="bg-gray-800 border-gray-600 text-white"
+ />
+
+
+
+
+
+
+
+
+ Generative Action (optional - for cultural context validation)
+
+ setTestAction(e.target.value)}
+ placeholder="Describe your generative action for cultural relevance testing..."
+ className="bg-gray-800 border-gray-600 text-white mt-1"
+ rows={3}
+ />
+
+
+
+ {isLoading ? (
+ <>
+
+ Validating...
+ >
+ ) : (
+ <>
+
+ Validate Proverb
+ >
+ )}
+
+
+ {/* Validation Results */}
+ {validationResult && (
+
+
+
+ {validationResult.isValid ? (
+
+ ) : (
+
+ )}
+
+ {validationResult.isValid ? "Valid African Proverb" : "Proverb Not Found"}
+
+
+
+ {validationResult.message}
+
+ {validationResult.proverb && (
+
+
+
+
+ {validationResult.proverb.language}
+
+
+
+ {validationResult.proverb.country}
+
+
+ {validationResult.proverb.region}
+
+
+
"{validationResult.proverb.translated_meaning}"
+ {validationResult.proverb.proverb !== validationResult.proverb.translated_meaning && (
+
Original: "{validationResult.proverb.proverb}"
+ )}
+
{validationResult.proverb.cultural_notes}
+ {validationResult.proverbHash && (
+
+
+ Hash: {validationResult.proverbHash.substring(0, 16)}...
+
+ )}
+
+ )}
+
+ {validationResult.culturalValidation && (
+
+
+
+ Cultural Relevance Analysis
+
+
+
Relevance Score:
+
+
= 0.7
+ ? "bg-green-500"
+ : validationResult.culturalValidation.culturalRelevance >= 0.4
+ ? "bg-yellow-500"
+ : "bg-red-500"
+ }`}
+ style={{
+ width: `${validationResult.culturalValidation.culturalRelevance * 100}%`,
+ }}
+ />
+
+
+ {Math.round(validationResult.culturalValidation.culturalRelevance * 100)}%
+
+
+
{validationResult.culturalValidation.message}
+
+ )}
+
+ {validationResult.suggestedProverb && (
+
+
+
+ Suggested Alternative
+
+
"{validationResult.suggestedProverb.translated_meaning}"
+
+
+ {validationResult.suggestedProverb.language}
+
+
+ {validationResult.suggestedProverb.country}
+
+
+
useProverbFromSearch(validationResult.suggestedProverb!)}
+ size="sm"
+ className="bg-blue-600 hover:bg-blue-700 text-white"
+ >
+ Use This Proverb
+
+
+ )}
+
+
+ )}
+
+
+
+ {/* Proverb Search & Browse Section */}
+
+
+
+
+ Browse Wisdom Database
+
+
+
+ {/* Filters */}
+
+
+ Filter by Region
+
+
+
+
+
+ {availableFilters.regions.map((region) => (
+
+ {region}
+
+ ))}
+
+
+
+
+
+ Filter by Language
+
+
+
+
+
+ {availableFilters.languages.map((language) => (
+
+ {language}
+
+ ))}
+
+
+
+
+
+ Filter by Country
+
+
+
+
+
+ {availableFilters.countries.map((country) => (
+
+ {country}
+
+ ))}
+
+
+
+
+
+
+ Clear Filters
+
+
+
+ {/* Search Results */}
+
+ {searchResults.map((proverb, index) => (
+
+
+
+
+
+ {proverb.language}
+
+
+ {proverb.country}
+
+
+
useProverbFromSearch(proverb)}
+ size="sm"
+ variant="outline"
+ className="border-yellow-600 text-yellow-400 hover:bg-yellow-600 hover:text-white text-xs"
+ >
+ Use
+
+
+ "{proverb.translated_meaning}"
+ {proverb.proverb !== proverb.translated_meaning && (
+ Original: "{proverb.proverb}"
+ )}
+ {proverb.cultural_notes}
+
+
+ ))}
+ {searchResults.length === 0 && (
+
+
+
No proverbs found with current filters
+
+ )}
+
+
+
+
+
+ {/* Learn & Earn Challenge Section */}
+
+
+
+
+ Learn & Earn Challenge
+
+ Test your knowledge of African wisdom and earn FLB tokens!
+
+
+
+
+ Generate New Challenge
+
+
+ {challenge && (
+
+
+
+
+
+ {challenge.proverb.language}
+
+
+ {challenge.proverb.country}
+
+
+ {challenge.proverb.region}
+
+
+
{challenge.question}
+
+
+
+ {challenge.options.map((option: string, index: number) => (
+ handleAnswerSelect(index)}
+ disabled={showAnswer}
+ variant="outline"
+ className={`w-full text-left justify-start p-3 h-auto ${
+ showAnswer
+ ? index === challenge.correctAnswer
+ ? "border-green-500 bg-green-500/20 text-green-400"
+ : selectedAnswer === index
+ ? "border-red-500 bg-red-500/20 text-red-400"
+ : "border-gray-600 text-gray-400"
+ : "border-gray-600 text-white hover:border-yellow-600"
+ }`}
+ >
+ {String.fromCharCode(65 + index)}.
+ {option}
+ {showAnswer && index === challenge.correctAnswer && (
+
+ )}
+ {showAnswer && selectedAnswer === index && index !== challenge.correctAnswer && (
+
+ )}
+
+ ))}
+
+
+ {showAnswer && (
+
+
+ {selectedAnswer === challenge.correctAnswer ? (
+
+ ) : (
+
+ )}
+
+ {selectedAnswer === challenge.correctAnswer ? "Correct! 🎉" : "Not quite right"}
+
+
+
+ Cultural Context: {challenge.proverb.cultural_notes}
+
+ {selectedAnswer === challenge.correctAnswer && (
+
+
+ You earned 10 FLB tokens for correct answer!
+
+ )}
+
+ )}
+
+
+ )}
+
+
+
+ {/* Database Stats */}
+
+
+
+
+ Wisdom Database Statistics
+
+
+
+
+
+
{availableFilters.countries.length}
+
Countries
+
+
+
{availableFilters.regions.length}
+
Regions
+
+
+
{availableFilters.languages.length}
+
Languages
+
+
+
+
+
+
+ )
+}
+
+export default ProverbTester
diff --git a/components/pulse/pulse-activity.tsx b/components/pulse/pulse-activity.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..9bd1dfe1b9fdab3278c59889c8780cc47b6faf54
--- /dev/null
+++ b/components/pulse/pulse-activity.tsx
@@ -0,0 +1,113 @@
+"use client"
+
+import { useState, useEffect } from "react"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Badge } from "@/components/ui/badge"
+import { Activity, Shield, Heart, Clock } from "lucide-react"
+
+// Mock data for activity feed
+const INITIAL_ACTIVITIES: any[] = []
+
+// Helper function to get badge color based on activity type
+const getActivityColor = (type: string) => {
+ switch (type) {
+ case "verification":
+ return "bg-guardian/20 text-guardian hover:bg-guardian/30"
+ case "donation":
+ return "bg-pulse/20 text-pulse hover:bg-pulse/30"
+ case "registration":
+ return "bg-flame/20 text-flame hover:bg-flame/30"
+ case "achievement":
+ return "bg-neon/20 text-neon hover:bg-neon/30"
+ default:
+ return "bg-gray-500/20 text-gray-400 hover:bg-gray-500/30"
+ }
+}
+
+export function PulseActivity() {
+ const [activities, setActivities] = useState(INITIAL_ACTIVITIES)
+ const [filter, setFilter] = useState("all")
+
+ // Simulate real-time updates
+ useEffect(() => {
+ // In a real implementation, this would connect to a WebSocket or polling API
+ // For now, we'll leave it empty to allow manual data entry
+
+ return () => {} // Empty cleanup function
+ }, [])
+
+ // Filter activities
+ const filteredActivities = filter === "all" ? activities : activities.filter((activity) => activity.type === filter)
+
+ return (
+
+
+
+
+
+ Live Activity Feed
+
+
+
+ setFilter("all")}
+ >
+ All
+
+ setFilter("verification")}
+ >
+ Verifications
+
+ setFilter("donation")}
+ >
+ Donations
+
+
+
+
+
+
+ {filteredActivities.map((activity) => (
+
+
+
+
+
+ {activity.user}
+ {activity.action}
+ {activity.target}
+
+
+
+ {activity.time}
+ •
+ {activity.location}
+
+
+
+
{activity.type}
+
+ ))}
+
+ {filteredActivities.length === 0 && (
+
+
+
No Activity Yet
+
Activities will appear here as users interact with the platform.
+
+ )}
+
+
+
+ )
+}
diff --git a/components/pulse/pulse-hero.tsx b/components/pulse/pulse-hero.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..13b3ca9427e92a54cbe43acaa76392668b1a968b
--- /dev/null
+++ b/components/pulse/pulse-hero.tsx
@@ -0,0 +1,28 @@
+import { Activity } from "lucide-react"
+import { PageIntro } from "@/components/page-intro"
+import { Button } from "@/components/ui/button"
+
+export function PulseHero() {
+ return (
+
}
+ color="neon"
+ >
+
+ Experience the living ecosystem of care in real-time. Watch as the flame spreads across Africa, connecting
+ healers and communities in a sacred bond of support and transformation.
+
+
+
+
+ Explore Impact Data
+
+
+ Join the Movement
+
+
+
+ )
+}
diff --git a/components/pulse/pulse-map.tsx b/components/pulse/pulse-map.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..c0fc43c0cb6b50f6f6a4719b984713b855b2443f
--- /dev/null
+++ b/components/pulse/pulse-map.tsx
@@ -0,0 +1,230 @@
+"use client"
+
+import type React from "react"
+
+import { useEffect, useRef, useState } from "react"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { MapPin, Info } from "lucide-react"
+
+// Mock data for map points
+const MAP_POINTS: any[] = []
+
+export function PulseMap() {
+ const canvasRef = useRef
(null)
+ const [hoveredPoint, setHoveredPoint] = useState<(typeof MAP_POINTS)[0] | null>(null)
+ const [canvasSize, setCanvasSize] = useState({ width: 0, height: 0 })
+
+ // Function to convert lat/lng to canvas coordinates
+ const latLngToCanvas = (lat: number, lng: number) => {
+ // Simple conversion for demonstration
+ const x = ((lng + 180) / 360) * canvasSize.width
+ const y = ((90 - lat) / 180) * canvasSize.height
+ return { x, y }
+ }
+
+ // Initialize and draw the map
+ useEffect(() => {
+ const canvas = canvasRef.current
+ if (!canvas) return
+
+ const ctx = canvas.getContext("2d")
+ if (!ctx) return
+
+ const handleResize = () => {
+ const container = canvas.parentElement
+ if (!container) return
+
+ const { width, height } = container.getBoundingClientRect()
+ canvas.width = width
+ canvas.height = height
+ setCanvasSize({ width, height })
+ }
+
+ window.addEventListener("resize", handleResize)
+ handleResize()
+
+ return () => {
+ window.removeEventListener("resize", handleResize)
+ }
+ }, [])
+
+ // Draw the map and points
+ useEffect(() => {
+ const canvas = canvasRef.current
+ if (!canvas || canvasSize.width === 0) return
+
+ const ctx = canvas.getContext("2d")
+ if (!ctx) return
+
+ const drawMap = () => {
+ // Clear canvas
+ ctx.clearRect(0, 0, canvas.width, canvas.height)
+
+ // Draw simplified Africa outline
+ ctx.beginPath()
+ ctx.strokeStyle = "rgba(255, 255, 255, 0.2)"
+ ctx.lineWidth = 1
+
+ // Very simplified Africa outline - just for demonstration
+ const africaOutline = [
+ { lat: 35.9, lng: -5.8 }, // Morocco
+ { lat: 37.3, lng: 9.8 }, // Tunisia
+ { lat: 31.7, lng: 25.1 }, // Egypt
+ { lat: 15.3, lng: 38.9 }, // Eritrea
+ { lat: 11.8, lng: 43.1 }, // Djibouti
+ { lat: 1.6, lng: 41.5 }, // Somalia
+ { lat: -4.6, lng: 39.2 }, // Tanzania
+ { lat: -34.8, lng: 20.0 }, // South Africa
+ { lat: -22.9, lng: 14.5 }, // Namibia
+ { lat: 6.4, lng: 3.4 }, // Nigeria
+ { lat: 14.7, lng: -17.4 }, // Senegal
+ { lat: 31.8, lng: -7.0 }, // Morocco (back to start)
+ ]
+
+ // Draw the outline
+ ctx.beginPath()
+ let firstPoint = true
+ africaOutline.forEach((point) => {
+ const { x, y } = latLngToCanvas(point.lat, point.lng)
+ if (firstPoint) {
+ ctx.moveTo(x, y)
+ firstPoint = false
+ } else {
+ ctx.lineTo(x, y)
+ }
+ })
+ ctx.closePath()
+ ctx.stroke()
+
+ // Fill with a subtle gradient
+ const gradient = ctx.createRadialGradient(
+ canvas.width / 2,
+ canvas.height / 2,
+ 0,
+ canvas.width / 2,
+ canvas.height / 2,
+ canvas.width / 2,
+ )
+ gradient.addColorStop(0, "rgba(255, 78, 0, 0.05)")
+ gradient.addColorStop(1, "rgba(0, 0, 0, 0)")
+ ctx.fillStyle = gradient
+ ctx.fill()
+
+ // Draw points
+ MAP_POINTS.forEach((point) => {
+ const { x, y } = latLngToCanvas(point.lat, point.lng)
+
+ // Draw glow
+ const isHovered = hoveredPoint?.id === point.id
+ const radius = isHovered ? 15 : 10
+ const alpha = isHovered ? 0.6 : 0.3
+
+ // Glow effect
+ ctx.beginPath()
+ ctx.arc(x, y, radius * 2, 0, Math.PI * 2)
+ const glowGradient = ctx.createRadialGradient(x, y, 0, x, y, radius * 2)
+ glowGradient.addColorStop(0, `rgba(255, 78, 0, ${alpha})`)
+ glowGradient.addColorStop(1, "rgba(255, 78, 0, 0)")
+ ctx.fillStyle = glowGradient
+ ctx.fill()
+
+ // Draw point
+ ctx.beginPath()
+ ctx.arc(x, y, radius / 2, 0, Math.PI * 2)
+ ctx.fillStyle = point.color
+ ctx.fill()
+
+ // Draw pulse animation
+ if (isHovered) {
+ ctx.beginPath()
+ ctx.arc(x, y, radius * 3, 0, Math.PI * 2)
+ ctx.strokeStyle = `rgba(255, 78, 0, ${Math.sin(Date.now() / 200) * 0.2 + 0.2})`
+ ctx.lineWidth = 2
+ ctx.stroke()
+ }
+ })
+ }
+
+ drawMap()
+
+ // Animation frame
+ const animationFrame = requestAnimationFrame(() => {
+ if (hoveredPoint) {
+ // Redraw only if there's a hovered point to animate
+ drawMap()
+ }
+ })
+
+ return () => cancelAnimationFrame(animationFrame)
+ }, [canvasSize, hoveredPoint])
+
+ // Handle mouse movement to detect hovering over points
+ const handleMouseMove = (e: React.MouseEvent) => {
+ const canvas = canvasRef.current
+ if (!canvas) return
+
+ const rect = canvas.getBoundingClientRect()
+ const x = e.clientX - rect.left
+ const y = e.clientY - rect.top
+
+ // Check if mouse is over any point
+ let hoveredPoint = null
+ for (const point of MAP_POINTS) {
+ const canvasPoint = latLngToCanvas(point.lat, point.lng)
+ const distance = Math.sqrt(Math.pow(x - canvasPoint.x, 2) + Math.pow(y - canvasPoint.y, 2))
+
+ if (distance < 20) {
+ // Detection radius
+ hoveredPoint = point
+ break
+ }
+ }
+
+ setHoveredPoint(hoveredPoint)
+ }
+
+ return (
+
+
+
+
+ Flameborn Global Impact
+
+
+
+
+
+
+ {MAP_POINTS.length === 0 && (
+
+
+
+
No impact data available yet.
+
+ Data will appear as health workers are verified and donations are made.
+
+
+
+ )}
+
+ {hoveredPoint && (
+
+
{hoveredPoint.country}
+
{hoveredPoint.chws} Verified CHWs
+
+ )}
+
+
+ Hover over points to see details
+
+
+
+
+ )
+}
diff --git a/components/pulse/pulse-stats.tsx b/components/pulse/pulse-stats.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..c4764b42a2a54ae0799cfec0152ea0cd951d57f3
--- /dev/null
+++ b/components/pulse/pulse-stats.tsx
@@ -0,0 +1,131 @@
+"use client"
+
+import type React from "react"
+
+import { useEffect, useState } from "react"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { UserCheck, HeartHandshake, DollarSign, Users } from "lucide-react"
+import { Skeleton } from "@/components/ui/skeleton"
+
+// Type for stats
+type PulseStatsData = {
+ verifiedCHWs: number
+ activeGuardians: number
+ totalSupport: number
+ livesImpacted: number
+ totalUsers: number
+ lastUpdated: string
+}
+
+export function PulseStats() {
+ const [stats, setStats] = useState>({})
+ const [loading, setLoading] = useState(true)
+ const [error, setError] = useState(null)
+
+ useEffect(() => {
+ const fetchStats = async () => {
+ try {
+ const response = await fetch("/api/stats")
+ if (!response.ok) {
+ throw new Error("Failed to fetch stats")
+ }
+
+ const data = await response.json()
+ setStats(data)
+ } catch (err) {
+ setError("Failed to load statistics")
+ console.error(err)
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ fetchStats()
+ }, [])
+
+ // Stat card component
+ const StatCard = ({
+ title,
+ value,
+ icon: Icon,
+ isLoading,
+ isError,
+ colorClass,
+ }: {
+ title: string
+ value: number | string
+ icon: React.ElementType
+ isLoading: boolean
+ isError: boolean
+ colorClass?: string
+ }) => (
+
+ {colorClass && (
+
+ )}
+
+ {title}
+
+
+
+ {isError ? (
+ Data unavailable
+ ) : isLoading ? (
+
+ ) : (
+ {value}
+ )}
+
+ {isLoading
+ ? "Loading..."
+ : error
+ ? "Update failed"
+ : stats.lastUpdated
+ ? `Updated: ${new Date(stats.lastUpdated).toLocaleString()}`
+ : "Live data"}
+
+
+
+ )
+
+ return (
+
+
+
+
+
+
+ )
+}
diff --git a/components/register-chw.tsx b/components/register-chw.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..dc56e05a30faaea5b82430b82d0f7d7a14047ea7
--- /dev/null
+++ b/components/register-chw.tsx
@@ -0,0 +1,700 @@
+"use client"
+
+import type React from "react"
+
+import { useState, useRef } from "react"
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Textarea } from "@/components/ui/textarea"
+import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
+import { Checkbox } from "@/components/ui/checkbox"
+import { Label } from "@/components/ui/label"
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
+import { motion } from "framer-motion"
+import { Camera, Upload, Check, AlertTriangle, Loader2 } from "lucide-react"
+
+// Regions for the dropdown
+const REGIONS = [
+ { value: "eastern", label: "Eastern Africa" },
+ { value: "western", label: "Western Africa" },
+ { value: "northern", label: "Northern Africa" },
+ { value: "southern", label: "Southern Africa" },
+ { value: "central", label: "Central Africa" },
+]
+
+// License types
+const LICENSE_TYPES = [
+ { value: "medical", label: "Medical License (MD)" },
+ { value: "nursing", label: "Nursing License" },
+ { value: "midwife", label: "Midwifery Certificate" },
+ { value: "chw", label: "Community Health Worker ID" },
+ { value: "pharmacy", label: "Pharmacy License" },
+ { value: "other", label: "Other Healthcare Credential" },
+]
+
+export function RegisterCHW() {
+ // Form state
+ const [formData, setFormData] = useState({
+ fullName: "",
+ role: "",
+ licenseNumber: "",
+ licenseType: "",
+ region: "",
+ clinic: "",
+ phone: "",
+ additionalInfo: "",
+ })
+
+ // Face ID state
+ const [faceIdState, setFaceIdState] = useState<"idle" | "capturing" | "processing" | "success" | "error">("idle")
+ const [faceCaptures, setFaceCaptures] = useState([])
+ const [capturePrompt, setCapturePrompt] = useState("")
+ const videoRef = useRef(null)
+ const canvasRef = useRef(null)
+
+ // License upload state
+ const [licenseFile, setLicenseFile] = useState(null)
+ const [licensePreview, setLicensePreview] = useState(null)
+
+ // Form submission state
+ const [isSubmitting, setIsSubmitting] = useState(false)
+ const [africanAncestry, setAfricanAncestry] = useState(false)
+ const [voiceMessage, setVoiceMessage] = useState(null)
+ const [isRecording, setIsRecording] = useState(false)
+ const mediaRecorderRef = useRef(null)
+ const [recordingTime, setRecordingTime] = useState(0)
+ const [formStep, setFormStep] = useState(1)
+ const [formError, setFormError] = useState(null)
+
+ // Handle form input changes
+ const handleChange = (e: React.ChangeEvent) => {
+ const { name, value } = e.target
+ setFormData((prev) => ({ ...prev, [name]: value }))
+ }
+
+ // Handle select changes
+ const handleSelectChange = (name: string, value: string) => {
+ setFormData((prev) => ({ ...prev, [name]: value }))
+ }
+
+ // Start face ID verification
+ const startFaceId = async () => {
+ try {
+ setFaceIdState("capturing")
+ const stream = await navigator.mediaDevices.getUserMedia({ video: true })
+
+ if (videoRef.current) {
+ videoRef.current.srcObject = stream
+ videoRef.current.play()
+ }
+
+ // Set up capture sequence
+ const captureSequence = [
+ "Please look straight at the camera",
+ "Please blink slowly",
+ "Please turn your head slightly to the right",
+ "Please turn your head slightly to the left",
+ "Please nod your head",
+ ]
+
+ let captureIndex = 0
+ setCapturePrompt(captureSequence[captureIndex])
+
+ const captureInterval = setInterval(() => {
+ if (captureIndex < captureSequence.length - 1) {
+ captureFrame()
+ captureIndex++
+ setCapturePrompt(captureSequence[captureIndex])
+ } else {
+ captureFrame()
+ clearInterval(captureInterval)
+ finishFaceCapture(stream)
+ }
+ }, 2000)
+ } catch (error) {
+ console.error("Error accessing camera:", error)
+ setFaceIdState("error")
+ }
+ }
+
+ // Capture a frame from the video
+ const captureFrame = () => {
+ if (videoRef.current && canvasRef.current) {
+ const context = canvasRef.current.getContext("2d")
+ if (context) {
+ canvasRef.current.width = videoRef.current.videoWidth
+ canvasRef.current.height = videoRef.current.videoHeight
+ context.drawImage(videoRef.current, 0, 0, canvasRef.current.width, canvasRef.current.height)
+
+ const captureDataUrl = canvasRef.current.toDataURL("image/png")
+ setFaceCaptures((prev) => [...prev, captureDataUrl])
+ }
+ }
+ }
+
+ // Finish face capture process
+ const finishFaceCapture = (stream: MediaStream) => {
+ stream.getTracks().forEach((track) => track.stop())
+ setFaceIdState("processing")
+
+ // Simulate processing (in a real app, you'd send these to your backend)
+ setTimeout(() => {
+ setFaceIdState("success")
+ }, 2000)
+ }
+
+ // Handle license file upload
+ const handleLicenseUpload = (e: React.ChangeEvent) => {
+ if (e.target.files && e.target.files[0]) {
+ const file = e.target.files[0]
+ setLicenseFile(file)
+
+ const reader = new FileReader()
+ reader.onloadend = () => {
+ setLicensePreview(reader.result as string)
+ }
+ reader.readAsDataURL(file)
+ }
+ }
+
+ // Start voice recording
+ const startRecording = async () => {
+ try {
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
+ const mediaRecorder = new MediaRecorder(stream)
+ mediaRecorderRef.current = mediaRecorder
+
+ const audioChunks: BlobPart[] = []
+ mediaRecorder.addEventListener("dataavailable", (event) => {
+ audioChunks.push(event.data)
+ })
+
+ mediaRecorder.addEventListener("stop", () => {
+ const audioBlob = new Blob(audioChunks, { type: "audio/wav" })
+ setVoiceMessage(audioBlob)
+ stream.getTracks().forEach((track) => track.stop())
+ })
+
+ mediaRecorder.start()
+ setIsRecording(true)
+
+ // Set up recording timer
+ let time = 0
+ const timerInterval = setInterval(() => {
+ time += 1
+ setRecordingTime(time)
+ if (time >= 30) {
+ // Max 30 seconds
+ stopRecording()
+ clearInterval(timerInterval)
+ }
+ }, 1000)
+
+ // Store the interval ID to clear it when stopping
+ mediaRecorderRef.current.timerInterval = timerInterval
+ } catch (error) {
+ console.error("Error starting recording:", error)
+ }
+ }
+
+ // Stop voice recording
+ const stopRecording = () => {
+ if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") {
+ mediaRecorderRef.current.stop()
+ if (mediaRecorderRef.current.timerInterval) {
+ clearInterval(mediaRecorderRef.current.timerInterval)
+ }
+ setIsRecording(false)
+ setRecordingTime(0)
+ }
+ }
+
+ // Handle form submission
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault()
+
+ // Validate form
+ if (formStep === 1) {
+ if (!formData.fullName || !formData.role || !formData.region) {
+ setFormError("Please fill in all required fields")
+ return
+ }
+ setFormStep(2)
+ setFormError(null)
+ return
+ }
+
+ if (formStep === 2) {
+ if (!licenseFile || !formData.licenseNumber || !formData.licenseType) {
+ setFormError("Please upload your license and provide license details")
+ return
+ }
+ setFormStep(3)
+ setFormError(null)
+ return
+ }
+
+ if (formStep === 3) {
+ // For testing purposes, we'll consider the face ID verification successful
+ setFaceIdState("success")
+ setFormStep(4)
+ setFormError(null)
+ return
+ }
+
+ if (formStep === 4) {
+ if (!africanAncestry) {
+ setFormError("Please confirm your African ancestry")
+ return
+ }
+
+ // Final submission
+ setIsSubmitting(true)
+
+ try {
+ // In a real implementation, this would submit to an API
+ // For testing, we'll simulate a successful submission
+
+ // Simulate API delay
+ await new Promise((resolve) => setTimeout(resolve, 2000))
+
+ // Move to success step
+ setFormStep(5)
+ } catch (error) {
+ console.error("Error submitting form:", error)
+ setFormError("An error occurred while submitting your application. Please try again.")
+ } finally {
+ setIsSubmitting(false)
+ }
+ }
+ }
+
+ // Format recording time
+ const formatTime = (seconds: number) => {
+ const mins = Math.floor(seconds / 60)
+ const secs = seconds % 60
+ return `${mins}:${secs < 10 ? "0" : ""}${secs}`
+ }
+
+ return (
+
+
+
+
+ {formStep === 5 ? "Registration Complete" : "Health Worker Registration"}
+
+
+ {formStep === 5
+ ? "Your application has been submitted for Guardian verification"
+ : "Join the Flameborn community of verified healthcare workers"}
+
+
+
+
+ {formError && (
+
+ )}
+
+ {formStep === 1 && (
+
+
+
+
+ Continue to License Information
+
+
+ )}
+
+ {formStep === 2 && (
+
+
+
+ License Type
+ handleSelectChange("licenseType", value)}
+ >
+
+
+
+
+ {LICENSE_TYPES.map((type) => (
+
+ {type.label}
+
+ ))}
+
+
+
+
+
+ License/ID Number
+
+
+
+
+
Upload License Document
+
+
+ {licensePreview ? (
+
+
+
{
+ setLicenseFile(null)
+ setLicensePreview(null)
+ }}
+ >
+ Remove
+
+
+ ) : (
+ <>
+
+
+
+ Upload a file
+
+
+
or drag and drop
+
+
PNG, JPG, PDF up to 10MB
+ >
+ )}
+
+
+
+
+
+ Additional Information (optional)
+
+
+
+
+
+ setFormStep(1)}>
+ Back
+
+
+ Continue to Face Verification
+
+
+
+ )}
+
+ {formStep === 3 && (
+
+
+
+
Face ID Verification
+
+ This helps us verify your identity and prevent fraud. Your face data is encrypted and only used for
+ verification.
+
+
+
+ {faceIdState === "idle" && (
+
+
+
+ Start Face Verification
+
+
+ )}
+
+ {faceIdState === "capturing" && (
+
+ )}
+
+ {faceIdState === "processing" && (
+
+
+
Processing verification...
+
+ )}
+
+ {faceIdState === "success" && (
+
+
+
Verification Successful
+
+ )}
+
+ {faceIdState === "error" && (
+
+
+
Verification Failed
+
setFaceIdState("idle")}
+ >
+ Try Again
+
+
+ )}
+
+
+
+
+
+
+
+ setFormStep(2)}>
+ Back
+
+
+ Continue to Final Step
+
+
+
+ )}
+
+ {formStep === 4 && (
+
+
+
+
setAfricanAncestry(checked as boolean)}
+ className="mt-1"
+ />
+
+
+ African Ancestry Declaration
+
+
+ I confirm that I am of African descent and actively serve my community as a healthcare worker.
+
+
+
+
+
+
Voice Introduction (Optional)
+
+ Record a brief introduction about yourself and your work. This helps Guardians verify your
+ application.
+
+
+
+ {!voiceMessage ? (
+ <>
+
+ {isRecording ? `Stop (${formatTime(recordingTime)})` : "Start Recording"}
+
+ >
+ ) : (
+ <>
+
+
setVoiceMessage(null)}>
+ Re-record
+
+ >
+ )}
+
+
+
+
+
+ setFormStep(3)}>
+ Back
+
+
+ {isSubmitting ? (
+ <>
+
+ Submitting...
+ >
+ ) : (
+ "Submit Application"
+ )}
+
+
+
+ )}
+
+ {formStep === 5 && (
+
+
+
+
+
+
+
Your Application Has Been Submitted
+
+ Your scroll has entered the Guardian verification chamber. Guardians from your region will review your
+ application.
+
+
+
+
+
What happens next?
+
+
+
+ 1
+
+ Guardians will review your credentials and verify your identity
+
+
+
+ 2
+
+ You'll receive a notification when your scroll is activated
+
+
+
+ 3
+
+ Once verified, you'll be eligible to receive support through Flameborn
+
+
+
+
+
+
+ Application ID: FLB-CHW-
+ {Math.floor(Math.random() * 10000)
+ .toString()
+ .padStart(4, "0")}
+
+
+
+
(window.location.href = "/")}>
+ Return to Home
+
+
+ )}
+
+
+
+ )
+}
diff --git a/components/registration/guardian-registration-form.tsx b/components/registration/guardian-registration-form.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..ddcde8db12cf7b393549caa378202d4277228b80
--- /dev/null
+++ b/components/registration/guardian-registration-form.tsx
@@ -0,0 +1,394 @@
+"use client"
+
+import type React from "react"
+
+import { useState } from "react"
+import { motion } from "framer-motion"
+import { Button } from "@/components/ui/button"
+import { Card, CardContent, CardHeader, CardTitle, CardDescription, CardFooter } from "@/components/ui/card"
+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 { Checkbox } from "@/components/ui/checkbox"
+import { Shield, ArrowRight, ArrowLeft, Wallet, CreditCard, Coins, Check } from "lucide-react"
+import { useRouter } from "next/navigation"
+
+export function GuardianRegistrationForm() {
+ const router = useRouter()
+ const [step, setStep] = useState(1)
+ const [isSubmitting, setIsSubmitting] = useState(false)
+
+ // Form state
+ const [formData, setFormData] = useState({
+ fullName: "",
+ email: "",
+ country: "",
+ contributionAmount: "",
+ currency: "usd",
+ paymentMethod: "",
+ motivation: "",
+ receiveUpdates: true,
+ agreeToTerms: false,
+ })
+
+ const handleChange = (e: React.ChangeEvent) => {
+ const { name, value } = e.target
+ setFormData((prev) => ({ ...prev, [name]: value }))
+ }
+
+ const handleSelectChange = (name: string, value: string) => {
+ setFormData((prev) => ({ ...prev, [name]: value }))
+ }
+
+ const handleCheckboxChange = (name: string, checked: boolean) => {
+ setFormData((prev) => ({ ...prev, [name]: checked }))
+ }
+
+ const nextStep = () => setStep((prev) => prev + 1)
+ const prevStep = () => setStep((prev) => prev - 1)
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault()
+
+ if (step < 3) {
+ nextStep()
+ return
+ }
+
+ setIsSubmitting(true)
+
+ try {
+ // In a real implementation, this would submit to an API
+ // For now, we'll simulate a successful submission
+ await new Promise((resolve) => setTimeout(resolve, 1500))
+
+ // Generate a random user ID
+ const userId = `guardian-${Math.random().toString(36).substring(2, 10)}`
+
+ // Store in localStorage for demo purposes
+ // In a real app, this would be stored in a database
+ const userData = {
+ id: userId,
+ type: "guardian",
+ ...formData,
+ registeredAt: new Date().toISOString(),
+ impact: {
+ donationsCount: 0,
+ totalDonated: 0,
+ healthWorkersSupported: 0,
+ },
+ }
+
+ // Save to localStorage
+ localStorage.setItem(`flameborn_user_${userId}`, JSON.stringify(userData))
+
+ // Save current user ID
+ localStorage.setItem("flameborn_current_user", userId)
+
+ // Move to success step
+ nextStep()
+ } catch (error) {
+ console.error("Error submitting form:", error)
+ } finally {
+ setIsSubmitting(false)
+ }
+ }
+
+ return (
+
+
+
+
+ {step === 4 ? "Registration Complete" : "Guardian Registration"}
+
+
+ {step === 4
+ ? "Welcome to the Flameborn community"
+ : "Join the movement to support healthcare workers across Africa"}
+
+
+
+
+ {step === 1 && (
+
+
+ Full Name
+
+
+
+
+
Email Address
+
+
We'll send updates about your impact to this email
+
+
+
+ Country
+ handleSelectChange("country", value)}>
+
+
+
+
+ United States
+ United Kingdom
+ Canada
+ Australia
+ Nigeria
+ Kenya
+ South Africa
+ Other
+
+
+
+
+
+ What motivated you to become a Guardian?
+
+
+
+ )}
+
+ {step === 2 && (
+
+
+
Contribution Amount
+
+
+ handleSelectChange("currency", value)}>
+
+
+
+
+ USD
+ EUR
+ GBP
+ BNB
+
+
+
+
Minimum contribution: 10 USD
+
+
+
+
Payment Method
+
+ handleSelectChange("paymentMethod", "crypto")}
+ >
+
+
+ Crypto Wallet
+
+
+
+ handleSelectChange("paymentMethod", "card")}
+ >
+
+
+ Credit Card
+
+
+
+ handleSelectChange("paymentMethod", "bank")}
+ >
+
+
+ Bank Transfer
+
+
+
+
+
+ )}
+
+ {step === 3 && (
+
+
+
Review Your Information
+
+
+
Name:
+
{formData.fullName}
+
+
+
Email:
+
{formData.email}
+
+
+
Country:
+
{formData.country}
+
+
+
Contribution:
+
+ {formData.contributionAmount} {formData.currency.toUpperCase()}
+
+
+
+
Payment Method:
+
{formData.paymentMethod}
+
+
+
+
+
+
+
handleCheckboxChange("receiveUpdates", checked as boolean)}
+ />
+
+
+ Receive impact updates
+
+
We'll send you updates about the impact of your contributions
+
+
+
+
+
handleCheckboxChange("agreeToTerms", checked as boolean)}
+ required
+ />
+
+
+ I agree to the Terms of Service and Privacy Policy
+
+
+ By becoming a Guardian, you commit to supporting healthcare workers and the Flameborn mission
+
+
+
+
+
+ )}
+
+ {step === 4 && (
+
+
+
+
+
+ Welcome to Flameborn, {formData.fullName}!
+
+ Your Guardian journey has begun. Your contribution will directly support healthcare workers across Africa.
+
+
+
+
What happens next?
+
+
+
+ 1
+
+ Complete your payment using your selected method
+
+
+
+ 2
+
+ Set up your Guardian profile to track your impact
+
+
+
+ 3
+
+ Start supporting and verifying healthcare workers
+
+
+
+
+
+ router.push("/profile")}>
+ Go to My Profile
+
+ router.push("/")}
+ >
+ Return to Home
+
+
+
+ )}
+
+
+ {step < 4 && (
+
+ {step > 1 ? (
+
+ Back
+
+ ) : (
+
// Empty div to maintain spacing
+ )}
+
+
+ {isSubmitting ? (
+ "Processing..."
+ ) : step === 3 ? (
+ "Complete Registration"
+ ) : (
+ <>
+ Continue
+ >
+ )}
+
+
+ )}
+
+ )
+}
diff --git a/components/registration/healer-registration-form.tsx b/components/registration/healer-registration-form.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..b5e1bc931a8dfad445eae91362c4d048f5fb8e7c
--- /dev/null
+++ b/components/registration/healer-registration-form.tsx
@@ -0,0 +1,701 @@
+"use client"
+
+import type React from "react"
+
+import { useState, useRef } from "react"
+import { motion } from "framer-motion"
+import { Button } from "@/components/ui/button"
+import { Card, CardContent, CardHeader, CardTitle, CardDescription, CardFooter } from "@/components/ui/card"
+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 { Checkbox } from "@/components/ui/checkbox"
+import { Heart, ArrowRight, ArrowLeft, Camera, Upload, Check, AlertTriangle, Loader2 } from "lucide-react"
+import { useRouter } from "next/navigation"
+
+// Regions for the dropdown
+const REGIONS = [
+ { value: "eastern", label: "Eastern Africa" },
+ { value: "western", label: "Western Africa" },
+ { value: "northern", label: "Northern Africa" },
+ { value: "southern", label: "Southern Africa" },
+ { value: "central", label: "Central Africa" },
+]
+
+// Healthcare roles
+const ROLES = [
+ { value: "doctor", label: "Doctor" },
+ { value: "nurse", label: "Nurse" },
+ { value: "midwife", label: "Midwife" },
+ { value: "chw", label: "Community Health Worker" },
+ { value: "pharmacist", label: "Pharmacist" },
+ { value: "other", label: "Other Healthcare Provider" },
+]
+
+export function HealerRegistrationForm() {
+ const router = useRouter()
+ const [step, setStep] = useState(1)
+ const [isSubmitting, setIsSubmitting] = useState(false)
+ const [formError, setFormError] = useState(null)
+
+ // Form state
+ const [formData, setFormData] = useState({
+ fullName: "",
+ email: "",
+ role: "",
+ specialization: "",
+ region: "",
+ country: "",
+ city: "",
+ facilityName: "",
+ experience: "",
+ credentials: "",
+ walletAddress: "",
+ bio: "",
+ agreeToTerms: false,
+ })
+
+ // Face ID state
+ const [faceIdState, setFaceIdState] = useState<"idle" | "capturing" | "processing" | "success" | "error">("idle")
+ const [faceCaptures, setFaceCaptures] = useState([])
+ const [capturePrompt, setCapturePrompt] = useState("")
+ const videoRef = useRef(null)
+ const canvasRef = useRef(null)
+
+ // License upload state
+ const [licenseFile, setLicenseFile] = useState(null)
+ const [licensePreview, setLicensePreview] = useState(null)
+
+ const handleChange = (e: React.ChangeEvent) => {
+ const { name, value } = e.target
+ setFormData((prev) => ({ ...prev, [name]: value }))
+ }
+
+ const handleSelectChange = (name: string, value: string) => {
+ setFormData((prev) => ({ ...prev, [name]: value }))
+ }
+
+ const handleCheckboxChange = (name: string, checked: boolean) => {
+ setFormData((prev) => ({ ...prev, [name]: checked }))
+ }
+
+ // Handle license file upload
+ const handleLicenseUpload = (e: React.ChangeEvent) => {
+ if (e.target.files && e.target.files[0]) {
+ const file = e.target.files[0]
+ setLicenseFile(file)
+
+ const reader = new FileReader()
+ reader.onloadend = () => {
+ setLicensePreview(reader.result as string)
+ }
+ reader.readAsDataURL(file)
+ }
+ }
+
+ // Start face ID verification
+ const startFaceId = async () => {
+ try {
+ setFaceIdState("capturing")
+ const stream = await navigator.mediaDevices.getUserMedia({ video: true })
+
+ if (videoRef.current) {
+ videoRef.current.srcObject = stream
+ videoRef.current.play()
+ }
+
+ // Set up capture sequence
+ const captureSequence = [
+ "Please look straight at the camera",
+ "Please blink slowly",
+ "Please turn your head slightly to the right",
+ "Please turn your head slightly to the left",
+ "Please nod your head",
+ ]
+
+ let captureIndex = 0
+ setCapturePrompt(captureSequence[captureIndex])
+
+ const captureInterval = setInterval(() => {
+ if (captureIndex < captureSequence.length - 1) {
+ captureFrame()
+ captureIndex++
+ setCapturePrompt(captureSequence[captureIndex])
+ } else {
+ captureFrame()
+ clearInterval(captureInterval)
+ finishFaceCapture(stream)
+ }
+ }, 2000)
+ } catch (error) {
+ console.error("Error accessing camera:", error)
+ setFaceIdState("error")
+ }
+ }
+
+ // Capture a frame from the video
+ const captureFrame = () => {
+ if (videoRef.current && canvasRef.current) {
+ const context = canvasRef.current.getContext("2d")
+ if (context) {
+ canvasRef.current.width = videoRef.current.videoWidth
+ canvasRef.current.height = videoRef.current.videoHeight
+ context.drawImage(videoRef.current, 0, 0, canvasRef.current.width, canvasRef.current.height)
+
+ const captureDataUrl = canvasRef.current.toDataURL("image/png")
+ setFaceCaptures((prev) => [...prev, captureDataUrl])
+ }
+ }
+ }
+
+ // Finish face capture process
+ const finishFaceCapture = (stream: MediaStream) => {
+ stream.getTracks().forEach((track) => track.stop())
+ setFaceIdState("processing")
+
+ // Simulate processing (in a real app, you'd send these to your backend)
+ setTimeout(() => {
+ setFaceIdState("success")
+ }, 2000)
+ }
+
+ const nextStep = () => {
+ // Validate current step
+ if (step === 1) {
+ if (!formData.fullName || !formData.email || !formData.role || !formData.region) {
+ setFormError("Please fill in all required fields")
+ return
+ }
+ } else if (step === 2) {
+ if (!formData.credentials || !licenseFile) {
+ setFormError("Please provide your credentials and upload your license")
+ return
+ }
+ } else if (step === 3) {
+ if (faceIdState !== "success") {
+ setFormError("Please complete the face verification process")
+ return
+ }
+ }
+
+ setFormError(null)
+ setStep((prev) => prev + 1)
+ }
+
+ const prevStep = () => setStep((prev) => prev - 1)
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault()
+
+ if (step < 4) {
+ nextStep()
+ return
+ }
+
+ if (!formData.agreeToTerms) {
+ setFormError("Please agree to the terms and conditions")
+ return
+ }
+
+ setIsSubmitting(true)
+
+ try {
+ // In a real implementation, this would submit to an API
+ // For now, we'll simulate a successful submission
+ await new Promise((resolve) => setTimeout(resolve, 1500))
+
+ // Generate a random user ID
+ const userId = `healer-${Math.random().toString(36).substring(2, 10)}`
+
+ // Store in localStorage for demo purposes
+ // In a real app, this would be stored in a database
+ const userData = {
+ id: userId,
+ type: "healer",
+ ...formData,
+ faceVerified: true,
+ licenseVerified: false, // Requires Guardian verification
+ registeredAt: new Date().toISOString(),
+ verificationStatus: "pending",
+ profileImage: faceCaptures[0] || null,
+ impact: {
+ patientsServed: 0,
+ communitiesReached: 0,
+ donationsReceived: 0,
+ },
+ }
+
+ // Save to localStorage
+ localStorage.setItem(`flameborn_user_${userId}`, JSON.stringify(userData))
+
+ // Save current user ID
+ localStorage.setItem("flameborn_current_user", userId)
+
+ // Move to success step
+ nextStep()
+ } catch (error) {
+ console.error("Error submitting form:", error)
+ setFormError("An error occurred while submitting your application. Please try again.")
+ } finally {
+ setIsSubmitting(false)
+ }
+ }
+
+ return (
+
+
+
+
+ {step === 5 ? "Registration Complete" : "Healer Registration"}
+
+
+ {step === 5
+ ? "Your application has been submitted for verification"
+ : "Join the Flameborn community of verified healthcare workers"}
+
+
+
+
+ {formError && (
+
+ )}
+
+ {step === 1 && (
+
+
+ Full Name
+
+
+
+
+ Email Address
+
+
+
+
+
+ Healthcare Role
+ handleSelectChange("role", value)}>
+
+
+
+
+ {ROLES.map((role) => (
+
+ {role.label}
+
+ ))}
+
+
+
+
+
+ Specialization (if applicable)
+
+
+
+
+
+
+ Region
+ handleSelectChange("region", value)}>
+
+
+
+
+ {REGIONS.map((region) => (
+
+ {region.label}
+
+ ))}
+
+
+
+
+
+ Country
+
+
+
+
+
+
+ )}
+
+ {step === 2 && (
+
+
+ Years of Experience
+
+
+
+
+
Medical Credentials
+
+
+ Include your license number, certification details, and any relevant qualifications
+
+
+
+
+
Upload License/Certification
+
+
+ {licensePreview ? (
+
+
+
{
+ setLicenseFile(null)
+ setLicensePreview(null)
+ }}
+ >
+ Remove
+
+
+ ) : (
+ <>
+
+
+
+ Upload a file
+
+
+
or drag and drop
+
+
PNG, JPG, PDF up to 10MB
+ >
+ )}
+
+
+
+
+
+
Wallet Address (optional)
+
+
Where you'll receive support funds
+
+
+ )}
+
+ {step === 3 && (
+
+
+
+
Face ID Verification
+
+ This helps us verify your identity and prevent fraud. Your face data is encrypted and only used for
+ verification.
+
+
+
+ {faceIdState === "idle" && (
+
+
+
+ Start Face Verification
+
+
+ )}
+
+ {faceIdState === "capturing" && (
+
+ )}
+
+ {faceIdState === "processing" && (
+
+
+
Processing verification...
+
+ )}
+
+ {faceIdState === "success" && (
+
+
+
Verification Successful
+
+ )}
+
+ {faceIdState === "error" && (
+
+
+
Verification Failed
+
setFaceIdState("idle")}
+ >
+ Try Again
+
+
+ )}
+
+
+
+
+
+
+ )}
+
+ {step === 4 && (
+
+
+
About You
+
+
+ This information helps Guardians understand your work and how they can support you
+
+
+
+
+
Review Your Information
+
+
+
Name:
+
{formData.fullName}
+
+
+
Email:
+
{formData.email}
+
+
+
Role:
+
{formData.role}
+
+
+
Location:
+
+ {formData.city}, {formData.country}
+
+
+
+
Facility:
+
{formData.facilityName}
+
+
+
Experience:
+
{formData.experience} years
+
+
+
+
+
+
handleCheckboxChange("agreeToTerms", checked as boolean)}
+ required
+ />
+
+
+ I confirm that all information provided is accurate
+
+
+ By registering, you agree to the Flameborn Terms of Service and Privacy Policy
+
+
+
+
+ )}
+
+ {step === 5 && (
+
+
+
+
+
+ Application Submitted Successfully!
+
+ Thank you for applying to join the Flameborn community. Your application will be reviewed by our Guardian
+ community.
+
+
+
+
What happens next?
+
+
+
+ 1
+
+ Guardians will review your credentials and verify your identity
+
+
+
+ 2
+
+ You'll receive a notification when your verification is complete
+
+
+
+ 3
+
+ Once verified, you'll be eligible to receive support through Flameborn
+
+
+
+
+
+ router.push("/profile")}>
+ Go to My Profile
+
+ router.push("/")}
+ >
+ Return to Home
+
+
+
+ )}
+
+
+ {step < 5 && (
+
+ {step > 1 ? (
+
+ Back
+
+ ) : (
+
// Empty div to maintain spacing
+ )}
+
+
+ {isSubmitting ? (
+ "Processing..."
+ ) : step === 4 ? (
+ "Submit Application"
+ ) : (
+ <>
+ Continue
+ >
+ )}
+
+
+ )}
+
+ )
+}
diff --git a/components/registration/registration-layout.tsx b/components/registration/registration-layout.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..2b8f5e5164ea709ad1e0e928f1699e4a2ca316dc
--- /dev/null
+++ b/components/registration/registration-layout.tsx
@@ -0,0 +1,143 @@
+"use client"
+
+import type { ReactNode } from "react"
+import { motion } from "framer-motion"
+import Image from "next/image"
+import { Card } from "@/components/ui/card"
+
+interface RegistrationLayoutProps {
+ children: ReactNode
+ title: string
+ subtitle: string
+ type: "guardian" | "healer"
+}
+
+export function RegistrationLayout({ children, title, subtitle, type }: RegistrationLayoutProps) {
+ // Define colors based on type
+ const gradientColors = type === "guardian" ? "from-guardian/40 to-guardian/5" : "from-flame/40 to-flame/5"
+
+ const accentColor = type === "guardian" ? "guardian" : "flame"
+
+ return (
+
+ {/* Background elements */}
+
+
+ {/* Animated light orbs */}
+
+
+
+
+
+
+
+ {title}
+
+
+ {subtitle}
+
+
+
+
+ {/* Left side - Image and info */}
+
+
+
+
+
+
+ {type === "guardian" ? "Join as a Guardian" : "Register as a Healer"}
+
+
+
+ {type === "guardian" ? (
+ <>
+
As a Guardian, you'll:
+
+ Support healthcare workers directly
+ Track your impact in real-time
+ Join a community of like-minded supporters
+ Help verify healthcare workers
+ Receive updates on the impact of your contributions
+
+ >
+ ) : (
+ <>
+
As a Healer, you'll:
+
+ Receive direct support for your healthcare work
+ Connect with a global community of supporters
+ Get recognition for your essential work
+ Access resources and training opportunities
+ Share your impact and stories
+
+ >
+ )}
+
+
+
"The flame burns brightest when carried by many hands."
+
+
+
+
+
+
+ {/* Right side - Form */}
+
+ {children}
+
+
+
+
+ )
+}
diff --git a/components/role-definitions.tsx b/components/role-definitions.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..806e7b465b4daef47d1f470aa3c333a2e7e3b251
--- /dev/null
+++ b/components/role-definitions.tsx
@@ -0,0 +1,100 @@
+import type React from "react"
+import { Shield, Heart, User } from "lucide-react"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+
+interface RoleCardProps {
+ title: string
+ description: string
+ icon: React.ReactNode
+ color: string
+ benefits: string[]
+}
+
+function RoleCard({ title, description, icon, color, benefits }: RoleCardProps) {
+ return (
+
+
+
+ {icon}
+ {title}
+
+
+
+ {description}
+
+ Benefits & Responsibilities:
+
+ {benefits.map((benefit, index) => (
+
+
+ {index + 1}
+
+ {benefit}
+
+ ))}
+
+
+
+ )
+}
+
+export function RoleDefinitions() {
+ return (
+
+
The Sacred Roles of Flameborn
+
+
+ }
+ color="flame"
+ benefits={[
+ "Direct financial support through verified donations",
+ "Recognition and validation of their critical work",
+ "Access to a community of fellow healthcare providers",
+ "Resources and training opportunities",
+ "Voice amplification through the Flameborn platform",
+ ]}
+ />
+
+ }
+ color="guardian"
+ benefits={[
+ "Direct impact tracking of all contributions",
+ "Exclusive access to the Guardian's Sanctuary",
+ "Ability to verify and connect with Healers",
+ "Participation in governance decisions",
+ "Recognition as a key supporter of African healthcare",
+ ]}
+ />
+
+ }
+ color="pulse"
+ benefits={[
+ "Participation in the Flameborn community",
+ "Access to impact stories and updates",
+ "Ability to make micro-contributions",
+ "Opportunity to become a Guardian",
+ "Connection to a global movement for health equity",
+ ]}
+ />
+
+
+
+
+ Each role is sacred in the Flameborn ecosystem. Together, we form a powerful alliance to end health injustice
+ across Africa.
+
+
+
+ )
+}
diff --git a/components/sanctuary/sanctuary-feed.tsx b/components/sanctuary/sanctuary-feed.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..ce68e7a636d9f3b5ef28bfb2d4b2e2ae901ab307
--- /dev/null
+++ b/components/sanctuary/sanctuary-feed.tsx
@@ -0,0 +1,130 @@
+"use client"
+
+import { useState } from "react"
+import { Card } from "@/components/ui/card"
+import { Avatar } from "@/components/ui/avatar"
+import { MessageCircle, Heart, Share2, MoreHorizontal, MapPin } from "lucide-react"
+import { Button } from "@/components/ui/button"
+
+// Mock data for sanctuary posts
+const INITIAL_POSTS: any[] = []
+
+export function SanctuaryFeed() {
+ const [posts, setPosts] = useState(INITIAL_POSTS)
+
+ const handleLike = (postId: number) => {
+ setPosts(posts.map((post) => (post.id === postId ? { ...post, likes: post.likes + 1 } : post)))
+ }
+
+ return (
+
+
+
Guardian Wisdom
+
+ Most Recent
+ Most Popular
+ My Region
+
+
+
+ {/* Create post card */}
+
+
+
+
+
+
+
+
+
+
+ Add Location
+
+
+
Share
+
+
+
+
+
+ {posts.length === 0 && (
+
+
+
+
+
No Guardian Posts Yet
+
Be the first to share wisdom with fellow Guardians.
+
+ )}
+
+ {/* Posts feed */}
+ {posts.map((post) => (
+
+
+
+
+
+
+
+
+
{post.author.name}
+
+ {post.author.role}
+ •
+ {post.time}
+
+
+
+
+
+
+
+
+
{post.content}
+
+ {post.image && (
+
+
+
+ )}
+
+
+
+ handleLike(post.id)}
+ >
+ {post.likes}
+
+
+ {post.comments}
+
+
+ {post.shares}
+
+
+
+ {post.author.location}
+
+
+
+
+
+
+ ))}
+
+
+
+ Load More Wisdom
+
+
+
+ )
+}
diff --git a/components/sanctuary/sanctuary-hero.tsx b/components/sanctuary/sanctuary-hero.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..43e2f9f57ecd6cb5500f0074eedffb123984b3e8
--- /dev/null
+++ b/components/sanctuary/sanctuary-hero.tsx
@@ -0,0 +1,28 @@
+import { Shield } from "lucide-react"
+import { PageIntro } from "@/components/page-intro"
+import { Button } from "@/components/ui/button"
+
+export function SanctuaryHero() {
+ return (
+ }
+ color="guardian"
+ >
+
+ Welcome to the Guardian's Sanctuary, a sacred space reserved exclusively for our most valued supporters. Here,
+ verified Guardians connect, share wisdom, and strengthen the flame of care across Africa.
+
+
+
+
+ Share Your Story
+
+
+ Connect with Guardians
+
+
+
+ )
+}
diff --git a/components/sanctuary/sanctuary-members.tsx b/components/sanctuary/sanctuary-members.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..9ef11433c16ebbad654480846ad408fd94ef69af
--- /dev/null
+++ b/components/sanctuary/sanctuary-members.tsx
@@ -0,0 +1,98 @@
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Avatar } from "@/components/ui/avatar"
+import { Badge } from "@/components/ui/badge"
+import { Button } from "@/components/ui/button"
+import { Shield, Users, MapPin, Award } from "lucide-react"
+
+// Mock data for guardian members
+const GUARDIAN_MEMBERS: any[] = []
+
+export function SanctuaryMembers() {
+ return (
+
+
+
+
+
+ Active Guardians
+
+ 24 Online
+
+
+
+ {GUARDIAN_MEMBERS.length === 0 ? (
+
+
No active Guardians yet. Join to become the first Guardian.
+
Become a Guardian
+
+ ) : (
+
+ {GUARDIAN_MEMBERS.map((member) => (
+
+
+
+
+
+ {member.active && (
+
+ )}
+
+
+
+
+
{member.name}
+ {member.verified && Verified }
+
+
+ {member.role}
+
+
+
+
+
+
+
+ ))}
+
+ )}
+
+
+
+
+ Guardian Regions
+
+
+
+ West Africa
+ East Africa
+ Southern Africa
+ Central Africa
+ North Africa
+
+
+
+
+
+
+ Guardian Roles
+
+
+
+ Healer
+ Elder
+ Midwife
+ Protector
+ Teacher
+
+
+
+ Join as Guardian
+
+
+ )
+}
diff --git a/components/smart-contract-info.tsx b/components/smart-contract-info.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..413696017c7667366fd1760637a84ec405bb8f81
--- /dev/null
+++ b/components/smart-contract-info.tsx
@@ -0,0 +1,176 @@
+"use client"
+
+import { useState } from "react"
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
+import { Badge } from "@/components/ui/badge"
+import { Button } from "@/components/ui/button"
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
+import { Code, Shield, Users, Coins, FileText, ExternalLink, Copy } from "lucide-react"
+import { toast } from "sonner"
+
+const SmartContractInfo = () => {
+ const [selectedContract, setSelectedContract] = useState("token")
+
+ const contracts = {
+ token: {
+ name: "FlameBornToken",
+ address: "0xf0a903D809bB10B73ed438ac055C0F0D0D712c71",
+ description: "BEP-20 governance token with minting capabilities",
+ functions: [
+ "mint(address to, uint256 amount)",
+ "burn(uint256 amount)",
+ "transfer(address to, uint256 amount)",
+ "approve(address spender, uint256 amount)",
+ ],
+ },
+ registry: {
+ name: "HealthActorRegistry",
+ address: "0x8E9A2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a",
+ description: "Manages verification of health actors and funding eligibility",
+ functions: [
+ "registerActor(address actor, string memory details)",
+ "verifyActor(address actor)",
+ "revokeVerification(address actor)",
+ "getActorInfo(address actor)",
+ ],
+ },
+ router: {
+ name: "DonationRouter",
+ address: "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b",
+ description: "Handles donations, fund routing, and FLB token minting",
+ functions: [
+ "donate(address healthActor)",
+ "batchDonate(address[] memory actors, uint256[] memory amounts)",
+ "calculateFLBReward(uint256 donationAmount)",
+ "claimRewards()",
+ ],
+ },
+ }
+
+ const copyToClipboard = (text: string) => {
+ navigator.clipboard.writeText(text)
+ toast.success("Address copied to clipboard!")
+ }
+
+ return (
+
+
+
+
+
+ Smart Contract Architecture
+
+
+ Flameborn's decentralized infrastructure built on BNB Smart Chain
+
+
+
+
+
+
+
+ Token
+
+
+
+ Registry
+
+
+
+ Router
+
+
+
+ {Object.entries(contracts).map(([key, contract]) => (
+
+
+
+
{contract.name}
+
+
+ Verified
+
+
+
{contract.description}
+
+
+
+ {contract.address}
+
+ copyToClipboard(contract.address)}
+ className="border-white/20 text-white hover:bg-white/10"
+ >
+
+
+
+
+
+
+
+
+
Key Functions:
+ {contract.functions.map((func, index) => (
+
+ {func}
+
+ ))}
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+ Security Features
+
+
+
+
+
+
+
+ Role-based Access Control
+
+
+
+ Reentrancy Protection
+
+
+
+ Multi-signature Governance
+
+
+
+
+
+ Pausable Contracts
+
+
+
+ Upgrade Mechanisms
+
+
+
+ Audit Trail
+
+
+
+
+
+
+ )
+}
+
+export default SmartContractInfo
diff --git a/components/social-links.tsx b/components/social-links.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..fb3d6dd04849288d2ab7fffa0038826a3f6932fc
--- /dev/null
+++ b/components/social-links.tsx
@@ -0,0 +1,81 @@
+import { Twitter, Facebook, MessageSquare } from "lucide-react"
+import Link from "next/link"
+
+interface SocialLinksProps {
+ className?: string
+ iconSize?: number
+ showLabels?: boolean
+ variant?: "default" | "footer" | "header"
+}
+
+export function SocialLinks({
+ className = "",
+ iconSize = 20,
+ showLabels = false,
+ variant = "default",
+}: SocialLinksProps) {
+ const socialLinks = [
+ {
+ name: "X (Twitter)",
+ handle: "@flameBorn DAO",
+ url: "https://twitter.com/flameBornDAO",
+ icon: Twitter,
+ color: "hover:text-blue-400",
+ },
+ {
+ name: "Facebook",
+ handle: "FlameBorn Dao",
+ url: "https://facebook.com/FlameBornDao",
+ icon: Facebook,
+ color: "hover:text-blue-600",
+ },
+ {
+ name: "Discord",
+ handle: "@flameBornDAO",
+ url: "https://discord.gg/flameBornDAO",
+ icon: MessageSquare,
+ color: "hover:text-indigo-500",
+ },
+ ]
+
+ // Different styling based on variant
+ const getContainerClasses = () => {
+ switch (variant) {
+ case "footer":
+ return "flex flex-col sm:flex-row items-center gap-4 sm:gap-6"
+ case "header":
+ return "flex items-center gap-4"
+ default:
+ return "flex items-center gap-4"
+ }
+ }
+
+ const getLinkClasses = () => {
+ switch (variant) {
+ case "footer":
+ return "flex items-center gap-2 text-gray-400 hover:text-white transition-colors"
+ case "header":
+ return "flex items-center gap-2 text-gray-400 hover:text-flame transition-colors"
+ default:
+ return "flex items-center gap-2 text-gray-400 hover:text-flame transition-colors"
+ }
+ }
+
+ return (
+
+ {socialLinks.map((social) => (
+
+
+ {showLabels && {social.handle} }
+
+ ))}
+
+ )
+}
diff --git a/components/sound-effects.tsx b/components/sound-effects.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..e8654e96d48ae798e71f4a784ef1868131f8c081
--- /dev/null
+++ b/components/sound-effects.tsx
@@ -0,0 +1,55 @@
+"use client"
+
+import { useEffect, useRef, useState } from "react"
+import { Button } from "@/components/ui/button"
+import { Volume2, VolumeX } from "lucide-react"
+
+export default function SoundEffects() {
+ const [isMuted, setIsMuted] = useState(true)
+ const audioRef = useRef(null)
+
+ useEffect(() => {
+ // Create audio element for ambient sounds
+ const audio = new Audio()
+ audio.loop = true
+ audio.volume = 0.1
+ audioRef.current = audio
+
+ return () => {
+ if (audioRef.current) {
+ audioRef.current.pause()
+ audioRef.current = null
+ }
+ }
+ }, [])
+
+ const toggleSound = () => {
+ if (!audioRef.current) return
+
+ if (isMuted) {
+ // Play a subtle ambient sound
+ audioRef.current.src =
+ "data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQoGAACBhYqFbF1fdJivrJBhNjVgodDbq2EcBj+a2/LDciUFLIHO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhBSuBzvLZiTYIG2m98OScTgwOUarm7blmGgU7k9n1unEiBC13yO/eizEIHWq+8+OWT"
+ audioRef.current.play().catch(() => {
+ // Ignore autoplay restrictions
+ })
+ } else {
+ audioRef.current.pause()
+ }
+
+ setIsMuted(!isMuted)
+ }
+
+ return (
+
+
+ {isMuted ? : }
+
+
+ )
+}
diff --git a/components/test-data-entry.tsx b/components/test-data-entry.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..8912e7a86233e355693b02cd79660c6b1c57b679
--- /dev/null
+++ b/components/test-data-entry.tsx
@@ -0,0 +1,394 @@
+"use client"
+
+import { useState } from "react"
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Label } from "@/components/ui/label"
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
+import { Badge } from "@/components/ui/badge"
+import { Plus, Heart, Activity } from "lucide-react"
+import { toast } from "sonner"
+
+interface HealthActor {
+ id: string
+ name: string
+ location: string
+ type: string
+ verified: boolean
+ flbEarned: number
+}
+
+interface Donation {
+ id: string
+ donor: string
+ recipient: string
+ amount: number
+ timestamp: string
+}
+
+interface ImpactData {
+ id: string
+ actor: string
+ metric: string
+ value: number
+ date: string
+}
+
+export function TestDataEntry() {
+ const [healthActors, setHealthActors] = useState([
+ {
+ id: "1",
+ name: "Dr. Amara Kone",
+ location: "Lagos, Nigeria",
+ type: "Community Health Worker",
+ verified: true,
+ flbEarned: 250,
+ },
+ {
+ id: "2",
+ name: "Nairobi Health Center",
+ location: "Nairobi, Kenya",
+ type: "Health Facility",
+ verified: false,
+ flbEarned: 0,
+ },
+ ])
+
+ const [donations, setDonations] = useState([
+ {
+ id: "1",
+ donor: "0x1234...5678",
+ recipient: "Dr. Amara Kone",
+ amount: 0.5,
+ timestamp: new Date().toISOString(),
+ },
+ ])
+
+ const [impactData, setImpactData] = useState([
+ {
+ id: "1",
+ actor: "Dr. Amara Kone",
+ metric: "Patients Treated",
+ value: 45,
+ date: new Date().toISOString().split("T")[0],
+ },
+ ])
+
+ const [newActor, setNewActor] = useState({
+ name: "",
+ location: "",
+ type: "Community Health Worker",
+ })
+
+ const [newDonation, setNewDonation] = useState({
+ donor: "",
+ recipient: "",
+ amount: "",
+ })
+
+ const [newImpact, setNewImpact] = useState({
+ actor: "",
+ metric: "",
+ value: "",
+ date: "",
+ })
+
+ const addHealthActor = () => {
+ if (!newActor.name || !newActor.location) {
+ toast.error("Please fill in all required fields")
+ return
+ }
+
+ const actor: HealthActor = {
+ id: Date.now().toString(),
+ name: newActor.name,
+ location: newActor.location,
+ type: newActor.type,
+ verified: false,
+ flbEarned: 0,
+ }
+
+ setHealthActors([...healthActors, actor])
+ setNewActor({ name: "", location: "", type: "Community Health Worker" })
+ toast.success("Health actor added successfully!")
+ }
+
+ const addDonation = () => {
+ if (!newDonation.donor || !newDonation.recipient || !newDonation.amount) {
+ toast.error("Please fill in all required fields")
+ return
+ }
+
+ const donation: Donation = {
+ id: Date.now().toString(),
+ donor: newDonation.donor,
+ recipient: newDonation.recipient,
+ amount: Number.parseFloat(newDonation.amount),
+ timestamp: new Date().toISOString(),
+ }
+
+ setDonations([...donations, donation])
+ setNewDonation({ donor: "", recipient: "", amount: "" })
+ toast.success("Donation recorded successfully!")
+ }
+
+ const addImpactData = () => {
+ if (!newImpact.actor || !newImpact.metric || !newImpact.value || !newImpact.date) {
+ toast.error("Please fill in all required fields")
+ return
+ }
+
+ const impact: ImpactData = {
+ id: Date.now().toString(),
+ actor: newImpact.actor,
+ metric: newImpact.metric,
+ value: Number.parseInt(newImpact.value),
+ date: newImpact.date,
+ }
+
+ setImpactData([...impactData, impact])
+ setNewImpact({ actor: "", metric: "", value: "", date: "" })
+ toast.success("Impact data added successfully!")
+ }
+
+ return (
+
+
+ Test Data Entry
+ Add test data to verify the system functionality
+
+
+
+
+ Health Actors
+ Donations
+ Impact Data
+
+
+
+
+
+
Add New Health Actor
+
+
+ Name
+
+ setNewActor({ ...newActor, name: e.target.value })}
+ placeholder="Dr. Amara Kone"
+ className="bg-gray-800 border-gray-600 text-white"
+ />
+
+
+
+ Location
+
+ setNewActor({ ...newActor, location: e.target.value })}
+ placeholder="Lagos, Nigeria"
+ className="bg-gray-800 border-gray-600 text-white"
+ />
+
+
+
+ Type
+
+ setNewActor({ ...newActor, type: e.target.value })}
+ className="w-full p-2 bg-gray-800 border border-gray-600 rounded text-white"
+ >
+ Community Health Worker
+ Health Facility
+ Mobile Health Unit
+ Maternal Health Clinic
+
+
+
+
+ Add Health Actor
+
+
+
+
+
Current Health Actors
+
+ {healthActors.map((actor) => (
+
+
+
+
{actor.name}
+
{actor.location}
+
{actor.type}
+
+
+
+ {actor.verified ? "Verified" : "Pending"}
+
+
{actor.flbEarned} FLB
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+
Record New Donation
+
+
+ Donor Address
+
+ setNewDonation({ ...newDonation, donor: e.target.value })}
+ placeholder="0x1234...5678"
+ className="bg-gray-800 border-gray-600 text-white"
+ />
+
+
+
+ Recipient
+
+ setNewDonation({ ...newDonation, recipient: e.target.value })}
+ placeholder="Dr. Amara Kone"
+ className="bg-gray-800 border-gray-600 text-white"
+ />
+
+
+
+ Amount (BNB)
+
+ setNewDonation({ ...newDonation, amount: e.target.value })}
+ placeholder="0.5"
+ className="bg-gray-800 border-gray-600 text-white"
+ />
+
+
+
+ Record Donation
+
+
+
+
+
Recent Donations
+
+ {donations.map((donation) => (
+
+
+
+
To: {donation.recipient}
+
From: {donation.donor}
+
{new Date(donation.timestamp).toLocaleDateString()}
+
+
{donation.amount} BNB
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
Impact Records
+
+ {impactData.map((impact) => (
+
+
+
+
{impact.actor}
+
{impact.metric}
+
{impact.date}
+
+
{impact.value}
+
+
+ ))}
+
+
+
+
+
+
+
+ )
+}
diff --git a/components/testnet-status.tsx b/components/testnet-status.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..a446f84b50fa87a1fc822b30352aaa5a8977549a
--- /dev/null
+++ b/components/testnet-status.tsx
@@ -0,0 +1,218 @@
+"use client"
+
+import { useState, useEffect } from "react"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Badge } from "@/components/ui/badge"
+import { Button } from "@/components/ui/button"
+import { testnetClient, type NetworkStats } from "@/lib/testnet-client"
+import { Activity, Server, Users, Zap, RefreshCw, Wifi, WifiOff, Heart, Globe } from "lucide-react"
+
+export function TestnetStatus() {
+ const [isConnected, setIsConnected] = useState(false)
+ const [networkStats, setNetworkStats] = useState(null)
+ const [manifest, setManifest] = useState(null)
+ const [isLoading, setIsLoading] = useState(true)
+ const [lastUpdate, setLastUpdate] = useState(null)
+ const [error, setError] = useState(null)
+
+ const checkConnection = async () => {
+ try {
+ setError(null)
+ const pingResponse = await testnetClient.ping()
+ const manifestResponse = await testnetClient.getManifest()
+ const statsResponse = await testnetClient.getNetworkStats()
+
+ setIsConnected(pingResponse.status === "alive")
+ setManifest(manifestResponse)
+ setNetworkStats(statsResponse)
+ setLastUpdate(new Date())
+ } catch (error) {
+ console.error("Failed to connect to testnet:", error)
+ setIsConnected(false)
+ setError(error instanceof Error ? error.message : "Unknown error")
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ useEffect(() => {
+ checkConnection()
+ const interval = setInterval(checkConnection, 30000) // Check every 30 seconds
+ return () => clearInterval(interval)
+ }, [])
+
+ if (isLoading) {
+ return (
+
+
+
+ Connecting to FlameBorn Testnet...
+
+
+ )
+ }
+
+ return (
+
+ {/* Connection Status */}
+
+
+
+
+ {isConnected ? : }
+ FlameBorn Testnet Connection
+
+
+
+ Refresh
+
+
+
+
+
+
+ Status:
+
+ {isConnected ? "Connected" : "Disconnected"}
+
+
+
+ {error && (
+
+ Error:
+ {error}
+
+ )}
+
+ {manifest && (
+ <>
+
+ Network:
+ {manifest.network}
+
+
+ Version:
+ {manifest.version}
+
+
+ Consensus:
+ {manifest.consensus}
+
+
+ Ubuntu Principle:
+ {manifest.ubuntu_principle}
+
+ >
+ )}
+
+ {lastUpdate && (
+
+ Last Update:
+ {lastUpdate.toLocaleTimeString()}
+
+ )}
+
+
+
+
+ {/* Network Statistics */}
+ {networkStats && (
+
+
+
+
+
+ Ubuntu Members
+
+
+
+ {networkStats.total_users}
+ Total registered
+
+
+
+
+
+
+
+ Validators
+
+
+
+
+ {networkStats.active_validators}/{networkStats.total_validators}
+
+ Active/Total
+
+
+
+
+
+
+
+ Transactions
+
+
+
+ {networkStats.total_transactions}
+ Total processed
+
+
+
+
+
+
+
+ FLB Supply
+
+
+
+ {networkStats.total_flb_supply.toLocaleString()}
+ Total minted
+
+
+
+ )}
+
+ {/* Network Features */}
+ {manifest && manifest.features && (
+
+
+
+
+ Ubuntu Network Features
+
+
+
+
+ {manifest.features.map((feature: string, index: number) => (
+
+ {feature.replace(/_/g, " ")}
+
+ ))}
+
+
+
+ )}
+
+ {/* Ubuntu Philosophy */}
+
+
+
+
+ Ubuntu Philosophy
+
+
+
+
+
"Umuntu ngumuntu ngabantu"
+
+ A person is a person through other persons. This ancient African wisdom guides our healthcare tokenization
+ network.
+
+
+
+
+
+ )
+}
diff --git a/components/testnet/testnet-suite.tsx b/components/testnet/testnet-suite.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..ac61d3bbe76395537a7288723e94ad48ec6e8fa7
--- /dev/null
+++ b/components/testnet/testnet-suite.tsx
@@ -0,0 +1,439 @@
+"use client"
+
+import { useState, useEffect } from "react"
+import { motion } from "framer-motion"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Button } from "@/components/ui/button"
+import { Badge } from "@/components/ui/badge"
+import { Progress } from "@/components/ui/progress"
+import {
+ Activity,
+ AlertCircle,
+ CheckCircle,
+ Clock,
+ Code,
+ Network,
+ Play,
+ RefreshCw,
+ Shield,
+ TestTube,
+ Zap,
+} from "lucide-react"
+
+interface TestResult {
+ id: string
+ name: string
+ status: "passed" | "failed" | "running" | "pending"
+ description: string
+ duration?: number
+ error?: string
+ category: "sovereignty" | "generative" | "cultural" | "technical"
+}
+
+interface NetworkMetrics {
+ blockHeight: number
+ gasPrice: number
+ tps: number
+ validators: number
+ uptime: number
+ lastBlockTime: number
+}
+
+const mockNetworkMetrics: NetworkMetrics = {
+ blockHeight: 15847,
+ gasPrice: 0.000001,
+ tps: 45,
+ validators: 12,
+ uptime: 99.8,
+ lastBlockTime: 2.1,
+}
+
+const mockTestResults: TestResult[] = [
+ {
+ id: "sovereignty-1",
+ name: "African Sovereignty Enforcement",
+ status: "passed",
+ description: "Verify no outsider transactions are processed",
+ duration: 1.2,
+ category: "sovereignty",
+ },
+ {
+ id: "generative-1",
+ name: "Generative Minting Validation",
+ status: "passed",
+ description: "Ensure FLB tokens only mint with valid actions",
+ duration: 0.8,
+ category: "generative",
+ },
+ {
+ id: "cultural-1",
+ name: "Proverb Anchoring System",
+ status: "passed",
+ description: "Test African wisdom validation pipeline",
+ duration: 0.5,
+ category: "cultural",
+ },
+ {
+ id: "technical-1",
+ name: "Smart Contract Deployment",
+ status: "running",
+ description: "Deploy and verify all FlameBorn contracts",
+ category: "technical",
+ },
+ {
+ id: "sovereignty-2",
+ name: "Ubuntu Philosophy Enforcement",
+ status: "passed",
+ description: "Validate community-first transaction logic",
+ duration: 1.5,
+ category: "sovereignty",
+ },
+ {
+ id: "generative-2",
+ name: "Action-Based Token Flow",
+ status: "failed",
+ description: "Test contribution-to-token conversion",
+ error: "Proverb validation timeout",
+ category: "generative",
+ },
+ {
+ id: "cultural-2",
+ name: "Multi-Language Proverb Support",
+ status: "pending",
+ description: "Test Akan, Yoruba, Swahili, and Zulu proverbs",
+ category: "cultural",
+ },
+ {
+ id: "technical-2",
+ name: "Network Performance Stress Test",
+ status: "pending",
+ description: "Simulate high-load African community usage",
+ category: "technical",
+ },
+]
+
+export function TestnetSuite() {
+ const [testResults, setTestResults] = useState(mockTestResults)
+ const [networkMetrics, setNetworkMetrics] = useState(mockNetworkMetrics)
+ const [isRunningTests, setIsRunningTests] = useState(false)
+ const [selectedCategory, setSelectedCategory] = useState("all")
+
+ useEffect(() => {
+ // Simulate real-time network updates
+ const interval = setInterval(() => {
+ setNetworkMetrics((prev) => ({
+ ...prev,
+ blockHeight: prev.blockHeight + Math.floor(Math.random() * 3),
+ tps: Math.max(20, prev.tps + Math.floor(Math.random() * 10) - 5),
+ lastBlockTime: Math.max(1.5, prev.lastBlockTime + (Math.random() - 0.5) * 0.5),
+ }))
+ }, 3000)
+
+ return () => clearInterval(interval)
+ }, [])
+
+ const runAllTests = async () => {
+ setIsRunningTests(true)
+
+ // Reset all tests to pending
+ setTestResults((prev) =>
+ prev.map((test) => ({
+ ...test,
+ status: "pending" as const,
+ duration: undefined,
+ error: undefined,
+ })),
+ )
+
+ // Run tests sequentially with delays
+ for (let i = 0; i < testResults.length; i++) {
+ await new Promise((resolve) => setTimeout(resolve, 1500))
+
+ setTestResults((prev) =>
+ prev.map((test, index) =>
+ index === i
+ ? {
+ ...test,
+ status: "running" as const,
+ }
+ : test,
+ ),
+ )
+
+ await new Promise((resolve) => setTimeout(resolve, 2000))
+
+ setTestResults((prev) =>
+ prev.map((test, index) =>
+ index === i
+ ? {
+ ...test,
+ status: Math.random() > 0.15 ? ("passed" as const) : ("failed" as const),
+ duration: Math.random() * 3 + 0.5,
+ error: Math.random() > 0.15 ? undefined : "Test failed due to network conditions",
+ }
+ : test,
+ ),
+ )
+ }
+
+ setIsRunningTests(false)
+ }
+
+ const runSingleTest = async (testId: string) => {
+ setTestResults((prev) =>
+ prev.map((test) =>
+ test.id === testId
+ ? {
+ ...test,
+ status: "running" as const,
+ duration: undefined,
+ error: undefined,
+ }
+ : test,
+ ),
+ )
+
+ await new Promise((resolve) => setTimeout(resolve, 2000))
+
+ setTestResults((prev) =>
+ prev.map((test) =>
+ test.id === testId
+ ? {
+ ...test,
+ status: Math.random() > 0.2 ? ("passed" as const) : ("failed" as const),
+ duration: Math.random() * 3 + 0.5,
+ error: Math.random() > 0.2 ? undefined : "Test execution failed",
+ }
+ : test,
+ ),
+ )
+ }
+
+ const getStatusIcon = (status: string) => {
+ switch (status) {
+ case "passed":
+ return
+ case "failed":
+ return
+ case "running":
+ return
+ case "pending":
+ return
+ default:
+ return
+ }
+ }
+
+ const getStatusColor = (status: string) => {
+ switch (status) {
+ case "passed":
+ return "text-green-500"
+ case "failed":
+ return "text-red-500"
+ case "running":
+ return "text-blue-500"
+ case "pending":
+ return "text-gray-500"
+ default:
+ return "text-gray-500"
+ }
+ }
+
+ const getCategoryIcon = (category: string) => {
+ switch (category) {
+ case "sovereignty":
+ return
+ case "generative":
+ return
+ case "cultural":
+ return
+ case "technical":
+ return
+ default:
+ return
+ }
+ }
+
+ const getCategoryColor = (category: string) => {
+ switch (category) {
+ case "sovereignty":
+ return "bg-red-600"
+ case "generative":
+ return "bg-orange-600"
+ case "cultural":
+ return "bg-blue-600"
+ case "technical":
+ return "bg-purple-600"
+ default:
+ return "bg-gray-600"
+ }
+ }
+
+ const filteredTests =
+ selectedCategory === "all" ? testResults : testResults.filter((test) => test.category === selectedCategory)
+
+ const testStats = {
+ total: testResults.length,
+ passed: testResults.filter((t) => t.status === "passed").length,
+ failed: testResults.filter((t) => t.status === "failed").length,
+ running: testResults.filter((t) => t.status === "running").length,
+ pending: testResults.filter((t) => t.status === "pending").length,
+ }
+
+ return (
+
+ {/* Network Status Header */}
+
+
+
+
+ FlameBorn Testnet Status
+
+
+
+
+
+
{networkMetrics.blockHeight.toLocaleString()}
+
Block Height
+
+
+
{networkMetrics.tps}
+
TPS
+
+
+
{networkMetrics.validators}
+
Validators
+
+
+
{networkMetrics.uptime}%
+
Uptime
+
+
+
{networkMetrics.lastBlockTime.toFixed(1)}s
+
Block Time
+
+
+
{networkMetrics.gasPrice.toFixed(6)}
+
Gas Price
+
+
+
+
+
+ {/* Test Controls */}
+
+
+
Test Suite
+
+ ✓ {testStats.passed} Passed
+ ✗ {testStats.failed} Failed
+ ⟳ {testStats.running} Running
+ ○ {testStats.pending} Pending
+
+
+
+
+
setSelectedCategory(e.target.value)}
+ className="bg-slate-700 border border-slate-600 rounded px-3 py-2 text-white"
+ >
+ All Categories
+ African Sovereignty
+ Generative Minting
+ Cultural Anchoring
+ Technical
+
+
+
+ {isRunningTests ? (
+ <>
+
+ Running Tests...
+ >
+ ) : (
+ <>
+
+ Run All Tests
+ >
+ )}
+
+
+
+
+ {/* Test Results */}
+
+ {filteredTests.map((test) => (
+
+
+
+
+ {getCategoryIcon(test.category)}
+
+
+
{test.name}
+
{test.description}
+ {test.error &&
Error: {test.error}
}
+
+
+
+
+ {test.duration &&
{test.duration.toFixed(1)}s }
+
+ {getStatusIcon(test.status)}
+ {test.status}
+
+
runSingleTest(test.id)}
+ disabled={test.status === "running" || isRunningTests}
+ variant="outline"
+ size="sm"
+ >
+
+
+
+
+
+ {test.status === "running" && (
+
+ )}
+
+ ))}
+
+
+ {/* Test Categories Overview */}
+
+ {["sovereignty", "generative", "cultural", "technical"].map((category) => {
+ const categoryTests = testResults.filter((t) => t.category === category)
+ const passed = categoryTests.filter((t) => t.status === "passed").length
+ const total = categoryTests.length
+
+ return (
+
+
+
+ {getCategoryIcon(category)}
+ {category.charAt(0).toUpperCase() + category.slice(1)}
+
+
+
+
+ {passed}/{total}
+
+
+ {Math.round((passed / total) * 100)}% passing
+
+
+ )
+ })}
+
+
+ )
+}
diff --git a/components/theme-provider.tsx b/components/theme-provider.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..55c2f6eb60b22a313a4c27bd0b2d728063cb8ab9
--- /dev/null
+++ b/components/theme-provider.tsx
@@ -0,0 +1,11 @@
+'use client'
+
+import * as React from 'react'
+import {
+ ThemeProvider as NextThemesProvider,
+ type ThemeProviderProps,
+} from 'next-themes'
+
+export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
+ return {children}
+}
diff --git a/components/token-distribution-explainer.tsx b/components/token-distribution-explainer.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..e3408694d813da977928f110d661a7dfd44f5d17
--- /dev/null
+++ b/components/token-distribution-explainer.tsx
@@ -0,0 +1,148 @@
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Shield, Heart, Coins, ArrowRight, BarChart3 } from "lucide-react"
+
+export function TokenDistributionExplainer() {
+ return (
+
+
How Flameborn Tokens Work
+
+
+
+
+
+
+ For Guardians (Supporters)
+
+
+
+
+ Guardians contribute resources to the Flameborn ecosystem and receive FLB tokens as proof of their
+ support.
+
+
+
+
Token Acquisition
+
+ Make a contribution in cryptocurrency or fiat
+ Receive FLB tokens proportional to your contribution
+ Tokens represent your stake in the ecosystem
+
+
+
+
+
Token Utility
+
+ • Governance rights in the Flameborn DAO
+ • Verification privileges for healthcare workers
+ • Access to exclusive Guardian features
+ • Proof of impact for your contributions
+
+
+
+
+
+
+
+
+
+ For Healers (Healthcare Workers)
+
+
+
+
+ Healers are verified healthcare workers who receive support through the Flameborn ecosystem.
+
+
+
+
Registration Process
+
+ Register with your credentials and location
+ Get verified by Guardians through the DAO
+ Receive your Healer status on the blockchain
+
+
+
+
+
Benefits
+
+ • Direct financial support from Guardians
+ • Recognition of your healthcare work
+ • Access to resources and community
+ • Immutable proof of your service
+
+
+
+
+
+
+
+
+
+
+ Token Flow & Distribution
+
+
+
+
+ {/* Flow diagram */}
+
+
+
+
Guardians
+
Contribute resources
+
+
+
+
+
+
+
FLB Tokens
+
Minted & distributed
+
+
+
+
+
+
+
Healers
+
Receive support
+
+
+
+ {/* Distribution breakdown */}
+
+
+
+ Token Distribution Breakdown
+
+
+
+
+
70%
+
Direct support for verified healthcare workers
+
+
+
+
20%
+
Community treasury for emergency response
+
+
+
+
10%
+
Platform maintenance and development
+
+
+
+
+
+
+
+
+
+ Flameborn tokens are not speculative assets. They represent real impact and support for healthcare workers
+ across Africa.
+
+
+
+ )
+}
diff --git a/components/token-minting.tsx b/components/token-minting.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..5256dafd93c10fdf9dea7c8a8bbdfe227e8ba054
--- /dev/null
+++ b/components/token-minting.tsx
@@ -0,0 +1,148 @@
+"use client"
+
+import type React from "react"
+
+import { useState } from "react"
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
+import { getFLBBalance } from "@/lib/contract-utils"
+import { motion } from "framer-motion"
+
+export function TokenMinting({ address }: { address: string | null }) {
+ const [amount, setAmount] = useState("")
+ const [isSubmitting, setIsSubmitting] = useState(false)
+ const [balance, setBalance] = useState(null)
+ const [result, setResult] = useState<{
+ success: boolean
+ message: string
+ } | null>(null)
+
+ const handleMint = async (e: React.FormEvent) => {
+ e.preventDefault()
+ if (!address || !amount) return
+
+ setIsSubmitting(true)
+ setResult(null)
+
+ try {
+ // In a real implementation, this would call the contract
+ // For testing data entry, we'll simulate a successful mint
+
+ // Simulate API delay
+ await new Promise((resolve) => setTimeout(resolve, 1000))
+
+ // Simulate success
+ const success = true
+
+ if (success) {
+ setResult({
+ success: true,
+ message: `Successfully minted FLB tokens for ${amount} BNB! Thank you for supporting Flameborn.`,
+ })
+
+ // Update balance - in a real implementation, this would fetch from the blockchain
+ const newBalance = Number.parseFloat(balance || "0") + Number.parseFloat(amount) * 100 // Simulate 100 FLB per BNB
+ setBalance(newBalance.toFixed(2))
+
+ // Clear form
+ setAmount("")
+ } else {
+ setResult({
+ success: false,
+ message: "Minting failed. Please try again or contact support.",
+ })
+ }
+ } catch (error) {
+ console.error("Error during minting:", error)
+ setResult({
+ success: false,
+ message: "An unexpected error occurred. Please try again.",
+ })
+ } finally {
+ setIsSubmitting(false)
+ }
+ }
+
+ // Fetch balance when address changes
+ const fetchBalance = async () => {
+ if (!address) return
+
+ try {
+ const balance = await getFLBBalance(address)
+ setBalance(balance)
+ } catch (error) {
+ console.error("Error fetching balance:", error)
+ }
+ }
+
+ return (
+
+
+
+ Mint FLB Tokens
+ Support the Flameborn mission by minting FLB tokens
+
+
+ {address ? (
+ <>
+
+
+ Check FLB Balance
+
+ {balance !== null && (
+
+ Current Balance: {balance} FLB
+
+ )}
+
+
+
+
+
+ Amount (BNB)
+
+
setAmount(e.target.value)}
+ required
+ className="flame-input"
+ placeholder="0.1"
+ />
+
Minimum contribution: 0.01 BNB
+
+
+
+ {isSubmitting ? "Processing..." : "Mint FLB Tokens"}
+
+
+ {result && (
+
+ {result.message}
+
+ )}
+
+ >
+ ) : (
+
+
Please connect your wallet to mint FLB tokens
+
+ )}
+
+
+
+ )
+}
diff --git a/components/token-registration-flow.tsx b/components/token-registration-flow.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..76c68524654d7c5ada92ed4e5da487fad78cb21a
--- /dev/null
+++ b/components/token-registration-flow.tsx
@@ -0,0 +1,335 @@
+"use client"
+
+import { useState } from "react"
+import { Button } from "@/components/ui/button"
+import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
+import { Input } from "@/components/ui/input"
+import { Textarea } from "@/components/ui/textarea"
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
+import { Checkbox } from "@/components/ui/checkbox"
+import { Label } from "@/components/ui/label"
+import { ArrowRight, Wallet, CreditCard, Coins, Shield, Heart, Users } from "lucide-react"
+
+export function TokenRegistrationFlow() {
+ const [step, setStep] = useState(1)
+ const [registrationType, setRegistrationType] = useState<"guardian" | "healer" | null>(null)
+ const [tokenAmount, setTokenAmount] = useState("")
+ const [paymentMethod, setPaymentMethod] = useState<"crypto" | "card" | "bank" | null>(null)
+
+ const nextStep = () => setStep(step + 1)
+ const prevStep = () => setStep(step - 1)
+
+ return (
+
+
+
+ Flameborn Registration & Token Flow
+ Join the movement to support healthcare workers across Africa
+
+
+
+ {step === 1 && (
+
+
Choose Your Role
+
How would you like to participate in the Flameborn ecosystem?
+
+
+
setRegistrationType("guardian")}
+ >
+
+
+
+
+
+
+
Guardian
+
Support healthcare workers with resources
+
+
+
+
+
+
setRegistrationType("healer")}
+ >
+
+
+
+
+
+
+
Healer
+
Register as a healthcare worker to receive support
+
+
+
+
+
+
+
+
+ )}
+
+ {step === 2 && registrationType === "guardian" && (
+
+
Guardian Registration
+
+ As a Guardian, you'll support healthcare workers with resources and verify their credentials.
+
+
+
+
+
Contribution Amount
+
+ setTokenAmount(e.target.value)}
+ className="flame-input"
+ />
+
+
+
+
+
+ USD
+ EUR
+ BNB
+
+
+
+
Minimum contribution: 10 USD
+
+
+
+
Payment Method
+
+ setPaymentMethod("crypto")}
+ >
+
+
+ Crypto Wallet
+
+
+
+ setPaymentMethod("card")}
+ >
+
+
+ Credit Card
+
+
+
+ setPaymentMethod("bank")}
+ >
+
+
+ Bank Transfer
+
+
+
+
+
+
+
+
+
+
+ I agree to the Terms of Service and Privacy Policy
+
+
+ By becoming a Guardian, you commit to supporting healthcare workers and the Flameborn mission.
+
+
+
+
+
+
+
+
+ Back
+
+
+ Continue
+
+
+
+ )}
+
+ {step === 2 && registrationType === "healer" && (
+
+
Healthcare Worker Registration
+
+ Register as a healthcare worker to receive support from the Flameborn community.
+
+
+
+
+ Full Name
+
+
+
+
+ Healthcare Role
+
+
+
+
+
+ Doctor
+ Nurse
+ Midwife
+ Community Health Worker
+ Other Healthcare Provider
+
+
+
+
+
+ Location
+
+
+
+
+ Medical Credentials
+
+
+
+
+
Wallet Address (optional)
+
+
Where you'll receive support funds
+
+
+
+
+
+
+
+ I confirm that I am a healthcare worker in Africa
+
+
+ Your application will be reviewed by Guardians for verification.
+
+
+
+
+
+
+
+
+ Back
+
+
+ Submit Application
+
+
+
+ )}
+
+ {step === 3 && registrationType === "guardian" && (
+
+
+
+
+
+
Guardian Registration Complete
+
+ Thank you for becoming a Guardian! Your contribution will directly support healthcare workers.
+
+
+
+
Your Guardian Benefits
+
+
+
+ Access to the Guardian's Sanctuary
+
+
+
+ Ability to verify healthcare workers
+
+
+
+ Direct impact tracking of your contributions
+
+
+
+
+
+
+ Your FLB tokens will be minted and sent to your wallet shortly.
+
+
Go to Guardian Dashboard
+
+
+
+ )}
+
+ {step === 3 && registrationType === "healer" && (
+
+
+
+
+
+
Application Submitted
+
+ Thank you for applying! Your application will be reviewed by our Guardian community.
+
+
+
+
Next Steps
+
+
+ Verification: Guardians will review your
+ credentials
+
+
+ Approval: Once verified, you'll receive your
+ Healer status
+
+
+ Support: You'll be eligible to receive direct
+ support from Guardians
+
+
+
+
+
+
+ We'll notify you once your application has been reviewed.
+
+
Check Application Status
+
+
+
+ )}
+
+
+
+ )
+}
diff --git a/components/typed-intro-text.tsx b/components/typed-intro-text.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..65f5ae383b13cec0f0d86b96a0e79c771e2eb038
--- /dev/null
+++ b/components/typed-intro-text.tsx
@@ -0,0 +1,53 @@
+"use client"
+
+import { useState, useEffect } from "react"
+
+const messages = [
+ "Life is simple. Only decide.",
+ "We are not responding to crises. We are eradicating them.",
+ "Every birth registered. Every healer verified. Every community connected.",
+ "Ubuntu: I am because we are.",
+]
+
+export function TypedIntroText() {
+ const [currentMessageIndex, setCurrentMessageIndex] = useState(0)
+ const [currentText, setCurrentText] = useState("")
+ const [isTyping, setIsTyping] = useState(true)
+
+ useEffect(() => {
+ const currentMessage = messages[currentMessageIndex]
+
+ if (isTyping) {
+ if (currentText.length < currentMessage.length) {
+ const timeout = setTimeout(() => {
+ setCurrentText(currentMessage.slice(0, currentText.length + 1))
+ }, 50)
+ return () => clearTimeout(timeout)
+ } else {
+ const timeout = setTimeout(() => {
+ setIsTyping(false)
+ }, 2000)
+ return () => clearTimeout(timeout)
+ }
+ } else {
+ if (currentText.length > 0) {
+ const timeout = setTimeout(() => {
+ setCurrentText(currentText.slice(0, -1))
+ }, 30)
+ return () => clearTimeout(timeout)
+ } else {
+ setCurrentMessageIndex((prev) => (prev + 1) % messages.length)
+ setIsTyping(true)
+ }
+ }
+ }, [currentText, currentMessageIndex, isTyping])
+
+ return (
+
+
+ {currentText}
+ |
+
+
+ )
+}
diff --git a/components/ubuntu-token-minting.tsx b/components/ubuntu-token-minting.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..a5789edc87140f7cf217cd1e6fffd2832ae48838
--- /dev/null
+++ b/components/ubuntu-token-minting.tsx
@@ -0,0 +1,520 @@
+"use client"
+
+import type React from "react"
+import { useState, useEffect } from "react"
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
+import { Badge } from "@/components/ui/badge"
+import { mintFLBTokens, getFLBBalance } from "@/lib/contract-utils"
+import { motion, AnimatePresence } from "framer-motion"
+import { Coins, Heart, Users, CheckCircle, AlertCircle, Loader2, ExternalLink, Wallet, Activity } from "lucide-react"
+
+interface ContributionTier {
+ bnb: string
+ flb: number
+ impact: string
+ color: string
+ popular?: boolean
+}
+
+const UBUNTU_TIERS: ContributionTier[] = [
+ {
+ bnb: "0.01",
+ flb: 100,
+ impact: "Support 1 community health worker for a day",
+ color: "from-green-400 to-emerald-600",
+ },
+ {
+ bnb: "0.05",
+ flb: 500,
+ impact: "Fund medical supplies for 5 families",
+ color: "from-blue-400 to-indigo-600",
+ popular: true,
+ },
+ {
+ bnb: "0.1",
+ flb: 1000,
+ impact: "Enable healthcare access for 10 mothers",
+ color: "from-purple-400 to-violet-600",
+ },
+ {
+ bnb: "0.25",
+ flb: 2500,
+ impact: "Establish mobile health clinic for 1 week",
+ color: "from-orange-400 to-red-600",
+ },
+ {
+ bnb: "0.5",
+ flb: 5000,
+ impact: "Train 5 traditional healers in modern practices",
+ color: "from-yellow-400 to-orange-600",
+ },
+ {
+ bnb: "1.0",
+ flb: 10000,
+ impact: "Build sustainable health infrastructure",
+ color: "from-pink-400 to-rose-600",
+ },
+]
+
+export function UbuntuTokenMinting({ address }: { address: string | null }) {
+ const [selectedTier, setSelectedTier] = useState(UBUNTU_TIERS[1])
+ const [customAmount, setCustomAmount] = useState("")
+ const [isCustom, setIsCustom] = useState(false)
+ const [isSubmitting, setIsSubmitting] = useState(false)
+ const [balance, setBalance] = useState(null)
+ const [result, setResult] = useState<{ success: boolean; message: string } | null>(null)
+ const [impactStats, setImpactStats] = useState({
+ totalContributions: 45678,
+ healthWorkersSupported: 1234,
+ familiesHelped: 5678,
+ communitiesReached: 89,
+ })
+
+ const handleMint = async (e: React.FormEvent) => {
+ e.preventDefault()
+ if (!address) return
+
+ const amount = isCustom ? customAmount : selectedTier.bnb
+ if (!amount || Number.parseFloat(amount) < 0.01) {
+ setResult({
+ success: false,
+ message: "Minimum contribution is 0.01 BNB to honor the Ubuntu spirit of collective support.",
+ })
+ return
+ }
+
+ setIsSubmitting(true)
+ setResult(null)
+
+ try {
+ const success = await mintFLBTokens(amount)
+ if (success) {
+ const flbAmount = isCustom ? Math.floor(Number.parseFloat(amount) * 1000) : selectedTier.flb
+
+ setResult({
+ success: true,
+ message: `Asante sana! Successfully minted ${flbAmount.toLocaleString()} Ubuntu tokens. Your contribution strengthens our collective healing power.`,
+ })
+
+ // Update impact stats
+ setImpactStats((prev) => ({
+ ...prev,
+ totalContributions: prev.totalContributions + Number.parseFloat(amount),
+ healthWorkersSupported: prev.healthWorkersSupported + Math.floor(Number.parseFloat(amount) * 10),
+ familiesHelped: prev.familiesHelped + Math.floor(Number.parseFloat(amount) * 20),
+ }))
+
+ // Reset form
+ setCustomAmount("")
+ setIsCustom(false)
+
+ // Update balance
+ if (address) {
+ const newBalance = await getFLBBalance(address)
+ setBalance(newBalance)
+ }
+
+ // Submit to Google Sheets
+ await submitToGoogleSheets({
+ address,
+ amount,
+ flbAmount,
+ timestamp: new Date().toISOString(),
+ })
+ } else {
+ setResult({
+ success: false,
+ message: "Ubuntu token minting failed. The ancestors guide us to try again with patience.",
+ })
+ }
+ } catch (error) {
+ console.error("Error during Ubuntu token minting:", error)
+ setResult({
+ success: false,
+ message: "An unexpected challenge arose. Ubuntu teaches us resilience - please try again.",
+ })
+ } finally {
+ setIsSubmitting(false)
+ }
+ }
+
+ const submitToGoogleSheets = async (data: any) => {
+ try {
+ // In a real implementation, this would submit to the Google Sheets API
+ // For now, we'll simulate the submission
+ console.log("Submitting to Google Sheets:", data)
+
+ // You would implement the actual Google Sheets API call here
+ // using the provided form URL or Sheets API
+ } catch (error) {
+ console.error("Error submitting to Google Sheets:", error)
+ }
+ }
+
+ const fetchBalance = async () => {
+ if (!address) return
+ try {
+ const balance = await getFLBBalance(address)
+ setBalance(balance)
+ } catch (error) {
+ console.error("Error fetching Ubuntu token balance:", error)
+ }
+ }
+
+ useEffect(() => {
+ if (address) {
+ fetchBalance()
+ }
+ }, [address])
+
+ const calculateFLB = (bnbAmount: string) => {
+ return Math.floor(Number.parseFloat(bnbAmount || "0") * 1000)
+ }
+
+ return (
+
+ {/* Ubuntu Philosophy Header */}
+
+
+
+
+ Ubuntu Token Minting
+
+
+
+ "I am because we are" - Support African healthcare through collective Ubuntu spirit
+
+
+
+
+ {/* Impact Statistics */}
+
+
+
+ {impactStats.totalContributions.toLocaleString()}
+ BNB Contributed
+
+
+
+
+
+ {impactStats.healthWorkersSupported.toLocaleString()}
+
+ Health Workers Supported
+
+
+
+
+ {impactStats.familiesHelped.toLocaleString()}
+ Families Helped
+
+
+
+
+ {impactStats.communitiesReached}
+ Communities Reached
+
+
+
+
+ {/* Main Minting Interface */}
+
+
+
+
+ Mint Ubuntu Tokens
+
+
+ Choose your contribution level to support African healthcare and earn Ubuntu tokens
+
+
+
+ {address ? (
+ <>
+ {/* Balance Display */}
+
+
+
+ Your Ubuntu Balance:
+
+
+ {balance !== null ? (
+
{balance.toLocaleString()} FLB
+ ) : (
+
+ )}
+
+
+
+
+
+
+ {/* Contribution Tiers */}
+
+
Choose Your Ubuntu Contribution
+
+ {UBUNTU_TIERS.map((tier) => (
+
+ {
+ setSelectedTier(tier)
+ setIsCustom(false)
+ }}
+ >
+ {tier.popular && (
+
+ Most Popular
+
+ )}
+
+
+
+
{tier.bnb} BNB
+
{tier.flb.toLocaleString()} FLB
+
+ {tier.impact}
+
+
+
+
+
+ ))}
+
+
+ {/* Custom Amount Option */}
+
setIsCustom(true)}
+ >
+
+
+
+
Custom Amount
+
Choose your own contribution level
+
+
+
+ {customAmount ? `${calculateFLB(customAmount).toLocaleString()} FLB` : "? FLB"}
+
+
1 BNB = 1,000 FLB
+
+
+
+
+
+
+ {/* Minting Form */}
+
+ {isCustom && (
+
+
Custom BNB Amount
+
setCustomAmount(e.target.value)}
+ placeholder="0.01"
+ className="bg-white/10 border-white/20 text-white placeholder-gray-400"
+ required
+ />
+
+ Minimum: 0.01 BNB • You will receive: {calculateFLB(customAmount).toLocaleString()} FLB
+
+
+ )}
+
+ {/* Selected Contribution Summary */}
+
+
Your Ubuntu Contribution
+
+
+
BNB Amount:
+
+ {isCustom ? customAmount || "0" : selectedTier.bnb} BNB
+
+
+
+
Ubuntu Tokens:
+
+ {isCustom ? calculateFLB(customAmount).toLocaleString() : selectedTier.flb.toLocaleString()} FLB
+
+
+
+
+
Impact:
+
+ {isCustom
+ ? `Support ${Math.floor(Number.parseFloat(customAmount || "0") * 100)} community members`
+ : selectedTier.impact}
+
+
+
+
+
+ {isSubmitting ? (
+ <>
+
+ Minting Ubuntu Tokens...
+ >
+ ) : (
+ <>
+
+ Mint Ubuntu Tokens
+ >
+ )}
+
+
+
+ {/* Result Message */}
+
+ {result && (
+
+
+ {result.success ? (
+
+ ) : (
+
+ )}
+
+
{result.message}
+ {result.success && (
+
+
+ window.open(
+ "https://docs.google.com/forms/d/e/1FAIpQLSfuxO0SEkiGhuvYqP5fE4c5RgqdyZc76rppeC70rDsu67ssgw/viewform?usp=sharing&ouid=117857227348165727286",
+ "_blank",
+ )
+ }
+ className="border-green-500/30 text-green-400 hover:bg-green-500/10"
+ >
+
+ Join Community
+
+
+ window.open(
+ "https://docs.google.com/spreadsheets/d/e/2PACX-1vQkTZ9GaUMp_nlR82WWKR_tFMZzWDE4jekk6N9GKZ60eiceuHsUOlftoiqdd1AB3vBPxiyk1DtLBBM4/pubhtml",
+ "_blank",
+ )
+ }
+ className="border-green-500/30 text-green-400 hover:bg-green-500/10"
+ >
+
+ View Live Data
+
+
+ )}
+
+
+
+ )}
+
+
+ {/* Ubuntu Philosophy Footer */}
+
+
+ "Ubuntu: I am because we are"
+
+ Your contribution joins thousands of others in building sustainable healthcare across Africa.
+ Together, we heal our communities and strengthen our collective Ubuntu spirit.
+
+
+
+ >
+ ) : (
+
+
+
Connect Your Wallet
+
+ Connect your wallet to mint Ubuntu tokens and join our collective healing mission
+
+
+
+ Connect Wallet
+
+
+ )}
+
+
+
+ {/* Live Community Feed Integration */}
+
+
+
+
+ Recent Ubuntu Contributors
+
+
+
+
+ {[
+ { name: "Amara from Lagos", amount: "0.1 BNB", flb: "1,000 FLB", time: "2 minutes ago" },
+ { name: "Kwame from Accra", amount: "0.05 BNB", flb: "500 FLB", time: "5 minutes ago" },
+ { name: "Fatima from Cairo", amount: "0.25 BNB", flb: "2,500 FLB", time: "8 minutes ago" },
+ ].map((contributor, index) => (
+
+
+
{contributor.name}
+
{contributor.time}
+
+
+
{contributor.flb}
+
{contributor.amount}
+
+
+ ))}
+
+
+
+ window.open(
+ "https://docs.google.com/spreadsheets/d/e/2PACX-1vQkTZ9GaUMp_nlR82WWKR_tFMZzWDE4jekk6N9GKZ60eiceuHsUOlftoiqdd1AB3vBPxiyk1DtLBBM4/pubhtml",
+ "_blank",
+ )
+ }
+ className="border-white/20 text-white hover:bg-white/10"
+ >
+
+ View All Contributors
+
+
+
+
+
+ )
+}
diff --git a/components/ui/accordion.tsx b/components/ui/accordion.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..24c788c2c44cb058812cdbb97d071439a87af5f0
--- /dev/null
+++ b/components/ui/accordion.tsx
@@ -0,0 +1,58 @@
+"use client"
+
+import * as React from "react"
+import * as AccordionPrimitive from "@radix-ui/react-accordion"
+import { ChevronDown } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Accordion = AccordionPrimitive.Root
+
+const AccordionItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AccordionItem.displayName = "AccordionItem"
+
+const AccordionTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+ svg]:rotate-180",
+ className
+ )}
+ {...props}
+ >
+ {children}
+
+
+
+))
+AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
+
+const AccordionContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+ {children}
+
+))
+
+AccordionContent.displayName = AccordionPrimitive.Content.displayName
+
+export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
diff --git a/components/ui/alert-dialog.tsx b/components/ui/alert-dialog.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..25e7b474464c9849cef836f5c863be0f38bdaac3
--- /dev/null
+++ b/components/ui/alert-dialog.tsx
@@ -0,0 +1,141 @@
+"use client"
+
+import * as React from "react"
+import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
+
+import { cn } from "@/lib/utils"
+import { buttonVariants } from "@/components/ui/button"
+
+const AlertDialog = AlertDialogPrimitive.Root
+
+const AlertDialogTrigger = AlertDialogPrimitive.Trigger
+
+const AlertDialogPortal = AlertDialogPrimitive.Portal
+
+const AlertDialogOverlay = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
+
+const AlertDialogContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+
+))
+AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
+
+const AlertDialogHeader = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+AlertDialogHeader.displayName = "AlertDialogHeader"
+
+const AlertDialogFooter = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+AlertDialogFooter.displayName = "AlertDialogFooter"
+
+const AlertDialogTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
+
+const AlertDialogDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AlertDialogDescription.displayName =
+ AlertDialogPrimitive.Description.displayName
+
+const AlertDialogAction = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
+
+const AlertDialogCancel = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
+
+export {
+ AlertDialog,
+ AlertDialogPortal,
+ AlertDialogOverlay,
+ AlertDialogTrigger,
+ AlertDialogContent,
+ AlertDialogHeader,
+ AlertDialogFooter,
+ AlertDialogTitle,
+ AlertDialogDescription,
+ AlertDialogAction,
+ AlertDialogCancel,
+}
diff --git a/components/ui/alert.tsx b/components/ui/alert.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..41fa7e0561a3fdb5f986c1213a35e563de740e96
--- /dev/null
+++ b/components/ui/alert.tsx
@@ -0,0 +1,59 @@
+import * as React from "react"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const alertVariants = cva(
+ "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
+ {
+ variants: {
+ variant: {
+ default: "bg-background text-foreground",
+ destructive:
+ "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+const Alert = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes & VariantProps
+>(({ className, variant, ...props }, ref) => (
+
+))
+Alert.displayName = "Alert"
+
+const AlertTitle = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+AlertTitle.displayName = "AlertTitle"
+
+const AlertDescription = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+AlertDescription.displayName = "AlertDescription"
+
+export { Alert, AlertTitle, AlertDescription }
diff --git a/components/ui/aspect-ratio.tsx b/components/ui/aspect-ratio.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..d6a5226f5e9cc09a2bd1ccbce465524e41f04345
--- /dev/null
+++ b/components/ui/aspect-ratio.tsx
@@ -0,0 +1,7 @@
+"use client"
+
+import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
+
+const AspectRatio = AspectRatioPrimitive.Root
+
+export { AspectRatio }
diff --git a/components/ui/avatar.tsx b/components/ui/avatar.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..51e507ba9d08bcdbb1fb630498f1cbdf2bf50093
--- /dev/null
+++ b/components/ui/avatar.tsx
@@ -0,0 +1,50 @@
+"use client"
+
+import * as React from "react"
+import * as AvatarPrimitive from "@radix-ui/react-avatar"
+
+import { cn } from "@/lib/utils"
+
+const Avatar = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+Avatar.displayName = AvatarPrimitive.Root.displayName
+
+const AvatarImage = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AvatarImage.displayName = AvatarPrimitive.Image.displayName
+
+const AvatarFallback = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
+
+export { Avatar, AvatarImage, AvatarFallback }
diff --git a/components/ui/badge.tsx b/components/ui/badge.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..f000e3ef5176395b067dfc3f3e1256a80c450015
--- /dev/null
+++ b/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-full 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 hover:bg-primary/80",
+ secondary:
+ "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
+ destructive:
+ "border-transparent bg-destructive text-destructive-foreground 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/components/ui/breadcrumb.tsx b/components/ui/breadcrumb.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..60e6c96f72f0350d08b47e4730cab8f3975dc853
--- /dev/null
+++ b/components/ui/breadcrumb.tsx
@@ -0,0 +1,115 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { ChevronRight, MoreHorizontal } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Breadcrumb = React.forwardRef<
+ HTMLElement,
+ React.ComponentPropsWithoutRef<"nav"> & {
+ separator?: React.ReactNode
+ }
+>(({ ...props }, ref) => )
+Breadcrumb.displayName = "Breadcrumb"
+
+const BreadcrumbList = React.forwardRef<
+ HTMLOListElement,
+ React.ComponentPropsWithoutRef<"ol">
+>(({ className, ...props }, ref) => (
+
+))
+BreadcrumbList.displayName = "BreadcrumbList"
+
+const BreadcrumbItem = React.forwardRef<
+ HTMLLIElement,
+ React.ComponentPropsWithoutRef<"li">
+>(({ className, ...props }, ref) => (
+
+))
+BreadcrumbItem.displayName = "BreadcrumbItem"
+
+const BreadcrumbLink = React.forwardRef<
+ HTMLAnchorElement,
+ React.ComponentPropsWithoutRef<"a"> & {
+ asChild?: boolean
+ }
+>(({ asChild, className, ...props }, ref) => {
+ const Comp = asChild ? Slot : "a"
+
+ return (
+
+ )
+})
+BreadcrumbLink.displayName = "BreadcrumbLink"
+
+const BreadcrumbPage = React.forwardRef<
+ HTMLSpanElement,
+ React.ComponentPropsWithoutRef<"span">
+>(({ className, ...props }, ref) => (
+
+))
+BreadcrumbPage.displayName = "BreadcrumbPage"
+
+const BreadcrumbSeparator = ({
+ children,
+ className,
+ ...props
+}: React.ComponentProps<"li">) => (
+ svg]:w-3.5 [&>svg]:h-3.5", className)}
+ {...props}
+ >
+ {children ?? }
+
+)
+BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
+
+const BreadcrumbEllipsis = ({
+ className,
+ ...props
+}: React.ComponentProps<"span">) => (
+
+
+ More
+
+)
+BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
+
+export {
+ Breadcrumb,
+ BreadcrumbList,
+ BreadcrumbItem,
+ BreadcrumbLink,
+ BreadcrumbPage,
+ BreadcrumbSeparator,
+ BreadcrumbEllipsis,
+}
diff --git a/components/ui/button.tsx b/components/ui/button.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..36496a28727a3643b4212a14225d4f6cbd50bda5
--- /dev/null
+++ b/components/ui/button.tsx
@@ -0,0 +1,56 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
+ {
+ variants: {
+ variant: {
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
+ destructive:
+ "bg-destructive text-destructive-foreground hover:bg-destructive/90",
+ outline:
+ "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
+ secondary:
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
+ ghost: "hover:bg-accent hover:text-accent-foreground",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ default: "h-10 px-4 py-2",
+ sm: "h-9 rounded-md px-3",
+ lg: "h-11 rounded-md px-8",
+ icon: "h-10 w-10",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+export interface ButtonProps
+ extends React.ButtonHTMLAttributes,
+ VariantProps {
+ asChild?: boolean
+}
+
+const Button = React.forwardRef(
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : "button"
+ return (
+
+ )
+ }
+)
+Button.displayName = "Button"
+
+export { Button, buttonVariants }
diff --git a/components/ui/calendar.tsx b/components/ui/calendar.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..61d2b451ef5fd1d9faa1162371628a8132dd51df
--- /dev/null
+++ b/components/ui/calendar.tsx
@@ -0,0 +1,66 @@
+"use client"
+
+import * as React from "react"
+import { ChevronLeft, ChevronRight } from "lucide-react"
+import { DayPicker } from "react-day-picker"
+
+import { cn } from "@/lib/utils"
+import { buttonVariants } from "@/components/ui/button"
+
+export type CalendarProps = React.ComponentProps
+
+function Calendar({
+ className,
+ classNames,
+ showOutsideDays = true,
+ ...props
+}: CalendarProps) {
+ return (
+ ,
+ IconRight: ({ ...props }) => ,
+ }}
+ {...props}
+ />
+ )
+}
+Calendar.displayName = "Calendar"
+
+export { Calendar }
diff --git a/components/ui/card.tsx b/components/ui/card.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..f62edea578d50058bef5e6bcc178b88d145564e9
--- /dev/null
+++ b/components/ui/card.tsx
@@ -0,0 +1,79 @@
+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/components/ui/carousel.tsx b/components/ui/carousel.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..ec505d00d95ecdbbc1501d1bfa7020e51d6010a7
--- /dev/null
+++ b/components/ui/carousel.tsx
@@ -0,0 +1,262 @@
+"use client"
+
+import * as React from "react"
+import useEmblaCarousel, {
+ type UseEmblaCarouselType,
+} from "embla-carousel-react"
+import { ArrowLeft, ArrowRight } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
+
+type CarouselApi = UseEmblaCarouselType[1]
+type UseCarouselParameters = Parameters
+type CarouselOptions = UseCarouselParameters[0]
+type CarouselPlugin = UseCarouselParameters[1]
+
+type CarouselProps = {
+ opts?: CarouselOptions
+ plugins?: CarouselPlugin
+ orientation?: "horizontal" | "vertical"
+ setApi?: (api: CarouselApi) => void
+}
+
+type CarouselContextProps = {
+ carouselRef: ReturnType[0]
+ api: ReturnType[1]
+ scrollPrev: () => void
+ scrollNext: () => void
+ canScrollPrev: boolean
+ canScrollNext: boolean
+} & CarouselProps
+
+const CarouselContext = React.createContext(null)
+
+function useCarousel() {
+ const context = React.useContext(CarouselContext)
+
+ if (!context) {
+ throw new Error("useCarousel must be used within a ")
+ }
+
+ return context
+}
+
+const Carousel = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes & CarouselProps
+>(
+ (
+ {
+ orientation = "horizontal",
+ opts,
+ setApi,
+ plugins,
+ className,
+ children,
+ ...props
+ },
+ ref
+ ) => {
+ const [carouselRef, api] = useEmblaCarousel(
+ {
+ ...opts,
+ axis: orientation === "horizontal" ? "x" : "y",
+ },
+ plugins
+ )
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false)
+ const [canScrollNext, setCanScrollNext] = React.useState(false)
+
+ const onSelect = React.useCallback((api: CarouselApi) => {
+ if (!api) {
+ return
+ }
+
+ setCanScrollPrev(api.canScrollPrev())
+ setCanScrollNext(api.canScrollNext())
+ }, [])
+
+ const scrollPrev = React.useCallback(() => {
+ api?.scrollPrev()
+ }, [api])
+
+ const scrollNext = React.useCallback(() => {
+ api?.scrollNext()
+ }, [api])
+
+ const handleKeyDown = React.useCallback(
+ (event: React.KeyboardEvent) => {
+ if (event.key === "ArrowLeft") {
+ event.preventDefault()
+ scrollPrev()
+ } else if (event.key === "ArrowRight") {
+ event.preventDefault()
+ scrollNext()
+ }
+ },
+ [scrollPrev, scrollNext]
+ )
+
+ React.useEffect(() => {
+ if (!api || !setApi) {
+ return
+ }
+
+ setApi(api)
+ }, [api, setApi])
+
+ React.useEffect(() => {
+ if (!api) {
+ return
+ }
+
+ onSelect(api)
+ api.on("reInit", onSelect)
+ api.on("select", onSelect)
+
+ return () => {
+ api?.off("select", onSelect)
+ }
+ }, [api, onSelect])
+
+ return (
+
+
+ {children}
+
+
+ )
+ }
+)
+Carousel.displayName = "Carousel"
+
+const CarouselContent = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => {
+ const { carouselRef, orientation } = useCarousel()
+
+ return (
+
+ )
+})
+CarouselContent.displayName = "CarouselContent"
+
+const CarouselItem = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => {
+ const { orientation } = useCarousel()
+
+ return (
+
+ )
+})
+CarouselItem.displayName = "CarouselItem"
+
+const CarouselPrevious = React.forwardRef<
+ HTMLButtonElement,
+ React.ComponentProps
+>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel()
+
+ return (
+
+
+ Previous slide
+
+ )
+})
+CarouselPrevious.displayName = "CarouselPrevious"
+
+const CarouselNext = React.forwardRef<
+ HTMLButtonElement,
+ React.ComponentProps
+>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
+ const { orientation, scrollNext, canScrollNext } = useCarousel()
+
+ return (
+
+
+ Next slide
+
+ )
+})
+CarouselNext.displayName = "CarouselNext"
+
+export {
+ type CarouselApi,
+ Carousel,
+ CarouselContent,
+ CarouselItem,
+ CarouselPrevious,
+ CarouselNext,
+}
diff --git a/components/ui/chart.tsx b/components/ui/chart.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..8620baa3b32301ef0e071b13049f2a00caa6f0bc
--- /dev/null
+++ b/components/ui/chart.tsx
@@ -0,0 +1,365 @@
+"use client"
+
+import * as React from "react"
+import * as RechartsPrimitive from "recharts"
+
+import { cn } from "@/lib/utils"
+
+// Format: { THEME_NAME: CSS_SELECTOR }
+const THEMES = { light: "", dark: ".dark" } as const
+
+export type ChartConfig = {
+ [k in string]: {
+ label?: React.ReactNode
+ icon?: React.ComponentType
+ } & (
+ | { color?: string; theme?: never }
+ | { color?: never; theme: Record }
+ )
+}
+
+type ChartContextProps = {
+ config: ChartConfig
+}
+
+const ChartContext = React.createContext(null)
+
+function useChart() {
+ const context = React.useContext(ChartContext)
+
+ if (!context) {
+ throw new Error("useChart must be used within a ")
+ }
+
+ return context
+}
+
+const ChartContainer = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentProps<"div"> & {
+ config: ChartConfig
+ children: React.ComponentProps<
+ typeof RechartsPrimitive.ResponsiveContainer
+ >["children"]
+ }
+>(({ id, className, children, config, ...props }, ref) => {
+ const uniqueId = React.useId()
+ const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
+
+ return (
+
+
+
+
+ {children}
+
+
+
+ )
+})
+ChartContainer.displayName = "Chart"
+
+const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
+ const colorConfig = Object.entries(config).filter(
+ ([_, config]) => config.theme || config.color
+ )
+
+ if (!colorConfig.length) {
+ return null
+ }
+
+ return (
+