diff --git a/README.md b/README.md index 42662f6..a6a55b1 100644 --- a/README.md +++ b/README.md @@ -1,69 +1,135 @@ -# React + TypeScript + Vite +## monitor-im-flur -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. +![Build Status](https://git.rivercry.com/wg/monitor-im-flur/badges/master/pipeline.svg) -Currently, two official plugins are available: +Hallway / common-area wall monitor dashboard. A single-page React + Vite app that surfaces useful household + transit + weather + fun data on a passive display. -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh +### Key Features +* Realtime-ish auto‑refresh via git commit hash file (`/git-hash.html`) – hot-reloads the deployed page when a new build is published. +* Dynamic theming (day / evening / night) based on current time. +* Public transport departures (KVV) for two nearby stops (IDs 7000044 & 7000045). +* Weather (current, hourly, daily min/max) via Open‑Meteo. +* Flatastic chores integration (tasks + flatmates) with API key. +* Home Assistant readings (tent temperature + humidity) displayed in a faux terminal with rotating shitposts. +* 4:20 easter egg card + Amogus sprite + other playful flourishes. +* Docker / Nginx static deployment image (`git.rivercry.com/wg/monitor-im-flur:latest`). +* Strict linting + formatting (ESLint AirBnB + Prettier + Biome optional). +* Zustand + RTK (toolkit present) state management (currently Zustand in active use). -## Expanding the ESLint configuration +### Tech Stack +* React 19 + TypeScript + Vite +* Zustand (with devtools) for app stores (weather, kvv, flatastic, home assistant) +* Nginx (static file serving) inside minimal Docker image +* Open‑Meteo, Flatastic, KVV, Home Assistant external APIs +* Git hash pipeline script to trigger client self‑reloads -If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: - -```js -export default tseslint.config([ - globalIgnores(["dist"]), - { - files: ["**/*.{ts,tsx}"], - extends: [ - // Other configs... - - // Remove tseslint.configs.recommended and replace with this - ...tseslint.configs.recommendedTypeChecked, - // Alternatively, use this for stricter rules - ...tseslint.configs.strictTypeChecked, - // Optionally, add this for stylistic rules - ...tseslint.configs.stylisticTypeChecked, - - // Other configs... - ], - languageOptions: { - parserOptions: { - project: ["./tsconfig.node.json", "./tsconfig.app.json"], - tsconfigRootDir: import.meta.dirname, - }, - // other options... - }, - }, -]); +### Project Structure (abridged) +``` +src/ + api/ # External data fetchers + components/ # UI building blocks (Cards, Timetable, Weather, Terminal, etc.) + store/ # Zustand stores encapsulating fetch + state + types/ # Type definitions for external data +pipeline/ + create-git-hash-html.sh # Injects current commit hash into dist +Dockerfile # Nginx static hosting +docker-compose.yml # Example runtime service definition ``` -You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: - -```js -// eslint.config.js -import reactX from "eslint-plugin-react-x"; -import reactDom from "eslint-plugin-react-dom"; - -export default tseslint.config([ - globalIgnores(["dist"]), - { - files: ["**/*.{ts,tsx}"], - extends: [ - // Other configs... - // Enable lint rules for React - reactX.configs["recommended-typescript"], - // Enable lint rules for React DOM - reactDom.configs.recommended, - ], - languageOptions: { - parserOptions: { - project: ["./tsconfig.node.json", "./tsconfig.app.json"], - tsconfigRootDir: import.meta.dirname, - }, - // other options... - }, - }, -]); +### Environment Configuration +Create a `.env` (or `.env.local`) for Vite with the following (only what you need): ``` +VITE_FLATTASTIC_API_KEY=your_flatastic_api_key +# (Planned) VITE_HOME_ASSISTANT_TOKEN=your_long_lived_token # NOTE: currently hardcoded – see Security section +``` + +Vite automatically exposes variables prefixed with `VITE_` to the client bundle. Do NOT place secrets without that prefix – but remember: anything in the client bundle is public. For sensitive data consider a tiny proxy backend instead of calling APIs directly from the browser. + +### Security Notice +`src/api/homeAssistant.ts` currently contains a hard‑coded long‑lived Home Assistant token. This should be refactored before any public deployment: +1. Remove the literal token from the repository. +2. Load it via environment variable during build (`import.meta.env.VITE_HOME_ASSISTANT_TOKEN`) OR +3. Prefer a minimal server proxy so the token never ships to the browser. + +### Development +Install dependencies (Bun is inferred from `bun.lock`, but npm/pnpm/yarn also work). +``` +bun install +``` +Run the dev server (HTTPS support possible with `vite-plugin-mkcert` if certificates are trusted): +``` +bun run dev +``` +Open: http://localhost:5173 + +### Lint & Format +``` +bun run lint +``` +Optionally run Biome (if desired): +``` +npx biome check --apply . +``` + +### Build +``` +bun run build +``` +Outputs production bundle to `dist/`. + +### Docker Image +Build locally: +``` +docker build -t monitor-im-flur:local . +``` +Run: +``` +docker run --rm -p 9123:80 monitor-im-flur:local +``` +Or use compose (uses published image): +``` +docker compose up -d +``` +Visit: http://localhost:9123 + +### Git Hash Auto‑Reload Mechanism +`pipeline/create-git-hash-html.sh` writes the current commit (`$GITHUB_SHA`) to `dist/git-hash.html` during CI. The dashboard polls `/git-hash.html` every 10s; when the value changes it performs `window.location.reload()`. Ensure your CI runs the script after `vite build` and before creating the Docker image. + +Pseudo CI step example: +``` +vite build +GITHUB_SHA=$(git rev-parse HEAD) ./pipeline/create-git-hash-html.sh +docker build -t git.rivercry.com/wg/monitor-im-flur:$(git rev-parse --short HEAD) . +``` + +### Data Sources +* KVV Departures: Public endpoint (JSON) for stop IDs 7000044 / 7000045. +* Weather: Open‑Meteo forecast API (lat 49.0094, lon 8.4044, Europe/Berlin TZ). +* Flatastic: Auth via `x-api-key` (user provided). +* Home Assistant: Long‑lived bearer token (refactor recommended). + +### Adding a New Card +1. Create a directory under `src/components//` with `NewCard.tsx` & optional `style.module.css`. +2. Wrap content with existing `Card` component (`icon` + `name` props). +3. Register inside `Dashboard.tsx` where layout lives (using `CardColumn` / `CardRow`). + +### State Management Notes +All data fetching is encapsulated inside zustand store `fetch` methods invoked on an interval within the respective components. Consider centralizing polling or using React Query if complexity grows. + +### Potential Improvements / TODO +* Remove hard‑coded Home Assistant token. +* Error + loading states (currently optimistic, failures would be silent / console only). +* Retry & backoff strategy for network calls. +* Dark mode override / manual theme toggle. +* Accessibility pass (ARIA, focus management) – current dashboard is mostly passive. +* Tests (none yet). Could add Vitest + React Testing Library. +* Switch transit API code to gracefully handle outages (KVV sometimes rate limits). + +### License +Add a license file if you plan to share externally (currently unspecified). + +### Support / Contact +Internal project (wg). For issues open a ticket on Gitea: https://git.rivercry.com/wg/monitor-im-flur + +--- +Generated README draft – adjust repository paths / badge branch name if different (e.g., replace `master` with your default branch). diff --git a/src/components/Amogus/Amogus.tsx b/src/components/Amogus/Amogus.tsx index ada808b..9ae0efc 100644 --- a/src/components/Amogus/Amogus.tsx +++ b/src/components/Amogus/Amogus.tsx @@ -39,8 +39,8 @@ const makeCrewmate = (imposter: boolean): Amogus => ({ isImposter: imposter, posX: randNum(0, width - amogusWidth), posY: randNum(0, height - amogusHeight), - speedX: Math.random() > 0.5 ? randNum(3, 10) : randNum(-3, -10), - speedY: Math.random() > 0.5 ? randNum(3, 10) : randNum(-3, -10), + speedX: Math.random() > 0.5 ? randNum(1, 2) : randNum(-1, -2), + speedY: Math.random() > 0.5 ? randNum(1, 2) : randNum(-1, -2), }); const intersect = (c1: Amogus, c2: Amogus): boolean => diff --git a/src/components/Amogus/style.module.css b/src/components/Amogus/style.module.css index 53ffe35..dea54e5 100644 --- a/src/components/Amogus/style.module.css +++ b/src/components/Amogus/style.module.css @@ -1,8 +1,13 @@ .container { z-index: 100; position: absolute; - scale: 50%; display: flex; justify-content: center; align-items: center; + + img { + width: 70px; + height: 70px; + object-fit: contain; + } } diff --git a/src/components/Dashboard/Dashboard.tsx b/src/components/Dashboard/Dashboard.tsx index 68fab17..ff38bc6 100644 --- a/src/components/Dashboard/Dashboard.tsx +++ b/src/components/Dashboard/Dashboard.tsx @@ -65,7 +65,6 @@ export default function Dashboard() { return (
-
diff --git a/src/components/Dashboard/style.module.css b/src/components/Dashboard/style.module.css index fe94495..05aec90 100644 --- a/src/components/Dashboard/style.module.css +++ b/src/components/Dashboard/style.module.css @@ -9,6 +9,8 @@ height: 100%; padding: 30px; overflow: scroll; + + scrollbar-width: none; } /* 7 to 16 */ diff --git a/src/components/DepartureList/style.module.css b/src/components/DepartureList/style.module.css index 8b18dd1..3d6f7a8 100644 --- a/src/components/DepartureList/style.module.css +++ b/src/components/DepartureList/style.module.css @@ -1,7 +1,7 @@ .container { display: flex; flex-direction: column; - align-items: stretch; + align-items: ; padding: 0 10px 20px 10px; margin-bottom: 20px; border-top: 2px solid white; @@ -13,8 +13,7 @@ .departureLists { display: flex; flex-direction: row; - justify-content: space-around; - width: 100%; + width: 600px; } .heading { diff --git a/src/components/Terminal/Terminal.tsx b/src/components/Terminal/Terminal.tsx index bd4e56a..3dd5c09 100644 --- a/src/components/Terminal/Terminal.tsx +++ b/src/components/Terminal/Terminal.tsx @@ -68,7 +68,7 @@ export default function Terminal() {
                         {"    "}NNssussNNN{"    "}plants{"    "}
-                        4
+                        2
                     
diff --git a/src/index.css b/src/index.css index d9326cf..6cdb37e 100644 --- a/src/index.css +++ b/src/index.css @@ -13,6 +13,8 @@ text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; + + scrollbar-width: none; } a {