Skip to content
Open
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
36 changes: 36 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"root": true,
"env": {
"browser": true,
"es2020": true,
"node": true
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
},
"plugins": [
"react",
"react-hooks",
"@typescript-eslint"
],
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:@typescript-eslint/recommended"
],
"settings": {
"react": {
"version": "detect"
}
},
"rules": {
"react/react-in-jsx-scope": "off",
"react/jsx-uses-react": "off"
}
}
19 changes: 19 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Dockerfile for Hello World React+Vite application
FROM node:18-alpine

WORKDIR /app

# Copy package manifest first for layer caching
COPY package.json ./

# Install dependencies
RUN npm install

# Copy the rest of the source code
COPY . .

# Expose Vite dev server port
EXPOSE 5173

# Start the Vite dev server, binding to all interfaces
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]
83 changes: 67 additions & 16 deletions RUNNING.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,84 @@
# Running the Todo API
# Hello World React+Vite App

## TEAM_BRIEF
stack: TypeScript/React+Vite
test_runner: npx vitest run
lint_tool: npx eslint src/ --ext .ts,.tsx
coverage_tool: vitest --coverage
coverage_threshold: 80
coverage_applies: true

## Prerequisites

- Python 3.10 or later
- **Node.js** >= 18
- **npm** >= 9
- **Docker** and **Docker Compose** (optional, for containerised workflow)

## Install dependencies
## Local Setup (without Docker)

```bash
pip install fastapi uvicorn pydantic
```
# Install dependencies
npm install

For running the test suite you will also need:
# Start the development server
npm run dev
# App available at http://localhost:5173

```bash
pip install httpx pytest
# Run tests
npm test

# Run tests in watch mode
npm run test:watch

# Run tests with coverage
npm run test:coverage

# Run linter
npm run lint

# Build for production
npm run build
```

## Start the server
## Docker Setup

```bash
uvicorn main:app --reload --host 0.0.0.0 --port 8000
```
# Build and start the container
docker compose up --build

The API will be available at <http://localhost:8000>.
# App available at http://localhost:5173

Interactive docs are served at <http://localhost:8000/docs>.
# Run tests inside the container
docker compose exec app npm test

## Run the tests
# Run linter inside the container
docker compose exec app npm run lint

# Stop and clean up
docker compose down
```

## Project Structure

```bash
pytest tests/
```
├── index.html # HTML entry point for Vite
├── src/
│ ├── main.tsx # React DOM render entry
│ ├── App.tsx # Main App component (renders Hello World)
│ ├── App.test.tsx # Test suite for App component
│ ├── index.css # Global styles (centred layout)
│ └── setupTests.ts # Vitest setup (jest-dom matchers)
├── package.json # Dependencies and scripts
├── tsconfig.json # TypeScript configuration
├── vite.config.ts # Vite + Vitest configuration
├── .eslintrc.json # ESLint configuration
├── Dockerfile # Container image definition
└── docker-compose.yml # Docker Compose service definition
```

## Acceptance Criteria

1. `npm test` passes — confirms "Hello World" renders correctly
2. The app displays "Hello World" in a centred `<h1>` element
3. `npm run lint` completes with no errors
4. Styles from `src/index.css` are applied (flexbox centering)
13 changes: 13 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
version: "3.8"

services:
app:
build: .
ports:
- "5173:5173"
volumes:
- ./src:/app/src
- ./index.html:/app/index.html
- ./vite.config.ts:/app/vite.config.ts
environment:
- NODE_ENV=development
35 changes: 35 additions & 0 deletions frontend/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"env": {
"browser": true,
"es2020": true,
"node": true
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
},
"plugins": [
"react",
"react-hooks",
"@typescript-eslint"
],
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:@typescript-eslint/recommended"
],
"settings": {
"react": {
"version": "detect"
}
},
"rules": {
"react/react-in-jsx-scope": "off",
"react/jsx-uses-react": "off"
}
}
19 changes: 19 additions & 0 deletions frontend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Stage 1: Development
FROM node:18-alpine AS dev

WORKDIR /app

# Copy package files first for better layer caching
COPY package.json ./

# Install dependencies
RUN npm install

# Copy remaining source code
COPY . .

# Expose Vite dev server port
EXPOSE 5173

# Start dev server
CMD ["npm", "run", "dev"]
18 changes: 18 additions & 0 deletions frontend/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
version: "3.8"

services:
app:
build:
context: .
dockerfile: Dockerfile
target: dev
ports:
- "5173:5173"
volumes:
- ./src:/app/src
- ./public:/app/public
- ./vite.config.ts:/app/vite.config.ts
- ./tsconfig.json:/app/tsconfig.json
- ./tsconfig.node.json:/app/tsconfig.node.json
environment:
- NODE_ENV=development
35 changes: 35 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "hello-world-react-vite",
"private": true,
"version": "1.0.0",
"type": "module",
"description": "A modern React+Vite+TypeScript Hello World application",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"test": "vitest run",
"test:watch": "vitest",
"lint": "eslint src/ --ext .ts,.tsx"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@testing-library/jest-dom": "^6.1.4",
"@testing-library/react": "^14.1.2",
"@types/react": "^18.2.43",
"@types/react-dom": "^18.2.17",
"@typescript-eslint/eslint-plugin": "^6.14.0",
"@typescript-eslint/parser": "^6.14.0",
"@vitejs/plugin-react": "^4.2.1",
"eslint": "^8.55.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"jsdom": "^23.0.1",
"typescript": "^5.3.3",
"vite": "^5.0.8",
"vitest": "^1.1.0"
}
}
12 changes: 12 additions & 0 deletions frontend/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Hello World - React + Vite</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
17 changes: 17 additions & 0 deletions frontend/src/App.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import App from './App';

describe('App', () => {
it('renders Hello World', () => {
render(<App />);
const heading = screen.getByText('Hello World');
expect(heading).toBeInTheDocument();
});

it('renders an h1 element', () => {
render(<App />);
const heading = screen.getByRole('heading', { level: 1 });
expect(heading).toHaveTextContent('Hello World');
});
});
15 changes: 15 additions & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';

/**
* Root application component.
* Renders a simple "Hello World" heading.
*/
function App(): React.JSX.Element {
return (
<div className="app">
<h1>Hello World</h1>
</div>
);
}

export default App;
31 changes: 31 additions & 0 deletions frontend/src/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/* Global styles */

*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}

body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background-color: #f5f5f5;
color: #333;
}

.app {
text-align: center;
}

h1 {
font-size: 2.5rem;
font-weight: 700;
}
20 changes: 20 additions & 0 deletions frontend/src/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './index.css';

/**
* Application entry point.
* Mounts the root React component into the DOM element with id "root".
*/
const rootElement = document.getElementById('root');

if (!rootElement) {
throw new Error('Root element not found. Ensure public/index.html contains <div id="root"></div>.');
}

ReactDOM.createRoot(rootElement).render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
5 changes: 5 additions & 0 deletions frontend/src/setupTests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/**
* Test setup file.
* Imports jest-dom matchers so they are available in all test files.
*/
import '@testing-library/jest-dom';
1 change: 1 addition & 0 deletions frontend/src/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="vite/client" />
Loading