From bed44a002853c5502e225fbad6f9d561ac57d3c4 Mon Sep 17 00:00:00 2001 From: Oliver Eyton-Williams Date: Fri, 26 Jan 2024 09:52:01 +0100 Subject: [PATCH 1/3] refactor: move components into own modules --- src/components/event-card.tsx | 22 +++++++++ src/components/location-form.tsx | 53 ++++++++++++++++++++ src/pages/index.tsx | 83 ++------------------------------ src/types.ts | 8 +++ 4 files changed, 87 insertions(+), 79 deletions(-) create mode 100644 src/components/event-card.tsx create mode 100644 src/components/location-form.tsx create mode 100644 src/types.ts diff --git a/src/components/event-card.tsx b/src/components/event-card.tsx new file mode 100644 index 0000000..59ed2d0 --- /dev/null +++ b/src/components/event-card.tsx @@ -0,0 +1,22 @@ +import { format } from "date-fns"; +import type { EventWithDistance } from "@/types"; + +export function EventCard({ event }: { event: EventWithDistance }) { + return ( +
+

+ Title: {event.name} +

+ + {/* TODO: replace with a spinner (or similar) to gracefully handle + the delay between receiving the HTML and the browser rendering + the date */} +

+ Date: {format(new Date(event.date), "E LLLL d, yyyy @ HH:mm")} +

+ {event.distance !== null && ( +

Distance to event: {event.distance.toFixed(2)} km

+ )} +
+ ); +} diff --git a/src/components/location-form.tsx b/src/components/location-form.tsx new file mode 100644 index 0000000..01acc79 --- /dev/null +++ b/src/components/location-form.tsx @@ -0,0 +1,53 @@ +import { typeboxResolver } from "@hookform/resolvers/typebox"; +import { useForm } from "react-hook-form"; +import { Button, Stack } from "@mui/material"; +import { type Point, type Feature } from "@turf/helpers"; + +import { type Location, locationSchema } from "@/validation/schema"; +import FormField from "@/components/form-field"; + +export function LocationForm({ + userPosition, + onSubmit, + }: { + userPosition: Feature; + onSubmit: (data: Location) => void; + }) { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + defaultValues: { + latitude: userPosition?.geometry.coordinates[1], + longitude: userPosition?.geometry.coordinates[0], + }, + resolver: typeboxResolver(locationSchema), + }); + + // TODO: DRY this and add-event out, they're almost identical. + return ( +
+ + + + + +
+ ); + } + \ No newline at end of file diff --git a/src/pages/index.tsx b/src/pages/index.tsx index c1adc3b..5d545c6 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,18 +1,15 @@ import Head from "next/head"; import { Inter } from "next/font/google"; import { GetStaticProps } from "next"; -import { format } from "date-fns"; import { useEffect, useState } from "react"; import distance from "@turf/distance"; import { point, type Point, type Feature } from "@turf/helpers"; -import { Button, Stack, Typography } from "@mui/material"; -import { typeboxResolver } from "@hookform/resolvers/typebox"; -import { Event } from "@prisma/client"; -import { useForm } from "react-hook-form"; +import { Typography } from "@mui/material"; import { prisma } from "@/db"; -import { type Location, locationSchema } from "@/validation/schema"; -import FormField from "@/components/form-field"; +import { EventCard } from "@/components/event-card"; +import { LocationForm } from "@/components/location-form"; +import type { EventInfo } from "@/types"; // Given that people can (currently) be assumed to be meeting on the surface of // the Earth, we can use its circumference to calculate a safe upper bound for @@ -21,13 +18,6 @@ const EARTH_CIRCUMFERENCE = "40075.017"; const inter = Inter({ subsets: ["latin"] }); -type EventInfo = { date: string } & Omit< - Event, - "createdAt" | "updatedAt" | "creatorId" | "date" ->; - -type EventWithDistance = EventInfo & { distance: number | null }; - type EventProps = { events: EventInfo[]; }; @@ -85,71 +75,6 @@ function eventInRadius( return distanceToEvent ? distanceToEvent <= radius : true; } -function EventCard({ event }: { event: EventWithDistance }) { - return ( -
-

- Title: {event.name} -

- - {/* TODO: replace with a spinner (or similar) to gracefully handle - the delay between receiving the HTML and the browser rendering - the date */} -

- Date: {format(new Date(event.date), "E LLLL d, yyyy @ HH:mm")} -

- {event.distance !== null && ( -

Distance to event: {event.distance.toFixed(2)} km

- )} -
- ); -} - -function LocationForm({ - userPosition, - onSubmit, -}: { - userPosition: Feature; - onSubmit: (data: Location) => void; -}) { - const { - register, - handleSubmit, - formState: { errors }, - } = useForm({ - defaultValues: { - latitude: userPosition?.geometry.coordinates[1], - longitude: userPosition?.geometry.coordinates[0], - }, - resolver: typeboxResolver(locationSchema), - }); - - // TODO: DRY this and add-event out, they're almost identical. - return ( -
- - - - - -
- ); -} - export default function Home({ events }: EventProps) { const [userPosition, setUserPosition] = useState | null>(null); const [geoLocationEnabled, setGeoLocationEnabled] = useState(true); diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..88173e2 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,8 @@ +import type { Event } from "@prisma/client"; + +export type EventInfo = { date: string } & Omit< + Event, + "createdAt" | "updatedAt" | "creatorId" | "date" +>; + +export type EventWithDistance = EventInfo & { distance: number | null }; From 990583027dc9bb706cc8c2a5caf0c279bcee576d Mon Sep 17 00:00:00 2001 From: Oliver Eyton-Williams Date: Thu, 8 Feb 2024 16:25:03 +0100 Subject: [PATCH 2/3] chore: add DB migration for event.organizedBy --- .../migration.sql | 8 +++++++ prisma/schema.prisma | 21 ++++++++++--------- 2 files changed, 19 insertions(+), 10 deletions(-) create mode 100644 prisma/migrations/20240208152336_add_event_organizer/migration.sql diff --git a/prisma/migrations/20240208152336_add_event_organizer/migration.sql b/prisma/migrations/20240208152336_add_event_organizer/migration.sql new file mode 100644 index 0000000..498a9cb --- /dev/null +++ b/prisma/migrations/20240208152336_add_event_organizer/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - Added the required column `organizedBy` to the `Event` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "Event" ADD COLUMN "organizedBy" TEXT NOT NULL; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index c2112ae..6308fa3 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -54,14 +54,15 @@ model User { } model Event { - id String @id @default(cuid()) - name String - date DateTime - link String - latitude Float - longitude Float - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - creator User @relation(fields: [creatorId], references: [id]) - creatorId String + id String @id @default(cuid()) + name String + date DateTime + link String + latitude Float + longitude Float + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + creator User @relation(fields: [creatorId], references: [id]) + organizedBy String + creatorId String } From e332075d34b14c962c5e36be6bb2118c67fd3f6d Mon Sep 17 00:00:00 2001 From: Oliver Eyton-Williams Date: Thu, 8 Feb 2024 17:17:39 +0100 Subject: [PATCH 3/3] feat: add organizedBy field to events --- cypress.config.ts | 1 + cypress/e2e/manage-events/add-event.cy.ts | 2 ++ prisma/seed.ts | 2 +- src/components/event-card.tsx | 2 ++ src/pages/add-event.tsx | 8 ++++++++ src/pages/api/event.ts | 3 ++- src/pages/index.tsx | 1 + src/validation/schema.ts | 1 + 8 files changed, 18 insertions(+), 2 deletions(-) diff --git a/cypress.config.ts b/cypress.config.ts index affd86c..da35859 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -18,6 +18,7 @@ const resetEvents = async () => { const data = events25To50Km.map((event) => ({ ...event, creatorId: user.id, + organizedBy: session.user.name, })); return await prisma.event.createMany({ data, diff --git a/cypress/e2e/manage-events/add-event.cy.ts b/cypress/e2e/manage-events/add-event.cy.ts index 38d5dc4..72d7553 100644 --- a/cypress/e2e/manage-events/add-event.cy.ts +++ b/cypress/e2e/manage-events/add-event.cy.ts @@ -12,6 +12,7 @@ describe("Add Event", () => { cy.get("[data-cy='input-link']").type("https://test.event"); cy.get("[data-cy='input-latitude-add'").type("0"); cy.get("[data-cy='input-longitude-add'").type("0"); + cy.get("[data-cy='input-organizedBy'").type("Test Organizer"); cy.get("[data-cy='submit-add-event'").click(); cy.location("pathname").should("eq", "/"); @@ -29,5 +30,6 @@ describe("Add Event", () => { // We should only see the event we just created cy.get("[data-cy='event-card']").should("have.length", 1); cy.contains("Test Event"); + cy.contains("Test Organizer"); }); }); diff --git a/prisma/seed.ts b/prisma/seed.ts index 4a95fe9..60e7523 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -39,6 +39,7 @@ const createEvents = async () => { const data = events25To50Km.map((event) => ({ ...event, creatorId: user.id, + organizedBy: session.user.name, })); await prisma.event.createMany({ data, @@ -51,4 +52,3 @@ export const setupDb = async () => { }; setupDb(); - diff --git a/src/components/event-card.tsx b/src/components/event-card.tsx index 59ed2d0..9690d2e 100644 --- a/src/components/event-card.tsx +++ b/src/components/event-card.tsx @@ -7,6 +7,8 @@ export function EventCard({ event }: { event: EventWithDistance }) {

Title: {event.name}

+ +

Organized by: {event.organizedBy}

{/* TODO: replace with a spinner (or similar) to gracefully handle the delay between receiving the HTML and the browser rendering diff --git a/src/pages/add-event.tsx b/src/pages/add-event.tsx index 4cdcf4f..6bbda65 100644 --- a/src/pages/add-event.tsx +++ b/src/pages/add-event.tsx @@ -48,6 +48,14 @@ export default function AddEvent() { {...register("name", { required: true })} /> + + { email: user.email, }, }, + organizedBy: req.body.organizedBy, }, }); } catch (error) { return res.status(500).json({ message: "Could not create event" }); } - await res.revalidate("/") + await res.revalidate("/"); res.status(200).json({ message: "Event created" }); }; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 5d545c6..82ba4a0 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -33,6 +33,7 @@ export const getStaticProps: GetStaticProps = async () => { link: true, latitude: true, longitude: true, + organizedBy: true, }, }); serializeableEvents = events.map((event) => ({ diff --git a/src/validation/schema.ts b/src/validation/schema.ts index fc3226c..d6424c7 100644 --- a/src/validation/schema.ts +++ b/src/validation/schema.ts @@ -28,6 +28,7 @@ export const eventSchema = Type.Composite([ name: Type.String({ minLength: 1, maxLength: 100 }), link: Type.String({ format: "uri" }), date: Type.String({ format: "date-time" }), + organizedBy: Type.String({ minLength: 1, maxLength: 100}), }), locationSchema, ]);