Skip to content

Commit

Permalink
feat(UI and Backend): Started implementing file upload logic [2024-10…
Browse files Browse the repository at this point in the history
…-27]
  • Loading branch information
CHRISCARLON committed Oct 27, 2024
1 parent 82adb76 commit 3918108
Show file tree
Hide file tree
Showing 4 changed files with 221 additions and 29 deletions.
61 changes: 61 additions & 0 deletions gridwalk-ui/src/app/api/upload/layer/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { NextRequest } from "next/server";

// Hardcoded auth token - replace with your actual token
const HARDCODED_AUTH_TOKEN = "";

export async function POST(request: NextRequest) {
try {
const formData = await request.formData();
const file = formData.get("file");

if (!file || !(file instanceof File)) {
return new Response(
JSON.stringify({
success: false,
error: "No file provided",
}),
{ status: 400 },
);
}

// Create a new FormData for the API call
const apiFormData = new FormData();
apiFormData.append("file", file);

// Hardcoded values
apiFormData.append("workspace_id", "");
apiFormData.append("name", "my-awesome-layer");

const response = await fetch("http://localhost:3001/upload_layer", {
method: "POST",
headers: {
Authorization: `Bearer ${HARDCODED_AUTH_TOKEN}`,
},
body: apiFormData,
});

if (!response.ok) {
throw new Error("Upload failed");
}

const data = await response.json();
return new Response(JSON.stringify({ success: true, data }), {
status: 200,
});
} catch (error) {
console.error("Upload error:", error);
return new Response(
JSON.stringify({
success: false,
error: "Upload failed",
}),
{ status: 500 },
);
}
}

export const config = {
api: {
bodyParser: false,
},
};
103 changes: 93 additions & 10 deletions gridwalk-ui/src/app/project/components/navBars/mainMapNavigation.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
import React from "react";
import { Map, Layers, Settings, Info, File, X } from "lucide-react";
import {
Map,
Layers,
Settings,
Info,
File,
X,
Upload,
Trash2,
} from "lucide-react";
import { ModalProps, MainMapNav } from "./types";

{
/* Create NavBar map Modal */
}
const MapModal: React.FC<ModalProps> = ({
isOpen,
onClose,
onNavItemClick,
selectedItem,
children,
layers,
onLayerUpload,
onLayerDelete,
isUploading,
error,
}) => {
const MainMapNavs: MainMapNav[] = [
{
Expand Down Expand Up @@ -45,6 +55,13 @@ const MapModal: React.FC<ModalProps> = ({
},
];

const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (file) {
onLayerUpload(file);
}
};

const getIconComponent = (iconName: string) => {
switch (iconName) {
case "map":
Expand All @@ -62,6 +79,76 @@ const MapModal: React.FC<ModalProps> = ({
}
};

const renderModalContent = () => {
if (!selectedItem) return null;

switch (selectedItem.id) {
case "upload":
return (
<div className="p-4">
<h2 className="text-xl font-bold mb-4">Upload</h2>

{/* Upload Section */}
<div className="mb-6">
<label className="flex flex-col items-center px-4 py-6 bg-white rounded-lg shadow-lg border-2 border-dashed border-gray-300 hover:border-blue-400 cursor-pointer transition-colors">
<Upload className="w-8 h-8 text-gray-400 mb-2" />
<span className="text-sm text-gray-500">
Click to upload a file
</span>
<input
type="file"
className="hidden"
onChange={handleFileChange}
accept=".geojson,.json,.kml,.gpx"
disabled={isUploading}
/>
</label>
</div>

{/* Status Messages */}
{isUploading && (
<div className="mb-4 text-blue-500">Uploading...</div>
)}
{error && <div className="mb-4 text-red-500">{error}</div>}

{/* Layers List */}
<div className="space-y-2">
{layers.length === 0 ? (
<p className="text-gray-500 text-sm">No layers uploaded yet</p>
) : (
layers.map((layer) => (
<div
key={layer.id}
className="flex items-center justify-between p-3 bg-white rounded-lg shadow-sm"
>
<div>
<p className="font-medium text-gray-800">{layer.name}</p>
<p className="text-sm text-gray-500">{layer.type}</p>
</div>
<button
onClick={() => onLayerDelete(layer.id)}
className="p-1 hover:bg-red-50 rounded-full text-red-500 transition-colors"
aria-label="Delete layer"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
))
)}
</div>
</div>
);

default:
return (
<div>
<h2 className="text-xl font-bold mb-4">{selectedItem.title}</h2>
<p className="text-gray-600">{selectedItem.description}</p>
</div>
);
}
};

return (
<>
{/* Navigation Bar */}
Expand Down Expand Up @@ -112,11 +199,7 @@ const MapModal: React.FC<ModalProps> = ({
</button>
{/* Content with black text override */}
<div className="max-h-[50vh] overflow-y-auto p-4">
<div className="text-gray-900">
{" "}
{/* Force black text for content */}
{children}
</div>
<div className="text-gray-900">{renderModalContent()}</div>
</div>
</div>
</div>
Expand Down
15 changes: 14 additions & 1 deletion gridwalk-ui/src/app/project/components/navBars/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export interface MainMapNav {
title: string;
icon?: string;
description?: string;
children?: React.ReactNode;
}

/* Simple Modal Prop Elements */
Expand All @@ -12,7 +13,11 @@ export interface ModalProps {
onClose: () => void;
onNavItemClick: (item: MainMapNav) => void;
selectedItem: MainMapNav | null;
children: React.ReactNode;
layers: LayerUpload[];
onLayerUpload: (file: File) => Promise<void>;
onLayerDelete: (layerId: string) => void;
isUploading: boolean;
error: string | null;
}

/* Map Edit Items */
Expand All @@ -30,3 +35,11 @@ export interface BaseEditNav {
icon?: string;
description: string;
}

/* Upload Layer */
export interface LayerUpload {
id: string;
name: string;
type: string;
visible?: boolean;
}
71 changes: 53 additions & 18 deletions gridwalk-ui/src/app/project/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import React, { useState } from "react";
import { useMapInit } from "./components/mapInit/mapInit";
import MainMapNavigation from "./components/navBars/mainMapNavigation";
import { MainMapNav } from "./components/navBars/types";
import { MainMapNav, LayerUpload } from "./components/navBars/types";
import MapEditNavigation from "./components/navBars/mapEditNavigation";
import { MapEditNav } from "./components/navBars/types";
import BaseLayerNavigation from "./components/navBars/baseLayerNavigation";
Expand All @@ -21,7 +21,6 @@ const INITIAL_MAP_CONFIG = {
zoom: 11,
};

// Set up page
export default function Project() {
// State management
const [selectedItem, setSelectedItem] = useState<MainMapNav | null>(null);
Expand All @@ -34,13 +33,58 @@ export default function Project() {
const [isModalOpen, setIsModalOpen] = useState(false);
const [currentStyle, setCurrentStyle] = useState<string>(MAP_STYLES.light);

// Layer state management
const [layers, setLayers] = useState<LayerUpload[]>([]);
const [isUploading, setIsUploading] = useState(false);
const [error, setError] = useState<string | null>(null);

// Map initialisation
const { mapContainer, mapError } = useMapInit({
...INITIAL_MAP_CONFIG,
styleUrl: currentStyle,
});

// Event handlers
// Layer handlers
const handleLayerUpload = async (file: File) => {
setIsUploading(true);
setError(null);

try {
const formData = new FormData();
formData.append("file", file);

const response = await fetch("/api/upload/layer", {
method: "POST",
body: formData,
});

if (!response.ok) {
throw new Error("Upload failed");
}

const data = await response.json();

setLayers((prev) => [
...prev,
{
id: data.data.id || Math.random().toString(),
name: file.name,
type: file.type,
visible: true,
},
]);
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to upload file");
} finally {
setIsUploading(false);
}
};

const handleLayerDelete = (layerId: string) => {
setLayers((prev) => prev.filter((layer) => layer.id !== layerId));
};

// Navigation handlers
const handleNavItemClick = (item: MainMapNav) => {
setSelectedItem(item);
setIsModalOpen(true);
Expand Down Expand Up @@ -72,38 +116,29 @@ export default function Project() {

return (
<div className="w-full h-screen relative">
{/* Error display */}
{mapError && (
<div className="absolute top-4 left-1/2 transform -translate-x-1/2 bg-red-500 text-white px-4 py-2 rounded shadow-lg">
{mapError}
</div>
)}

{/* Map container */}
<div className="absolute inset-0 pl-10">
<div ref={mapContainer} className="h-full w-full" />
</div>

{/* Navigation components */}
<MapEditNavigation
onEditItemClick={handleEditItemClick}
selectedEditItem={selectedEditItem}
/>

<MainMapNavigation
isOpen={isModalOpen}
onClose={handleModalClose}
onNavItemClick={handleNavItemClick}
selectedItem={selectedItem}
>
{selectedItem && (
<div>
<h2 className="text-xl font-bold mb-4">{selectedItem.title}</h2>
<p className="text-gray-600">{selectedItem.description}</p>
</div>
)}
</MainMapNavigation>

layers={layers}
onLayerUpload={handleLayerUpload}
onLayerDelete={handleLayerDelete}
isUploading={isUploading}
error={error}
/>
<BaseLayerNavigation
onBaseItemClick={handleBaseItemClick}
selectedBaseItem={selectedBaseItem}
Expand Down

0 comments on commit 3918108

Please sign in to comment.