Skip to content
Merged
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
113 changes: 68 additions & 45 deletions components/EventSelectForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,16 @@ import {
Select,
SelectChangeEvent,
Typography,
CircularProgress,
} from "@mui/material";
import { Label } from "./ui/label";
import { Plus, Trash } from "lucide-react";
import { Event, Location } from "@prisma/client";
import LocationAdder from "./LocationCreator";

import { useSession } from "next-auth/react";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { useState, startTransition } from "react";
import LocationAdder from "./LocationCreator";
import PinpointLoader from "./PinpointLoader";

import {
AlertDialog,
Expand All @@ -45,23 +46,23 @@ export default function EventSelectForm({
events: Array<EventWithLocationIds>;
}) {
const router = useRouter();

const [eventSelected, setEventSelected] = useState(false);
const [eventId, setEventId] = useState("");
const [selectedEventLocations, setSelectedEventLocations] = useState<
Location[]
>([]);
const [locationAdderOpen, setLocationAdderOpen] = useState(false);

const [isLoading, setIsLoading] = useState(false);
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [insertDialogOpen, setInsertDialogOpen] = useState(false);
const [eventToCreate, setEventToCreate] = useState<string>("");
const [dropdownEvents, setDropdownEvents] = useState<Event[]>(events);
const [isNavigating, setIsNavigating] = useState(false);
const [entityToDelete, setEntityToDelete] = useState<{
entity: Event | Location;
type: "event" | "location";
}>();

const [insertDialogOpen, setInsertDialogOpen] = useState(false);
const [eventToCreate, setEventToCreate] = useState<string>("");

const [dropdownEvents, setDropdownEvents] = useState<Event[]>(events);
const [selectedEventLocations, setSelectedEventLocations] = useState<
Location[]
>([]);

function deleteEvent(id: string) {
DeleteEntity("event", id);
Expand Down Expand Up @@ -89,6 +90,7 @@ export default function EventSelectForm({

const handleChange = async (e: SelectChangeEvent) => {
setEventSelected(true);
setIsLoading(true);
const selectedEventId = e.target.value;
setEventId(selectedEventId);

Expand All @@ -111,6 +113,7 @@ export default function EventSelectForm({

setSelectedEventLocations(updatedLocations ?? []);
}
setIsLoading(false);
};

const { data: session } = useSession();
Expand Down Expand Up @@ -203,43 +206,61 @@ export default function EventSelectForm({
id="eventLocations"
className="space-y-2 rounded-md border-gray-200 border-2 p-2 pt-0 transition-all duration-300 max-h-[45vh] overflow-y-auto"
>
<div
className="flex items-center justify-center sticky top-0 bg-white z-10 mt-1"
onClick={() => {
setLocationAdderOpen(true);
}}
>
<div className="flex items-center space-x-1 hover:bg-gray-100 p-1 rounded-md transition-all duration-300 text-sm text-gray-600 cursor-pointer pr-2">
<Plus className="h-6 w-6 text-blue-500 cursor-pointer hover:text-blue-600 hover:bg-gray-100 rounded-full p-1 transition-all duration-300" />
Add Location
{isLoading ? (
<div className="flex justify-center items-center p-6">
<CircularProgress size={30} />
</div>
</div>
{selectedEventLocations.map((location) => (
<div
key={location.id}
className="flex flex-row items-center justify-between w-full hover:bg-gray-100 p-2 rounded-md transition-all duration-300 cursor-pointer"
onClick={(e) => {
// Prevent the click event from triggering when clicking the trash button
if ((e.target as HTMLElement).closest(".trash-button"))
return;
router.push(`/event/edit/${eventId}/${location.id}`);
}}
>
<div className="flex-1 text-sm text-gray-600 h-full flex items-stretch">
{location.name}{" "}
) : (
<>
<div
className="flex items-center justify-center sticky top-0 bg-white z-10 mt-1"
onClick={() => {
setLocationAdderOpen(true);
}}
>
<div className="flex items-center space-x-1 hover:bg-gray-100 p-1 rounded-md transition-all duration-300 text-sm text-gray-600 cursor-pointer pr-2">
<Plus className="h-6 w-6 text-blue-500 cursor-pointer hover:text-blue-600 hover:bg-gray-100 rounded-full p-1 transition-all duration-300" />
Add Location
</div>
</div>
{canEdit && (
<Trash
className="h-4 w-4 text-red-500 cursor-pointer hover:text-red-600 hover:bg-red-100 transition-all duration-300 rounded-md"
{selectedEventLocations.map((location) => (
<div
key={location.id}
className="flex flex-row items-center justify-between w-full hover:bg-gray-100 p-2 rounded-md transition-all duration-300 cursor-pointer"
onClick={(e) => {
e.stopPropagation();
setDeleteDialogOpen(true);
setEntityToDelete({ entity: location, type: "location" });
// Prevent the click event from triggering when clicking the trash button
if ((e.target as HTMLElement).closest(".trash-button"))
return;

// Show loading immediately
setIsNavigating(true);

// Use startTransition for the navigation to indicate it's a UI update
startTransition(() => {
router.push(`/event/edit/${eventId}/${location.id}`);
});
}}
/>
)}
</div>
))}
>
<div className="flex-1 text-sm text-gray-600 h-full flex items-stretch">
{location.name}{" "}
</div>
{canEdit && (
<Trash
className="h-4 w-4 text-red-500 cursor-pointer hover:text-red-600 hover:bg-red-100 transition-all duration-300 rounded-md"
onClick={(e) => {
e.stopPropagation();
setDeleteDialogOpen(true);
setEntityToDelete({
entity: location,
type: "location",
});
}}
/>
)}
</div>
))}
</>
)}
</div>
</div>
)}
Expand Down Expand Up @@ -327,6 +348,8 @@ export default function EventSelectForm({
setSelectedEventLocations((prev) => [...prev, location]);
}}
/>

{isNavigating && <PinpointLoader />}
</div>
);
}
78 changes: 78 additions & 0 deletions components/PinpointLoader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"use client";

import Heading from "@components/Heading";
import { useEffect, useState } from "react";

const PinpointLoader = () => {
const [progress, setProgress] = useState(0);
const [loadingText, setLoadingText] = useState("Initializing maps...");

useEffect(() => {
// Loading phrases...
const loadingPhrases = [
"Initializing maps",
"Loading terrain data",
"Preparing navigation",
"Calculating routes",
"Syncing location data",
"Rendering map tiles",
];

// Fake loading progress
const interval = setInterval(() => {
setProgress((prev) => {
if (prev >= 100) {
clearInterval(interval);
return 100;
}
return prev + 1;
});

// Change loading text every now and then
if (progress % 16 === 0) {
const nextPhrase = `${
loadingPhrases[Math.floor((progress / 16) % loadingPhrases.length)]
}...`;
setLoadingText(nextPhrase);
}
}, 80);

return () => clearInterval(interval);
}, [progress]);

return (
<div
className="fixed inset-0 flex flex-col items-center justify-center bg-gradient-to-b from-sky-50 to-white"
style={{
zIndex: 9999, // Ensure highest z-index to overlay everything
}}
>
<div className="flex flex-col items-center w-full max-w-md mx-auto p-6 space-y-12">
{/* Logo */}
<div className="w-full flex justify-center">
<Heading />
</div>

{/* Loading progress */}
<div className="w-full space-y-3">
<div className="flex items-center justify-between">
<div className="text-sm font-[550]">{loadingText}</div>
<span className="text-sm font-[550]">{progress}%</span>
</div>

<div className="h-2 w-full bg-slate-200 rounded-full overflow-hidden">
<div
className="h-full rounded-full transition-all duration-200 ease-out"
style={{
width: `${progress}%`,
background: "linear-gradient(to right, #357ad2, #62d6ae)",
}}
/>
</div>
</div>
</div>
</div>
);
};

export default PinpointLoader;
Loading