Spaces:
Running
Running
Layout & Upload page skeleton
Browse files- backend/main.go +1 -1
- frontend/package-lock.json +53 -1
- frontend/package.json +3 -1
- frontend/src/App.tsx +22 -9
- frontend/src/components/HeaderNav.tsx +50 -0
- frontend/src/layouts/RootLayout.tsx +12 -0
- frontend/src/pages/AnalyticsPage.tsx +9 -0
- frontend/src/pages/ExplorePage.tsx +9 -0
- frontend/src/pages/HelpPage.tsx +9 -0
- frontend/src/pages/UploadPage.tsx +109 -0
backend/main.go
CHANGED
|
@@ -10,5 +10,5 @@ func main() {
|
|
| 10 |
r.GET("/health", func(c *gin.Context) {
|
| 11 |
c.JSON(http.StatusOK, gin.H{"status": "ok"})
|
| 12 |
})
|
| 13 |
-
r.Run()
|
| 14 |
}
|
|
|
|
| 10 |
r.GET("/health", func(c *gin.Context) {
|
| 11 |
c.JSON(http.StatusOK, gin.H{"status": "ok"})
|
| 12 |
})
|
| 13 |
+
r.Run()
|
| 14 |
}
|
frontend/package-lock.json
CHANGED
|
@@ -10,8 +10,10 @@
|
|
| 10 |
"dependencies": {
|
| 11 |
"@ifrc-go/icons": "^2.0.1",
|
| 12 |
"@ifrc-go/ui": "^1.3.0",
|
|
|
|
| 13 |
"react": "^18.2.0",
|
| 14 |
-
"react-dom": "^18.2.0"
|
|
|
|
| 15 |
},
|
| 16 |
"devDependencies": {
|
| 17 |
"@eslint/js": "^9.30.1",
|
|
@@ -870,6 +872,15 @@
|
|
| 870 |
"node": ">= 8"
|
| 871 |
}
|
| 872 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 873 |
"node_modules/@rolldown/pluginutils": {
|
| 874 |
"version": "1.0.0-beta.11",
|
| 875 |
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.11.tgz",
|
|
@@ -3326,6 +3337,15 @@
|
|
| 3326 |
"loose-envify": "cli.js"
|
| 3327 |
}
|
| 3328 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3329 |
"node_modules/magic-string": {
|
| 3330 |
"version": "0.30.17",
|
| 3331 |
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
|
|
@@ -3804,6 +3824,38 @@
|
|
| 3804 |
}
|
| 3805 |
}
|
| 3806 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3807 |
"node_modules/react-style-singleton": {
|
| 3808 |
"version": "2.2.3",
|
| 3809 |
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
|
|
|
|
| 10 |
"dependencies": {
|
| 11 |
"@ifrc-go/icons": "^2.0.1",
|
| 12 |
"@ifrc-go/ui": "^1.3.0",
|
| 13 |
+
"lucide-react": "^0.525.0",
|
| 14 |
"react": "^18.2.0",
|
| 15 |
+
"react-dom": "^18.2.0",
|
| 16 |
+
"react-router-dom": "^6.30.1"
|
| 17 |
},
|
| 18 |
"devDependencies": {
|
| 19 |
"@eslint/js": "^9.30.1",
|
|
|
|
| 872 |
"node": ">= 8"
|
| 873 |
}
|
| 874 |
},
|
| 875 |
+
"node_modules/@remix-run/router": {
|
| 876 |
+
"version": "1.23.0",
|
| 877 |
+
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz",
|
| 878 |
+
"integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==",
|
| 879 |
+
"license": "MIT",
|
| 880 |
+
"engines": {
|
| 881 |
+
"node": ">=14.0.0"
|
| 882 |
+
}
|
| 883 |
+
},
|
| 884 |
"node_modules/@rolldown/pluginutils": {
|
| 885 |
"version": "1.0.0-beta.11",
|
| 886 |
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.11.tgz",
|
|
|
|
| 3337 |
"loose-envify": "cli.js"
|
| 3338 |
}
|
| 3339 |
},
|
| 3340 |
+
"node_modules/lucide-react": {
|
| 3341 |
+
"version": "0.525.0",
|
| 3342 |
+
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.525.0.tgz",
|
| 3343 |
+
"integrity": "sha512-Tm1txJ2OkymCGkvwoHt33Y2JpN5xucVq1slHcgE6Lk0WjDfjgKWor5CdVER8U6DvcfMwh4M8XxmpTiyzfmfDYQ==",
|
| 3344 |
+
"license": "ISC",
|
| 3345 |
+
"peerDependencies": {
|
| 3346 |
+
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
| 3347 |
+
}
|
| 3348 |
+
},
|
| 3349 |
"node_modules/magic-string": {
|
| 3350 |
"version": "0.30.17",
|
| 3351 |
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
|
|
|
|
| 3824 |
}
|
| 3825 |
}
|
| 3826 |
},
|
| 3827 |
+
"node_modules/react-router": {
|
| 3828 |
+
"version": "6.30.1",
|
| 3829 |
+
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.1.tgz",
|
| 3830 |
+
"integrity": "sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ==",
|
| 3831 |
+
"license": "MIT",
|
| 3832 |
+
"dependencies": {
|
| 3833 |
+
"@remix-run/router": "1.23.0"
|
| 3834 |
+
},
|
| 3835 |
+
"engines": {
|
| 3836 |
+
"node": ">=14.0.0"
|
| 3837 |
+
},
|
| 3838 |
+
"peerDependencies": {
|
| 3839 |
+
"react": ">=16.8"
|
| 3840 |
+
}
|
| 3841 |
+
},
|
| 3842 |
+
"node_modules/react-router-dom": {
|
| 3843 |
+
"version": "6.30.1",
|
| 3844 |
+
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.1.tgz",
|
| 3845 |
+
"integrity": "sha512-llKsgOkZdbPU1Eg3zK8lCn+sjD9wMRZZPuzmdWWX5SUs8OFkN5HnFVC0u5KMeMaC9aoancFI/KoLuKPqN+hxHw==",
|
| 3846 |
+
"license": "MIT",
|
| 3847 |
+
"dependencies": {
|
| 3848 |
+
"@remix-run/router": "1.23.0",
|
| 3849 |
+
"react-router": "6.30.1"
|
| 3850 |
+
},
|
| 3851 |
+
"engines": {
|
| 3852 |
+
"node": ">=14.0.0"
|
| 3853 |
+
},
|
| 3854 |
+
"peerDependencies": {
|
| 3855 |
+
"react": ">=16.8",
|
| 3856 |
+
"react-dom": ">=16.8"
|
| 3857 |
+
}
|
| 3858 |
+
},
|
| 3859 |
"node_modules/react-style-singleton": {
|
| 3860 |
"version": "2.2.3",
|
| 3861 |
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
|
frontend/package.json
CHANGED
|
@@ -29,7 +29,9 @@
|
|
| 29 |
"dependencies": {
|
| 30 |
"@ifrc-go/icons": "^2.0.1",
|
| 31 |
"@ifrc-go/ui": "^1.3.0",
|
|
|
|
| 32 |
"react": "^18.2.0",
|
| 33 |
-
"react-dom": "^18.2.0"
|
|
|
|
| 34 |
}
|
| 35 |
}
|
|
|
|
| 29 |
"dependencies": {
|
| 30 |
"@ifrc-go/icons": "^2.0.1",
|
| 31 |
"@ifrc-go/ui": "^1.3.0",
|
| 32 |
+
"lucide-react": "^0.525.0",
|
| 33 |
"react": "^18.2.0",
|
| 34 |
+
"react-dom": "^18.2.0",
|
| 35 |
+
"react-router-dom": "^6.30.1"
|
| 36 |
}
|
| 37 |
}
|
frontend/src/App.tsx
CHANGED
|
@@ -1,11 +1,24 @@
|
|
| 1 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
<
|
| 6 |
-
|
| 7 |
-
<
|
| 8 |
-
|
| 9 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
}
|
| 11 |
-
export default App;
|
|
|
|
| 1 |
+
/* src/App.tsx */
|
| 2 |
+
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
|
| 3 |
+
import RootLayout from './layouts/RootLayout';
|
| 4 |
+
import UploadPage from './pages/UploadPage';
|
| 5 |
+
import AnalyticsPage from './pages/AnalyticsPage';
|
| 6 |
+
import ExplorePage from './pages/ExplorePage';
|
| 7 |
+
import HelpPage from './pages/HelpPage';
|
| 8 |
|
| 9 |
+
const router = createBrowserRouter([
|
| 10 |
+
{
|
| 11 |
+
element: <RootLayout />, // header sticks here
|
| 12 |
+
children: [
|
| 13 |
+
{ path: '/', element: <UploadPage /> },
|
| 14 |
+
{ path: '/upload', element: <UploadPage /> },
|
| 15 |
+
{ path: '/analytics', element: <AnalyticsPage /> },
|
| 16 |
+
{ path: '/explore', element: <ExplorePage /> },
|
| 17 |
+
{ path: '/help', element: <HelpPage /> },
|
| 18 |
+
],
|
| 19 |
+
},
|
| 20 |
+
]);
|
| 21 |
+
|
| 22 |
+
export default function App() {
|
| 23 |
+
return <RouterProvider router={router} />;
|
| 24 |
}
|
|
|
frontend/src/components/HeaderNav.tsx
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { NavLink } from "react-router-dom";
|
| 2 |
+
import { IconButton } from "@ifrc-go/ui";
|
| 3 |
+
import {
|
| 4 |
+
UploadCloudLineIcon,
|
| 5 |
+
AnalysisIcon,
|
| 6 |
+
SearchLineIcon,
|
| 7 |
+
QuestionLineIcon,
|
| 8 |
+
} from "@ifrc-go/icons";
|
| 9 |
+
|
| 10 |
+
/* Style helper for active vs. inactive nav links */
|
| 11 |
+
const navLink = ({ isActive }: { isActive: boolean }) =>
|
| 12 |
+
`flex items-center gap-1 px-3 py-2 text-sm transition-colors ${
|
| 13 |
+
isActive ? "text-ifrcRed font-semibold" : "text-gray-600 hover:text-ifrcRed"
|
| 14 |
+
}`;
|
| 15 |
+
|
| 16 |
+
/* Put page info in one list so it’s easy to extend */
|
| 17 |
+
const navItems = [
|
| 18 |
+
{ to: "/upload", label: "Upload", Icon: UploadCloudLineIcon },
|
| 19 |
+
{ to: "/analytics", label: "Analytics", Icon: AnalysisIcon },
|
| 20 |
+
{ to: "/explore", label: "Explore", Icon: SearchLineIcon },
|
| 21 |
+
];
|
| 22 |
+
|
| 23 |
+
export default function HeaderNav() {
|
| 24 |
+
return (
|
| 25 |
+
<header className="bg-white border-b border-ifrcRed/40">
|
| 26 |
+
<div className="flex items-center justify-between px-6 py-3">
|
| 27 |
+
|
| 28 |
+
{/* ── Logo + title ─────────────────────────── */}
|
| 29 |
+
<NavLink to="/" className="flex items-center gap-2">
|
| 30 |
+
<img src="/ifrc-logo.svg" alt="IFRC logo" className="h-6" />
|
| 31 |
+
<span className="font-semibold">PromptAid Vision</span>
|
| 32 |
+
</NavLink>
|
| 33 |
+
|
| 34 |
+
{/* ── Centre nav links ─────────────────────── */}
|
| 35 |
+
<nav className="flex gap-6">
|
| 36 |
+
{navItems.map(({ to, label, Icon }) => (
|
| 37 |
+
<NavLink key={to} to={to} className={navLink}>
|
| 38 |
+
<Icon className="w-4 h-4" /> {label}
|
| 39 |
+
</NavLink>
|
| 40 |
+
))}
|
| 41 |
+
</nav>
|
| 42 |
+
|
| 43 |
+
{/* ── Right-side utility buttons ───────────── */}
|
| 44 |
+
<NavLink to="/help" className={navLink}>
|
| 45 |
+
<QuestionLineIcon className="w-4 h-4" />
|
| 46 |
+
</NavLink>
|
| 47 |
+
</div>
|
| 48 |
+
</header>
|
| 49 |
+
);
|
| 50 |
+
}
|
frontend/src/layouts/RootLayout.tsx
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Outlet } from 'react-router-dom';
|
| 2 |
+
import HeaderNav from '../components/HeaderNav';
|
| 3 |
+
|
| 4 |
+
export default function RootLayout() {
|
| 5 |
+
return (
|
| 6 |
+
<>
|
| 7 |
+
<HeaderNav />
|
| 8 |
+
{/* All routed pages render here */}
|
| 9 |
+
<Outlet />
|
| 10 |
+
</>
|
| 11 |
+
);
|
| 12 |
+
}
|
frontend/src/pages/AnalyticsPage.tsx
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { PageContainer, Heading } from '@ifrc-go/ui';
|
| 2 |
+
|
| 3 |
+
export default function AnalyticsPage() {
|
| 4 |
+
return (
|
| 5 |
+
<PageContainer className="py-10 text-center">
|
| 6 |
+
<Heading level={2}>Analytics</Heading>
|
| 7 |
+
</PageContainer>
|
| 8 |
+
);
|
| 9 |
+
}
|
frontend/src/pages/ExplorePage.tsx
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { PageContainer, Heading } from '@ifrc-go/ui';
|
| 2 |
+
|
| 3 |
+
export default function ExplorePage() {
|
| 4 |
+
return (
|
| 5 |
+
<PageContainer className="py-10 text-center">
|
| 6 |
+
<Heading level={2}>Explore Dataset</Heading>
|
| 7 |
+
</PageContainer>
|
| 8 |
+
);
|
| 9 |
+
}
|
frontend/src/pages/HelpPage.tsx
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { PageContainer, Heading } from '@ifrc-go/ui';
|
| 2 |
+
|
| 3 |
+
export default function HelpPage() {
|
| 4 |
+
return (
|
| 5 |
+
<PageContainer className="py-10 text-center">
|
| 6 |
+
<Heading level={2}>Help & Support</Heading>
|
| 7 |
+
</PageContainer>
|
| 8 |
+
);
|
| 9 |
+
}
|
frontend/src/pages/UploadPage.tsx
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* UploadPage.tsx ------------------------------------------------------ */
|
| 2 |
+
import { useCallback, useState } from 'react';
|
| 3 |
+
import type { DragEvent } from 'react';
|
| 4 |
+
import {
|
| 5 |
+
PageContainer,
|
| 6 |
+
Heading,
|
| 7 |
+
Button,
|
| 8 |
+
RawFileInput, // thin wrapper around <input type="file">
|
| 9 |
+
Container,
|
| 10 |
+
} from '@ifrc-go/ui';
|
| 11 |
+
import {
|
| 12 |
+
UploadCloudLineIcon,
|
| 13 |
+
ArrowRightLineIcon,
|
| 14 |
+
} from '@ifrc-go/icons';
|
| 15 |
+
import { Link } from 'react-router-dom';
|
| 16 |
+
|
| 17 |
+
export default function UploadPage() {
|
| 18 |
+
const [file, setFile] = useState<File | null>(null);
|
| 19 |
+
|
| 20 |
+
/* ---- drag-and-drop + file-picker handlers -------------------------- */
|
| 21 |
+
const onDrop = useCallback((e: DragEvent<HTMLDivElement>) => {
|
| 22 |
+
e.preventDefault();
|
| 23 |
+
const dropped = e.dataTransfer.files?.[0];
|
| 24 |
+
if (dropped) setFile(dropped);
|
| 25 |
+
}, []);
|
| 26 |
+
|
| 27 |
+
const onFileChange = useCallback<
|
| 28 |
+
React.ChangeEventHandler<HTMLInputElement>
|
| 29 |
+
>((e) => {
|
| 30 |
+
const chosen = e.target.files?.[0];
|
| 31 |
+
if (chosen) setFile(chosen);
|
| 32 |
+
}, []);
|
| 33 |
+
|
| 34 |
+
/* ------------------------------------------------------------------- */
|
| 35 |
+
return (
|
| 36 |
+
<PageContainer>
|
| 37 |
+
<div className="mx-auto max-w-3xl text-center px-4 py-10">
|
| 38 |
+
{/* Title & intro copy */}
|
| 39 |
+
<Heading level={2}>Upload Your Crisis Map</Heading>
|
| 40 |
+
|
| 41 |
+
<p className="mt-3 text-gray-700 leading-relaxed">
|
| 42 |
+
This app evaluates how well multimodal AI models turn emergency maps
|
| 43 |
+
into meaningful text. Upload your map, let the AI generate a
|
| 44 |
+
description, then review and rate the result based on your expertise.
|
| 45 |
+
</p>
|
| 46 |
+
|
| 47 |
+
{/* “More »” link */}
|
| 48 |
+
<div className="mt-2">
|
| 49 |
+
<Link
|
| 50 |
+
to="/help"
|
| 51 |
+
className="text-ifrcRed text-xs hover:underline flex items-center gap-1"
|
| 52 |
+
>
|
| 53 |
+
More <ArrowRightLineIcon className="w-3 h-3" />
|
| 54 |
+
</Link>
|
| 55 |
+
</div>
|
| 56 |
+
|
| 57 |
+
{/* Drop-zone */}
|
| 58 |
+
<div
|
| 59 |
+
className="mt-10 border-2 border-dashed border-gray-300 bg-gray-50
|
| 60 |
+
rounded-xl p-10 flex flex-col items-center gap-4
|
| 61 |
+
hover:bg-gray-100 transition-colors"
|
| 62 |
+
onDragOver={(e) => e.preventDefault()}
|
| 63 |
+
onDrop={onDrop}
|
| 64 |
+
>
|
| 65 |
+
<UploadCloudLineIcon className="w-10 h-10 text-ifrcRed" />
|
| 66 |
+
|
| 67 |
+
{file ? (
|
| 68 |
+
<p className="text-sm font-medium text-gray-800">
|
| 69 |
+
Selected file: {file.name}
|
| 70 |
+
</p>
|
| 71 |
+
) : (
|
| 72 |
+
<>
|
| 73 |
+
<p className="text-sm text-gray-600">
|
| 74 |
+
Drag & Drop a file here
|
| 75 |
+
</p>
|
| 76 |
+
<p className="text-xs text-gray-500">or</p>
|
| 77 |
+
|
| 78 |
+
{/* File-picker button */}
|
| 79 |
+
<input
|
| 80 |
+
type="file"
|
| 81 |
+
accept="image/*"
|
| 82 |
+
onChange={onFileChange}
|
| 83 |
+
className="hidden"
|
| 84 |
+
id="file-upload"
|
| 85 |
+
/>
|
| 86 |
+
<label htmlFor="file-upload" className="cursor-pointer">
|
| 87 |
+
<button className="px-4 py-2 border border-gray-300 rounded-md hover:bg-gray-50">
|
| 88 |
+
Upload
|
| 89 |
+
</button>
|
| 90 |
+
</label>
|
| 91 |
+
</>
|
| 92 |
+
)}
|
| 93 |
+
</div>
|
| 94 |
+
|
| 95 |
+
{/* Generate button */}
|
| 96 |
+
<Button
|
| 97 |
+
name="generate"
|
| 98 |
+
className="mt-8"
|
| 99 |
+
disabled={!file}
|
| 100 |
+
onClick={() => {
|
| 101 |
+
/* TODO: POST /maps, then POST /maps/{id}/caption */
|
| 102 |
+
}}
|
| 103 |
+
>
|
| 104 |
+
Generate
|
| 105 |
+
</Button>
|
| 106 |
+
</div>
|
| 107 |
+
</PageContainer>
|
| 108 |
+
);
|
| 109 |
+
}
|