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