From 49d4c5e69be0462cd95335b5d417d8804cb54226 Mon Sep 17 00:00:00 2001 From: gitea_admin Date: Fri, 6 Feb 2026 17:16:27 +0000 Subject: [PATCH] source dir --- src/App.css | 176 ++++++++++++++++++++++++++++++++++++++++++++++++ src/App.jsx | 182 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/api.js | 27 ++++++++ src/index.css | 68 +++++++++++++++++++ src/main.jsx | 10 +++ 5 files changed, 463 insertions(+) create mode 100644 src/App.css create mode 100644 src/App.jsx create mode 100644 src/api.js create mode 100644 src/index.css create mode 100644 src/main.jsx diff --git a/src/App.css b/src/App.css new file mode 100644 index 0000000..a405ba4 --- /dev/null +++ b/src/App.css @@ -0,0 +1,176 @@ +/* Simple styling for inventory app - no framework, just basic CSS */ + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 2rem; +} + +header { + text-align: center; + margin-bottom: 3rem; + border-bottom: 2px solid #646cff; + padding-bottom: 1rem; +} + +header h1 { + margin: 0; + color: #646cff; +} + +header p { + margin: 0.5rem 0 0 0; + color: #888; +} + +/* Form Section */ +.form-section { + background: #1a1a1a; + padding: 2rem; + border-radius: 8px; + margin-bottom: 2rem; +} + +.form-section h2 { + margin-top: 0; + color: #646cff; +} + +form { + display: flex; + flex-direction: column; + gap: 1rem; + max-width: 500px; +} + +.form-group { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 0.5rem; +} + +.form-group label { + font-weight: bold; + color: #fff; +} + +.form-group input { + width: 100%; + padding: 0.75rem; + border: 1px solid #444; + border-radius: 4px; + background: #2a2a2a; + color: #fff; + font-size: 1rem; +} + +.form-group input:focus { + outline: none; + border-color: #646cff; +} + +.form-group input:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +button[type="submit"] { + padding: 0.75rem 2rem; + background: #646cff; + color: white; + border: none; + border-radius: 4px; + font-size: 1rem; + font-weight: bold; + cursor: pointer; + transition: background 0.3s; +} + +button[type="submit"]:hover:not(:disabled) { + background: #535bf2; +} + +button[type="submit"]:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +/* List Section */ +.list-section { + background: #1a1a1a; + padding: 2rem; + border-radius: 8px; +} + +.list-section h2 { + margin-top: 0; + color: #646cff; +} + +.loading, .error, .empty { + text-align: center; + padding: 2rem; + font-size: 1.1rem; +} + +.loading { + color: #646cff; +} + +.error { + color: #ff6b6b; +} + +.empty { + color: #888; +} + +/* Table Styles */ +table { + width: 100%; + border-collapse: collapse; + margin-top: 1rem; +} + +thead { + background: #2a2a2a; +} + +th, td { + padding: 1rem; + text-align: left; + border-bottom: 1px solid #444; +} + +th { + font-weight: bold; + color: #646cff; +} + +td { + color: #fff; +} + +tbody tr:hover { + background: #2a2a2a; +} + +tbody tr:last-child td { + border-bottom: none; +} + +/* Responsive */ +@media (max-width: 768px) { + .container { + padding: 1rem; + } + + table { + font-size: 0.9rem; + } + + th, td { + padding: 0.5rem; + } +} diff --git a/src/App.jsx b/src/App.jsx new file mode 100644 index 0000000..59c98a8 --- /dev/null +++ b/src/App.jsx @@ -0,0 +1,182 @@ +// Single component app - list + form (minimal design) +import { useState, useEffect } from 'react'; +import { getItems, createItem } from './api'; +import './App.css'; + +function App() { + const [items, setItems] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [formData, setFormData] = useState({ + name: '', + quantity: '', + price: '' + }); + const [submitting, setSubmitting] = useState(false); + + // Fetch items on mount + useEffect(() => { + fetchItems(); + }, []); + + const fetchItems = async () => { + try { + setLoading(true); + setError(null); + const data = await getItems(); + setItems(data); + } catch (err) { + setError('Failed to load items: ' + err.message); + console.error('Error fetching items:', err); + } finally { + setLoading(false); + } + }; + + const handleInputChange = (e) => { + const { name, value } = e.target; + setFormData(prev => ({ + ...prev, + [name]: value + })); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + + // Basic validation + if (!formData.name.trim()) { + alert('Name is required'); + return; + } + if (formData.quantity === '' || formData.quantity < 0) { + alert('Valid quantity is required'); + return; + } + if (formData.price === '' || formData.price < 0) { + alert('Valid price is required'); + return; + } + + try { + setSubmitting(true); + await createItem({ + name: formData.name.trim(), + quantity: parseInt(formData.quantity), + price: parseFloat(formData.price) + }); + + // Reset form + setFormData({ name: '', quantity: '', price: '' }); + + // Refresh list + await fetchItems(); + + alert('Item added successfully!'); + } catch (err) { + alert('Failed to add item: ' + err.message); + console.error('Error creating item:', err); + } finally { + setSubmitting(false); + } + }; + + return ( +
+
+

Inventory Management

+

Simple 2-tier app for DevOps demo

+
+ + {/* Add Item Form */} +
+

Add New Item

+
+
+ + +
+
+ + +
+
+ + +
+ +
+
+ + {/* Items List */} +
+

Items List

+ + {loading &&

Loading items...

} + + {error &&

{error}

} + + {!loading && !error && items.length === 0 && ( +

No items found. Add one above!

+ )} + + {!loading && !error && items.length > 0 && ( + + + + + + + + + + + {items.map(item => ( + + + + + + + ))} + +
IDNameQuantityPrice
{item.id}{item.name}{item.quantity}${parseFloat(item.price).toFixed(2)}
+ )} +
+
+ ); +} + +export default App; diff --git a/src/api.js b/src/api.js new file mode 100644 index 0000000..7cbe56d --- /dev/null +++ b/src/api.js @@ -0,0 +1,27 @@ +// API service for inventory management +import axios from 'axios'; + +const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3000'; + +// Create axios instance with base configuration +const api = axios.create({ + baseURL: API_URL, + timeout: 5000, + headers: { + 'Content-Type': 'application/json' + } +}); + +// Get all items +export const getItems = async () => { + const response = await api.get('/api/items'); + return response.data; +}; + +// Create new item +export const createItem = async (itemData) => { + const response = await api.post('/api/items', itemData); + return response.data; +}; + +export default api; diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..08a3ac9 --- /dev/null +++ b/src/index.css @@ -0,0 +1,68 @@ +:root { + font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/src/main.jsx b/src/main.jsx new file mode 100644 index 0000000..b9a1a6d --- /dev/null +++ b/src/main.jsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App.jsx' + +createRoot(document.getElementById('root')).render( + + + , +)