Skip to content

Commit

Permalink
Merge pull request #7 from dockersamples/add-web-demo-client
Browse files Browse the repository at this point in the history
Add web demo client
  • Loading branch information
mikesir87 authored Dec 23, 2024
2 parents 20a0dae + 7f8c686 commit fed72ad
Show file tree
Hide file tree
Showing 26 changed files with 2,766 additions and 22 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ To start the app, follow these steps:
yarn dev
```

4. Once everything is up and running, you can open the demo client at http://localhost:5173

### Debugging the application

The project contains configuration for VS Code to enable quick debgging. Once the app is running, you can start a debug session by using the **Debug** task in the "Run and Debug" panel. This currently only works when the app is running natively on the machine.
Expand Down Expand Up @@ -84,6 +86,7 @@ $ yarn integration-test

Once the development environment is up and running, the following URLs can be leveraged:

- [http://localhost:5173](http://localhost:5173) - a simple React app that provides the ability to interact with the API via a web interface (helpful during demos)
- [http://localhost:5050](http://localhost:5050) - [pgAdmin](https://www.pgadmin.org/) to visualize the database. Login using the password `postgres` (configured in the Compose file)
- [http://localhost:8080](http://localhost:8080) - [kafbat](https://github.com/kafbat/kafka-ui) to visualize the Kafka cluster

Expand Down
8 changes: 8 additions & 0 deletions compose.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
services:
demo-client:
image: node:lts-slim
working_dir: /usr/local/app
command: yarn dev
ports:
- 5173:5173
volumes:
- ./dev/webapp:/usr/local/app

############## DATABASE AND VISUALIZER ##############
postgres:
Expand Down
3 changes: 2 additions & 1 deletion dev/db/1-create-schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
upc VARCHAR(12) NOT NULL UNIQUE,
price DECIMAL(10, 2) NOT NULL
price DECIMAL(10, 2) NOT NULL,
has_image BOOLEAN NOT NULL DEFAULT FALSE
);
16 changes: 16 additions & 0 deletions dev/inventory-mocks/mappings/inventory1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"request": {
"method": "GET",
"url": "/api/inventory?upc=100000000001"
},
"response": {
"status": 200,
"jsonBody": {
"id": 1231,
"quantity": 12
},
"headers": {
"Content-Type": "application/json"
}
}
}
Binary file removed dev/scripts/docker-logo-blue.png
Binary file not shown.
Binary file added dev/scripts/product-image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions dev/scripts/replace-file.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/bash

id=$1

if [ -z "$id" ]; then
echo "id is not set. Exiting..."
exit 1
fi

aws --endpoint=http://localhost:4566 s3 cp product-image.png s3://product-images/${id}/product.png
2 changes: 1 addition & 1 deletion dev/scripts/upload-file.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ if [ -z "$id" ]; then
exit 1
fi

curl -F "file=@docker-logo-blue.png" "http://localhost:3000/api/products/${id}/image"
curl -F "file=@product-image.png" "http://localhost:3000/api/products/${id}/image"
24 changes: 24 additions & 0 deletions dev/webapp/.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?
8 changes: 8 additions & 0 deletions dev/webapp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# React + Vite

This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.

Currently, two official plugins are available:

- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
38 changes: 38 additions & 0 deletions dev/webapp/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import js from "@eslint/js";
import globals from "globals";
import react from "eslint-plugin-react";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";

export default [
{ ignores: ["dist"] },
{
files: ["**/*.{js,jsx}"],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: "latest",
ecmaFeatures: { jsx: true },
sourceType: "module",
},
},
settings: { react: { version: "18.3" } },
plugins: {
react,
"react-hooks": reactHooks,
"react-refresh": reactRefresh,
},
rules: {
...js.configs.recommended.rules,
...react.configs.recommended.rules,
...react.configs["jsx-runtime"].rules,
...reactHooks.configs.recommended.rules,
"react/jsx-no-target-blank": "off",
"react-refresh/only-export-components": [
"warn",
{ allowConstantExport: true },
],
},
},
];
12 changes: 12 additions & 0 deletions dev/webapp/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>Demo client</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
28 changes: 28 additions & 0 deletions dev/webapp/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "webapp",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "yarn install && vite --host 0.0.0.0",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@eslint/js": "^9.15.0",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.4",
"eslint": "^9.15.0",
"eslint-plugin-react": "^7.37.2",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.14",
"globals": "^15.12.0",
"vite": "^6.0.1"
}
}
23 changes: 23 additions & 0 deletions dev/webapp/src/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}

table {
border-collapse: collapse;
}

table thead tr th {
border-bottom: 1px solid #ccc;
}

table tbody td {
padding: 0.5rem 1rem;
font-size: 0.9rem;
}

td img {
max-height: 60px;
}
80 changes: 80 additions & 0 deletions dev/webapp/src/App.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { useCallback, useEffect, useState } from "react";
import "./App.css";
import { ProductRow } from "./ProductRow";

function App() {
const [lastRequest, setLastRequest] = useState(null);
const [catalog, setCatalog] = useState(null);

const fetchCatalog = useCallback(() => {
fetch("/api/products")
.then((response) => response.json())
.then((data) => {
setCatalog(data);
setLastRequest({
method: "GET",
url: "/api/products",
status: 200,
response: data,
});
});
}, []);

const createProduct = useCallback(() => {
const body = {
name: "New Product",
price: 100,
upc: 100000000000 + catalog.length + 1,
};

fetch("/api/products", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
}).then(fetchCatalog);
}, [catalog, fetchCatalog]);

useEffect(() => {
fetchCatalog();
}, [fetchCatalog]);

return (
<>
<h1>Demo catalog client</h1>

<p>
<button onClick={fetchCatalog}>Refresh catalog</button>
&nbsp;
<button onClick={createProduct}>Create product</button>
</p>

{catalog ? (
<table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Price</th>
<th>UPC</th>
<th>Inventory</th>
<th>Image</th>
</tr>
</thead>
<tbody>
{catalog.map((product) => (
<ProductRow
key={product.id}
product={product}
onChange={() => fetchCatalog()}
/>
))}
</tbody>
</table>
) : (
<p>Loading catalog...</p>
)}
</>
);
}

export default App;
64 changes: 64 additions & 0 deletions dev/webapp/src/ProductRow.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { useCallback, useState } from "react";
import productImage from "./product-image.png";

export function ProductRow({ product, onChange }) {
const [inventoryDetails, setInventoryDetails] = useState(null);

const fetchInventoryDetails = useCallback(() => {
fetch(`/api/products/${product.id}`)
.then((response) => response.json())
.then(({ inventory }) => setInventoryDetails(inventory))
.then(() => onChange());
}, [setInventoryDetails]);

const uploadImage = useCallback(() => {
fetch(productImage)
.then((r) => r.blob())
.then((fileBlob) => {
const file = new File([fileBlob], "product-image.png", {
type: "image/png",
});

const formData = new FormData();
formData.append("file", file);

fetch(`/api/products/${product.id}/image`, {
method: "POST",
body: formData,
}).then(() => onChange());
});
}, []);

return (
<tr>
<td>{product.id}</td>
<td>{product.name}</td>
<td>{product.price}</td>
<td>{product.upc}</td>
<td>
{inventoryDetails ? (
<>
{inventoryDetails.error ? (
<span className="error">{inventoryDetails.message}</span>
) : (
<span>{inventoryDetails.quantity}</span>
)}
</>
) : (
<button className="smaller" onClick={fetchInventoryDetails}>
Fetch
</button>
)}
</td>
<td>
{product.has_image ? (
<img src={`/api/products/${product.id}/image`} alt={product.name} />
) : (
<button className="smaller" onClick={uploadImage}>
Upload
</button>
)}
</td>
</tr>
);
}
Loading

0 comments on commit fed72ad

Please sign in to comment.