Skip to content

Commit

Permalink
Merge pull request #8 from Vizzuality/SS-46-basemap-with-settings-cen…
Browse files Browse the repository at this point in the history
…tred-on-south-sudan

Display a basemap
  • Loading branch information
clementprdhomme authored Oct 21, 2024
2 parents 170c8f2 + 7ceaf29 commit 9fa60e0
Show file tree
Hide file tree
Showing 21 changed files with 1,147 additions and 51 deletions.
48 changes: 45 additions & 3 deletions client/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,49 @@
{
"extends": [
"next/core-web-vitals",
"plugin:prettier/recommended",
"plugin:@typescript-eslint/recommended",
"next/core-web-vitals"
]
}
"plugin:import/recommended",
"plugin:import/typescript"
],
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint", "import"],
"rules": {
// Global rules here
},
"overrides": [
{
"files": ["**/*.ts", "**/*.tsx"],
"rules": {
"import/order": [
"warn",
{
"groups": [
"builtin", // Node.js built-in modules
"external", // External modules (npm packages)
"internal", // Internal modules
["parent", "sibling"], // Parent and sibling files
"index", // Index file imports
"object", // Imports that look like `import foo = require('foo')`
"type", // Type imports (useful in TypeScript)
"unknown" // For unknown types of imports
],
"newlines-between": "always", // Enforce new lines between groups
"alphabetize": {
"order": "asc",
"caseInsensitive": true
}
}
]
}
}
],
"settings": {
"import/resolver": {
"typescript": {
"alwaysTryTypes": true,
"project": "./tsconfig.json"
}
}
}
}
20 changes: 20 additions & 0 deletions client/components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "src/app/globals.css",
"baseColor": "neutral",
"cssVariables": false,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
}
}
14 changes: 13 additions & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,36 @@
"lint": "next lint"
},
"dependencies": {
"@radix-ui/react-slot": "^1.1.0",
"@t3-oss/env-nextjs": "0.11.1",
"@types/mapbox-gl": "3.4.0",
"class-variance-authority": "0.7.0",
"clsx": "2.1.1",
"express": "4.21.1",
"mapbox-gl": "3.7.0",
"next": "14.2.15",
"nuqs": "1.20.0",
"pino-http": "10.3.0",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-map-gl": "7.1.7",
"tailwind-merge": "2.5.4",
"tailwindcss-animate": "1.0.7",
"typescript-eslint": "8.9.0",
"zod": "3.23.8"
},
"devDependencies": {
"@svgr/webpack": "^8.1.0",
"@svgr/webpack": "8.1.0",
"@types/node": "22.7.6",
"@types/react": "18.3.1",
"@types/react-dom": "18.3.1",
"@typescript-eslint/eslint-plugin": "8.9.0",
"@typescript-eslint/parser": "8.9.0",
"eslint": "8.57.0",
"eslint-config-next": "14.2.15",
"eslint-config-prettier": "9.1.0",
"eslint-import-resolver-typescript": "3.6.3",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-prettier": "5.2.1",
"jiti": "1.21.6",
"postcss": "^8",
Expand Down
Binary file removed client/src/app/favicon.ico
Binary file not shown.
31 changes: 5 additions & 26 deletions client/src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,11 @@
@tailwind components;
@tailwind utilities;

:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
}

@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
}
}

body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(
to bottom,
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
@apply text-rhino-blue-950;
}

@layer utilities {
.text-balance {
text-wrap: balance;
@layer base {
:root {
--radius: 0.5rem
}
}
}
25 changes: 22 additions & 3 deletions client/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,37 @@
import { Jost, DM_Serif_Text } from "next/font/google";

import type { Metadata } from "next";

import "./globals.css";

export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
title: {
template: "%s | Hydrological Information Management Systems",
default: "Hydrological Information Management Systems",
},
description:
"Pilot for flood and drought hazard maps in South Sudan, enhancing access to water management data and supporting informed decision-making.",
};

const jost = Jost({
subsets: ["latin"],
weight: ["300", "400", "600", "700", "800"],
variable: "--font-jost",
});

const dmSerifText = DM_Serif_Text({
subsets: ["latin"],
weight: ["400"],
variable: "--font-dm-serif-text",
});

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<html lang="en" className={`${jost.variable} ${dmSerifText.variable}`}>
<body>{children}</body>
</html>
);
Expand Down
18 changes: 15 additions & 3 deletions client/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
import Image from "next/image";
import { Metadata } from "next";
import dynamic from "next/dynamic";

// By forcing the map to load in the client, we can perform some media queries immediately. Without
// this, the map would still be loaded in the client only anyway.
const Map = dynamic(() => import("@/components/map"), { ssr: false });

export const metadata: Metadata = {
alternates: {
// Make sure that the query strings can be ignored by search engines
canonical: "/",
},
};

export default function Home() {
return (
<main className="flex min-h-screen flex-col items-center p-24">
<div>WIMS South Sudan</div>
<main className="h-svh w-svw">
<Map />
</main>
);
}
16 changes: 16 additions & 0 deletions client/src/components/map/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { LngLatLike } from "react-map-gl";

export const DEFAULT_BOUNDS: [LngLatLike, LngLatLike] = [
[23.4392, 3.4882], // Southwest corner (west, south)
[35.95, 12.2212], // Northeast corner (east, north)
];

export const DESKTOP_MAX_BOUNDS: [LngLatLike, LngLatLike] = [
[14, 1],
[44, 15],
];

export const MOBILE_MAX_BOUNDS: [LngLatLike, LngLatLike] = [
[19, -8],
[39, 23],
];
40 changes: 40 additions & 0 deletions client/src/components/map/controls.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { useCallback } from "react";
import { useMap } from "react-map-gl";

import { Button } from "@/components/ui/button";
import MinusIcon from "@/svgs/minus.svg";
import PlusIcon from "@/svgs/plus.svg";

const Controls = () => {
const { current: map } = useMap();

const onClickZoomIn = useCallback(() => map?.zoomIn(), [map]);
const onClickZoomOut = useCallback(() => map?.zoomOut(), [map]);

return (
<div className="absolute bottom-6 right-10 z-10 flex flex-col gap-px">
<Button
type="button"
variant="yellow"
size="icon"
className="hidden xl:inline-flex"
onClick={onClickZoomIn}
>
<span className="sr-only">Zoom in</span>
<PlusIcon aria-hidden />
</Button>
<Button
type="button"
variant="yellow"
size="icon"
className="hidden xl:inline-flex"
onClick={onClickZoomOut}
>
<span className="sr-only">Zoom out</span>
<MinusIcon aria-hidden />
</Button>
</div>
);
};

export default Controls;
49 changes: 49 additions & 0 deletions client/src/components/map/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"use client";

import { useCallback, useRef } from "react";
import ReactMapGL from "react-map-gl";

import { env } from "@/env";
import useBreakpoint from "@/hooks/use-breakpoint";
import useMapBounds from "@/hooks/use-map-bounds";

import { DESKTOP_MAX_BOUNDS, MOBILE_MAX_BOUNDS } from "./constants";
import Controls from "./controls";

import type { MapRef, LngLatLike } from "react-map-gl";

import "mapbox-gl/dist/mapbox-gl.css";

const Map = () => {
const isDesktop = useBreakpoint("xl", false, true);
const mapRef = useRef<MapRef>(null);

const [bounds, setBounds] = useMapBounds();

const onMove = useCallback(() => {
if (mapRef.current) {
setBounds(mapRef.current.getBounds()?.toArray() as [LngLatLike, LngLatLike]);
}
}, [mapRef, setBounds]);

return (
<ReactMapGL
ref={mapRef}
mapboxAccessToken={env.NEXT_PUBLIC_MAPBOX_TOKEN}
initialViewState={{
bounds: bounds,
fitBoundsOptions: {
padding: isDesktop ? 100 : 20,
},
}}
maxBounds={isDesktop ? DESKTOP_MAX_BOUNDS : MOBILE_MAX_BOUNDS}
style={{ width: "100%", height: "100%" }}
mapStyle={env.NEXT_PUBLIC_MAPBOX_STYLE}
onMove={onMove}
>
<Controls />
</ReactMapGL>
);
};

export default Map;
45 changes: 45 additions & 0 deletions client/src/components/ui/button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import * as React from "react";

import { cn } from "@/lib/utils";

const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm transition-colors focus-visible:outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-6 [&_svg]:shrink-0",
{
variants: {
variant: {
default:
"bg-neutral-900 text-neutral-50 hover:bg-neutral-900/90 ring-offset-white focus-visible:ring-neutral-950",
yellow:
"bg-supernova-yellow-400 hover:bg-supernova-yellow-300 text-casper-blue-950 focus-visible:ring-casper-blue-400",
},
size: {
default: "h-10 px-4 py-2",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
);

export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return (
<Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />
);
},
);
Button.displayName = "Button";

export { Button, buttonVariants };
Loading

0 comments on commit 9fa60e0

Please sign in to comment.