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 ? (
) : (
-