summaryrefslogtreecommitdiff
path: root/frontend
diff options
context:
space:
mode:
Diffstat (limited to 'frontend')
-rw-r--r--frontend/.dockerignore10
-rw-r--r--frontend/.env.docker (renamed from frontend/.env)0
-rw-r--r--frontend/Dockerfile28
-rw-r--r--frontend/eslint.config.ts (renamed from frontend/eslint.config.js)0
-rw-r--r--frontend/nginx.conf27
-rw-r--r--frontend/src/components/ErrorBanner.tsx5
-rw-r--r--frontend/src/components/Sidebar.tsx8
-rw-r--r--frontend/src/pages/Login.tsx13
-rw-r--r--frontend/src/styles/login.css1
9 files changed, 81 insertions, 11 deletions
diff --git a/frontend/.dockerignore b/frontend/.dockerignore
new file mode 100644
index 0000000..9f8fe0e
--- /dev/null
+++ b/frontend/.dockerignore
@@ -0,0 +1,10 @@
+**/.env
+**/.git
+**/.gitignore
+**/Dockerfile*
+**/node_modules
+**/npm-debug.log
+**/build
+**/dist
+LICENSE
+README.md \ No newline at end of file
diff --git a/frontend/.env b/frontend/.env.docker
index cd41370..cd41370 100644
--- a/frontend/.env
+++ b/frontend/.env.docker
diff --git a/frontend/Dockerfile b/frontend/Dockerfile
new file mode 100644
index 0000000..791cd87
--- /dev/null
+++ b/frontend/Dockerfile
@@ -0,0 +1,28 @@
+# syntax=docker/dockerfile:1
+
+# BUILD STAGE
+FROM node:22-alpine AS builder
+
+WORKDIR /app
+
+COPY package*.json ./
+
+RUN npm ci
+
+# Copy .env.docker as .env for Vite
+COPY .env.docker .env
+
+COPY . .
+
+# Compile TypeScript & create optimized production build
+RUN npm run build
+
+# PRODUCTION STAGE
+FROM nginx:alpine AS production
+
+# Copy production build files to nginx
+COPY --from=builder /app/dist /usr/share/nginx/html
+
+COPY nginx.conf /etc/nginx/conf.d/default.conf
+
+EXPOSE 80 \ No newline at end of file
diff --git a/frontend/eslint.config.js b/frontend/eslint.config.ts
index 6343bef..6343bef 100644
--- a/frontend/eslint.config.js
+++ b/frontend/eslint.config.ts
diff --git a/frontend/nginx.conf b/frontend/nginx.conf
new file mode 100644
index 0000000..151afe8
--- /dev/null
+++ b/frontend/nginx.conf
@@ -0,0 +1,27 @@
+# For Docker Compose
+server {
+ listen 80;
+ server_name localhost;
+ root /usr/share/nginx/html;
+ index index.html;
+
+ # Enable gzip compression
+ gzip on;
+ gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
+
+ # SPA routing - send all requests to index.html
+ location / {
+ try_files $uri $uri/ /index.html;
+ }
+
+ # Cache static assets
+ location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
+ expires 1y;
+ add_header Cache-Control "public, immutable";
+ }
+
+ # Security headers
+ add_header X-Frame-Options "SAMEORIGIN" always;
+ add_header X-Content-Type-Options "nosniff" always;
+ add_header X-XSS-Protection "1; mode=block" always;
+} \ No newline at end of file
diff --git a/frontend/src/components/ErrorBanner.tsx b/frontend/src/components/ErrorBanner.tsx
new file mode 100644
index 0000000..fdb3bf1
--- /dev/null
+++ b/frontend/src/components/ErrorBanner.tsx
@@ -0,0 +1,5 @@
+function ErrorBanner({ message }: { message: string }) {
+ return <div className="error-message">{message}</div>;
+}
+
+export default ErrorBanner;
diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx
index 3760d4e..bdfadad 100644
--- a/frontend/src/components/Sidebar.tsx
+++ b/frontend/src/components/Sidebar.tsx
@@ -4,6 +4,7 @@ import "../styles/button.css";
import "../styles/sidebar.css";
import type { Robot } from "../types/robot";
import AddRobotForm from "./AddRobotForm";
+import ErrorBanner from "./ErrorBanner";
import RobotList from "./RobotList";
import SimulationActions from "./SimulationActions";
@@ -58,10 +59,8 @@ function Sidebar({
/>
)}
- {errorMessage && (
- <div className="error-message">{errorMessage}</div>
- )}
-
+ {errorMessage && <ErrorBanner message={errorMessage} />}
+
<SimulationActions
activeSimulation={activeSimulation}
apiUrl={API_URL}
@@ -81,4 +80,3 @@ function Sidebar({
}
export default Sidebar;
-
diff --git a/frontend/src/pages/Login.tsx b/frontend/src/pages/Login.tsx
index 11b7c11..463af8e 100644
--- a/frontend/src/pages/Login.tsx
+++ b/frontend/src/pages/Login.tsx
@@ -1,6 +1,8 @@
import { useEffect, useState, type ChangeEvent, type FormEvent } from "react";
import { useNavigate } from "react-router-dom";
import { FadeLoader } from "react-spinners";
+import mapBackground from "../assets/map-blurred.png";
+import ErrorBanner from "../components/ErrorBanner";
import Logo from "../components/Logo";
import API_URL from "../config";
import "../styles/button.css";
@@ -84,7 +86,10 @@ function Login() {
}, []);
return (
- <div className="login-page">
+ <div
+ className="login-page"
+ style={{ backgroundImage: `url(${mapBackground})` }}
+ >
<div className="login-card">
<Logo />
<p className="subtitle">🤖 Please log in to use the app 🤖</p>
@@ -119,10 +124,8 @@ function Login() {
/>
</div>
- {errorMessage && (
- <div className="error-message">{errorMessage}</div>
- )}
-
+ {errorMessage && <ErrorBanner message={errorMessage} />}
+
<button
className="btn btn-start"
disabled={isLoading}
diff --git a/frontend/src/styles/login.css b/frontend/src/styles/login.css
index 4592016..1da01ea 100644
--- a/frontend/src/styles/login.css
+++ b/frontend/src/styles/login.css
@@ -1,5 +1,4 @@
.login-page {
- background-image: url("../src/assets/map-blurred.png");
background-position: center;
background-repeat: no-repeat;
background-size: cover;