feat: fetching signatures from backend endpoint
This commit is contained in:
@ -1,4 +1,5 @@
|
||||
import { ENV } from "./env";
|
||||
import { getPetitions } from "./routes/get-petitions";
|
||||
import { signPetition } from "./routes/sign-petition";
|
||||
|
||||
const CORS_HEADERS = {
|
||||
@ -29,7 +30,8 @@ const server = Bun.serve({
|
||||
port: ENV.PORT,
|
||||
routes: {
|
||||
"/health": new Response("alive!"),
|
||||
"/sign-petition": {
|
||||
"/sign": {
|
||||
GET: withCors(getPetitions),
|
||||
POST: withCors(signPetition),
|
||||
OPTIONS: allowCors,
|
||||
},
|
||||
|
||||
@ -1,8 +1,19 @@
|
||||
import { db } from "./database";
|
||||
import { signaturesTable } from "./schema";
|
||||
|
||||
export const insertSignature = async (signature: typeof signaturesTable.$inferInsert): Promise<typeof signaturesTable.$inferSelect | undefined> => {
|
||||
const [insertedSignature] = await db.insert(signaturesTable).values(signature).returning();
|
||||
export const insertSignature = async (
|
||||
signature: typeof signaturesTable.$inferInsert,
|
||||
): Promise<typeof signaturesTable.$inferSelect | undefined> => {
|
||||
const [insertedSignature] = await db
|
||||
.insert(signaturesTable)
|
||||
.values(signature)
|
||||
.returning();
|
||||
|
||||
return insertedSignature;
|
||||
}
|
||||
return insertedSignature;
|
||||
};
|
||||
|
||||
export const getSignatures = async (): Promise<
|
||||
Array<typeof signaturesTable.$inferSelect>
|
||||
> => {
|
||||
return db.select().from(signaturesTable);
|
||||
};
|
||||
|
||||
10
packages/backend/src/routes/get-petitions.ts
Normal file
10
packages/backend/src/routes/get-petitions.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { signedPetitionArraySchema } from "types";
|
||||
import { getSignatures } from "../models";
|
||||
|
||||
export const getPetitions = async (_: Request): Promise<Response> => {
|
||||
const signatures = await getSignatures();
|
||||
|
||||
const parsedSignatures = signedPetitionArraySchema.parse(signatures);
|
||||
|
||||
return Response.json(parsedSignatures, { status: 200 });
|
||||
};
|
||||
@ -34,12 +34,22 @@ export const PetitionForm = ({ compact = false }: PetitionFormProps) => {
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (isSubmitting) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSubmitting(true);
|
||||
|
||||
await onSignPetition({
|
||||
email,
|
||||
comment,
|
||||
name,
|
||||
});
|
||||
|
||||
setName(undefined);
|
||||
setEmail(undefined);
|
||||
setComment(undefined);
|
||||
|
||||
setIsSubmitting(false);
|
||||
};
|
||||
|
||||
|
||||
@ -3,16 +3,33 @@ import { signedPetitionSchema, signPetitionSchema } from "types";
|
||||
|
||||
const backendUrl = import.meta.env.VITE_BACKEND_URL;
|
||||
|
||||
export const signedPetitionWithParsedDate = signedPetitionSchema.extend({
|
||||
createdAt: z.string().transform((date: string) => new Date(date)),
|
||||
});
|
||||
|
||||
const signedPetitionSignatures = z.array(signedPetitionWithParsedDate);
|
||||
|
||||
export const getSignatures = async (): Promise<
|
||||
z.infer<typeof signedPetitionSignatures>
|
||||
> => {
|
||||
const res = await fetch(`${backendUrl}/sign`);
|
||||
|
||||
const body = await res.json();
|
||||
const validatedBody = signedPetitionSignatures.parse(body);
|
||||
|
||||
return validatedBody;
|
||||
};
|
||||
|
||||
export const signPetition = async (
|
||||
signature: z.infer<typeof signPetitionSchema>,
|
||||
): Promise<z.infer<typeof signedPetitionSchema>> => {
|
||||
const res = await fetch(`${backendUrl}/sign-petition`, {
|
||||
): Promise<z.infer<typeof signedPetitionWithParsedDate>> => {
|
||||
const res = await fetch(`${backendUrl}/sign`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(signature),
|
||||
});
|
||||
|
||||
const body = await res.json();
|
||||
const validatedBody = signedPetitionSchema.parse(body);
|
||||
const validatedBody = signedPetitionWithParsedDate.parse(body);
|
||||
|
||||
return validatedBody;
|
||||
};
|
||||
|
||||
@ -11,7 +11,7 @@ import emptyBuildingParking from "@/assets/empty-building-parking.jpg";
|
||||
import asahiBuilding from "@/assets/asahi-building.jpg";
|
||||
import doubletreeHilton from "@/assets/doubletree-hilton.jpg";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useState } from "react";
|
||||
import { usePetitions } from "@/state";
|
||||
|
||||
const STATIC_TESTEMONIES = [
|
||||
{
|
||||
@ -36,7 +36,8 @@ const STATIC_TESTEMONIES = [
|
||||
|
||||
const Index = () => {
|
||||
const navigate = useNavigate();
|
||||
const [signatureCount, setSignatureCount] = useState(0);
|
||||
|
||||
const { signatures } = usePetitions();
|
||||
|
||||
const scrollToPetition = () => {
|
||||
document.getElementById("petition")?.scrollIntoView({ behavior: "smooth" });
|
||||
@ -67,12 +68,13 @@ const Index = () => {
|
||||
Sign the Petition
|
||||
</h2>
|
||||
<p className="text-lg text-muted-foreground">
|
||||
{signatureCount > 0 && (
|
||||
{signatures.length > 0 && (
|
||||
<span className="font-semibold text-accent">
|
||||
{signatureCount} people
|
||||
{signatures.length} people
|
||||
</span>
|
||||
)}
|
||||
{signatureCount > 0 ? " have" : "Be the first to"} signed so far
|
||||
{signatures.length > 0 ? " have" : "Be the first to"} signed so
|
||||
far
|
||||
</p>
|
||||
</div>
|
||||
<PetitionForm compact />
|
||||
@ -438,7 +440,8 @@ const Index = () => {
|
||||
className="gap-2 bg-primary text-primary-foreground hover:bg-primary/90"
|
||||
>
|
||||
<MessageSquare className="w-5 h-5" />
|
||||
Read All {signatureCount > 0 && `${signatureCount} `}Testimonies
|
||||
Read All {signatures.length > 0 && `${signatures.length} `}
|
||||
Testimonies
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,20 +1,16 @@
|
||||
import { useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { TestimonialCard } from "@/components/TestimonialCard";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { formatDistanceToNow } from "date-fns";
|
||||
import { ArrowLeft, Users } from "lucide-react";
|
||||
|
||||
interface Signature {
|
||||
id: string;
|
||||
name: string;
|
||||
comment: string | null;
|
||||
created_at: string;
|
||||
}
|
||||
import { usePetitions } from "@/state";
|
||||
|
||||
const Testimonies = () => {
|
||||
const [signatures, setSignatures] = useState<Signature[]>([]);
|
||||
const [totalCount, setTotalCount] = useState(0);
|
||||
const { signatures } = usePetitions();
|
||||
console.log(signatures);
|
||||
|
||||
const totalCount = signatures.length;
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
@ -55,7 +51,7 @@ const Testimonies = () => {
|
||||
key={signature.id}
|
||||
name={signature.name}
|
||||
comment={signature.comment || ""}
|
||||
date={formatDistanceToNow(new Date(signature.created_at), {
|
||||
date={formatDistanceToNow(new Date(signature.createdAt), {
|
||||
addSuffix: true,
|
||||
})}
|
||||
/>
|
||||
|
||||
@ -1,17 +1,22 @@
|
||||
import { signPetition } from "@/network";
|
||||
import {
|
||||
getSignatures,
|
||||
signedPetitionWithParsedDate,
|
||||
signPetition,
|
||||
} from "@/network";
|
||||
import {
|
||||
createContext,
|
||||
ReactNode,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
import { toast } from "sonner";
|
||||
import { signedPetitionSchema, signPetitionSchema } from "types";
|
||||
import { signPetitionSchema } from "types";
|
||||
import { z } from "zod";
|
||||
|
||||
// submitted is used to determine if the signature was inserted correctly
|
||||
type SignatureWithState = z.infer<typeof signedPetitionSchema> & {
|
||||
type SignatureWithState = z.infer<typeof signedPetitionWithParsedDate> & {
|
||||
submitted: boolean;
|
||||
};
|
||||
|
||||
@ -36,6 +41,12 @@ export const PetitionStateProvider = ({
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
getSignatures().then((signatures) => {
|
||||
setSignatures(signatures.map((s) => ({ ...s, submitted: true })));
|
||||
});
|
||||
}, []);
|
||||
|
||||
const onSignPetition = useCallback<PetitionStateType["onSignPetition"]>(
|
||||
async (signature) => {
|
||||
const eagerPetitionId = Date.now().toString();
|
||||
@ -43,7 +54,7 @@ export const PetitionStateProvider = ({
|
||||
setSignatures((petitions) => [
|
||||
{
|
||||
id: eagerPetitionId,
|
||||
createdAt: new Date().toISOString(),
|
||||
createdAt: new Date(),
|
||||
submitted: false,
|
||||
...signature,
|
||||
},
|
||||
@ -68,10 +79,6 @@ export const PetitionStateProvider = ({
|
||||
|
||||
return [...petitions];
|
||||
});
|
||||
|
||||
toast.error(
|
||||
"Sorry, had a problem inserting your signature. Please try again.",
|
||||
);
|
||||
} catch (err) {
|
||||
console.warn(err);
|
||||
|
||||
|
||||
@ -10,3 +10,5 @@ export const signedPetitionSchema = signPetitionSchema.extend({
|
||||
id: z.uuid(),
|
||||
createdAt: z.date().transform((date: Date) => date.toISOString()),
|
||||
});
|
||||
|
||||
export const signedPetitionArraySchema = z.array(signedPetitionSchema);
|
||||
|
||||
Reference in New Issue
Block a user