diff --git a/README.md b/README.md
index ef6c85f4..b57a4a4f 100644
--- a/README.md
+++ b/README.md
@@ -1,36 +1,177 @@
-
-# Boundless: Stellar-Based Crowdfunding Platform
-
-Boundless is a decentralized crowdfunding platform built on the Stellar blockchain, connecting innovators, creators, and supporters within the Stellar ecosystem.
-
-## Features
-
-- Secure user registration with KYC verification
-- Seamless Stellar wallet integration
-- Campaign creation and management
-- Project discovery and funding for backers
-- Milestone-based fund release
-- Token rewards for backers
-- Influencer referral system
-
-## Tech Stack
-
--
-
-## Getting Started
-
+# Boundless Contribution Guide
+
+## Introduction
+Welcome to the **Boundless** project! Boundless is a crowdfunding platform built on the **Stellar blockchain**, using **Soroban smart contracts** to facilitate transparent and decentralized project funding. This document outlines how to contribute to the project, including setting up your development environment, best practices, and contribution guidelines.
+
+## Table of Contents
+1. [Project Overview](#project-overview)
+2. [Technology Stack](#technology-stack)
+3. [Getting Started](#getting-started)
+4. [How to Contribute](#how-to-contribute)
+5. [Code Standards & Best Practices](#code-standards--best-practices)
+6. [Smart Contract Development](#smart-contract-development)
+7. [Testing](#testing)
+8. [Deployment & CI/CD](#deployment--cicd)
+9. [Reporting Issues](#reporting-issues)
+10. [Community & Support](#community--support)
+
+---
+
+## 1. Project Overview
+Boundless enables users to create, support, and manage projects in a decentralized and trustless manner. **Key features include:**
+- User Authentication (email & social login, KYC verification)
+- Project Creation & Discovery
+- Voting & Feedback Mechanism
+- Crowdfunding via Stellar Wallets
+- Milestone-Based Fund Releases
+
+## 2. Technology Stack
+### Backend:
+- **Next.js API Routes** (for server-side logic)
+- **Prisma ORM** (for database interactions)
+- **PostgreSQL** (for storage)
+- **Stellar SDK** (for blockchain interactions)
+
+### Frontend:
+- **Next.js (App Router)** (for the main web application)
+- **Shadcn** (for UI development)
+- **Tailwind CSS** (for styling)
+- **Zustand** (for state management)
+
+### Infrastructure:
+- **Vercel** (for frontend hosting & deployment)
+- **Docker** (for containerization)
+- **GitHub Actions** (for CI/CD pipelines)
+
+## 3. Getting Started
### Prerequisites
+Before contributing, ensure you have the following installed:
+- **Node.js** & **npm**
+- **Docker** (for containerized development)
+- **PostgreSQL** (for database setup)
+- **Rust** & **Soroban CLI** (for smart contract development)
+- **Git** (for version control)
+
+### Setup
+1. **Fork the repository https://github.com/0xdevcollins/boundless and clone your fork:**
+ ```sh
+ git clone https://github.com/YOUR_USERNAME/boundless.git
+ cd boundless
+ ```
+2. **Install dependencies:**
+ ```sh
+ npm install
+ ```
+3. **Set up environment variables:**
+ Create a `.env.local` file based on `.env.example` and configure database, Stellar, and authentication settings.
+4. **Start development server:**
+ ```sh
+ npm run dev
+ ```
+
+---
+
+## 4. How to Contribute
+### 1. Fork & Clone the Repository
+- Fork the repository to your GitHub account.
+- Clone it locally using:
+ ```sh
+ git clone https://github.com/YOUR_USERNAME/boundless.git
+ ```
+
+### 2. Create a Feature Branch
+- Use descriptive branch names:
+ ```sh
+ git checkout -b feature/add-user-profile
+ ```
+
+### 3. Make Changes & Commit
+- Follow the coding standards (see next section).
+- Commit with meaningful messages:
+ ```sh
+ git commit -m "feat: added user profile section"
+ ```
+
+### 4. Push Changes & Create a Pull Request (PR)
+- Push your branch:
+ ```sh
+ git push origin feature/add-user-profile
+ ```
+- Create a pull request on GitHub and describe your changes.
+
+### 5. Review & Merge
+- Maintainers will review your PR and request changes if necessary.
+- Once approved, it will be merged.
+
+---
+
+## 5. Code Standards & Best Practices
+- Use **ESLint & Prettier** for formatting.
+- Follow **Conventional Commits** (`feat:`, `fix:`, `chore:`, etc.).
+- Use **TypeScript**.
+- Keep functions modular and reusable.
+
+---
+
+## 6. Smart Contract Development
+Boundless uses **Soroban smart contracts** for funding and milestone tracking.
+### Key Contracts:
+1. `create_project(project_id, creator_address, metadata_uri, funding_target, milestone_count)`
+2. `vote_project(project_id, vote_value)`
+3. `fund_project(project_id, amount)`
+4. `release_milestone(project_id, milestone_number)`
+5. `refund(project_id)`
+
+### Developing Smart Contracts
+1. **Install Soroban CLI:**
+ https://developers.stellar.org/docs/build/smart-contracts/getting-started/setup
+2. **Write a new contract in Rust:**
+ - Place it under `contracts/` directory.
+ - Example structure:
+ ```rust
+ #[soroban_sdk::contract]
+ pub struct ProjectContract;
+ ```
+3. **test & Build:**
+ ```sh
+ cargo test
+ cargo build
+ ```
+
+---
+
+## 7. Testing
+- **Test smart contracts:**
+ ```sh
+ cargo test
+ ```
+
+---
+
+## 8. Deployment & CI/CD
+Boundless uses **GitHub Actions** for deployment.
+### Deployment Steps:
+1. **Push to `main` branch**
+2. **GitHub Actions** runs tests & builds the project
+3. **Vercel** automatically deploys frontend
+4. **Backend updates** are deployed manually
+
+---
+
+## 9. Reporting Issues
+Found a bug? Have a feature request? Open an **issue** on GitHub:
+- Go to [Issues](https://github.com/0xdevcollins/boundless/issues)
+- Provide clear details & screenshots (if applicable).
+
+---
+
+## 10. Community & Support
+### Join Our Channels:
+- **Discord:** [Join Here](https://discord.gg/juUmBmwC3s)
+- **GitHub Discussions:** [Boundless Discussions](https://github.com/0xdevcollins/boundless/discussions)
+- **Twitter:** [Follow @boundless_fi](https://x.com/boundless_fi)
+
+---
+
+Thank you for contributing to Boundless! 🚀
--
-
-### Installation
-
-## Contributing
-
-
-## License
-
-
-## Contact
-
-For any inquiries, please reach out to us at support@boundless.io or join our [community Discord](https://discord.gg/boundless).
diff --git a/app/(dashboard)/layout.tsx b/app/(dashboard)/layout.tsx
index 61a5064f..b6797201 100644
--- a/app/(dashboard)/layout.tsx
+++ b/app/(dashboard)/layout.tsx
@@ -1,3 +1,4 @@
+import ConnectWalletButton from "@/components/connect-wallet";
import { MobileSidebar } from "@/components/mobile-sidebar";
import { Sidebar } from "@/components/sidebar";
@@ -18,6 +19,7 @@ export default function Layout({
{/* Mobile Header with Menu */}
{children}
diff --git a/app/(dashboard)/projects/my-projects/page.tsx b/app/(dashboard)/projects/my-projects/page.tsx
new file mode 100644
index 00000000..ac487794
--- /dev/null
+++ b/app/(dashboard)/projects/my-projects/page.tsx
@@ -0,0 +1,12 @@
+import { MyProjectsList } from "@/components/projects/my-project-list";
+
+export const dynamic = "force-dynamic";
+
+export default function ProjectsPage() {
+ return (
+
+
Projects
+
+
+ );
+}
diff --git a/app/api/projects/create/route.ts b/app/api/projects/create/route.ts
index 6d2c6b0e..a2e45e4a 100644
--- a/app/api/projects/create/route.ts
+++ b/app/api/projects/create/route.ts
@@ -17,6 +17,8 @@ export async function POST(request: Request) {
}
try {
const formData = await request.formData();
+ const projectId = formData.get("projectId") as string;
+ const signedTx = formData.get("signedTx") as string;
const title = formData.get("title") as string;
const description = formData.get("description") as string;
const fundingGoal = Number(formData.get("fundingGoal"));
@@ -48,6 +50,7 @@ export async function POST(request: Request) {
const project = await prisma.project.create({
data: {
+ id: projectId,
userId: session.user.id,
title,
description,
@@ -55,7 +58,7 @@ export async function POST(request: Request) {
category,
bannerUrl,
profileUrl,
- blockchainTx: null,
+ blockchainTx: signedTx || null,
},
});
diff --git a/app/api/projects/route.ts b/app/api/projects/route.ts
index a614de84..79322cc9 100644
--- a/app/api/projects/route.ts
+++ b/app/api/projects/route.ts
@@ -1,6 +1,8 @@
import { NextResponse } from "next/server";
import prisma from "@/lib/prisma";
import type { Prisma, ValidationStatus } from "@prisma/client";
+import { getServerSession } from "next-auth";
+import { authOptions } from "@/lib/auth.config";
export async function GET(request: Request) {
try {
@@ -53,3 +55,60 @@ export async function GET(request: Request) {
);
}
}
+
+export async function POST(request: Request) {
+ try {
+ const { forUser } = await request.json();
+
+ const session = await getServerSession(authOptions);
+
+ const where: Prisma.ProjectWhereInput = {};
+
+ if (forUser && session?.user?.id) {
+ where.userId = session.user.id;
+ }
+
+ // if (category) {
+ // where.category = category;
+ // }
+
+ // if (status) {
+ // where.ideaValidation = status as ValidationStatus;
+ // }
+
+ const projects = await prisma.project.findMany({
+ where,
+ include: {
+ user: {
+ select: {
+ id: true,
+ name: true,
+ image: true,
+ },
+ },
+ votes: {
+ select: {
+ id: true,
+ userId: true,
+ },
+ },
+ _count: {
+ select: {
+ votes: true,
+ },
+ },
+ },
+ orderBy: {
+ createdAt: "desc",
+ },
+ });
+
+ return NextResponse.json(projects);
+ } catch (error) {
+ console.error("Error fetching projects:", error);
+ return NextResponse.json(
+ { error: "Internal Server Error" },
+ { status: 500 },
+ );
+ }
+}
diff --git a/app/api/projects/upload-metadata/route.ts b/app/api/projects/upload-metadata/route.ts
new file mode 100644
index 00000000..4d3b976c
--- /dev/null
+++ b/app/api/projects/upload-metadata/route.ts
@@ -0,0 +1,83 @@
+import { NextResponse } from "next/server";
+import { v2 as cloudinary } from "cloudinary";
+
+cloudinary.config({
+ cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
+ api_key: process.env.CLOUDINARY_API_KEY,
+ api_secret: process.env.CLOUDINARY_API_SECRET,
+});
+
+async function uploadImage(file: File): Promise {
+ const buffer = await file.arrayBuffer();
+ const base64 = Buffer.from(buffer).toString("base64");
+ const dataURI = `data:${file.type};base64,${base64}`;
+
+ return new Promise((resolve, reject) => {
+ cloudinary.uploader.upload(
+ dataURI,
+ { resource_type: "auto", folder: "project_images" },
+ (error, result) => {
+ if (error) reject(null);
+ else resolve(result?.secure_url || null);
+ },
+ );
+ });
+}
+
+async function uploadMetadata(
+ metadata: object,
+): Promise<{ secure_url: string }> {
+ const jsonStr = JSON.stringify(metadata);
+ const buffer = Buffer.from(jsonStr);
+ const base64 = buffer.toString("base64");
+ const dataURI = `data:application/json;base64,${base64}`;
+
+ return new Promise<{ secure_url: string }>((resolve, reject) => {
+ cloudinary.uploader.upload(
+ dataURI,
+ { resource_type: "raw", folder: "project_metadata" },
+ (error, result) => {
+ if (error) reject(error);
+ else resolve(result as { secure_url: string });
+ },
+ );
+ });
+}
+
+export async function POST(request: Request) {
+ try {
+ const formData = await request.formData();
+ const title = formData.get("title") as string;
+ const description = formData.get("description") as string;
+ const category = formData.get("category") as string;
+ const bannerImage = formData.get("bannerImage") as File | null;
+ const profileImage = formData.get("profileImage") as File | null;
+
+ if (!title || !description || !category) {
+ return NextResponse.json(
+ { error: "Missing required fields" },
+ { status: 400 },
+ );
+ }
+
+ let bannerUrl = null;
+ let profileUrl = null;
+ if (bannerImage) bannerUrl = await uploadImage(bannerImage);
+ if (profileImage) profileUrl = await uploadImage(profileImage);
+
+ const metadata = { title, description, category, bannerUrl, profileUrl };
+
+ const metadataUpload = await uploadMetadata(metadata);
+
+ return NextResponse.json(
+ { metadataUri: metadataUpload.secure_url },
+ { status: 201 },
+ );
+ } catch (error) {
+ console.error("Metadata upload error:", error);
+ return NextResponse.json(
+ { error: "Internal Server Error" },
+ { status: 500 },
+ );
+ }
+}
diff --git a/components/connect-wallet.tsx b/components/connect-wallet.tsx
index 3aa983e4..9475fcc2 100644
--- a/components/connect-wallet.tsx
+++ b/components/connect-wallet.tsx
@@ -1,23 +1,26 @@
"use client";
-
import { useState, useEffect } from "react";
import { Button } from "@/components/ui/button";
import { Loader2, LogOut, Copy, Check } from "lucide-react";
import { toast } from "sonner";
-import { connect, disconnect, getPublicKey } from "@/hooks/useStellarWallet";
+import { useWalletStore } from "@/store/useWalletStore";
+import { formatAddress } from "@/lib/utils";
const ConnectWalletButton = ({ className = "" }) => {
+ const {
+ publicKey,
+ connecting,
+ connect: connectWallet,
+ disconnect: disconnectWallet,
+ } = useWalletStore();
+
const [isChecking, setIsChecking] = useState(true);
- const [isConnecting, setIsConnecting] = useState(false);
- const [walletAddress, setWalletAddress] = useState(null);
const [isCopied, setIsCopied] = useState(false);
useEffect(() => {
const checkConnection = async () => {
try {
- const address = await getPublicKey();
- if (address) {
- setWalletAddress(address);
+ if (publicKey) {
toast.success("Wallet Reconnected", {
description: "Welcome back!",
});
@@ -30,49 +33,39 @@ const ConnectWalletButton = ({ className = "" }) => {
};
checkConnection();
- }, []);
+ }, [publicKey]);
- const connectWallet = async () => {
+ const handleConnectWallet = async () => {
try {
- setIsConnecting(true);
- await connect(async () => {
- const address = await getPublicKey();
- if (!address) throw new Error("Failed to retrieve wallet address");
-
- setWalletAddress(address);
+ await connectWallet();
+ if (publicKey) {
toast.success("Wallet Connected", {
description: "Successfully connected to wallet",
});
- });
+ }
} catch (error) {
console.log(error);
toast.error("Connection Failed", {
description: "Failed to connect to the wallet.",
});
- } finally {
- setIsConnecting(false);
}
};
- const disconnectWallet = async () => {
- await disconnect();
- setWalletAddress(null);
+ const handleDisconnectWallet = async () => {
+ await disconnectWallet();
toast.info("Wallet Disconnected", {
description: "You have been disconnected.",
});
};
const copyToClipboard = async () => {
- if (!walletAddress) return;
-
+ if (!publicKey) return;
try {
- await navigator.clipboard.writeText(walletAddress);
+ await navigator.clipboard.writeText(publicKey);
setIsCopied(true);
toast.success("Address Copied", {
description: "Wallet address copied to clipboard",
});
-
- // Reset the copied state after 2 seconds
setTimeout(() => {
setIsCopied(false);
}, 2000);
@@ -83,27 +76,23 @@ const ConnectWalletButton = ({ className = "" }) => {
}
};
- const formatAddress = (address: string) => {
- return `${address.slice(0, 6)}...${address.slice(-4)}`;
- };
-
return (
{isChecking ? (
- ) : isConnecting ? (
+ ) : connecting ? (
- ) : walletAddress ? (
+ ) : publicKey ? (
) : (
-