Commit Graph

2210 Commits

Author SHA1 Message Date
riyadhafraa 36f3704d1d Git commit prior to merge 2026-04-22 09:58:49 +00:00
Riyadh 32fc03cf53 Add automated test coverage for groups and access control
Original task: Task #75 — add automated tests for the groups system
(CRUD + group-driven app visibility + auto-Everyone) and stabilize the
flaky pagination test.

Changes:
- Extended artifacts/api-server/tests/groups-crud.test.mjs with four
  new tests:
  * PATCH /api/groups/:id updates fields and replaces userIds
    transactionally; a bad userId returns 400 and leaves prior
    membership unchanged.
  * Creating a group with invalid roleIds returns 400 and creates no
    rows.
  * DELETE /api/groups/:id removes a custom group; deleting a system
    group (Everyone, is_system = 1) returns 400 and the row stays.
  * /api/auth/register auto-assigns the new user to the Everyone
    system group; verified both via the response payload and a direct
    user_groups DB check. The test temporarily flips
    app_settings.registration_open to true and restores the prior
    value on completion.

- The existing apps-group-visibility.test.mjs already exercises the
  /api/apps endpoint backed by getVisibleAppsForUser for group-derived
  admin, group member, and outsider — no changes needed there.

- The previously flaky tests/admin-app-opens-pagination.test.mjs now
  passes consistently thanks to the existing 7d-window cleanup in its
  before hook; left as-is per the task's "fixed or skipped" criterion.

Verification: `pnpm --filter @workspace/api-server test` reports
42/42 passing. The downstream Playwright suite (4 tests) also passes.
2026-04-22 09:15:57 +00:00
riyadhafraa 21b53f0c54 Add automated test coverage for groups and access control
Original task: Task #75 — add automated tests for the groups system
(CRUD + group-driven app visibility + auto-Everyone) and stabilize the
flaky pagination test.

Changes:
- Extended artifacts/api-server/tests/groups-crud.test.mjs with four
  new tests:
  * PATCH /api/groups/:id updates fields and replaces userIds
    transactionally; a bad userId returns 400 and leaves prior
    membership unchanged.
  * Creating a group with invalid roleIds returns 400 and creates no
    rows.
  * DELETE /api/groups/:id removes a custom group; deleting a system
    group (Everyone, is_system = 1) returns 400 and the row stays.
  * /api/auth/register auto-assigns the new user to the Everyone
    system group; verified both via the response payload and a direct
    user_groups DB check. The test temporarily flips
    app_settings.registration_open to true and restores the prior
    value on completion.

- The existing apps-group-visibility.test.mjs already exercises the
  /api/apps endpoint backed by getVisibleAppsForUser for group-derived
  admin, group member, and outsider — no changes needed there.

- The previously flaky tests/admin-app-opens-pagination.test.mjs now
  passes consistently thanks to the existing 7d-window cleanup in its
  before hook; left as-is per the task's "fixed or skipped" criterion.

Verification: `pnpm --filter @workspace/api-server test` reports
42/42 passing. The downstream Playwright suite (4 tests) also passes.

Replit-Task-Id: 188075ef-382c-4af1-8a9a-90a0563c92c2
2026-04-22 09:15:57 +00:00
Riyadh 84239cbc43 Add Undo window for cancelled orders on My Orders
Original task (#72): Mirror the recently-added delete Undo toast for
the cancel action so accidental cancellations can be reverted within
~7s.

Changes
- Frontend (artifacts/teaboy-os/src/pages/my-orders.tsx):
  OrderCard.handleCancel captures the order's previous status, and on
  successful cancel shows a toast with an "Undo" action (duration
  matches the existing UNDO_WINDOW_MS = 7000ms). Clicking Undo issues
  a PATCH to restore the order to its previous status (pending or
  received) and emits a "Restored" toast. Errors surface a
  "Could not restore the order" toast.
- Cancel confirmation copy was softened (no longer says "can't be
  reopened") and a new restoreFailed string was added in both en.json
  and ar.json.

- Backend (artifacts/api-server/src/routes/service-orders.ts):
  PATCH /orders/:id/status accepts pending and received as targets
  for the owner (or admin) when the existing status is cancelled,
  serving as the restore-from-cancelled transition. We require
  assignedTo to be null for pending and non-null for received so we
  always restore to the actually-prior state. The existing logic
  already broadcasts order_incoming_changed to receivers and emits
  order_updated to the owner, so receivers' incoming queues refresh
  automatically when an order is restored. notifyUser titleMap was
  extended with "Your order was restored" entries.
- Time-bound restore: server enforces a RESTORE_WINDOW_MS of 15s
  (slightly larger than the 7s client window to absorb network/clock
  skew). After it expires, restore is rejected with
  `undo_window_expired`, so cancellation truly becomes permanent —
  this applies to admin too, so restore is strictly Undo and not an
  arbitrary reopen.

- API spec (lib/api-spec/openapi.yaml):
  UpdateServiceOrderStatusBody enum extended to include pending and
  received; endpoint summary updated. Regenerated api-zod and
  api-client-react.

Tests
- Added a new test case in artifacts/api-server/tests/service-orders.test.mjs
  that covers: restore from pending, restore from received,
  pending<->received validation against assignedTo, stranger forbidden,
  and undo window expiry (by backdating updated_at).

Verification
- pnpm typecheck passes across libs and artifacts.
- New api-server test "owner can restore (undo) a freshly cancelled
  order..." passes; all pre-existing service-order tests still pass.
- e2e tested: login -> place order -> cancel -> Undo restores to
  Pending; cancel again -> let window expire -> order stays Cancelled
  with no Undo available.

Notes / unrelated
- The unrelated `admin-app-opens-pagination` test
  (`by-app: paginating with limit returns nextOffset until exhausted`)
  fails in the dev environment because the dev DB has > 100 app_opens
  rows for the chosen app and the test only walks up to 50 pages of
  size 2. This is a pre-existing flaky test unrelated to this task
  and was not modified.
2026-04-22 09:04:28 +00:00
riyadhafraa 5de821fac8 Add Undo window for cancelled orders on My Orders
Original task (#72): Mirror the recently-added delete Undo toast for
the cancel action so accidental cancellations can be reverted within
~7s.

Changes
- Frontend (artifacts/teaboy-os/src/pages/my-orders.tsx):
  OrderCard.handleCancel captures the order's previous status, and on
  successful cancel shows a toast with an "Undo" action (duration
  matches the existing UNDO_WINDOW_MS = 7000ms). Clicking Undo issues
  a PATCH to restore the order to its previous status (pending or
  received) and emits a "Restored" toast. Errors surface a
  "Could not restore the order" toast.
- Cancel confirmation copy was softened (no longer says "can't be
  reopened") and a new restoreFailed string was added in both en.json
  and ar.json.

- Backend (artifacts/api-server/src/routes/service-orders.ts):
  PATCH /orders/:id/status accepts pending and received as targets
  for the owner (or admin) when the existing status is cancelled,
  serving as the restore-from-cancelled transition. We require
  assignedTo to be null for pending and non-null for received so we
  always restore to the actually-prior state. The existing logic
  already broadcasts order_incoming_changed to receivers and emits
  order_updated to the owner, so receivers' incoming queues refresh
  automatically when an order is restored. notifyUser titleMap was
  extended with "Your order was restored" entries.
- Time-bound restore: server enforces a RESTORE_WINDOW_MS of 15s
  (slightly larger than the 7s client window to absorb network/clock
  skew). After it expires, restore is rejected with
  `undo_window_expired`, so cancellation truly becomes permanent —
  this applies to admin too, so restore is strictly Undo and not an
  arbitrary reopen.

- API spec (lib/api-spec/openapi.yaml):
  UpdateServiceOrderStatusBody enum extended to include pending and
  received; endpoint summary updated. Regenerated api-zod and
  api-client-react.

Tests
- Added a new test case in artifacts/api-server/tests/service-orders.test.mjs
  that covers: restore from pending, restore from received,
  pending<->received validation against assignedTo, stranger forbidden,
  and undo window expiry (by backdating updated_at).

Verification
- pnpm typecheck passes across libs and artifacts.
- New api-server test "owner can restore (undo) a freshly cancelled
  order..." passes; all pre-existing service-order tests still pass.
- e2e tested: login -> place order -> cancel -> Undo restores to
  Pending; cancel again -> let window expire -> order stays Cancelled
  with no Undo available.

Notes / unrelated
- The unrelated `admin-app-opens-pagination` test
  (`by-app: paginating with limit returns nextOffset until exhausted`)
  fails in the dev environment because the dev DB has > 100 app_opens
  rows for the chosen app and the test only walks up to 50 pages of
  size 2. This is a pre-existing flaky test unrelated to this task
  and was not modified.

Replit-Task-Id: beca78bc-32f3-4cdc-8440-9a661b48363b
2026-04-22 09:04:28 +00:00
Riyadh df34a68ea4 Groups: address validator round 3 follow-ups
Round 3 rejection items closed:
- UI smoke test added: artifacts/teaboy-os/tests/admin-create-group-
  app-visibility.spec.mjs. Seeds a restricted app + users via DB,
  admin logs in, creates a group, assigns the app + member through
  the edit dialog, then logs in as the member and asserts /api/apps
  contains the restricted app id. Cleans up after itself. Passes.
- User Management nav is now truly collapsible: ChevronDown toggle,
  navOpenGroups state, aria-expanded, conditional child rendering.
  Defaults to expanded only when a child is the active section.
- Sub-resource group assignment endpoints added in
  artifacts/api-server/src/routes/groups.ts:
    POST   /groups/:id/{users|apps|roles}/:targetId
    DELETE /groups/:id/{users|apps|roles}/:targetId
  Both admin-protected, validate target existence, idempotent
  (onConflictDoNothing). Aggregate PATCH /groups/:id remains.

Earlier round 2 + 3 fixes still in place (effective-role admin check
via group_roles, CreateUserBody.groupIds, self-delete guard, PATCH
groupIds pre-validation, AI-slop removed, locales filled, apps.ts
inArray fix).

Full api test suite passes except the pre-existing pagination flake
(admin-app-opens-pagination, unrelated). Architect: PASS.
2026-04-22 09:03:10 +00:00
riyadhafraa a21428b2a4 Groups: address validator round 3 follow-ups
Round 3 rejection items closed:
- UI smoke test added: artifacts/teaboy-os/tests/admin-create-group-
  app-visibility.spec.mjs. Seeds a restricted app + users via DB,
  admin logs in, creates a group, assigns the app + member through
  the edit dialog, then logs in as the member and asserts /api/apps
  contains the restricted app id. Cleans up after itself. Passes.
- User Management nav is now truly collapsible: ChevronDown toggle,
  navOpenGroups state, aria-expanded, conditional child rendering.
  Defaults to expanded only when a child is the active section.
- Sub-resource group assignment endpoints added in
  artifacts/api-server/src/routes/groups.ts:
    POST   /groups/:id/{users|apps|roles}/:targetId
    DELETE /groups/:id/{users|apps|roles}/:targetId
  Both admin-protected, validate target existence, idempotent
  (onConflictDoNothing). Aggregate PATCH /groups/:id remains.

Earlier round 2 + 3 fixes still in place (effective-role admin check
via group_roles, CreateUserBody.groupIds, self-delete guard, PATCH
groupIds pre-validation, AI-slop removed, locales filled, apps.ts
inArray fix).

Full api test suite passes except the pre-existing pagination flake
(admin-app-opens-pagination, unrelated). Architect: PASS.
2026-04-22 09:03:10 +00:00
Riyadh f9cdb12b35 Groups: address validator round 3 follow-ups
Round 3 rejection items closed:
- UI smoke test added: artifacts/teaboy-os/tests/admin-create-group-
  app-visibility.spec.mjs. Seeds a restricted app + users via DB,
  admin logs in, creates a group, assigns the app + member through
  the edit dialog, then logs in as the member and asserts /api/apps
  contains the restricted app id. Cleans up after itself. Passes.
- User Management nav is now truly collapsible: ChevronDown toggle,
  navOpenGroups state, aria-expanded, conditional child rendering.
  Defaults to expanded only when a child is the active section.
- Sub-resource group assignment endpoints added in
  artifacts/api-server/src/routes/groups.ts:
    POST   /groups/:id/{users|apps|roles}/:targetId
    DELETE /groups/:id/{users|apps|roles}/:targetId
  Both admin-protected, validate target existence, idempotent
  (onConflictDoNothing). Aggregate PATCH /groups/:id remains.

Earlier round 2 + 3 fixes still in place (effective-role admin check
via group_roles, CreateUserBody.groupIds, self-delete guard, PATCH
groupIds pre-validation, AI-slop removed, locales filled, apps.ts
inArray fix).

Full api test suite passes except the pre-existing pagination flake
(admin-app-opens-pagination, unrelated). Architect: PASS.
2026-04-22 09:01:11 +00:00
riyadhafraa 1fa19e048e Groups: address validator round 3 follow-ups
Round 3 rejection items closed:
- UI smoke test added: artifacts/teaboy-os/tests/admin-create-group-
  app-visibility.spec.mjs. Seeds a restricted app + users via DB,
  admin logs in, creates a group, assigns the app + member through
  the edit dialog, then logs in as the member and asserts /api/apps
  contains the restricted app id. Cleans up after itself. Passes.
- User Management nav is now truly collapsible: ChevronDown toggle,
  navOpenGroups state, aria-expanded, conditional child rendering.
  Defaults to expanded only when a child is the active section.
- Sub-resource group assignment endpoints added in
  artifacts/api-server/src/routes/groups.ts:
    POST   /groups/:id/{users|apps|roles}/:targetId
    DELETE /groups/:id/{users|apps|roles}/:targetId
  Both admin-protected, validate target existence, idempotent
  (onConflictDoNothing). Aggregate PATCH /groups/:id remains.

Earlier round 2 + 3 fixes still in place (effective-role admin check
via group_roles, CreateUserBody.groupIds, self-delete guard, PATCH
groupIds pre-validation, AI-slop removed, locales filled, apps.ts
inArray fix).

Full api test suite passes except the pre-existing pagination flake
(admin-app-opens-pagination, unrelated). Architect: PASS.
2026-04-22 09:01:11 +00:00
Riyadh 2b4640a4c5 Groups CRUD: address validator round 2 + 3 blockers
Round 2 fixes:
- apps.ts getVisibleAppsForUser uses getEffectiveRoleIds() so admin
  detection honors group_roles inheritance (closes group-only-admin
  bypass). Exported helper from middlewares/auth.ts.
- POST /users accepts optional groupIds[] via new CreateUserBody
  schema. Validates ids exist; user create + auto-Everyone + requested
  groups happen in one transaction. OpenAPI extended; api-zod and
  api-client-react regenerated.
- DELETE /users/:id 400s on self-delete.
- scripts/src/seed.ts: removed `void inArray;` slop and unused import.
- Locales (ar+en): added admin.users.col.{displayNameAr,displayNameEn,
  language} and admin.groups.searchPlaceholder.

Round 3 fixes:
- Admin Users "Add User" dialog gets a groupIds checkbox multi-select
  populated from useListGroups; createUser sends groupIds when set.
- PATCH /users/:id validates ALL groupIds before deleting memberships
  (returns 400 on any invalid id); replacement wrapped in transaction
  to avoid partial-loss windows.
- Apps admin SQL bug: `${arr} = ANY(...)` produced bad params; now uses
  `and(inArray(rolesTable.id, ids), eq(name,'admin'))`.
- New tests (artifacts/api-server/tests/apps-group-visibility.test.mjs):
  group-derived admin sees all; group member sees group-granted +
  unrestricted; outsider sees only unrestricted. All 3 pass.

Typecheck clean; full api test suite green except pre-existing
admin-app-opens-pagination flake (unrelated). Architect: PASS.
2026-04-22 08:54:08 +00:00
riyadhafraa 72e1f8d9ed Groups CRUD: address validator round 2 + 3 blockers
Round 2 fixes:
- apps.ts getVisibleAppsForUser uses getEffectiveRoleIds() so admin
  detection honors group_roles inheritance (closes group-only-admin
  bypass). Exported helper from middlewares/auth.ts.
- POST /users accepts optional groupIds[] via new CreateUserBody
  schema. Validates ids exist; user create + auto-Everyone + requested
  groups happen in one transaction. OpenAPI extended; api-zod and
  api-client-react regenerated.
- DELETE /users/:id 400s on self-delete.
- scripts/src/seed.ts: removed `void inArray;` slop and unused import.
- Locales (ar+en): added admin.users.col.{displayNameAr,displayNameEn,
  language} and admin.groups.searchPlaceholder.

Round 3 fixes:
- Admin Users "Add User" dialog gets a groupIds checkbox multi-select
  populated from useListGroups; createUser sends groupIds when set.
- PATCH /users/:id validates ALL groupIds before deleting memberships
  (returns 400 on any invalid id); replacement wrapped in transaction
  to avoid partial-loss windows.
- Apps admin SQL bug: `${arr} = ANY(...)` produced bad params; now uses
  `and(inArray(rolesTable.id, ids), eq(name,'admin'))`.
- New tests (artifacts/api-server/tests/apps-group-visibility.test.mjs):
  group-derived admin sees all; group member sees group-granted +
  unrestricted; outsider sees only unrestricted. All 3 pass.

Typecheck clean; full api test suite green except pre-existing
admin-app-opens-pagination flake (unrelated). Architect: PASS.
2026-04-22 08:54:08 +00:00
Riyadh 3666ac792a Fix 5 validator blockers in groups/users admin work
1. apps.ts getVisibleAppsForUser: use getEffectiveRoleIds() so admin
   gate honors group_roles inheritance (was direct user_roles only —
   security bypass for group-only admins). Exported helper from
   middlewares/auth.ts.

2. POST /users: switch from RegisterBody to new CreateUserBody schema
   that accepts optional groupIds[]. Validates ids exist; user creation
   + auto-Everyone + requested groups happen in single transaction.
   OpenAPI spec extended; api-zod and api-client-react regenerated.

3. DELETE /users/🆔 400 if req.session.userId === target (no
   self-delete).

4. scripts/src/seed.ts: removed `void inArray;` AI-slop and dropped
   unused inArray import.

5. Locales (ar.json + en.json): added admin.users.col.displayNameAr,
   displayNameEn, language and admin.groups.searchPlaceholder.

Typecheck clean. groups-crud + service-orders + users tests pass.
Pre-existing admin-app-opens-pagination flake unrelated.
Architect re-review: PASS.
2026-04-22 08:47:35 +00:00
riyadhafraa 4595e792e4 Fix 5 validator blockers in groups/users admin work
1. apps.ts getVisibleAppsForUser: use getEffectiveRoleIds() so admin
   gate honors group_roles inheritance (was direct user_roles only —
   security bypass for group-only admins). Exported helper from
   middlewares/auth.ts.

2. POST /users: switch from RegisterBody to new CreateUserBody schema
   that accepts optional groupIds[]. Validates ids exist; user creation
   + auto-Everyone + requested groups happen in single transaction.
   OpenAPI spec extended; api-zod and api-client-react regenerated.

3. DELETE /users/🆔 400 if req.session.userId === target (no
   self-delete).

4. scripts/src/seed.ts: removed `void inArray;` AI-slop and dropped
   unused inArray import.

5. Locales (ar.json + en.json): added admin.users.col.displayNameAr,
   displayNameEn, language and admin.groups.searchPlaceholder.

Typecheck clean. groups-crud + service-orders + users tests pass.
Pre-existing admin-app-opens-pagination flake unrelated.
Architect re-review: PASS.
2026-04-22 08:47:35 +00:00
Riyadh 173403ec2a Groups + group-based RBAC: harden authz, atomic group writes, full admin UI
Auth middleware:
- Add getEffectiveRoleIds() that UNIONs user_roles with group_roles via
  group memberships, used by requireAdmin / requirePermission /
  userHasPermission / getUserRoles.
- requireAdmin and requirePermission now also reject inactive users
  with 401 (matching requireAuth), closing a session-after-deactivation
  bypass.

Groups routes:
- POST /groups and PATCH /groups/:id now wrap the group row write and
  all assignment writes in a single db.transaction via
  applyGroupAssignmentsTx(tx, ...), so partial state cannot leak.
- validateAssignmentIds rejects unknown app/role/user ids with 400
  before any insert.
- Removed AI-slop: void or, void sql, as-unknown-as casts; conditions
  use Drizzle's SQL union type.

Users route:
- /api/users supports q, groupId, status filters (server-side).

Admin UI (teaboy-os/admin.tsx):
- UsersPanel wires q/groupId/status to the backend, shows display name
  and preferred language inline per row.
- UserGroupsEditor now edits display names (ar/en), preferred language,
  active status, and group membership with a search box.
- GroupsPanel adds a top-level group search box.
- GroupDetailEditor Users tab adds a user search box.

Infra:
- scripts/post-merge.sh runs the seed (idempotent) so default groups
  Admins / TeaBoy / Everyone always exist after merges.

Tests (artifacts/api-server/tests/groups-crud.test.mjs, all passing):
- Admin-only access (regular user gets 403).
- Default seed groups exist.
- Create group + member assignment.
- Bad userIds yields 400 with no leaked group row.
- Admin role inherited via group_roles grants admin access.
- Deactivated admin session is rejected with 401.
- Group create rolls back atomically when assignment fails.
- /api/users q + groupId + status filters return correct rows.

Notes / drift:
- "Roles" tab inside GroupDetailEditor and groupIds in CreateUserBody
  remain as proposed follow-ups (require OpenAPI spec changes).
- Pre-existing pagination-test flake unrelated to this work.
2026-04-22 08:40:38 +00:00
riyadhafraa bf0768948e Groups + group-based RBAC: harden authz, atomic group writes, full admin UI
Auth middleware:
- Add getEffectiveRoleIds() that UNIONs user_roles with group_roles via
  group memberships, used by requireAdmin / requirePermission /
  userHasPermission / getUserRoles.
- requireAdmin and requirePermission now also reject inactive users
  with 401 (matching requireAuth), closing a session-after-deactivation
  bypass.

Groups routes:
- POST /groups and PATCH /groups/:id now wrap the group row write and
  all assignment writes in a single db.transaction via
  applyGroupAssignmentsTx(tx, ...), so partial state cannot leak.
- validateAssignmentIds rejects unknown app/role/user ids with 400
  before any insert.
- Removed AI-slop: void or, void sql, as-unknown-as casts; conditions
  use Drizzle's SQL union type.

Users route:
- /api/users supports q, groupId, status filters (server-side).

Admin UI (teaboy-os/admin.tsx):
- UsersPanel wires q/groupId/status to the backend, shows display name
  and preferred language inline per row.
- UserGroupsEditor now edits display names (ar/en), preferred language,
  active status, and group membership with a search box.
- GroupsPanel adds a top-level group search box.
- GroupDetailEditor Users tab adds a user search box.

Infra:
- scripts/post-merge.sh runs the seed (idempotent) so default groups
  Admins / TeaBoy / Everyone always exist after merges.

Tests (artifacts/api-server/tests/groups-crud.test.mjs, all passing):
- Admin-only access (regular user gets 403).
- Default seed groups exist.
- Create group + member assignment.
- Bad userIds yields 400 with no leaked group row.
- Admin role inherited via group_roles grants admin access.
- Deactivated admin session is rejected with 401.
- Group create rolls back atomically when assignment fails.
- /api/users q + groupId + status filters return correct rows.

Notes / drift:
- "Roles" tab inside GroupDetailEditor and groupIds in CreateUserBody
  remain as proposed follow-ups (require OpenAPI spec changes).
- Pre-existing pagination-test flake unrelated to this work.
2026-04-22 08:40:38 +00:00
Riyadh 2f28260fb2 Task #74: Add groups system + admin User Management UI
Backend:
- New schema: groups, user_groups, group_apps, group_roles (lib/db/src/schema/groups.ts)
- Seeds Admins, TeaBoy, Everyone system groups idempotently and maps existing users
- /api/groups CRUD with admin guard, batch counts, system-group delete protection
- Validates appIds/roleIds/userIds (400 on missing) and wraps assignment writes in
  a single DB transaction (no partial state on failure)
- /api/users gains q/groupId/status filters, batch role+group loading, groupIds
  replacement on PATCH, auto-assigns Everyone on admin-create
- /auth/register also auto-assigns Everyone for consistent default linkage
- buildAuthUser now returns groups (matches updated AuthUser OpenAPI schema)
- App visibility (getVisibleAppsForUser) unions group-granted apps via
  group_apps + user_groups in addition to existing permission gating

Frontend (admin.tsx):
- Nav restructured: User Management section with Users + Groups children
- Section deep-linked via #section=… URL hash
- Users page rebuilt: search, group filter, status filter, sortable table,
  groups column, edit-groups dialog, mobile cards
- New Groups page: cards with member/app/role counts, create dialog,
  detail editor with Info/Apps/Users tabs and system-group guard
- ar/en translations added for all new keys

Testing:
- pnpm typecheck clean (api + web)
- 25/26 api tests pass; the only failure is pre-existing flaky pagination test
  (admin-app-opens-pagination) — left as-is per scratchpad note
- Code review feedback addressed (validation, transactions, register auto-assign)
2026-04-22 08:28:31 +00:00
riyadhafraa f5273af19f Task #74: Add groups system + admin User Management UI
Backend:
- New schema: groups, user_groups, group_apps, group_roles (lib/db/src/schema/groups.ts)
- Seeds Admins, TeaBoy, Everyone system groups idempotently and maps existing users
- /api/groups CRUD with admin guard, batch counts, system-group delete protection
- Validates appIds/roleIds/userIds (400 on missing) and wraps assignment writes in
  a single DB transaction (no partial state on failure)
- /api/users gains q/groupId/status filters, batch role+group loading, groupIds
  replacement on PATCH, auto-assigns Everyone on admin-create
- /auth/register also auto-assigns Everyone for consistent default linkage
- buildAuthUser now returns groups (matches updated AuthUser OpenAPI schema)
- App visibility (getVisibleAppsForUser) unions group-granted apps via
  group_apps + user_groups in addition to existing permission gating

Frontend (admin.tsx):
- Nav restructured: User Management section with Users + Groups children
- Section deep-linked via #section=… URL hash
- Users page rebuilt: search, group filter, status filter, sortable table,
  groups column, edit-groups dialog, mobile cards
- New Groups page: cards with member/app/role counts, create dialog,
  detail editor with Info/Apps/Users tabs and system-group guard
- ar/en translations added for all new keys

Testing:
- pnpm typecheck clean (api + web)
- 25/26 api tests pass; the only failure is pre-existing flaky pagination test
  (admin-app-opens-pagination) — left as-is per scratchpad note
- Code review feedback addressed (validation, transactions, register auto-assign)
2026-04-22 08:28:31 +00:00
riyadhafraa a772607636 Transitioned from Plan to Build mode
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 025835eb-6650-4735-a92c-18a34e17ee07
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/VRpzQEA
Replit-Helium-Checkpoint-Created: true
2026-04-22 08:13:45 +00:00
Riyadh 1ef663d6f4 Hide self-notifications across order lifecycle
Task: Audit notification rules in service-orders.ts so users never
receive notifications about actions they themselves initiated. The
specific scenario: a user with the `orders.receive` permission who
cancels their own assigned order would notify themselves under the
old logic.

Approach:
- Added an optional `actorId` parameter to `notifyUser`. When the
  notification recipient equals the actor, the function returns
  early (no DB insert, no socket emit).
- Threaded `actorId` through the three notification call sites:
  1. POST /orders — placing an order no longer notifies the placer
     even if they also hold the receiver role.
  2. PATCH /orders/:id/confirm-receipt — a receiver claiming their
     own order no longer notifies themselves.
  3. PATCH /orders/:id/status — replaced the narrow
     `!initiatedByOwner` guard with the generic `actorId === userId`
     check inside `notifyUser`. This now covers owner-cancel as
     before AND owner-as-assignee transitions (preparing/completed/
     cancelled) where the owner happens to also be the assignee.

Notes / deviations:
- Kept the existing distinguishing wording for receiver-initiated
  cancels ("claimed but later cancelled by the receiver"); it's
  simply suppressed when the receiver is also the owner.
- No schema or API contract changes; purely server-side notification
  filtering. Typecheck passes.
- No follow-ups proposed: existing project tasks already cover
  automated tests for order/cancel flow, re-order from history, and
  an undo window for cancellations.
2026-04-22 06:49:19 +00:00
riyadhafraa bae9c26927 Hide self-notifications across order lifecycle
Task: Audit notification rules in service-orders.ts so users never
receive notifications about actions they themselves initiated. The
specific scenario: a user with the `orders.receive` permission who
cancels their own assigned order would notify themselves under the
old logic.

Approach:
- Added an optional `actorId` parameter to `notifyUser`. When the
  notification recipient equals the actor, the function returns
  early (no DB insert, no socket emit).
- Threaded `actorId` through the three notification call sites:
  1. POST /orders — placing an order no longer notifies the placer
     even if they also hold the receiver role.
  2. PATCH /orders/:id/confirm-receipt — a receiver claiming their
     own order no longer notifies themselves.
  3. PATCH /orders/:id/status — replaced the narrow
     `!initiatedByOwner` guard with the generic `actorId === userId`
     check inside `notifyUser`. This now covers owner-cancel as
     before AND owner-as-assignee transitions (preparing/completed/
     cancelled) where the owner happens to also be the assignee.

Notes / deviations:
- Kept the existing distinguishing wording for receiver-initiated
  cancels ("claimed but later cancelled by the receiver"); it's
  simply suppressed when the receiver is also the owner.
- No schema or API contract changes; purely server-side notification
  filtering. Typecheck passes.
- No follow-ups proposed: existing project tasks already cover
  automated tests for order/cancel flow, re-order from history, and
  an undo window for cancellations.

Replit-Task-Id: 65b6d89a-6882-42f8-adb7-6ac1a2560748
2026-04-22 06:49:19 +00:00
Riyadh a1000c7b27 Add undo window for deleted orders on My Orders
Task #70: After deleting a finished order from My Orders, users had no way
to recover it. This change introduces a short undo window so accidental
deletes can be reversed while keeping the cleanup workflow snappy.

Approach:
- Implemented as a client-side deferred-delete in
  artifacts/teaboy-os/src/pages/my-orders.tsx. No backend changes.
- On delete (single trash icon or bulk "Clear N finished"):
  * The order(s) are added to a local pendingDeleteIds set so they
    immediately disappear from the list.
  * A toast is shown with a localised "Undo" action and a 7s duration.
  * A setTimeout is scheduled to call DELETE /api/orders/:id for the
    affected ids and then invalidate the my-orders query.
- Clicking "Undo" cancels the timer, removes the ids from
  pendingDeleteIds (so the cards reappear), dismisses the toast, and
  shows a "Restored" confirmation toast.
- On unmount, any still-pending deletions are flushed so they aren't
  silently lost if the user navigates away mid-window.
- If the deferred DELETE later fails, the ids are restored to the list
  and a "Could not delete the order" destructive toast is shown.

OrderCard now delegates the actual delete to a new onDelete callback
from the page (the confirmation dialog stays in the card). Bulk clear
no longer fires N sequential mutations from inside a confirm dialog
spinner — it just schedules them.

i18n: added myOrders.undo / myOrders.undone strings to en.json and
ar.json.

Verification:
- pnpm tsc --noEmit passes for teaboy-os.
- e2e (runTest) covered: single delete + undo restores; single delete
  with no undo becomes permanent after 7s; bulk clear + undo restores
  all. DB state was asserted in each case.

Follow-up proposed: #72 — apply the same undo pattern to cancelled
orders.
2026-04-22 06:46:42 +00:00
riyadhafraa d2c8e05c6c Add undo window for deleted orders on My Orders
Task #70: After deleting a finished order from My Orders, users had no way
to recover it. This change introduces a short undo window so accidental
deletes can be reversed while keeping the cleanup workflow snappy.

Approach:
- Implemented as a client-side deferred-delete in
  artifacts/teaboy-os/src/pages/my-orders.tsx. No backend changes.
- On delete (single trash icon or bulk "Clear N finished"):
  * The order(s) are added to a local pendingDeleteIds set so they
    immediately disappear from the list.
  * A toast is shown with a localised "Undo" action and a 7s duration.
  * A setTimeout is scheduled to call DELETE /api/orders/:id for the
    affected ids and then invalidate the my-orders query.
- Clicking "Undo" cancels the timer, removes the ids from
  pendingDeleteIds (so the cards reappear), dismisses the toast, and
  shows a "Restored" confirmation toast.
- On unmount, any still-pending deletions are flushed so they aren't
  silently lost if the user navigates away mid-window.
- If the deferred DELETE later fails, the ids are restored to the list
  and a "Could not delete the order" destructive toast is shown.

OrderCard now delegates the actual delete to a new onDelete callback
from the page (the confirmation dialog stays in the card). Bulk clear
no longer fires N sequential mutations from inside a confirm dialog
spinner — it just schedules them.

i18n: added myOrders.undo / myOrders.undone strings to en.json and
ar.json.

Verification:
- pnpm tsc --noEmit passes for teaboy-os.
- e2e (runTest) covered: single delete + undo restores; single delete
  with no undo becomes permanent after 7s; bulk clear + undo restores
  all. DB state was asserted in each case.

Follow-up proposed: #72 — apply the same undo pattern to cancelled
orders.

Replit-Task-Id: 1854a0b1-f6d3-4148-b2c2-b0940202d0df
2026-04-22 06:46:42 +00:00
Riyadh dba20998d6 Adjust service cards to be smaller and tappable directly
Modify service card layout for better density, enable direct image tapping to open orders, and shorten "Place order" button text to "Send".
2026-04-22 06:42:23 +00:00
riyadhafraa 15b32d5ac5 Adjust service cards to be smaller and tappable directly
Modify service card layout for better density, enable direct image tapping to open orders, and shorten "Place order" button text to "Send".

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: ff3bca96-6332-4411-9809-103494a69c1f
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/VRpzQEA
Replit-Helium-Checkpoint-Created: true
2026-04-22 06:42:23 +00:00
Riyadh 3e5c6d6481 Task #69: Delete past orders from My Orders
- Add DELETE /api/orders/:id endpoint: owner can delete completed/cancelled
  orders; admin can delete any. 401/403/404/204 responses. Emits
  order_deleted to owner and assignee for live invalidation.
- Update OpenAPI spec with deleteServiceOrder operation; regenerate
  api-client-react + api-zod.
- Frontend: per-order trash button on completed/cancelled cards with
  confirm dialog; bulk "Clear finished (N)" button at top of list with
  confirm dialog and toast summary (handles partial failures).
- Add ar/en locale keys under myOrders for delete + clear flows
  (singular/plural variants).
- Backend tests cover all paths: pending owner=403, stranger=403,
  owner-cancelled=204, owner-completed=204, admin-pending=204, 404, 401.

All 25 service-order tests pass; the 1 failing test (admin-app-opens-
pagination) is unrelated/pre-existing.

Follow-ups proposed: #70 undo for deleted orders, #71 audit
self-notification on receiver-cancel-own-order.
2026-04-22 06:38:13 +00:00
riyadhafraa 1cea719b80 Task #69: Delete past orders from My Orders
- Add DELETE /api/orders/:id endpoint: owner can delete completed/cancelled
  orders; admin can delete any. 401/403/404/204 responses. Emits
  order_deleted to owner and assignee for live invalidation.
- Update OpenAPI spec with deleteServiceOrder operation; regenerate
  api-client-react + api-zod.
- Frontend: per-order trash button on completed/cancelled cards with
  confirm dialog; bulk "Clear finished (N)" button at top of list with
  confirm dialog and toast summary (handles partial failures).
- Add ar/en locale keys under myOrders for delete + clear flows
  (singular/plural variants).
- Backend tests cover all paths: pending owner=403, stranger=403,
  owner-cancelled=204, owner-completed=204, admin-pending=204, 404, 401.

All 25 service-order tests pass; the 1 failing test (admin-app-opens-
pagination) is unrelated/pre-existing.

Follow-ups proposed: #70 undo for deleted orders, #71 audit
self-notification on receiver-cancel-own-order.
2026-04-22 06:38:13 +00:00
Riyadh 287b10415b Task #69: Delete past orders from My Orders
- Add DELETE /api/orders/:id endpoint: owner can delete completed/cancelled
  orders; admin can delete any. 401/403/404/204 responses. Emits
  order_deleted to owner and assignee for live invalidation.
- Update OpenAPI spec with deleteServiceOrder operation; regenerate
  api-client-react + api-zod.
- Frontend: per-order trash button on completed/cancelled cards with
  confirm dialog; bulk "Clear finished (N)" button at top of list with
  confirm dialog and toast summary (handles partial failures).
- Add ar/en locale keys under myOrders for delete + clear flows
  (singular/plural variants).
- Backend tests cover all paths: pending owner=403, stranger=403,
  owner-cancelled=204, owner-completed=204, admin-pending=204, 404, 401.

All 25 service-order tests pass; the 1 failing test (admin-app-opens-
pagination) is unrelated/pre-existing.

Follow-ups proposed: #70 undo for deleted orders, #71 audit
self-notification on receiver-cancel-own-order.
2026-04-22 06:37:01 +00:00
riyadhafraa 58553d3d36 Task #69: Delete past orders from My Orders
- Add DELETE /api/orders/:id endpoint: owner can delete completed/cancelled
  orders; admin can delete any. 401/403/404/204 responses. Emits
  order_deleted to owner and assignee for live invalidation.
- Update OpenAPI spec with deleteServiceOrder operation; regenerate
  api-client-react + api-zod.
- Frontend: per-order trash button on completed/cancelled cards with
  confirm dialog; bulk "Clear finished (N)" button at top of list with
  confirm dialog and toast summary (handles partial failures).
- Add ar/en locale keys under myOrders for delete + clear flows
  (singular/plural variants).
- Backend tests cover all paths: pending owner=403, stranger=403,
  owner-cancelled=204, owner-completed=204, admin-pending=204, 404, 401.

All 25 service-order tests pass; the 1 failing test (admin-app-opens-
pagination) is unrelated/pre-existing.

Follow-ups proposed: #70 undo for deleted orders, #71 audit
self-notification on receiver-cancel-own-order.
2026-04-22 06:37:01 +00:00
Riyadh 996a046623 Transitioned from Plan to Build mode 2026-04-22 06:31:13 +00:00
riyadhafraa 7574357c2f Transitioned from Plan to Build mode
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: e1ccd1d4-0ad5-4172-ab5b-92d6d7b62227
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/VRpzQEA
Replit-Helium-Checkpoint-Created: true
2026-04-22 06:31:13 +00:00
Riyadh c920fe5266 Notify requester distinctly when receiver cancels their claimed order (Task #67)
Context
- After Task #64 receivers can cancel an order they have already claimed.
- Previously the requester's cancellation notification body was the same regardless of who cancelled (admin vs receiver), giving them no signal that their order had been claimed and then dropped.

Changes
- artifacts/api-server/src/routes/service-orders.ts (PATCH /orders/:id/status, cancel branch):
  - When the cancellation is initiated by the order's currently assigned receiver
    (existing.assignedTo === userId && next === "cancelled"), the requester's
    notification body now includes the bilingual phrase
    "استلم طلبك ولكن تم إلغاؤه لاحقاً" / "Your order was claimed but later
    cancelled by the receiver", appended to the service name for context.
  - Admin / non-assignee cancellations keep the existing service-name body so
    the two cases remain distinguishable in the requester's notifications list.
  - The notification is created via the existing notifyUser() helper, which
    persists to notifications and emits notification_created over Socket.IO,
    so the bell badge updates in real time.
- artifacts/api-server/tests/service-orders.test.mjs:
  - Added "requester gets a distinct notification when the receiver cancels a
    claimed order" covering the full flow: place → confirm-receipt → cancel,
    then asserts the owner has a cancellation notification whose body matches
    the new bilingual receiver-cancel copy.

Verification
- All 7 tests in service-orders.test.mjs pass locally against the running API.

Notes / deviations
- Locale JSON files (ar.json/en.json) were inspected but not modified: notification
  copy is stored server-side as titleAr/titleEn/bodyAr/bodyEn on the notifications
  row; the client just renders those strings directly.
2026-04-22 06:28:29 +00:00
riyadhafraa 6c4c5600de Notify requester distinctly when receiver cancels their claimed order (Task #67)
Context
- After Task #64 receivers can cancel an order they have already claimed.
- Previously the requester's cancellation notification body was the same regardless of who cancelled (admin vs receiver), giving them no signal that their order had been claimed and then dropped.

Changes
- artifacts/api-server/src/routes/service-orders.ts (PATCH /orders/:id/status, cancel branch):
  - When the cancellation is initiated by the order's currently assigned receiver
    (existing.assignedTo === userId && next === "cancelled"), the requester's
    notification body now includes the bilingual phrase
    "استلم طلبك ولكن تم إلغاؤه لاحقاً" / "Your order was claimed but later
    cancelled by the receiver", appended to the service name for context.
  - Admin / non-assignee cancellations keep the existing service-name body so
    the two cases remain distinguishable in the requester's notifications list.
  - The notification is created via the existing notifyUser() helper, which
    persists to notifications and emits notification_created over Socket.IO,
    so the bell badge updates in real time.
- artifacts/api-server/tests/service-orders.test.mjs:
  - Added "requester gets a distinct notification when the receiver cancels a
    claimed order" covering the full flow: place → confirm-receipt → cancel,
    then asserts the owner has a cancellation notification whose body matches
    the new bilingual receiver-cancel copy.

Verification
- All 7 tests in service-orders.test.mjs pass locally against the running API.

Notes / deviations
- Locale JSON files (ar.json/en.json) were inspected but not modified: notification
  copy is stored server-side as titleAr/titleEn/bodyAr/bodyEn on the notifications
  row; the client just renders those strings directly.

Replit-Task-Id: 5aa480a9-c6e0-4a06-8919-b2b6784d8a98
2026-04-22 06:28:29 +00:00
Riyadh 7f599430ee Task #64: Service Orders — receiver page + admin role toggle
Backend (artifacts/api-server)
- Add admin-only role-toggle endpoints:
  - POST /users/:id/roles { roleName } — idempotent, returns UserProfile
  - DELETE /users/:id/roles/:roleName — idempotent, returns UserProfile
- Allow assigned receiver to cancel their own claimed order
  (received/preparing) in PATCH /orders/:id/status. Owner & admin
  rules unchanged.
- New backend test cases: receiver can cancel own assigned order;
  another receiver gets 403.

API spec / codegen
- Add AddUserRoleBody schema and the two new role endpoints
  under a new "roles" tag in lib/api-spec/openapi.yaml.
- Regenerated api-zod and api-client-react.

Frontend (artifacts/teaboy-os)
- New page src/pages/orders-incoming.tsx at /orders/incoming:
  RBAC-gated (admin || order_receiver), shows "My active orders"
  and "Awaiting receiver" sections, with claim, mark preparing,
  mark completed and cancel buttons. Handles 409 already_claimed.
- Add Inbox button to home top bar, conditional on the same roles.
- Admin users table now has an "Order Receiver" Switch wired to
  the new role-toggle hooks.
- Extend use-notifications-socket to invalidate the incoming-orders
  query on order_incoming_changed and on notification_created with
  type === "order".
- Bilingual locale keys (ar/en) for the new page and admin label.

Tests
- All 25 api-server tests pass (24 existing + 1 new receiver-cancel
  case). All 3 teaboy-os e2e tests pass.

Follow-up filed: #67 (notify requester when receiver cancels).
2026-04-21 18:59:33 +00:00
riyadhafraa 0672da245a Task #64: Service Orders — receiver page + admin role toggle
Backend (artifacts/api-server)
- Add admin-only role-toggle endpoints:
  - POST /users/:id/roles { roleName } — idempotent, returns UserProfile
  - DELETE /users/:id/roles/:roleName — idempotent, returns UserProfile
- Allow assigned receiver to cancel their own claimed order
  (received/preparing) in PATCH /orders/:id/status. Owner & admin
  rules unchanged.
- New backend test cases: receiver can cancel own assigned order;
  another receiver gets 403.

API spec / codegen
- Add AddUserRoleBody schema and the two new role endpoints
  under a new "roles" tag in lib/api-spec/openapi.yaml.
- Regenerated api-zod and api-client-react.

Frontend (artifacts/teaboy-os)
- New page src/pages/orders-incoming.tsx at /orders/incoming:
  RBAC-gated (admin || order_receiver), shows "My active orders"
  and "Awaiting receiver" sections, with claim, mark preparing,
  mark completed and cancel buttons. Handles 409 already_claimed.
- Add Inbox button to home top bar, conditional on the same roles.
- Admin users table now has an "Order Receiver" Switch wired to
  the new role-toggle hooks.
- Extend use-notifications-socket to invalidate the incoming-orders
  query on order_incoming_changed and on notification_created with
  type === "order".
- Bilingual locale keys (ar/en) for the new page and admin label.

Tests
- All 25 api-server tests pass (24 existing + 1 new receiver-cancel
  case). All 3 teaboy-os e2e tests pass.

Follow-up filed: #67 (notify requester when receiver cancels).
2026-04-21 18:59:33 +00:00
Riyadh 7c1e8bc85f Task #64: Service Orders — receiver page + admin role toggle
Backend (artifacts/api-server)
- Add admin-only role-toggle endpoints:
  - POST /users/:id/roles { roleName } — idempotent, returns UserProfile
  - DELETE /users/:id/roles/:roleName — idempotent, returns UserProfile
- Allow assigned receiver to cancel their own claimed order
  (received/preparing) in PATCH /orders/:id/status. Owner & admin
  rules unchanged.
- New backend test cases: receiver can cancel own assigned order;
  another receiver gets 403.

API spec / codegen
- Add AddUserRoleBody schema and the two new role endpoints
  under a new "roles" tag in lib/api-spec/openapi.yaml.
- Regenerated api-zod and api-client-react.

Frontend (artifacts/teaboy-os)
- New page src/pages/orders-incoming.tsx at /orders/incoming:
  RBAC-gated (admin || order_receiver), shows "My active orders"
  and "Awaiting receiver" sections, with claim, mark preparing,
  mark completed and cancel buttons. Handles 409 already_claimed.
- Add Inbox button to home top bar, conditional on the same roles.
- Admin users table now has an "Order Receiver" Switch wired to
  the new role-toggle hooks.
- Extend use-notifications-socket to invalidate the incoming-orders
  query on order_incoming_changed and on notification_created with
  type === "order".
- Bilingual locale keys (ar/en) for the new page and admin label.

Tests
- All 25 api-server tests pass (24 existing + 1 new receiver-cancel
  case). All 3 teaboy-os e2e tests pass.

Follow-up filed: #67 (notify requester when receiver cancels).
2026-04-21 18:58:19 +00:00
riyadhafraa 7ebf59d4b7 Task #64: Service Orders — receiver page + admin role toggle
Backend (artifacts/api-server)
- Add admin-only role-toggle endpoints:
  - POST /users/:id/roles { roleName } — idempotent, returns UserProfile
  - DELETE /users/:id/roles/:roleName — idempotent, returns UserProfile
- Allow assigned receiver to cancel their own claimed order
  (received/preparing) in PATCH /orders/:id/status. Owner & admin
  rules unchanged.
- New backend test cases: receiver can cancel own assigned order;
  another receiver gets 403.

API spec / codegen
- Add AddUserRoleBody schema and the two new role endpoints
  under a new "roles" tag in lib/api-spec/openapi.yaml.
- Regenerated api-zod and api-client-react.

Frontend (artifacts/teaboy-os)
- New page src/pages/orders-incoming.tsx at /orders/incoming:
  RBAC-gated (admin || order_receiver), shows "My active orders"
  and "Awaiting receiver" sections, with claim, mark preparing,
  mark completed and cancel buttons. Handles 409 already_claimed.
- Add Inbox button to home top bar, conditional on the same roles.
- Admin users table now has an "Order Receiver" Switch wired to
  the new role-toggle hooks.
- Extend use-notifications-socket to invalidate the incoming-orders
  query on order_incoming_changed and on notification_created with
  type === "order".
- Bilingual locale keys (ar/en) for the new page and admin label.

Tests
- All 25 api-server tests pass (24 existing + 1 new receiver-cancel
  case). All 3 teaboy-os e2e tests pass.

Follow-up filed: #67 (notify requester when receiver cancels).
2026-04-21 18:58:19 +00:00
Riyadh 1bf1c0b9a8 Task #63: Service Orders client UI (place order + My Orders)
Adds the user-facing half of the service ordering workflow on top of the
backend foundation merged in Task #62.

What's new:
- Order button on each available service card (hidden when service is
  unavailable) opens a focused OrderServiceModal showing only the
  service name + image with a multi-line notes textarea (500-char cap
  with live counter). Submitting calls POST /orders, shows a toast, and
  invalidates the My Orders cache.
- New /my-orders page lists the user's own orders newest-first with a
  status pill, a horizontal 4-step timeline (pending → received →
  preparing → completed), and a red terminal pill for cancelled orders.
- Cancel action with confirm dialog is shown only while the order is
  pending or received; it calls PATCH /orders/:id/status with
  cancelled.
- "My Orders" link added to the services page header.
- Realtime: the existing notifications socket also invalidates the
  my-orders query on notification_created (when type === 'order') and
  on a new order_updated event.
- Locale keys mirrored in ar.json and en.json for services.order/*,
  myOrders.*, and orderStatus.*. RTL/LTR handled.
- Friendly load-error state with retry, plus client-side sort by
  createdAt desc.

Verification: typecheck clean, all 3 e2e tests pass, manual end-to-end
UI flow (place + cancel + locale switch) verified via testing skill,
backend smoke test confirmed POST /orders + GET /orders/my wiring.
2026-04-21 18:48:48 +00:00
riyadhafraa 41d5c7e757 Task #63: Service Orders client UI (place order + My Orders)
Adds the user-facing half of the service ordering workflow on top of the
backend foundation merged in Task #62.

What's new:
- Order button on each available service card (hidden when service is
  unavailable) opens a focused OrderServiceModal showing only the
  service name + image with a multi-line notes textarea (500-char cap
  with live counter). Submitting calls POST /orders, shows a toast, and
  invalidates the My Orders cache.
- New /my-orders page lists the user's own orders newest-first with a
  status pill, a horizontal 4-step timeline (pending → received →
  preparing → completed), and a red terminal pill for cancelled orders.
- Cancel action with confirm dialog is shown only while the order is
  pending or received; it calls PATCH /orders/:id/status with
  cancelled.
- "My Orders" link added to the services page header.
- Realtime: the existing notifications socket also invalidates the
  my-orders query on notification_created (when type === 'order') and
  on a new order_updated event.
- Locale keys mirrored in ar.json and en.json for services.order/*,
  myOrders.*, and orderStatus.*. RTL/LTR handled.
- Friendly load-error state with retry, plus client-side sort by
  createdAt desc.

Verification: typecheck clean, all 3 e2e tests pass, manual end-to-end
UI flow (place + cancel + locale switch) verified via testing skill,
backend smoke test confirmed POST /orders + GET /orders/my wiring.
2026-04-21 18:48:48 +00:00
Riyadh 4eb8cbfad3 Task #63: Service Orders client UI (place order + My Orders)
Adds the user-facing half of the service ordering workflow on top of the
backend foundation merged in Task #62.

What's new:
- Order button on each available service card (hidden when service is
  unavailable) opens a focused OrderServiceModal showing only the
  service name + image with a multi-line notes textarea (500-char cap
  with live counter). Submitting calls POST /orders, shows a toast, and
  invalidates the My Orders cache.
- New /my-orders page lists the user's own orders newest-first with a
  status pill, a horizontal 4-step timeline (pending → received →
  preparing → completed), and a red terminal pill for cancelled orders.
- Cancel action with confirm dialog is shown only while the order is
  pending or received; it calls PATCH /orders/:id/status with
  cancelled.
- "My Orders" link added to the services page header.
- Realtime: the existing notifications socket also invalidates the
  my-orders query on notification_created (when type === 'order') and
  on a new order_updated event.
- Locale keys mirrored in ar.json and en.json for services.order/*,
  myOrders.*, and orderStatus.*. RTL/LTR handled.
- Friendly load-error state with retry, plus client-side sort by
  createdAt desc.

Verification: typecheck clean, all 3 e2e tests pass, manual end-to-end
UI flow (place + cancel + locale switch) verified via testing skill,
backend smoke test confirmed POST /orders + GET /orders/my wiring.
2026-04-21 18:47:30 +00:00
riyadhafraa c20d411c7f Task #63: Service Orders client UI (place order + My Orders)
Adds the user-facing half of the service ordering workflow on top of the
backend foundation merged in Task #62.

What's new:
- Order button on each available service card (hidden when service is
  unavailable) opens a focused OrderServiceModal showing only the
  service name + image with a multi-line notes textarea (500-char cap
  with live counter). Submitting calls POST /orders, shows a toast, and
  invalidates the My Orders cache.
- New /my-orders page lists the user's own orders newest-first with a
  status pill, a horizontal 4-step timeline (pending → received →
  preparing → completed), and a red terminal pill for cancelled orders.
- Cancel action with confirm dialog is shown only while the order is
  pending or received; it calls PATCH /orders/:id/status with
  cancelled.
- "My Orders" link added to the services page header.
- Realtime: the existing notifications socket also invalidates the
  my-orders query on notification_created (when type === 'order') and
  on a new order_updated event.
- Locale keys mirrored in ar.json and en.json for services.order/*,
  myOrders.*, and orderStatus.*. RTL/LTR handled.
- Friendly load-error state with retry, plus client-side sort by
  createdAt desc.

Verification: typecheck clean, all 3 e2e tests pass, manual end-to-end
UI flow (place + cancel + locale switch) verified via testing skill,
backend smoke test confirmed POST /orders + GET /orders/my wiring.
2026-04-21 18:47:30 +00:00
Riyadh 310dad874a Task #62: Service Orders backend foundation (corrected)
After prior code review rejection, refactored to match spec exactly:
- Permission renamed orders:receive → orders.receive (dot form), seeded with order_receiver role
- service_orders table: user_id, service_id, notes, status (pending/received/preparing/completed/cancelled with CHECK), assigned_to, created_at, updated_at
- /confirm-receipt is now the receiver atomic claim (UPDATE WHERE pending+unassigned), 409 already_claimed on miss
- /status accepts preparing|completed|cancelled with permission matrix:
  * preparing/completed: assigned receiver or admin
  * cancelled: owner (pending|received) OR admin (any non-cancelled status)
- requirePermission middleware no longer auto-bypasses admin; admin gets the permission via explicit seed grant
- Notifications use type='order', relatedType='order'
- Realtime emits notification_created (per receiver) + order_incoming_changed (broadcast) + order_updated (owner)
- OpenAPI Order schema rewritten (no quantity, no per-status timestamps), endpoint summaries updated, codegen run
- Tests cover: client place+list, non-receiver 403, no-role 403, parallel claim race (200/409), status matrix, owner-cancel rules, admin-cancel-completed, descriptions excluded from service summary

All 24 api-server tests pass. Ready for code review re-check.
2026-04-21 18:36:25 +00:00
riyadhafraa a8b30baaac Task #62: Service Orders backend foundation (corrected)
After prior code review rejection, refactored to match spec exactly:
- Permission renamed orders:receive → orders.receive (dot form), seeded with order_receiver role
- service_orders table: user_id, service_id, notes, status (pending/received/preparing/completed/cancelled with CHECK), assigned_to, created_at, updated_at
- /confirm-receipt is now the receiver atomic claim (UPDATE WHERE pending+unassigned), 409 already_claimed on miss
- /status accepts preparing|completed|cancelled with permission matrix:
  * preparing/completed: assigned receiver or admin
  * cancelled: owner (pending|received) OR admin (any non-cancelled status)
- requirePermission middleware no longer auto-bypasses admin; admin gets the permission via explicit seed grant
- Notifications use type='order', relatedType='order'
- Realtime emits notification_created (per receiver) + order_incoming_changed (broadcast) + order_updated (owner)
- OpenAPI Order schema rewritten (no quantity, no per-status timestamps), endpoint summaries updated, codegen run
- Tests cover: client place+list, non-receiver 403, no-role 403, parallel claim race (200/409), status matrix, owner-cancel rules, admin-cancel-completed, descriptions excluded from service summary

All 24 api-server tests pass. Ready for code review re-check.
2026-04-21 18:36:25 +00:00
Riyadh 37bceb4565 Task #62: Service Orders backend foundation (corrected)
After prior code review rejection, refactored to match spec exactly:
- Permission renamed orders:receive → orders.receive (dot form), seeded with order_receiver role
- service_orders table: user_id, service_id, notes, status (pending/received/preparing/completed/cancelled with CHECK), assigned_to, created_at, updated_at
- /confirm-receipt is now the receiver atomic claim (UPDATE WHERE pending+unassigned), 409 already_claimed on miss
- /status accepts preparing|completed|cancelled with permission matrix:
  * preparing/completed: assigned receiver or admin
  * cancelled: owner (pending|received) OR admin (any non-cancelled status)
- requirePermission middleware no longer auto-bypasses admin; admin gets the permission via explicit seed grant
- Notifications use type='order', relatedType='order'
- Realtime emits notification_created (per receiver) + order_incoming_changed (broadcast) + order_updated (owner)
- OpenAPI Order schema rewritten (no quantity, no per-status timestamps), endpoint summaries updated, codegen run
- Tests cover: client place+list, non-receiver 403, no-role 403, parallel claim race (200/409), status matrix, owner-cancel rules, admin-cancel-completed, descriptions excluded from service summary

All 24 api-server tests pass. Ready for code review re-check.
2026-04-21 18:34:14 +00:00
riyadhafraa 2602edaca0 Task #62: Service Orders backend foundation (corrected)
After prior code review rejection, refactored to match spec exactly:
- Permission renamed orders:receive → orders.receive (dot form), seeded with order_receiver role
- service_orders table: user_id, service_id, notes, status (pending/received/preparing/completed/cancelled with CHECK), assigned_to, created_at, updated_at
- /confirm-receipt is now the receiver atomic claim (UPDATE WHERE pending+unassigned), 409 already_claimed on miss
- /status accepts preparing|completed|cancelled with permission matrix:
  * preparing/completed: assigned receiver or admin
  * cancelled: owner (pending|received) OR admin (any non-cancelled status)
- requirePermission middleware no longer auto-bypasses admin; admin gets the permission via explicit seed grant
- Notifications use type='order', relatedType='order'
- Realtime emits notification_created (per receiver) + order_incoming_changed (broadcast) + order_updated (owner)
- OpenAPI Order schema rewritten (no quantity, no per-status timestamps), endpoint summaries updated, codegen run
- Tests cover: client place+list, non-receiver 403, no-role 403, parallel claim race (200/409), status matrix, owner-cancel rules, admin-cancel-completed, descriptions excluded from service summary

All 24 api-server tests pass. Ready for code review re-check.
2026-04-21 18:34:14 +00:00
Riyadh fbab7ac5a6 Service Orders — backend foundation (Task #62)
- New `service_orders` table (status pending|claimed|delivered|received|cancelled)
- New `orders:receive` permission + `order_receiver` role; admins implicitly allowed
- Added `requirePermission(name)` and `userHasPermission(userId, name)` middleware helpers
- New routes:
  - POST   /api/orders                    place order (authenticated)
  - GET    /api/orders/my                 list current user's orders
  - GET    /api/orders/incoming           receivers see pending+active visible orders
  - PATCH  /api/orders/:id/status         claim (atomic), deliver, cancel
  - PATCH  /api/orders/:id/confirm-receipt requester confirms a delivered order
- Atomic claim via UPDATE ... WHERE status='pending' AND assigned_to IS NULL
  (returns 409 already_claimed on race)
- Realtime: emits `notification_created` to receivers/requester,
  `order_incoming_changed` to all receivers, `order_updated` to requester
- Service shape in order responses limited to id/nameAr/nameEn/imageUrl
  (no description fields), per spec
- OpenAPI updated with new paths and schemas; codegen run
- Seed updated idempotently (permission, role, role_permissions)
- New tests in artifacts/api-server/tests/service-orders.test.mjs
  (full lifecycle, atomic claim race, unauth rejection) — all 21 api tests pass

No deviations from the planned scope. Tasks #63 (client UI) and #64
(receiver page + admin role toggle) remain blocked-by #62 and are next.
2026-04-21 18:24:20 +00:00
riyadhafraa cdf5bf4d33 Service Orders — backend foundation (Task #62)
- New `service_orders` table (status pending|claimed|delivered|received|cancelled)
- New `orders:receive` permission + `order_receiver` role; admins implicitly allowed
- Added `requirePermission(name)` and `userHasPermission(userId, name)` middleware helpers
- New routes:
  - POST   /api/orders                    place order (authenticated)
  - GET    /api/orders/my                 list current user's orders
  - GET    /api/orders/incoming           receivers see pending+active visible orders
  - PATCH  /api/orders/:id/status         claim (atomic), deliver, cancel
  - PATCH  /api/orders/:id/confirm-receipt requester confirms a delivered order
- Atomic claim via UPDATE ... WHERE status='pending' AND assigned_to IS NULL
  (returns 409 already_claimed on race)
- Realtime: emits `notification_created` to receivers/requester,
  `order_incoming_changed` to all receivers, `order_updated` to requester
- Service shape in order responses limited to id/nameAr/nameEn/imageUrl
  (no description fields), per spec
- OpenAPI updated with new paths and schemas; codegen run
- Seed updated idempotently (permission, role, role_permissions)
- New tests in artifacts/api-server/tests/service-orders.test.mjs
  (full lifecycle, atomic claim race, unauth rejection) — all 21 api tests pass

No deviations from the planned scope. Tasks #63 (client UI) and #64
(receiver page + admin role toggle) remain blocked-by #62 and are next.
2026-04-21 18:24:20 +00:00
riyadhafraa de8333e659 Transitioned from Plan to Build mode
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: bf12c694-72b5-40f8-bb03-65f73ca59aa2
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/PwtbShH
Replit-Helium-Checkpoint-Created: true
2026-04-21 18:17:39 +00:00
Riyadh c118490643 Move the ability to hide the top bar clock into the clock settings menu
Integrates the top bar clock's visibility toggle into the ClockStylePicker component and removes the standalone eye button. Updates locale files for Arabic and English to reflect the new string for showing the top bar clock.
2026-04-21 18:04:17 +00:00
riyadhafraa 59105b0bbf Move the ability to hide the top bar clock into the clock settings menu
Integrates the top bar clock's visibility toggle into the ClockStylePicker component and removes the standalone eye button. Updates locale files for Arabic and English to reflect the new string for showing the top bar clock.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 89fb8094-c538-4564-8b9b-29c0f4236486
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/PwtbShH
Replit-Helium-Checkpoint-Created: true
2026-04-21 18:04:17 +00:00
Riyadh 5b1b1e9bab Add ability to hide the top bar clock display independently
Introduced a new hook `useTopbarClockVisibility` in `artifacts/teaboy-os/src/components/clock.tsx` to manage the visibility of the top bar clock, separate from the home clock widget. Updated `artifacts/teaboy-os/src/pages/home.tsx` to utilize this new hook, allowing the eye icon to toggle the top bar clock's visibility.
2026-04-21 18:02:45 +00:00