From 4371b26423e709b079b844914acb17d51c1f92bf Mon Sep 17 00:00:00 2001 From: John Costa Date: Mon, 10 Nov 2025 21:33:30 +0000 Subject: [PATCH] feat: actually running the server --- packages/backend/src/env/index.ts | 20 +- packages/backend/src/index.ts | 39 + packages/backend/src/models/schema.ts | 11 +- packages/backend/src/routes/sign-petition.ts | 39 +- packages/frontend/src/App.tsx | 38 +- .../frontend/src/components/PetitionForm.tsx | 232 +++-- .../src/integrations/supabase/client.ts | 17 - .../src/integrations/supabase/types.ts | 198 ----- packages/frontend/src/network/index.ts | 23 +- packages/frontend/src/pages/Index.tsx | 838 ++++++++++-------- packages/frontend/src/pages/Testimonies.tsx | 183 ++-- packages/frontend/src/state/index.ts | 0 packages/frontend/src/state/index.tsx | 97 ++ packages/frontend/vite.config.ts | 22 +- packages/types/index.ts | 13 +- 15 files changed, 850 insertions(+), 920 deletions(-) create mode 100644 packages/backend/src/index.ts delete mode 100644 packages/frontend/src/integrations/supabase/client.ts delete mode 100644 packages/frontend/src/integrations/supabase/types.ts delete mode 100644 packages/frontend/src/state/index.ts create mode 100644 packages/frontend/src/state/index.tsx diff --git a/packages/backend/src/env/index.ts b/packages/backend/src/env/index.ts index 375dca8..5559c93 100644 --- a/packages/backend/src/env/index.ts +++ b/packages/backend/src/env/index.ts @@ -1,16 +1,16 @@ -import { z } from 'zod'; -import dotenv from 'dotenv'; +import { z } from "zod"; +import dotenv from "dotenv"; -dotenv.config(); +dotenv.config({ quiet: true }); const envSchema = z.object({ - PORT: z - .string() - .refine( - (port) => parseInt(port) > 0 && parseInt(port) < 65536, - "Invalid port number" - ), - DATABASE_URL: z.string().min(10) + PORT: z + .string() + .refine( + (port) => parseInt(port) > 0 && parseInt(port) < 65536, + "Invalid port number", + ), + DATABASE_URL: z.string().min(10), }); type Env = z.infer; diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts new file mode 100644 index 0000000..8de4c51 --- /dev/null +++ b/packages/backend/src/index.ts @@ -0,0 +1,39 @@ +import { ENV } from "./env"; +import { signPetition } from "./routes/sign-petition"; + +const CORS_HEADERS = { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS", + "Access-Control-Allow-Headers": "Content-Type, Authorization", +}; + +const allowCors = async (_: Request): Promise => { + return new Response(null, { status: 200, headers: CORS_HEADERS }); +}; + +type Handler = (req: Request) => Promise; + +const withCors = (fn: Handler): Handler => { + return async (req) => { + const res = await fn(req); + + for (const [header, value] of Object.entries(CORS_HEADERS)) { + res.headers.set(header, value); + } + + return res; + }; +}; + +const server = Bun.serve({ + port: ENV.PORT, + routes: { + "/health": new Response("alive!"), + "/sign-petition": { + POST: withCors(signPetition), + OPTIONS: allowCors, + }, + }, +}); + +console.log(`server running on ${server.url}`); diff --git a/packages/backend/src/models/schema.ts b/packages/backend/src/models/schema.ts index 1a5d581..fa0c3d3 100644 --- a/packages/backend/src/models/schema.ts +++ b/packages/backend/src/models/schema.ts @@ -1,8 +1,9 @@ -import { pgTable, text, uuid } from "drizzle-orm/pg-core"; +import { pgTable, text, uuid, timestamp } from "drizzle-orm/pg-core"; export const signaturesTable = pgTable("signatures", { - id: uuid().primaryKey().defaultRandom(), - email: text().notNull(), - name: text(), - comment: text(), + id: uuid().primaryKey().defaultRandom(), + email: text().notNull(), + name: text(), + comment: text(), + createdAt: timestamp().defaultNow().notNull(), }); diff --git a/packages/backend/src/routes/sign-petition.ts b/packages/backend/src/routes/sign-petition.ts index 9562157..bc760c0 100644 --- a/packages/backend/src/routes/sign-petition.ts +++ b/packages/backend/src/routes/sign-petition.ts @@ -1,26 +1,29 @@ -import type z from "zod"; import { insertSignature } from "../models"; -import { signedPetitionSchema, signPetitionSchema } from 'types' +import { signedPetitionSchema, signPetitionSchema } from "types"; export const signPetition = async (req: Request): Promise => { - const body = await req.json() + const body = await req.json(); - const validatedBody = signPetitionSchema.safeParse(body); - if (!validatedBody.success) { - return Response.json({ error: validatedBody.error }, { status: 400 }); - } + const validatedBody = signPetitionSchema.safeParse(body); + if (!validatedBody.success) { + console.log(validatedBody.error); + return Response.json({ error: validatedBody.error }, { status: 400 }); + } - const _insertedSignature = await insertSignature({ - email: validatedBody.data.email, - name: validatedBody.data.name, - comment: validatedBody.data.comment, - }) + const insertedSignature = await insertSignature({ + email: validatedBody.data.email, + name: validatedBody.data.name, + comment: validatedBody.data.comment, + }); - if (!_insertedSignature) { - return Response.json({ error: "inserting signature in database" }, { status: 500 }); - } + if (!insertedSignature) { + return Response.json( + { error: "inserting signature in database" }, + { status: 500 }, + ); + } - const insertedSignature = _insertedSignature satisfies z.infer + const parsedSignedSignature = signedPetitionSchema.parse(insertedSignature); - return Response.json(insertedSignature, { status: 200 }); -} + return Response.json(parsedSignedSignature, { status: 200 }); +}; diff --git a/packages/frontend/src/App.tsx b/packages/frontend/src/App.tsx index f0e24b3..a6709c2 100644 --- a/packages/frontend/src/App.tsx +++ b/packages/frontend/src/App.tsx @@ -1,7 +1,6 @@ import { Toaster } from "@/components/ui/toaster"; import { Toaster as Sonner } from "@/components/ui/sonner"; import { TooltipProvider } from "@/components/ui/tooltip"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { BrowserRouter, Routes, Route } from "react-router-dom"; import { Navbar } from "@/components/Navbar"; import Index from "./pages/Index"; @@ -9,27 +8,26 @@ import Testimonies from "./pages/Testimonies"; import Contact from "./pages/Contact"; import Briefing from "./pages/Briefing"; import NotFound from "./pages/NotFound"; - -const queryClient = new QueryClient(); +import { PetitionStateProvider } from "./state"; const App = () => ( - - - - - - - - } /> - } /> - } /> - } /> - {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} - } /> - - - - + + + + + + + + } /> + } /> + } /> + } /> + {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} + } /> + + + + ); export default App; diff --git a/packages/frontend/src/components/PetitionForm.tsx b/packages/frontend/src/components/PetitionForm.tsx index d8c5a00..6e9c3c9 100644 --- a/packages/frontend/src/components/PetitionForm.tsx +++ b/packages/frontend/src/components/PetitionForm.tsx @@ -4,137 +4,111 @@ import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Checkbox } from "@/components/ui/checkbox"; import { Label } from "@/components/ui/label"; -import { toast } from "sonner"; -import { supabase } from "@/integrations/supabase/client"; -import { z } from "zod"; - -const petitionSchema = z.object({ - name: z.string().trim().min(1, "Name is required").max(100, "Name must be less than 100 characters"), - email: z.string().trim().email("Invalid email address").max(255, "Email must be less than 255 characters"), - comment: z.string().trim().max(1000, "Comment must be less than 1000 characters").optional(), -}); - -const anonymousSchema = z.object({ - email: z.string().trim().email("Invalid email address").max(255, "Email must be less than 255 characters"), - comment: z.string().trim().max(1000, "Comment must be less than 1000 characters").optional(), -}); +import { usePetitions } from "@/state"; interface PetitionFormProps { - compact?: boolean; + compact?: boolean; } -export const PetitionForm = ({ compact = false }: PetitionFormProps) => { - const [formData, setFormData] = useState({ - name: "", - email: "", - comment: "", - }); - const [isSubmitting, setIsSubmitting] = useState(false); - const [isAnonymous, setIsAnonymous] = useState(false); - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - - // Validate input based on mode - try { - if (isAnonymous) { - anonymousSchema.parse({ email: formData.email, comment: formData.comment }); - } else { - petitionSchema.parse(formData); - } - } catch (error) { - if (error instanceof z.ZodError) { - toast.error(error.errors[0].message); - return; - } - } - - setIsSubmitting(true); - - try { - const { error } = await supabase - .from('petition_signatures') - .insert([{ - name: isAnonymous ? 'Anonymous' : formData.name.trim(), - email: formData.email.trim(), - comment: formData.comment.trim() || null, - }]); - - if (error) throw error; - - toast.success("Thank you for signing! Your voice matters."); - setFormData({ name: "", email: "", comment: "" }); - setIsAnonymous(false); - } catch (error) { - console.error('Error signing petition:', error); - toast.error("Failed to submit signature. Please try again."); - } finally { - setIsSubmitting(false); - } - }; - - return ( -
-
- setIsAnonymous(checked as boolean)} - /> - -
- - {!isAnonymous && ( -
- setFormData({ ...formData, name: e.target.value })} - required - className="bg-background border-border" - /> -
- )} - -
- setFormData({ ...formData, email: e.target.value })} - required - className="bg-background border-border" - /> -
- -
-