feat: fetching signatures from backend endpoint

This commit is contained in:
2025-11-10 22:36:54 +00:00
parent 4371b26423
commit f8712015c0
9 changed files with 91 additions and 33 deletions

View File

@@ -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);
};

View File

@@ -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;
};

View File

@@ -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>

View File

@@ -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,
})}
/>

View File

@@ -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);