Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions frontend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
25 changes: 25 additions & 0 deletions frontend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Stage 1: Build React App
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 2: Serve via NGINX
FROM nginx:alpine

# Remove default nginx static assets
RUN rm -rf /usr/share/nginx/html/*

# Copy built frontend
COPY --from=builder /app/dist /usr/share/nginx/html

# Copy custom NGINX configuration
COPY nginx.conf /etc/nginx/conf.d/default.conf

# Expose port
EXPOSE 80

# Start NGINX
CMD ["nginx", "-g", "daemon off;"]
78 changes: 78 additions & 0 deletions frontend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Backend Architect Portfolio

A modern, high-performance portfolio application designed with a cyberpunk and terminal-inspired aesthetic, tailored for a Backend Systems Architect. This project features a robust, stateless architecture decoupled from the backend, comprehensive testing, and a secure administration panel.

## 🚀 Features

- **Decoupled Architecture**: Fully stateless React frontend designed to be served via NGINX in a Kubernetes cluster.
- **Dynamic Content Management**: All content (titles, subtitles, tech stack, experience, etc.) is fetched via API and can be modified in real-time through the Admin Panel.
- **Secured Admin Dashboard**: Protected routes using **JSON Web Tokens (JWT)** and Role-Based Access Control (RBAC).
- **State Management**: Built with **Redux Toolkit** and **RTK Query** for efficient data fetching, caching, and state synchronization.
- **Testing Suite**: Comprehensive Unit, Integration, and Regression (Snapshot) testing powered by **Vitest**, **React Testing Library**, and **Mock Service Worker (MSW)**.
- **CI/CD Ready**: Configured with GitHub Actions for automated testing, code quality checks via **SonarCloud**, and automated deployment.

## 🛠️ Technology Stack

- **Core**: React 19, Vite
- **Styling**: Tailwind CSS (Custom Design System with CSS variables)
- **Routing**: React Router DOM v7
- **State/API**: Redux Toolkit (RTK Query)
- **Testing**: Vitest, React Testing Library, MSW
- **Security**: jwt-decode (Frontend JWT parsing)
- **Infrastructure**: Docker, NGINX, Kubernetes manifests ready

## 📦 Getting Started

### Prerequisites
- Node.js (v18 or higher recommended)
- npm

### Installation

1. Clone the repository and install dependencies:
```bash
npm install
```

### Running Locally (Development)

This project currently uses `json-server` to mock the future Java Quarkus backend. You need to run both the server and the Vite frontend.

1. **Start the Mock API Server**:
```bash
npm run server
```
*This will run `json-server` on port 3001 using `db.json`.*

2. **Start the Frontend Development Server** (in a new terminal):
```bash
npm run dev
```
*This will run the Vite server (usually on port 5173).*

### Testing

Run the full testing suite (Unit, Integration, and Snapshots):
```bash
npm run test
```
*The test suite automatically uses MSW to mock network requests, ensuring tests do not depend on the local json-server.*

## 🛡️ Admin Access

To access the administrative dashboard in the local development environment:
1. Navigate to `/login`
2. Use the default mock credentials:
- **Username**: `admin`
- **Password**: `root`
3. This will generate a mock JWT token and grant access to the `/admin` routes.

## 🏗️ Architecture & Deployment

The application is built to be deployed as a static site via an NGINX container.
- **Dockerfile**: Implements a multi-stage build. It builds the React app using Node, then copies the static assets into a lightweight NGINX alpine container.
- **nginx.conf**: Configured with robust security headers (CSP, X-Frame-Options) and handles routing for the React Single Page Application (SPA).
- **Kubernetes**: Manifests (`k8s/`) are provided for deploying the NGINX container as a stateless deployment.

---
*Architected and developed with a focus on clean code, testability, and resilient infrastructure.*
29 changes: 29 additions & 0 deletions frontend/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import { defineConfig, globalIgnores } from 'eslint/config'

export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{js,jsx}'],
extends: [
js.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: 'latest',
ecmaFeatures: { jsx: true },
sourceType: 'module',
},
},
rules: {
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
},
},
])
13 changes: 13 additions & 0 deletions frontend/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en" class="dark">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>tmp-app</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
30 changes: 30 additions & 0 deletions frontend/nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
server {
listen 80;
server_name localhost;

root /usr/share/nginx/html;
index index.html;

# Security Headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline' 'unsafe-eval'" always;

location / {
try_files $uri $uri/ /index.html;
}

# Cache static assets
location ~* \.(?:ico|css|js|gif|jpe?g|png|woff2?|eot|ttf|svg)$ {
expires 6M;
access_log off;
add_header Cache-Control "public, max-age=15552000, immutable";
}

# Deny access to hidden files
location ~ /\. {
deny all;
}
}
Loading