riyadhafraa a15365f459 Task #531: Redesign DeletionWarningDialog for a more professional look
Scope: artifacts/tx-os/src/pages/admin.tsx — the shared confirm
dialog used for deleting Apps, Services, and Users on the admin
page.

What changed
- Three explicit visual regions instead of a flat stack:
  - Header: AlertTriangle icon in a destructive-tinted circle next
    to the bold title (which already contains the item name from
    the existing translation strings).
  - Body: warning copy, then impacted items rendered inside a
    sub-card (rounded border + subtle bg + custom bullet dots
    instead of `list-disc`), then a destructive-tinted callout
    holding the "cannot be undone" hint.
  - Footer: separated by a top border and a faint bg tint;
    Cancel = ghost (quieter), Confirm/Anyway = destructive (clear
    primary action). When `isPending`, a small spinner appears
    inside the destructive button.
- Slightly larger max-width (max-w-md), shadow-xl, and overflow-
  hidden so the rounded corners read cleanly with the new footer
  divider.
- `role="alertdialog"` + `aria-modal="true"` added on the panel.

Behaviour preserved
- Component props, call sites, and all `data-testid` hooks are
  unchanged (testId, confirmTestId — covers app-delete-dialog/
  -confirm, service-delete-dialog/-confirm, user-delete-dialog/
  -confirm).
- No translation keys added; reuses every existing
  admin.deleteApp/Service/User.* string.
- Cancel renders before Confirm in the DOM so tab order is
  Cancel -> Destructive (no a11y regression). Visual order is
  controlled with `justify-end`, which mirrors correctly in RTL.
- Pure UI change: no API/handler/permission logic touched.

Notes
- Architect flagged an earlier `flex-row-reverse` footer for
  putting the destructive button first in tab order; fixed before
  marking complete.
- Pre-existing TS18046 / TS2345 errors in admin.tsx are unrelated.
2026-05-13 15:50:54 +00:00
2026-04-18 02:00:09 +00:00
2026-04-18 02:00:09 +00:00
2026-04-18 02:00:09 +00:00

Tx OS

A bilingual (Arabic / English) internal "office OS" web platform. Single-tenant, self-hosted, designed to live behind a TLS-terminating reverse proxy on a private VPS or on-prem host.

The repo is a pnpm monorepo containing:

Package Purpose
artifacts/api-server Express 5 + Socket.IO + Drizzle ORM API
artifacts/tx-os React 19 + Vite SPA (the user-facing app)
artifacts/mockup-sandbox Internal component preview server (dev-only)
lib/db Drizzle schema + migrations
lib/api-zod + lib/api-client-react OpenAPI-generated Zod schemas + React Query hooks
scripts DB seed + maintenance scripts

Features

  • Glassmorphism OS UI with animated gradient backgrounds and an app grid / dock.
  • Bilingual (RTL Arabic + LTR English) with per-user persisted locale.
  • Session auth (express-session + Postgres-backed connect-pg-simple, bcrypt password hashing) with full RBAC (admin / user roles + role-permission matrix + group-derived permissions).
  • Real-time chat / notifications / executive-meeting alerts via Socket.IO.
  • Executive Meetings module — scheduling, change requests, approvals, optimistic locking on postpones, audit log, and a Playwright-rendered HTML PDF export.
  • S3-compatible object storage with signed-URL uploads (production: MinIO; local dev: built-in filesystem driver — no extra services required).

Quick start (Docker)

The fastest path to a running stack on a Linux VPS with Docker installed.

git clone <this-repo>
cd tx-os
cp .env.example .env
# Edit .env — at minimum change SESSION_SECRET, POSTGRES_PASSWORD,
# S3_SECRET_ACCESS_KEY, SEED_ADMIN_PASSWORD, SEED_USER_PASSWORD.
$EDITOR .env

docker compose build
docker compose up -d db minio minio-init
docker compose run --rm migrate          # one-shot: pnpm run migrate (db push + seed)
docker compose up -d api web

After this, the running stack exposes:

Service Default host port URL
SPA (web) 3000 http://<host>:3000/
API (api) 8080 http://<host>:8080/api/healthz
MinIO console not exposed (proxy to :9001 if you need it)
mockup-sandbox 8081 dev profile only — docker compose --profile dev up -d mockup-sandbox then http://<host>:8081/__mockup

Front the SPA (port WEB_PORT, default 3000) with Caddy / Nginx / Traefik for TLS and HTTP/2; bind the API port (API_PORT, default 8080) to 127.0.0.1 in production so the edge proxy is the sole external entry.

Default seeded accounts

Username Password Role
admin value of SEED_ADMIN_PASSWORD admin
ahmed value of SEED_USER_PASSWORD user

Both are seeded by docker compose run --rm migrate and only if they don't already exist (the seed is idempotent on conflict).

Common compose commands

docker compose logs -f api               # tail API logs
docker compose exec db psql -U tx tx_os  # psql shell
docker compose run --rm migrate          # re-run migrations (safe to repeat)
docker compose down                      # stop everything (data persists)
docker compose down -v                   # stop AND wipe volumes (DANGER)

Local development (without Docker)

You can run the stack natively if you have Node 24+, pnpm 10, and Postgres 16.

pnpm install
createdb tx_os
export DATABASE_URL=postgres://localhost/tx_os
export PORT=8080 BASE_PATH=/ ALLOWED_ORIGINS=http://localhost:25785
export SESSION_SECRET=$(openssl rand -hex 32)
export PRIVATE_OBJECT_DIR=/local/private
export PUBLIC_OBJECT_SEARCH_PATHS=/local/public
# Note: with no S3_ENDPOINT set, the API falls back to the local-FS storage
# driver and persists uploads under ./storage/. Safe for development only.

pnpm --filter db run push           # apply schema
pnpm --filter scripts run seed      # seed admin/ahmed accounts
pnpm --filter @workspace/api-server dev
# In another terminal:
PORT=25785 BASE_PATH=/ pnpm --filter @workspace/tx-os dev

Configuration reference

Every option is read from environment variables. See .env.example for the complete list with comments. Highlights:

Variable Purpose
DATABASE_URL Postgres connection string. Required.
SESSION_SECRET HMAC key for session cookies + local-driver upload tokens. Required in production.
ALLOWED_ORIGINS Comma-separated CORS allow-list. Defaults to * (dev only).
STORAGE_DRIVER s3 or local. Defaults to s3 if S3_ENDPOINT set, else local.
S3_ENDPOINT etc. MinIO / S3 connection. Required when STORAGE_DRIVER=s3.
PRIVATE_OBJECT_DIR Path inside the bucket for private uploads, e.g. /tx-private/private.
PUBLIC_OBJECT_SEARCH_PATHS Comma-separated bucket paths searched by GET /storage/public-objects/*.
LOCAL_STORAGE_ROOT Filesystem root used by the local driver. Defaults to ./storage.
SEED_ADMIN_PASSWORD / SEED_USER_PASSWORD Required by the seed script in production.
SMTP_* Optional outbound mail config for Executive Meetings notifications.
LOG_LEVEL pino log level. Defaults to info.

Storage drivers

The API server has two object-storage backends, selected automatically:

  • s3 (production): targets any S3-compatible endpoint (MinIO, AWS S3, R2, Backblaze B2, ...) via @aws-sdk/client-s3 + presigned PUT URLs.
  • local (dev fallback, default when S3_ENDPOINT is unset): persists files under LOCAL_STORAGE_ROOT and issues HMAC-signed upload URLs that the API server validates and accepts on PUT /api/storage/_local/upload. Single-host only; not suitable for production.

The route layer is unaware of which driver is active — both expose the same StoredObject interface in lib/objectStorage.ts.


Tests

pnpm --filter @workspace/api-server build
pnpm --filter @workspace/api-server start &        # boot API on 8080
pnpm --filter @workspace/api-server test           # node --test suite
pnpm --filter @workspace/tx-os test:e2e            # Playwright UI tests

The test workflow chains all of the above. Tests use the same database specified in DATABASE_URL and clean up after themselves with LIKE-prefixed fixture rows.


Production checklist

Before fronting Tx OS with a public domain:

  1. Set every secret in .env. No defaults in production.
  2. Run behind TLS. Caddy / Nginx / Traefik should terminate HTTPS and forward to web on ${WEB_PORT}. The API server trusts X-Forwarded-For from the first proxy hop (app.set("trust proxy", 1)).
  3. Restrict the API port. The api service should never be exposed directly to the internet — only web is intended to be reachable.
  4. Back up the volumes. db_data and minio_data are the only stateful surfaces. Snapshot them on a schedule.
  5. Rotate seeded passwords. The first thing an admin should do is change the seeded admin and ahmed passwords from the user-management screen.
  6. Review threat_model.md. Document-level threat model lives in the repo root and lists the trust boundaries this deployment relies on.

Project conventions

  • Package manager: pnpm 10. The repo refuses to install with npm/yarn.
  • Node: 24 LTS. Older Nodes will not run the API bundle.
  • TypeScript: 5.9, strict mode, project-references build (pnpm typecheck).
  • API contracts: hand-written Zod schemas in lib/api-zod are the source of truth; the React-Query client in lib/api-client-react is generated from the same OpenAPI spec via Orval.
  • Migrations: Drizzle Kit. pnpm --filter db run push-force for dev, pnpm --filter db run push for production.

License

MIT.

S
Description
No description provided
Readme 138 MiB
Languages
TypeScript 69.2%
JavaScript 29.2%
CSS 0.6%
Shell 0.6%
Dockerfile 0.2%
Other 0.2%