diff --git a/DevoteApp/app/api/proposals/route.ts b/DevoteApp/app/api/proposals/route.ts
index c168a16..33c4f4c 100644
--- a/DevoteApp/app/api/proposals/route.ts
+++ b/DevoteApp/app/api/proposals/route.ts
@@ -1,35 +1,85 @@
import { NextResponse } from "next/server";
import connectToDb from "../../../lib/mongodb/mongodb";
import Proposal from "../../../models/proposal";
+import fs from "fs";
+import path from "path";
export async function POST(req: Request) {
- try {
- const { title, description, file } = await req.json();
-
- if (!title || !description) {
+ try {
+ const formData = await req.formData();
+ const title = formData.get("title") as string;
+ const description = formData.get("description") as string;
+ const file = formData.get("file") as File | null;
+
+ if (!title || !description) {
+ return NextResponse.json(
+ { message: "Title and description are required" },
+ { status: 400 }
+ );
+ }
+
+ await connectToDb();
+
+ const newProposal = new Proposal({ title, description });
+ await newProposal.save();
+
+ // Handle file upload if a file is provided
+ let filePath = null;
+ if (file) {
+ // Check MIME type
+ const validTypes = [
+ "application/pdf",
+ "application/msword",
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
+ ];
+
+ if (!validTypes.includes(file.type)) {
return NextResponse.json(
- { message: "Title and description are required" },
+ { message: "Invalid file type" },
{ status: 400 }
);
}
-
- await connectToDb();
-
- const newProposal = new Proposal({ title, description, file });
+
+ // Check extension
+ const validExtensions = [".pdf", ".doc", ".docx"];
+ const fileExtension = path.extname(file.name).toLowerCase();
+ if (!validExtensions.includes(fileExtension)) {
+ return NextResponse.json(
+ { message: "Invalid file extension" },
+ { status: 400 }
+ );
+ }
+
+ // Upload file
+ const uploadDir = path.join(process.cwd(), "public/uploads/proposals");
+ const proposalDir = path.join(uploadDir, newProposal.id.toString());
+
+ if (!fs.existsSync(proposalDir)) {
+ fs.mkdirSync(proposalDir, { recursive: true });
+ }
+
+ filePath = path.join(proposalDir, file.name);
+ const arrayBuffer = await file.arrayBuffer();
+ fs.writeFileSync(filePath, Buffer.from(arrayBuffer));
+
+ // Update proposal with file path (in production, we should use a proper storage service like cloudinary or AWS S3)
+
+ newProposal.file = `/uploads/proposals/${newProposal.id}/${file.name}`;
await newProposal.save();
-
- return NextResponse.json(
- { message: "Proposal created successfully", proposal: newProposal },
- { status: 201 }
- );
- } catch (error) {
- console.error(error);
- return NextResponse.json(
- { message: "Internal server error" },
- { status: 500 }
- );
}
+
+ return NextResponse.json(
+ { message: "Proposal created successfully", proposal: newProposal },
+ { status: 201 }
+ );
+ } catch (error) {
+ console.error(error);
+ return NextResponse.json(
+ { message: "Internal server error" },
+ { status: 500 }
+ );
}
+}
export async function PUT(req: Request) {
try {
@@ -43,8 +93,11 @@ export async function PUT(req: Request) {
await connectToDb();
- const updateFields: { title?: string; description?: string; file?: string } =
- {};
+ const updateFields: {
+ title?: string;
+ description?: string;
+ file?: string;
+ } = {};
if (title) updateFields.title = title;
if (description) updateFields.description = description;
if (file) updateFields.file = file;
diff --git a/DevoteApp/app/voting/[id]/page.tsx b/DevoteApp/app/voting/[id]/page.tsx
index 842e10b..e66a502 100644
--- a/DevoteApp/app/voting/[id]/page.tsx
+++ b/DevoteApp/app/voting/[id]/page.tsx
@@ -95,6 +95,34 @@ export default function VotingStationPage() {
+ {proposal?.file && (
+
+
+
+ Proposal Context
+
+
+ Download or view the supporting document for this proposal
+
+
+
+
+ View Proposal Context (
+ {proposal.file.endsWith(".pdf")
+ ? ".pdf"
+ : proposal.file.endsWith(".docx")
+ ? ".docx"
+ : ".doc"}
+ )
+
+
+
+ )}
diff --git a/DevoteApp/components/CreateProposalModal.tsx b/DevoteApp/components/CreateProposalModal.tsx
index 0f59450..e9fc9f7 100644
--- a/DevoteApp/components/CreateProposalModal.tsx
+++ b/DevoteApp/components/CreateProposalModal.tsx
@@ -1,71 +1,121 @@
-"use client"
+"use client";
-import type React from "react"
+import type React from "react";
-import { useState } from "react"
-import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"
-import { Button } from "@/components/ui/button"
-import { Input } from "@/components/ui/input"
-import { Textarea } from "@/components/ui/textarea"
-import { Label } from "@/components/ui/label"
-import { useToast } from "@/hooks/use-toast"
+import { useState } from "react";
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Textarea } from "@/components/ui/textarea";
+import { Label } from "@/components/ui/label";
+import { useToast } from "@/hooks/use-toast";
interface CreateProposalModalProps {
- isOpen: boolean
- onClose: () => void
+ isOpen: boolean;
+ onClose: () => void;
}
-export default function CreateProposalModal({ isOpen, onClose }: CreateProposalModalProps) {
- const [proposalId, setProposalId] = useState("")
- const [proposalTitle, setProposalTitle] = useState("")
- const [proposalDescription, setProposalDescription] = useState("")
- const [votingOptions, setVotingOptions] = useState([""])
- const [pdfDocument, setPdfDocument] = useState(null)
+export default function CreateProposalModal({
+ isOpen,
+ onClose,
+}: CreateProposalModalProps) {
+ const [proposalId, setProposalId] = useState("");
+ const [proposalTitle, setProposalTitle] = useState("");
+ const [proposalDescription, setProposalDescription] = useState("");
+ const [votingOptions, setVotingOptions] = useState([""]);
+ const [document, setDocument] = useState(null);
+ const [isLoading, setIsLoading] = useState(false);
- const { toast } = useToast()
+ const { toast } = useToast();
const handleAddOption = () => {
- setVotingOptions([...votingOptions, ""])
- }
+ setVotingOptions([...votingOptions, ""]);
+ };
const handleRemoveOption = (index: number) => {
- const newOptions = votingOptions.filter((_, i) => i !== index)
- setVotingOptions(newOptions)
- }
+ const newOptions = votingOptions.filter((_, i) => i !== index);
+ setVotingOptions(newOptions);
+ };
const handleOptionChange = (index: number, value: string) => {
- const newOptions = [...votingOptions]
- newOptions[index] = value
- setVotingOptions(newOptions)
- }
+ const newOptions = [...votingOptions];
+ newOptions[index] = value;
+ setVotingOptions(newOptions);
+ };
- const handlePdfUpload = (e: React.ChangeEvent) => {
+ const handleDocUpload = (e: React.ChangeEvent) => {
if (e.target.files && e.target.files[0]) {
- setPdfDocument(e.target.files[0])
+ setDocument(e.target.files[0]);
}
- }
+ };
- const handleCreateProposal = () => {
- console.log("Creating proposal:", { proposalId, proposalTitle, proposalDescription, votingOptions, pdfDocument })
- setProposalId("")
- setProposalTitle("")
- setProposalDescription("")
- setVotingOptions([""])
- setPdfDocument(null)
- toast({
- title: "Success!",
- description: "Your proposal has been created successfully.",
- variant: "success",
- })
- onClose()
- }
+ const handleCreateProposal = async () => {
+ console.log("Creating proposal:", {
+ proposalId,
+ proposalTitle,
+ proposalDescription,
+ votingOptions,
+ document,
+ });
+ const formData = new FormData();
+ // formData.append("proposalId", proposalId);
+ formData.append("title", proposalTitle);
+ formData.append("description", proposalDescription);
+ // votingOptions.forEach((option, index) =>
+ // formData.append(`votingOption${index + 1}`, option)
+ // );
+ if (document) {
+ formData.append("file", document);
+ }
+
+ try {
+ setIsLoading(true);
+ const response = await fetch("/api/proposals", {
+ method: "POST",
+ body: formData,
+ });
+ if (!response.ok) {
+ throw new Error("Failed to create proposal");
+ }
+ const data = await response.json();
+ console.log("Proposal created:", data);
+
+ // If successful, reset form and close modal
+ toast({
+ title: "Success!",
+ description: "Your proposal has been created successfully.",
+ variant: "success",
+ });
+ setProposalId("");
+ setProposalTitle("");
+ setProposalDescription("");
+ setVotingOptions([""]);
+ setDocument(null);
+ onClose();
+ } catch (error) {
+ toast({
+ title: "Error",
+ description: (error as Error).message,
+ variant: "destructive",
+ });
+ } finally {
+ setIsLoading(false);
+ }
+ };
return (
- )
+ );
}
-
diff --git a/DevoteApp/interfaces/Proposal.tsx b/DevoteApp/interfaces/Proposal.tsx
index 825f3fb..86e9ec3 100644
--- a/DevoteApp/interfaces/Proposal.tsx
+++ b/DevoteApp/interfaces/Proposal.tsx
@@ -27,4 +27,5 @@ export interface ProposalPublic {
has_voted: number;
type_votes: ProposalVoteTypeStruct[];
voter: ProposalVoterStruct;
+ file?: string;
}