diff --git a/frontend/Dockerfile b/frontend/Dockerfile
new file mode 100644
index 0000000..085bdd9
--- /dev/null
+++ b/frontend/Dockerfile
@@ -0,0 +1,16 @@
+# ---- build stage ----
+FROM node:20.16-alpine AS build
+WORKDIR /app
+COPY package.json package-lock.json* ./
+RUN npm install
+COPY . .
+ARG VITE_API_BASE_URL
+ENV VITE_API_BASE_URL=$VITE_API_BASE_URL
+RUN npm run build
+
+# ---- serve stage ----
+FROM nginx:1.27-alpine AS serve
+COPY --from=build /app/dist /usr/share/nginx/html
+COPY nginx.conf /etc/nginx/conf.d/default.conf
+EXPOSE 80
+CMD ["nginx", "-g", "daemon off;"]
diff --git a/frontend/index.html b/frontend/index.html
new file mode 100644
index 0000000..0694783
--- /dev/null
+++ b/frontend/index.html
@@ -0,0 +1,11 @@
+
+
+
+
+ DocHub
+
+
+
+
+
+
diff --git a/frontend/nginx.conf b/frontend/nginx.conf
new file mode 100644
index 0000000..6ee6ec0
--- /dev/null
+++ b/frontend/nginx.conf
@@ -0,0 +1,18 @@
+server {
+ listen 80;
+ server_name _;
+ root /usr/share/nginx/html;
+ index index.html;
+
+ location / {
+ try_files $uri $uri/ /index.html;
+ }
+
+ location /api/ {
+ proxy_pass http://backend:8000/api/;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ }
+}
diff --git a/frontend/package.json b/frontend/package.json
new file mode 100644
index 0000000..ecba67e
--- /dev/null
+++ b/frontend/package.json
@@ -0,0 +1,29 @@
+{
+ "name": "dochub-frontend",
+ "private": true,
+ "version": "0.1.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc -b && vite build",
+ "preview": "vite preview",
+ "test": "vitest run"
+ },
+ "dependencies": {
+ "@monaco-editor/react": "4.6.0",
+ "react": "18.3.1",
+ "react-dom": "18.3.1"
+ },
+ "devDependencies": {
+ "@testing-library/jest-dom": "6.4.8",
+ "@testing-library/react": "16.0.0",
+ "@testing-library/user-event": "14.5.2",
+ "@types/react": "18.3.3",
+ "@types/react-dom": "18.3.0",
+ "@vitejs/plugin-react": "4.3.1",
+ "jsdom": "24.1.1",
+ "typescript": "5.5.4",
+ "vite": "5.4.1",
+ "vitest": "2.0.5"
+ }
+}
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
new file mode 100644
index 0000000..7dd74e3
--- /dev/null
+++ b/frontend/src/App.tsx
@@ -0,0 +1,7 @@
+export default function App() {
+ return (
+
+
DocHub
+
+ );
+}
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
new file mode 100644
index 0000000..abd10b8
--- /dev/null
+++ b/frontend/src/main.tsx
@@ -0,0 +1,9 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import App from './App';
+
+ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
+
+
+
+);
diff --git a/frontend/tests/App.test.tsx b/frontend/tests/App.test.tsx
new file mode 100644
index 0000000..8312a27
--- /dev/null
+++ b/frontend/tests/App.test.tsx
@@ -0,0 +1,8 @@
+import { render, screen } from '@testing-library/react';
+import { test, expect } from 'vitest';
+import App from '../src/App';
+
+test('renders the DocHub heading', () => {
+ render();
+ expect(screen.getByRole('heading', { name: 'DocHub' })).toBeInTheDocument();
+});
diff --git a/frontend/tests/setup.ts b/frontend/tests/setup.ts
new file mode 100644
index 0000000..bb02c60
--- /dev/null
+++ b/frontend/tests/setup.ts
@@ -0,0 +1 @@
+import '@testing-library/jest-dom/vitest';
diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json
new file mode 100644
index 0000000..1fcd70f
--- /dev/null
+++ b/frontend/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "types": ["vite/client", "vitest/globals", "@testing-library/jest-dom"]
+ },
+ "include": ["src", "tests", "vite.config.ts"]
+}
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
new file mode 100644
index 0000000..0037f8a
--- /dev/null
+++ b/frontend/vite.config.ts
@@ -0,0 +1,11 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+
+export default defineConfig({
+ plugins: [react()],
+ test: {
+ globals: true,
+ environment: 'jsdom',
+ setupFiles: ['./tests/setup.ts'],
+ },
+});