diff --git a/@types/nextjs-routes.d.ts b/@types/nextjs-routes.d.ts index 39b3213..9bf6fbe 100644 --- a/@types/nextjs-routes.d.ts +++ b/@types/nextjs-routes.d.ts @@ -12,10 +12,11 @@ declare module "nextjs-routes" { export type Route = | StaticRoute<"/"> - | StaticRoute<"/building"> | DynamicRoute<"/event/[slug]/preview", { "slug": string }> | DynamicRoute<"/event/[slug]/settings", { "slug": string }> - | StaticRoute<"/rooms">; + | DynamicRoute<"/rejestracja/[participationSlug]", { "participationSlug": string }> + | DynamicRoute<"/rejestracja/[participationSlug]/[blockId]/[reservationId]", { "participationSlug": string; "blockId": string; "reservationId": string }> + | DynamicRoute<"/rejestracja/[participationSlug]/[blockId]/formularz", { "participationSlug": string; "blockId": string }>; interface StaticRoute { pathname: Pathname; diff --git a/next.config.mjs b/next.config.mjs index 401cd2a..e5d970b 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -6,7 +6,11 @@ const withRoutes = nextRoutes(); const nextConfig = { reactStrictMode: true, images: { - domains: ["cms.solvro.pl"], + remotePatterns: [ + { + hostname: "cms.solvro.pl", + }, + ], }, }; diff --git a/package.json b/package.json index c5308d1..54e245c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "my-app", "version": "0.1.0", + "type": "module", "private": true, "scripts": { "dev": "next dev", diff --git a/src/components/Block.tsx b/src/components/Block.tsx new file mode 100644 index 0000000..cb23850 --- /dev/null +++ b/src/components/Block.tsx @@ -0,0 +1,20 @@ +import { cva } from "class-variance-authority"; +import React, { type ComponentProps } from "react"; + +export const blockCardVariants = cva( + "flex h-72 w-72 border-spacing-1 rounded-md border border-[#71717A] transition-all duration-300 ease-in-out hover:shadow-md", +); + +export const BlockCard = ({ + className, + ...props +}: ComponentProps<"button">) => { + return ( + + ))} diff --git a/src/pages/event/[slug]/settings.tsx b/src/pages/event/[slug]/settings.tsx index 5673591..3db57e7 100644 --- a/src/pages/event/[slug]/settings.tsx +++ b/src/pages/event/[slug]/settings.tsx @@ -2,7 +2,7 @@ import { CalendarIcon, ReloadIcon } from "@radix-ui/react-icons"; import { useMutation } from "@tanstack/react-query"; import { format } from "date-fns"; import type { InferGetServerSidePropsType } from "next"; -import type { GetServerSidePropsContext } from "nextjs-routes"; +import { type GetServerSidePropsContext, route } from "nextjs-routes"; import { parseAsString, useQueryState } from "nuqs"; import { useEffect } from "react"; import { toast } from "sonner"; @@ -33,6 +33,7 @@ import { supabase } from "@/lib/supabase"; import { createSSRClient } from "@/lib/supabaseSSR"; import type { TablesUpdate } from "@/lib/types"; import { useEvent } from "@/lib/useEvent"; +import { useIsClient } from "@/lib/useIsClient"; import { useZodForm } from "@/lib/useZodForm"; import { cn } from "@/lib/utils"; @@ -96,6 +97,8 @@ export default function Dashboard({ }, }); + const isClient = useIsClient(); + return (
- + {isClient ? ( + + ) : null} + + +
+ + ); +}; +export const getServerSideProps = async ( + ctx: GetServerSidePropsContext<"/rejestracja/[participationSlug]/[blockId]/formularz">, +) => { + const blockId = z.string().uuid().parse(ctx.params.blockId); + const participationSlug = z.string().parse(ctx.params.participationSlug); + + const event = await createSSRClient(ctx) + .from("events") + .select("*") + .eq("participantsSlug", participationSlug) + .single(); + + const block = await createSSRClient(ctx) + .from("blocks") + .select("*, reservations(*)") + .eq("blockId", blockId) + .single() + .throwOnError(); + + if (!block.data) { + return { + notFound: true, + }; + } + + return { + props: { + event, + blockId, + block: block.data, + participationSlug, + }, + }; +}; + +export default Submit; diff --git a/src/pages/rejestracja/[participationSlug]/index.tsx b/src/pages/rejestracja/[participationSlug]/index.tsx new file mode 100644 index 0000000..1788916 --- /dev/null +++ b/src/pages/rejestracja/[participationSlug]/index.tsx @@ -0,0 +1,208 @@ +import { useQuery } from "@tanstack/react-query"; +import { motion } from "framer-motion"; +import type { InferGetServerSidePropsType } from "next"; +import Link from "next/link"; +import type { GetServerSidePropsContext } from "nextjs-routes"; +import { parseAsString, useQueryState } from "nuqs"; +import * as React from "react"; +import { FaArrowLeft } from "react-icons/fa6"; +import { z } from "zod"; + +import { BlockCard, blockCardVariants } from "@/components/Block"; +import { buttonVariants } from "@/components/ui/button"; +import { supabase } from "@/lib/supabase"; +import { createSSRClient } from "@/lib/supabaseSSR"; +import { useUserEvent } from "@/lib/useUserEvent"; +import type { Block } from "@/types/Block"; + +function buildBreadcrumbs(blocks: Block[], currentBlockId: string) { + const blockMap: { [key: string]: Block } = {}; + blocks.forEach((block) => { + blockMap[block.blockId] = block; + }); + + const breadcrumbs: Block[] = []; + let currentBlock: Block | undefined = blockMap[currentBlockId]; + + while (currentBlock) { + breadcrumbs.unshift(currentBlock); + if (currentBlock.parentBlockId !== null) { + currentBlock = blockMap[currentBlock.parentBlockId]; + } else { + currentBlock = undefined; + } + } + + return breadcrumbs; +} + +export default function Building({ + participantsSlug, + ...rest +}: InferGetServerSidePropsType) { + const event = useUserEvent(participantsSlug); + + const eventId = event.data?.eventId ?? rest.eventId; + + const [queryBlockId, setBlockId] = useQueryState( + "blockId", + parseAsString.withOptions({ history: "push" }), + ); + + const blockId = + queryBlockId === "" || queryBlockId === null ? null : queryBlockId; + + const allBlocksQuery = useQuery({ + queryKey: ["blocks", eventId], + queryFn: async () => { + const blocks = await supabase + .from("blocks") + .select("*, reservations (*)") + .eq("eventId", eventId); + + return blocks.data; + }, + }); + + const currentBlocks = allBlocksQuery.data + ?.filter((block) => block.parentBlockId === blockId) + .slice() + .sort((a, b) => a.name.localeCompare(b.name)); + + const breadcrumbs = + typeof blockId === "string" + ? buildBreadcrumbs(allBlocksQuery.data ?? [], blockId) + : null; + + const currentBlock = allBlocksQuery.data?.find( + (block) => block.blockId === blockId, + ); + + const parentId = breadcrumbs?.[breadcrumbs.length - 2]?.blockId; + + return ( +
+
+ {event.data?.name} +
+
+
+
+ {typeof currentBlock !== "undefined" ? ( + + ) : ( +
+ )} +
+
{currentBlock?.name}
+
+ + + {currentBlocks?.map((block) => + typeof block.capacity === "number" ? ( +
+
+

{block.name}

+ {typeof block.capacity === "number" ? ( +

+ {block.reservations.length}/{block.capacity} +

+ ) : null} +
+
    + {block.reservations.map((reservation) => ( +
  1. + {reservation.firstName} {reservation.lastName} +
  2. + ))} +
+ { + e.stopPropagation(); + }} + > + Zapisz się + +
+ ) : ( + { + void setBlockId(block.blockId); + }} + key={block.blockId} + > +
+

{block.name}

+ {typeof block.capacity === "number" ? ( +

+ Liczba miejsc: {block.capacity} +

+ ) : null} +
+
    + {block.reservations.map((reservation) => ( +
  • + {reservation.firstName} {reservation.lastName} +
  • + ))} +
+
+ ), + )} +
+
+
+ ); +} + +export const getServerSideProps = async ( + ctx: GetServerSidePropsContext<"/rejestracja/[participationSlug]">, +) => { + const slug = z.string().parse(ctx.params.participationSlug); + + const event = await createSSRClient(ctx) + .from("events") + .select("*") + .eq("participantsSlug", slug) + .single(); + + if (!event.data) { + return { + notFound: true, + }; + } + + return { + props: { + participantsSlug: slug, + eventId: event.data.eventId, + } satisfies Partial<(typeof event)["data"]>, + }; +}; diff --git a/src/pages/rooms.tsx b/src/pages/rooms.tsx deleted file mode 100644 index 044950d..0000000 --- a/src/pages/rooms.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import Link from "next/link"; -import { useState } from "react"; -import { FaArrowLeft } from "react-icons/fa6"; - -export default function Rooms() { - const [expandedCard, setExpandedCard] = useState(null); - - const toggleCard = (cardId: number) => { - setExpandedCard(expandedCard === cardId ? null : cardId); - }; - - return ( -
-

- Rajd wiosenny W4 -

-
-
- - - -
-
Budynek nr 1
-
- - {/* Kontener dla kart */} -
- {[1, 2, 3, 4, 5, 6, 7, 8].map((room) => ( -
{ - toggleCard(room); - }} - onKeyDown={(e) => { - if (e.key === "Enter") { - toggleCard(room); - } - }} - tabIndex={0} - className="relative flex flex-col overflow-hidden rounded-lg border border-black" - role="button" - > -
-

Pokój nr {room}

-

Miejsca: 4/{room > 4 ? room : 5}

-

Kliknij tutaj, aby zobaczyć szczegóły

-
- -
-
-

- Dodatkowe informacje o pokoju nr {room}. -

-
    -
  • Imie Nazwisko
  • -
  • Imie Nazwisko
  • -
  • Imie Nazwisko
  • -
  • Imie Nazwisko
  • -
-

Odklinij, aby zobaczyć mniej

-
-
- - -
- ))} -
-
- ); -} diff --git a/src/types/Block.ts b/src/types/Block.ts new file mode 100644 index 0000000..be4e07e --- /dev/null +++ b/src/types/Block.ts @@ -0,0 +1,3 @@ +import type { Tables } from "@/lib/types"; + +export type Block = Tables<"blocks">; diff --git a/tailwind.config.ts b/tailwind.config.ts index 7fdf064..1343318 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -27,9 +27,6 @@ const config: Config = { ring: "hsl(var(--ring))", background: "hsl(var(--background))", foreground: "hsl(var(--foreground))", - solvroblue: "#D9E8FF", - solvrogray: "#D9D9D9", - solvrobutton: "#203560", primary: { DEFAULT: "hsl(var(--primary))", foreground: "hsl(var(--primary-foreground))",