SCGR commited on
Commit
66e0c05
·
1 Parent(s): 833e3f8

Layout & Upload page skeleton

Browse files
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() // listens on :8080
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
- import { Button } from "@ifrc-go/ui";
 
 
 
 
 
 
2
 
3
- function App() {
4
- return (
5
- <main className="flex flex-col items-center gap-6 p-8">
6
- <h1 className="text-3xl font-bold text-ifrcRed">PromptAid Vision</h1>
7
- <Button name="ifrc-button" size={2} variant="primary">IFRC Button</Button>
8
- </main>
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 &amp; 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 &amp; 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
+ }