Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 119 additions & 45 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 2 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,8 @@
}
},
"dependencies": {
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/modifiers": "^9.0.0",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@dnd-kit/helpers": "^0.3.2",
"@dnd-kit/react": "^0.3.2",
"@hcaptcha/react-hcaptcha": "^1.14.0",
"@hookform/resolvers": "^4.1.3",
"@next/env": "16.0.4",
Expand Down
40 changes: 21 additions & 19 deletions src/app/dashboard/events/[id]/blocks/[blockId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type { AttributeBase } from "@/types/attributes";
import type { Block } from "@/types/blocks";
import type { Participant } from "@/types/participant";

import { BlockEntry } from "./block-entry";
import { SortableBlockGrid } from "./sortable-block-grid";

async function getRootBlock(
eventId: string,
Expand Down Expand Up @@ -156,6 +156,15 @@ export default async function EventBlockEditPage({
if (rootBlock == null) {
notFound();
} else {
const participantsByBlock: Record<number, Participant[]> = {};
for (const childBlock of rootBlock.children) {
participantsByBlock[childBlock.id] = getParticipantsInChildBlock(
participantsInRootBlock,
rootBlockId,
childBlock.id.toString(),
);
}

return (
<div className="flex grow flex-col gap-8">
<div className="flex flex-col items-center justify-between gap-4 text-center md:flex-row md:text-left">
Expand All @@ -174,30 +183,23 @@ export default async function EventBlockEditPage({
parentId={rootBlock.id.toString()}
/>
</div>
<div className="flex flex-wrap justify-center gap-8 sm:justify-start">
{rootBlock.children.length > 0 ? (
rootBlock.children.map((childBlock) => (
<BlockEntry
key={childBlock.id}
block={childBlock}
attributeId={rootBlockId}
eventId={eventId}
participantsInBlock={getParticipantsInChildBlock(
participantsInRootBlock,
rootBlockId,
childBlock.id.toString(),
)}
/>
))
) : (
{rootBlock.children.length > 0 ? (
<SortableBlockGrid
blocks={rootBlock.children}
eventId={eventId}
attributeId={rootBlockId}
participantsByBlock={participantsByBlock}
/>
) : (
<div className="flex flex-wrap justify-center gap-8 sm:justify-start">
<div className="flex w-full flex-col items-center justify-center py-12 text-center">
<Cuboid className="text-muted-foreground mb-4 size-12" />
<h3 className="text-muted-foreground text-lg">
Nie masz jeszcze żadnego bloku w bloku
</h3>
</div>
)}
</div>
</div>
)}
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"use client";

import { SortableTileGrid } from "@/components/sortable-tile-grid";
import type { Block } from "@/types/blocks";
import type { Participant } from "@/types/participant";

import { reorderBlocks } from "../actions";
import { BlockEntry } from "./block-entry";

function SortableBlockGrid({
blocks,
eventId,
attributeId,
participantsByBlock,
}: {
blocks: Block[];
eventId: string;
attributeId: string;
participantsByBlock: Record<number, Participant[]>;
}) {
return (
<SortableTileGrid
items={blocks}
onReorder={async (orderedIds) =>
reorderBlocks(eventId, attributeId, orderedIds)
}
renderItem={(block) => (
<BlockEntry
block={block}
eventId={eventId}
attributeId={attributeId}
participantsInBlock={participantsByBlock[block.id] ?? []}
/>
)}
/>
);
}

export { SortableBlockGrid };
83 changes: 83 additions & 0 deletions src/app/dashboard/events/[id]/blocks/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,46 @@ import { redirect } from "next/navigation";
import { API_URL } from "@/lib/api";
import { verifySession } from "@/lib/session";

export async function reorderBlockAttributes(
eventId: string,
orderedIds: number[],
) {
const session = await verifySession();

if (session == null) {
redirect("/auth/login");
}

const { bearerToken } = session;

//TODO: as soon as backend exposes an endpoint for reordering block attributes, replace this with a single request
const results = await Promise.all(
orderedIds.map(async (id, index) =>
fetch(`${API_URL}/events/${eventId}/attributes/${id.toString()}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${bearerToken}`,
},
body: JSON.stringify({ order: index }),
}),
),
);

const failed = results.find((r) => !r.ok);
if (failed !== undefined) {
console.error(
`[reorderBlockAttributes action] Failed to reorder block attributes for event ${eventId}`,
);
return {
success: false,
error: `Błąd ${failed.status.toString()} ${failed.statusText}`,
};
}

return { success: true };
}

export async function createBlock(
eventId: string,
attributeId: string,
Expand Down Expand Up @@ -100,6 +140,49 @@ export async function updateBlock(
}
}

export async function reorderBlocks(
eventId: string,
attributeId: string,
orderedIds: number[],
) {
const session = await verifySession();

if (session == null) {
redirect("/auth/login");
}

const { bearerToken } = session;

const results = await Promise.all(
orderedIds.map(async (id, index) =>
fetch(
`${API_URL}/events/${eventId}/attributes/${attributeId}/blocks/${id.toString()}`,
{
method: "PATCH",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${bearerToken}`,
},
body: JSON.stringify({ order: index }),
},
),
),
);

const failed = results.find((r) => !r.ok);
if (failed !== undefined) {
console.error(
`[reorderBlocks action] Failed to reorder blocks for event ${eventId}`,
);
return {
success: false,
error: `Błąd ${failed.status.toString()} ${failed.statusText}`,
};
}

return { success: true };
}

export async function deleteBlock(
eventId: string,
blockId: string,
Expand Down
33 changes: 9 additions & 24 deletions src/app/dashboard/events/[id]/blocks/page.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Cuboid, PackageOpenIcon } from "lucide-react";
import { Cuboid } from "lucide-react";
import type { Metadata } from "next";
import Link from "next/link";
import { notFound, redirect } from "next/navigation";

import { Button } from "@/components/ui/button";
import { API_URL } from "@/lib/api";
import { verifySession } from "@/lib/session";
import type { Attribute } from "@/types/attributes";

import { SortableBlockAttributeGrid } from "./sortable-block-attribute-grid";

export const metadata: Metadata = {
title: "Bloki",
};
Expand Down Expand Up @@ -43,33 +43,18 @@ export default async function DashboardEventBlocksPage({
return (
<div className="flex flex-col gap-8">
<h1 className="text-3xl font-bold">Bloki</h1>
<div className="flex flex-wrap justify-center gap-8 sm:justify-start">
{blocks.length > 0 ? (
blocks.map((block) => (
<div
className="flex h-64 w-64 flex-col justify-between rounded-md border border-slate-500 p-4"
key={block.id}
>
<div className="flex grow flex-col items-center justify-center gap-2 text-center">
<p className="text-lg font-bold">{block.name}</p>
<Button variant="outline" className="w-full" asChild>
<Link href={`blocks/${block.id.toString()}`}>
<PackageOpenIcon />
Otwórz
</Link>
</Button>
</div>
</div>
))
) : (
{blocks.length > 0 ? (
<SortableBlockAttributeGrid blocks={blocks} eventId={id} />
) : (
<div className="flex flex-wrap justify-center gap-8 sm:justify-start">
<div className="flex w-full flex-col items-center justify-center py-12 text-center">
<Cuboid className="text-muted-foreground mb-4 size-12" />
<h3 className="text-muted-foreground text-lg">
Nie masz jeszcze żadnego bloku
</h3>
</div>
)}
</div>
</div>
)}
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"use client";

import { PackageOpenIcon } from "lucide-react";
import Link from "next/link";

import { SortableTileGrid } from "@/components/sortable-tile-grid";
import { Button } from "@/components/ui/button";
import type { Attribute } from "@/types/attributes";

import { reorderBlockAttributes } from "./actions";

function SortableBlockAttributeGrid({
blocks,
eventId,
}: {
blocks: Attribute[];
eventId: string;
}) {
return (
<SortableTileGrid
items={blocks}
onReorder={async (orderedIds) =>
reorderBlockAttributes(eventId, orderedIds)
}
renderItem={(block) => (
<div className="bg-background flex h-64 w-64 flex-col justify-between rounded-md border border-slate-500 p-4">
<div className="flex grow flex-col items-center justify-center gap-2 text-center">
<p className="text-lg font-bold">{block.name}</p>
<Button variant="outline" className="w-full" asChild>
<Link href={`blocks/${block.id.toString()}`}>
<PackageOpenIcon />
Otwórz
</Link>
</Button>
</div>
</div>
)}
/>
);
}

export { SortableBlockAttributeGrid };
35 changes: 35 additions & 0 deletions src/app/dashboard/events/[id]/emails/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,41 @@ export async function updateEventEmail(
return { success: true };
}

export async function reorderEmails(eventId: string, orderedIds: number[]) {
const session = await verifySession();

if (session == null) {
return { success: false, error: "Brak autoryzacji" };
}

//TODO: as soon as backend exposes an endpoint for reordering block attributes, replace this with a single request
const results = await Promise.all(
orderedIds.map(async (id, index) =>
fetch(`${API_URL}/events/${eventId}/emails/${id.toString()}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${session.bearerToken}`,
},
body: JSON.stringify({ order: index }),
}),
),
);

const failed = results.find((r) => !r.ok);
if (failed !== undefined) {
console.error(
`[reorderEmails action] Failed to reorder emails for event ${eventId}`,
);
return {
success: false,
error: `Błąd ${failed.status.toString()} ${failed.statusText}`,
};
}

return { success: true };
}

export async function deleteEventMail(eventId: string, mailId: string) {
const session = await verifySession();

Expand Down
Loading