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. +

+
+ +
+
+ Guardian Hero +
+
+

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.

+ + + +
+
+ ) +} + +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 */} + + + {/* Mobile Menu Button */} + +
+
+ + {/* 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

+ + + + +
+ +
+

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. +

+ + + +
+ + + +
+
+
+ ) +} 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 ( +
+
+ +

FLAMEBORN

+ + +
+ +
+

Debug Dashboard

+ +
+ + + + Debug Tools + + +
+ + + +
+
+
+
+ +
+ + + +
+
+
+ ) +} 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. +

+
+ + + + +
+
+
+ ) +} 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

+ + + +
+ +
+ + + 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.

+
+ +
+
+ +
+

+ Join the DAO +

+

Help guide aid. Propose, vote, change lives.

+
+ +
+
+ +
+

+ Spread the Word +

+

Tell your tribe. Light another torch.

+
+ +
+
+ +
+

+ Volunteer +

+

Coders, designers, medics, storytellers — your gifts matter here.

+
+ +
+
+
+
+ + +

+ 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. +

+ +
+ + + + +
+
+
+
+ ) +} 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}

+
+
+
+
+
+ ))} + +
+ +
+
+ ) + + 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) => ( + + ))} +
+ {userAnswers[index] !== undefined && ( +
+

+ Ubuntu Wisdom: {question.explanation} +

+
+ )} +
+
+ ))} + + {userAnswers.length === questions.length && ( +
+
+

Your Ubuntu Score: {calculateQuizScore()}%

+
+ +
+ )} +
+ ) + + case "story": + return ( +
+
+

{activeModule.title}

+

{activeModule.description}

+
+ + + +

Ubuntu Story Prompt:

+

{activeModule.content.prompt}

+ +
+ +