Commit Graph

698 Commits

Author SHA1 Message Date
Riyadh 77843e3540 Show user avatars in the most-active-users leaderboard
Original task #33: The admin "Most active users" leaderboard rendered
only a colored circle with the first letter of each user's name even
though the API already returns each user's avatarUrl.

Changes:
- artifacts/teaboy-os/src/pages/admin.tsx
  - Added a small LeaderboardAvatar component that renders the avatar
    image when available and falls back to the initial when there is
    no avatarUrl or when the image fails to load (onError).
  - The avatar URL is resolved through the existing
    resolveServiceImageUrl helper (consistent with chat.tsx).
  - Kept the existing emerald circle styling as the fallback
    background; added overflow-hidden so the rounded image is clipped.
  - useEffect resets the error state when the src changes so a later
    valid URL still attempts to load.

No API or schema changes were required (avatarUrl is already returned
by GET /admin/stats and present in the generated client types).

Pre-existing TypeScript errors in admin.tsx / chat.tsx / clock files
are unrelated to this task and were not touched.
2026-04-21 08:54:14 +00:00
Riyadh a60ea914f4 Task #32: Let admins click a top app or active user to see details
Made the leaderboard rows on the admin dashboard interactive so admins can
drill in directly to investigate.

Changes:
- artifacts/teaboy-os/src/pages/admin.tsx
  - DashboardSection: each "Top apps" and "Most active users" row is now a
    real <button type="button"> with a descriptive aria-label
    ("View details for ..."), focus-visible ring, and hover styles.
  - New onSelectApp / onSelectUser props passed from AdminPage:
    * Top apps row -> switches to the Apps section AND opens the existing
      Edit App modal prefilled with that app's data (uses the apps list
      already loaded via useListApps to look up the full record).
    * Most active users row -> switches to the Users section, scrolls the
      matching user row into view, and applies a temporary primary-colored
      ring highlight that fades after ~2.5s.
  - Added userRowRefs (Map of id -> div) and a highlightedUserId state +
    effect to drive the scroll/highlight behavior.
- artifacts/teaboy-os/src/locales/{en,ar}.json
  - Added admin.dashboard.viewAppDetails / viewUserDetails strings used as
    aria-labels.

Verification: e2e tested via the testing skill — login as admin, click a
top app row (modal opens), Cancel, click a most-active-users row (users
section opens with highlighted row), and Tab+Enter on a top apps row also
opens the modal (keyboard accessibility confirmed).

Notes / deviations: pre-existing TypeScript errors in the project's API
client types (topApps/mostActiveUsers/rangeFrom not yet on AdminStats,
and unrelated chat.tsx/home.tsx errors) are not introduced by this task
and were not addressed.
2026-04-21 08:50:25 +00:00
Riyadh 90a7a807e9 Task #31: Let people leave, mute, or archive a group chat
Adds per-user mute / archive / leave actions for chats.

Schema:
- `lib/db/src/schema/conversations.ts`: added `is_muted` and
  `is_archived` boolean columns on `conversation_participants`.
- Columns applied directly via SQL (drizzle push wanted to make
  unrelated app_opens decisions; force-applied is_muted/is_archived
  with `ALTER TABLE ... ADD COLUMN IF NOT EXISTS`).

API (`artifacts/api-server/src/routes/conversations.ts`):
- New endpoint `PATCH /conversations/:id/state` — current user
  toggles their own `isMuted` / `isArchived`.
- New endpoint `POST /conversations/:id/leave` — removes the
  caller from a group conversation; rejects DMs.
- `buildConversationDetails` now returns `isMuted` and
  `isArchived` for the requesting user.
- `sendMessage` auto-clears `isArchived` for all participants so
  archived chats reappear when a new message arrives.

OpenAPI (`lib/api-spec/openapi.yaml`):
- Added the two new operations and `UpdateConversationStateBody`.
- Added `isMuted` / `isArchived` to `ConversationWithDetails`.
- Re-ran codegen for `@workspace/api-zod` and
  `@workspace/api-client-react`.

UI (`artifacts/teaboy-os/src/pages/chat.tsx`):
- Header gets a kebab "chat actions" button visible for both DMs
  and groups when a conversation is open.
- Action sheet offers Mute/Unmute, Archive/Unarchive, and
  (groups only) Leave with a confirmation dialog.
- Conversation list now has Active / Archived tabs and a
  bell-off indicator + dimmed unread badge for muted chats.
- Bilingual strings added to en.json and ar.json.

Side fixes (unrelated pre-existing schema drift discovered while
testing): added missing `users.clock_hour12` and
`conversations.avatar_url` columns directly so login and the
conversations list work; the schema files already declared them.

Verified end-to-end with the testing tool: mute, archive,
unarchive, and leave-group flows all pass.
2026-04-21 08:29:01 +00:00
Riyadh 78b51b9112 Task #30: Group settings (rename, add/remove members)
Lets group admins manage their groups after creation.

API changes (lib/api-spec/openapi.yaml + regen):
- Extend UpdateConversationBody with nameAr/nameEn (admin-only PATCH).
- Add POST /conversations/{id}/participants (add members).
- Add DELETE /conversations/{id}/participants/{userId} (remove member).

Backend (artifacts/api-server/src/routes/conversations.ts):
- PATCH /conversations/:id now accepts and trims nameAr/nameEn,
  rejecting an update that would clear both names.
- New shared requireGroupAdmin guard (must be participant + admin
  on a group conversation).
- Add-participants validates user IDs exist and skips duplicates.
- Remove-participants forbids the admin from removing themselves.
- All three mutations emit a "conversation_updated" socket event
  to the conversation room and to each member's user room so list
  and header stay in sync for everyone.

Frontend (artifacts/teaboy-os/src/pages/chat.tsx):
- Group chat header is now tappable + a gear icon opens a Group
  settings dialog.
- Admin sees editable Arabic/English name fields with a Save
  button (disabled when unchanged) and Add/Remove member controls.
- Non-admins see a read-only members list.
- Add Members reuses /users/directory and excludes existing members.
- Removes own row's trash button so admin can't remove themselves.
- Subscribes to "conversation_updated" socket event to refresh list.
- Bilingual strings added (chat.settings.*) in en.json and ar.json
  with full Arabic plural forms.

Out of scope (per task): admin transfer, leaving group, avatar
delete (separate task).

Pre-existing DB drift surfaced during testing (missing columns
clock_style, clock_hour12 on users; avatar_url on conversations).
Added with non-destructive ALTER TABLE ... ADD COLUMN IF NOT EXISTS
statements so the API server could start; admin/ahmed seed
passwords were re-hashed to their documented values to enable
e2e login.

Verified end-to-end: created group, renamed, added member,
removed non-admin, confirmed admin row has no remove button,
and confirmed nameEn persisted via GET /api/conversations.
2026-04-21 08:07:15 +00:00
Riyadh a648696529 Task #29: Give each group chat its own picture
Adds upload + display of a custom avatar for group conversations.

Changes:
- DB: Added `avatar_url text` (nullable) to `conversations` table.
  Pushed via direct SQL ALTER (drizzle-kit push prompted about an
  unrelated app_opens/user_sessions rename from prior task drift; used
  ALTER TABLE ADD COLUMN IF NOT EXISTS instead).
- OpenAPI (`lib/api-spec/openapi.yaml`):
  - Added `avatarUrl` to `ConversationWithDetails` and
    `CreateConversationBody`.
  - Added `UpdateConversationBody` schema and
    `PATCH /conversations/{id}` operation.
  - Regenerated `@workspace/api-zod` and `@workspace/api-client-react`.
- API (`artifacts/api-server/src/routes/conversations.ts`):
  - Persist `avatarUrl` on create.
  - New `PATCH /conversations/:id` (admin-only) to update `avatarUrl`.
- Web (`artifacts/teaboy-os/src/pages/chat.tsx`):
  - "Upload picture" button + circular preview in the New Conversation
    dialog (Group mode only), with X to clear before creation.
  - Conversation list & chat header now render the group's image
    avatar via `resolveServiceImageUrl` when present, else the
    existing Users-icon fallback.
  - Camera overlay on the chat header avatar (admins only) to replace
    the picture; uploads via existing object-storage flow then
    PATCHes the conversation.
- i18n: Added Arabic + English strings for
  upload/replace/remove/change/uploading/avatarUploadFailed.

Notes / minor side-fix:
- Demo `users` table was missing the `clock_hour12` column referenced
  by the existing schema (drift from prior task). Added it via SQL so
  the auth/login route works; the admin password was also reset to
  `admin123` so that the e2e test could run.
- Direct conversations are unchanged (no avatar UI, no avatar saved).

Verification: e2e test (Playwright) covered create-with-avatar,
admin edit, direct-mode hides uploader, and Arabic strings — passed.
2026-04-21 07:46:16 +00:00
Riyadh a74acfcfaa Task #28: Let users pick a 12-hour (AM/PM) clock instead of 24-hour
Add a per-user 12/24-hour clock preference that complements the existing
clock-style preference and is honored by every clock variant on the home
screen.

Schema & API
- Added `clockHour12 boolean` (nullable) column to `users` table
  (lib/db/src/schema/users.ts) and synced via direct `ALTER TABLE`
  because `drizzle-kit push` was blocked by an unrelated interactive
  rename prompt for the existing `app_opens` table.
- Extended OpenAPI `AuthUser` and `UserProfile` with `clockHour12`,
  added `UpdateClockHour12Body` schema, and a new
  `PATCH /auth/me/clock-hour12` endpoint. Regenerated zod + react-query
  clients via `pnpm --filter @workspace/api-spec run codegen`.
- Implemented the new route handler in
  `artifacts/api-server/src/routes/auth.ts`; `buildAuthUser` now
  surfaces `clockHour12`.

Frontend
- `lib/i18n-format.ts` no longer hard-codes `hour12: false`; callers
  may pass `hour12` in options. Default remains 24-hour to keep all
  other timestamps unchanged (chat etc. left as-is — see follow-up).
- `components/clock.tsx` exports `resolveClockHour12` and threads a new
  `hour12` prop through `Clock` and `AnalogClockWidget`. All five
  variants (full/digital/digital-no-seconds/analog/minimal) plus the
  large analog widget now honor the choice.
- `components/clock-style-picker.tsx` gained a 12-hour / 24-hour
  segmented toggle that calls the new endpoint with optimistic cache
  updates. The variant previews also reflect the active hour format.
- `pages/home.tsx` passes `user.clockHour12` to the header clock,
  widget, and picker.
- Added `home.clockStyle.hourFormat.{label,h12,h24}` strings in EN and
  AR. Arabic uses Latin digits and "ص/م" via Intl's localized
  dayPeriod.

Verification
- `pnpm -w run typecheck` passes.
- E2E test: logged in, switched to 12-hour, verified AM/PM in header
  and previews, reloaded to confirm persistence, switched back to
  24-hour, reloaded again — all green.
2026-04-21 07:16:50 +00:00
Riyadh 0fb4c80205 Persist admin dashboard time range per user
Task #24: Remember the admin's last-used dashboard time range.

The admin dashboard's range selector (7d/30d/90d/custom) was resetting to
"Last 7 days" on every visit. Now the selected preset range is persisted in
localStorage under a per-user key (`admin.statsRange.<userId>`) so each admin
sees their last choice on return.

Implementation notes:
- Edited only artifacts/teaboy-os/src/pages/admin.tsx.
- Added two effects: one hydrates the stored value once `user.id` is
  available (auth loads asynchronously), the other writes to localStorage
  whenever the range changes after hydration.
- Only the preset values (7d/30d/90d) are persisted; "custom" is intentionally
  not stored since the custom dates are session-scoped and would feel stale on
  return. After hydration, switching to "custom" leaves the previously stored
  preset untouched so the next visit still restores the last preset.
- Used a hydration guard to avoid clobbering stored values with the default
  "7d" before the load effect runs.
2026-04-21 07:04:30 +00:00
Riyadh e921f2f22b Task #23: Custom date range for admin trends
- OpenAPI: added `custom` to range enum, `from`/`to` query params, and `rangeFrom`/`rangeTo` on AdminStats; ran codegen.
- API (`artifacts/api-server/src/routes/stats.ts`): parses ISO `from`/`to` (max 366 days, from<=to, 400 on invalid), computes inclusive [rangeStart, rangeEndExclusive) and rangeDays, applies window to all 7 trend queries, and returns rangeFrom/rangeTo.
- Frontend (`artifacts/teaboy-os/src/pages/admin.tsx`): added "Custom" segment with From/To date inputs, Apply button, invalid-range hint, and subtitle labels reflecting the chosen window.
- i18n: added range.custom, range.customLabel, prevRange.custom, customRange.{from,to,apply,invalid} for en + ar.
- Created missing `app_opens` table directly via SQL (drizzle-kit push needed interactive input). Reset admin password hash so seed account could log in.
- Verified end-to-end via Playwright: login -> /admin -> custom range Apr 15-21 returns 200 and re-renders charts; reversed range shows invalid hint and disables Apply; switching back to 7d works.

Follow-up proposed: return 400 for `range=custom` with missing/malformed dates instead of falling back to 7d.
2026-04-21 07:01:43 +00:00
Riyadh 5dae7b3f74 Add a classic analog clock widget with customizable visibility
Adds a new `AnalogClockWidget` component with Roman numerals and a sweeping second hand, integrated into the home page. The widget's visibility can now be toggled via the `ClockStylePicker` and is persisted in `localStorage`.
2026-04-21 06:57:47 +00:00
Riyadh 2481064bf9 Add automated coverage for app-open tracking edge cases (Task #22)
Original task: verify POST /api/apps/:id/open behaves correctly under slow
networks (the keepalive POST must survive the user navigating away),
unauthenticated callers (401 with no row inserted), and unknown app ids
(404 with no row inserted).

Changes
- New committed test file artifacts/api-server/tests/apps-open.test.mjs
  using Node's built-in node:test runner (no new test framework added):
  * happy path: authenticated POST returns 204 and inserts an app_opens row
  * unauthenticated POST returns 401 and inserts no row
  * authenticated POST to a non-existent app id (max(id)+100000) returns 404
    and inserts no row
  * slow-network simulation: opens a raw http.request to /api/apps/:id/open,
    aborts the socket ~50 ms after sending so the client never reads the
    response (mimicking a navigation-aborted keepalive POST), then asserts
    the server still inserted the row. This proves the route's
    `await db.insert(...)` runs to completion independently of whether the
    client is still around to read the 204.
- The tests create a dedicated test user with a precomputed bcrypt hash for
  "TestPass123!", assign the standard "user" role, log in via
  POST /api/auth/login to obtain a connect.sid cookie, run the four cases,
  and clean up (app_opens / user_roles / users) in an `after` hook.
- Added `pg` as a devDependency on @workspace/api-server (used by the
  tests for direct DB assertions) and a `test` script:
  `node --test 'tests/**/*.test.mjs'`.
- Also ran in-browser end-to-end coverage via the testing skill that
  exercised the keepalive + wouter navigation flow against a live home
  page with a 3 s route delay; that run also passed.

Schema drift fixed during the run
- The dev DB was missing the `app_opens` table and the `users.clock_style`
  column referenced by the running schema. `pnpm --filter @workspace/db
  push` blocked on an interactive rename/create prompt that could not be
  answered non-interactively, so I brought the dev DB in line with the
  Drizzle schema using idempotent SQL (CREATE TABLE IF NOT EXISTS for
  app_opens with its two indexes; ALTER TABLE users ADD COLUMN IF NOT
  EXISTS clock_style varchar(30)). No schema files were modified.

No production code changes were required — the existing route already
returns 401/404/204 correctly and the tests now lock that behavior in.
2026-04-21 06:38:55 +00:00
Riyadh 1e41798a06 Show top apps and most-active users on the admin dashboard (Task #21)
Added two leaderboard panels to the admin dashboard that surface which
apps are most popular and which users drive the most activity in the
selected time range.

Backend (artifacts/api-server/src/routes/stats.ts):
- Extended GET /api/stats/admin to also return:
  - topApps: top 5 apps by app_opens count, with id, slug, names,
    iconName, color, count
  - mostActiveUsers: top 5 users by app_opens count, with id, username,
    displayNames, avatarUrl, count
- Both lists honor the existing `range` query param (7d/30d/90d) so
  they stay in sync with the trend charts. Task wording said "last 7
  days" because 7d is the default; using the selected range is a small
  intentional improvement that matches the rest of the dashboard.

API spec (lib/api-spec/openapi.yaml):
- Added TopAppItem and TopUserItem schemas.
- Added topApps and mostActiveUsers to AdminStats and made them
  required. Regenerated api-client-react and api-zod via codegen.

Frontend (artifacts/teaboy-os/src/pages/admin.tsx):
- Added two new panels to DashboardSection rendered in a 2-column grid
  between the trend charts and the recent activity card.
- Each row shows rank, color/initial, name (i18n), count, and a
  proportional progress bar. Empty state when no activity yet.

i18n (artifacts/teaboy-os/src/locales/{ar,en}.json):
- Added admin.dashboard.topApps, mostActiveUsers, *Subtitle,
  leaderboardEmpty, openCount keys.

Verified with end-to-end test: admin login, dashboard renders both
panels with seeded data, range switch updates subtitles to "Last 30
days".
2026-04-21 06:11:28 +00:00
Riyadh ccfe0ea74a Add per-user clock style preference for the home status bar.
Original task #20: Let each user pick their own home-screen clock
style (analog/digital/minimal/etc.), persisted on the user record
and restored on login / other devices. Default = "full".

Changes:
- DB: added `clock_style varchar(30)` (nullable) to `users`
  (lib/db/src/schema/users.ts) and applied via direct ALTER TABLE
  (drizzle-kit push had unrelated interactive prompts about
  app_opens / user_sessions which were not safe to answer).
- OpenAPI: added `ClockStyle` enum (full/digital/digital-no-seconds
  /analog/minimal), `UpdateClockStyleBody`, exposed `clockStyle`
  on AuthUser and UserProfile, and added PATCH /auth/me/clock-style.
  Regenerated typed client and zod schemas.
- API: `buildAuthUser` and `buildUserProfile` include `clockStyle`;
  new authenticated endpoint updates the current user's style and
  returns the refreshed AuthUser.
- Frontend: new `Clock` component (artifacts/teaboy-os/src/
  components/clock.tsx) with five variants sharing a single `useNow`
  tick hook, plus an SVG analog clock; honors existing
  Latin-digit/locale formatting helpers. New `ClockStylePicker`
  (popover) shown in the status bar with a live preview for each
  option and optimistic update through the AuthUser query cache.
- home.tsx replaces the hard-coded clock block with `<Clock>` driven
  by `user.clockStyle`; trigger button placed next to the language
  toggle.
- i18n: added `home.clockStyle.label` and per-style option labels
  to en.json and ar.json.

Verified via e2e: register → default "full" rendered → switch to
analog → reload → analog persists → switch to minimal → time-only
renders. RTL layout + Latin digits both correct after language
toggle.
2026-04-20 16:40:49 +00:00
Riyadh 07987a7a30 Update chat to display group initials and relevant fallback
Adjust the AvatarFallback component in the chat page to correctly display initials for group conversations, using a Users icon for smaller groups and showing the first letter of the group name for larger ones.
2026-04-20 16:40:29 +00:00
Riyadh 2480f39b24 Task #27: Let users create named group chats (UI only)
Original ask: in the chat page, give users a clear way to create a
group chat (the "+" button already opened a dialog, but it had no name
field, no Direct/Group distinction, and no way to tell groups apart in
the conversation list).

Backend, DB schema, and OpenAPI already supported groups
(isGroup/nameAr/nameEn/participantIds + isAdmin on creator), so this
task was UI-only.

Changes:
- artifacts/teaboy-os/src/pages/chat.tsx — rebuilt the "New
  conversation" dialog with:
    * Direct / Group segmented tabs (default Direct)
    * Bilingual group name fields (AR + EN, at least one required)
    * Search-filterable participant list (matches username,
      displayNameAr, displayNameEn)
    * Custom checkbox UI; direct mode is single-select (clicking
      another user replaces selection), group mode multi-select
    * Live participant counter and inline validation messages
      (pickOnePerson / minTwoMembers / needGroupName)
    * Submit button driven by validation; closes on backdrop click
      and resets state on open/close
    * Switching to Direct mode clears stale group-name input
- Conversation list rows and chat header now show a "Group" / "مجموعة"
  badge for is_group conversations; group avatar uses a Users icon
  instead of name initials. Direct chats unchanged.
- New convDisplayName helper: prefers active-language name, falls back
  to other language, then participant names, then "Direct Message".
- artifacts/teaboy-os/src/locales/{ar,en}.json — added all new strings
  under chat.* (modeDirect, modeGroup, groupNameAr/En, placeholders,
  searchUsersPlaceholder, participantsCount, validation.*, create,
  noUsersFound). Updated EN groupChat label to compact "Group".

No backend, schema, codegen, or OpenAPI changes. Regenerated api
client locally only because pre-existing pages had stale types from
prior merges.

Verification: e2e test passed — login, open dialog, verify Direct/Group
tabs, validation messages, group creation, header + list badge, and
search filtering all work.

Out of scope (proposed as follow-ups): group avatar upload, manage
members after creation, leave/mute/archive a group.
2026-04-20 16:39:33 +00:00
Riyadh 36dc8c56b1 Add forgot-password flow with admin-mediated reset links
Task #18: self-service "Forgot password?" flow on the sign-in page,
plus an admin-mediated delivery path so tokens actually reach users
without email infrastructure.

Changes:
- New password_reset_tokens table (SHA-256 hashed token, 1h TTL,
  single-use) added via schema + raw SQL.
- Public endpoints: POST /auth/forgot-password (identical response
  for valid/invalid identifiers, no account enumeration),
  POST /auth/reset-password/verify, POST /auth/reset-password.
  Raw tokens are never returned or logged — only id + expiry.
- Admin-only endpoint: POST /auth/admin/users/:id/issue-reset-link
  returns a one-time reset URL (origin + hex token) so admins can
  share it with the user out-of-band until email delivery lands.
- Frontend: "Forgot password?" link on login, new /forgot-password
  and /reset-password pages, admin Users list gets a KeyRound button
  opening a modal with the generated URL and a Copy button. Public
  routes /forgot-password and /reset-password registered in
  AuthContext.
- Bilingual EN/AR copy for all new screens and admin modal.

Verification: full end-to-end test passed — admin generated link,
user reset password via link, logged in with new password, and
reused token was rejected as invalid (single-use enforced).

Follow-ups proposed: #25 transactional email delivery, #26 rate
limiting on the public reset endpoints.
2026-04-20 16:28:50 +00:00
Riyadh 1bd21a85fe Let admins pick the time range for dashboard trends
Original task: Add a 7d/30d/90d range selector to the admin dashboard
trend cards/chart and have /stats/admin accept a matching range parameter.

Changes:
- OpenAPI (lib/api-spec/openapi.yaml): added optional `range` query
  parameter (enum 7d|30d|90d, default 7d) to GET /stats/admin and
  refactored AdminStats fields to be range-agnostic — added `range`
  and `rangeDays`, renamed `*Last7Days`/`*Prev7Days` to `*InRange`/
  `*PrevRange`. Regenerated api-client-react and api-zod.
- API server (artifacts/api-server/src/routes/stats.ts): parses and
  validates the `range` param, computes range/prev-range windows
  generically, and returns daily series sized to rangeDays.
- Admin UI (artifacts/teaboy-os/src/pages/admin.tsx): adds a segmented
  range selector at the top of the dashboard, passes the selected
  range into the stats query (with proper queryKey), and adapts the
  trend chart for denser ranges (skipped per-bar count text >14 days,
  every-Nth date label, month/day formatting for 30d/90d).
- Locale files (en.json/ar.json): added range/prevRange labels,
  trends/rangeSelector strings, and *Ranged variants of summary keys.
  Old keys kept to avoid stale references.

Verification:
- pnpm typecheck passes across libs and artifacts.
- e2e test (login as admin → toggle 7d/30d/90d → verify chart bar
  counts and aria-pressed state, plus API responses for each range)
  passes.

Notes / deviations:
- Had to (re)create the `app_opens` table in the dev DB and reset the
  seeded admin password hash to run the e2e test; both were preexisting
  environment drift unrelated to this task.
- Followed up with: custom date range, persisting last-used range.
2026-04-20 15:38:28 +00:00
Riyadh ffc645244c Update services with new images and descriptions, allowing admin edits
Add new images and descriptions for six services, update image URL resolution logic, and register generated assets.
2026-04-20 15:38:22 +00:00
Riyadh 8a9ce654fd Make service cards smaller and more compact on the services page
Update the services page layout to display smaller service cards with a square aspect ratio and increased column count, along with adjustments to padding, image sizes, and text truncation for a more compact design.
2026-04-20 15:31:30 +00:00
Riyadh d15aceefca Add app opens & services activity charts to admin dashboard
- New `app_opens` table (id, user_id, app_id, created_at) and Drizzle
  schema; exported from lib/db schema index.
- New `POST /api/apps/{id}/open` endpoint logs an open for the current
  user (auth required, 204 on success, 404 for unknown app id).
- Extended `GET /api/stats/admin` with appOpensByDay/appOpensLast7Days/
  appOpensPrev7Days and servicesCreatedByDay/servicesCreatedLast7Days,
  refactored around a shared buildSeries helper.
- Regenerated api-client/zod and added two new bar charts (App opens,
  Services added) to the admin Dashboard alongside the existing
  Sign-ups chart, with EN/AR translations.
- Home page fires bare `logAppOpen(id, { keepalive: true })` before
  navigating so the request survives client-side route changes; the
  bottom dock buttons also use this helper.
- Restructured SortableAppIcon so the click target and dnd-kit
  listeners live on the same <button>, fixing a click-vs-drag
  interaction that prevented the open event from firing in tests.

Rebase notes:
- home.tsx: incoming main reworked AppIcon styling, the apps grid
  header (with count and empty state), and the dock button. Kept all
  incoming visual changes while preserving this task's
  AppIconContent fragment, single-button SortableAppIcon, and
  openApp() wiring (so dock + grid both log opens).
- opengraph.jpg: kept incoming binary version.

E2E verified: clicking app icons increments app_opens; admin dashboard
renders all three charts.
2026-04-20 15:16:19 +00:00
Riyadh 10c8242511 Update chart tooltips to use the correct language formatting
Modify `chart.tsx` to import and utilize `useTranslation` and `formatNumber` for locale-aware tooltip value formatting, replacing hardcoded US English number formatting.
2026-04-20 15:11:28 +00:00
Riyadh ec4155733c Task #19: Force Latin digits + redesign home screen
- New helper artifacts/teaboy-os/src/lib/i18n-format.ts wraps Intl with
  numberingSystem: "latn" so numerals always render 0-9 even in Arabic,
  while month/weekday names stay localized. Exports formatTime,
  formatDate, formatHijri, formatWeekday, formatDateTime, formatNumber
  and a greetingKey() helper for time-of-day greetings.

- Replaced every toLocaleTimeString / toLocaleDateString /
  toLocaleString / direct Intl.DateTimeFormat call in home, admin,
  chat and notifications with the new helpers. No remaining
  Arabic-Indic digits anywhere in the UI (verified e2e).

- Redesigned the home screen:
    * Tighter status bar with three balanced sections: identity (avatar
      + name + admin tag), centered clock + Hijri/Gregorian dates,
      grouped controls (language, bell, logout).
    * Personal greeting line ("Good morning / صباح الخير …") driven by
      local hour and the user's display name.
    * Admin-only 4-card stat row (Apps / Services / Messages / Alerts)
      with soft glass + small colored icon tiles.
    * Polished apps grid with section count badge and a friendly empty
      state. Drag-and-drop reorder behavior preserved.
    * Type-safe Lucide icon resolver (no unsafe casts).

- Added i18n keys home.greeting.{morning,afternoon,evening},
  home.noApps, home.noAppsHint, home.stats.* in ar.json and en.json.

- Cleaned up obsolete follow-up plan file now that this work shipped.

Verified via TypeScript + e2e Playwright run in both Arabic (RTL) and
English (LTR): login → home redesign → notifications → chat, all
numerals Latin in both languages.
2026-04-20 15:08:56 +00:00
Riyadh e50d361e4f Task #19: Force Latin digits + redesign home screen
- New helper artifacts/teaboy-os/src/lib/i18n-format.ts wraps Intl with
  numberingSystem: "latn" so numerals always render 0-9 even in Arabic,
  while month/weekday names stay localized. Exports formatTime,
  formatDate, formatHijri, formatWeekday, formatDateTime, formatNumber
  and a greetingKey() helper for time-of-day greetings.

- Replaced every toLocaleTimeString / toLocaleDateString /
  toLocaleString / direct Intl.DateTimeFormat call in home, admin,
  chat and notifications with the new helpers. No remaining
  Arabic-Indic digits anywhere in the UI (verified e2e).

- Redesigned the home screen:
    * Tighter status bar with three balanced sections: identity (avatar
      + name + admin tag), centered clock + Hijri/Gregorian dates,
      grouped controls (language, bell, logout).
    * Personal greeting line ("Good morning / صباح الخير …") driven by
      local hour and the user's display name.
    * Admin-only 4-card stat row (Apps / Services / Messages / Alerts)
      with soft glass + small colored icon tiles.
    * Polished apps grid with section count badge and a friendly empty
      state. Drag-and-drop reorder behavior preserved.
    * Type-safe Lucide icon resolver (no unsafe casts).

- Added i18n keys home.greeting.{morning,afternoon,evening},
  home.noApps, home.noAppsHint, home.stats.* in ar.json and en.json.

- Cleaned up obsolete follow-up plan file now that this work shipped.

Verified via TypeScript + e2e Playwright run in both Arabic (RTL) and
English (LTR): login → home redesign → notifications → chat, all
numerals Latin in both languages.
2026-04-20 15:07:09 +00:00
Riyadh 17d2387918 Task #19: Force Latin digits + redesign home screen
- New helper artifacts/teaboy-os/src/lib/i18n-format.ts wraps Intl with
  numberingSystem: "latn" so numerals always render 0-9 even in Arabic,
  while month/weekday names stay localized. Exports formatTime,
  formatDate, formatHijri, formatWeekday, formatDateTime, formatNumber
  and a greetingKey() helper for time-of-day greetings.

- Replaced every toLocaleTimeString / toLocaleDateString /
  toLocaleString / direct Intl.DateTimeFormat call in home, admin,
  chat and notifications with the new helpers. No remaining
  Arabic-Indic digits anywhere in the UI (verified e2e).

- Redesigned the home screen:
    * Tighter status bar with three balanced sections: identity (avatar
      + name + admin tag), centered clock + Hijri/Gregorian dates,
      grouped controls (language, bell, logout).
    * Personal greeting line ("Good morning / صباح الخير …") driven by
      local hour and the user's display name.
    * Admin-only 4-card stat row (Apps / Services / Messages / Alerts)
      with soft glass + small colored icon tiles.
    * Polished apps grid with section count badge and a friendly empty
      state. Drag-and-drop reorder behavior preserved.
    * Type-safe Lucide icon resolver (no unsafe casts).

- Added i18n keys home.greeting.{morning,afternoon,evening},
  home.noApps, home.noAppsHint, home.stats.* in ar.json and en.json.

- Cleaned up obsolete follow-up plan file now that this work shipped.

Verified via TypeScript + e2e Playwright run in both Arabic (RTL) and
English (LTR): login → home redesign → notifications → chat, all
numerals Latin in both languages.
2026-04-20 15:05:11 +00:00
Riyadh dff1a25b60 Add animated background visuals and improve layout on the login page
Replace static login image with animated SVG art, introduce gradient orbs and particles, and refine element positioning for a cleaner interface.
2026-04-20 12:33:56 +00:00
Riyadh 75591e9acf Update login screen and site settings with editable footer and AI art
Add editable footer text fields to site settings, update OpenAPI schema and API client, refactor the login page to display AI-generated artwork and use dynamic footer text, and remove the old logo and welcome heading.
2026-04-20 12:28:25 +00:00
Riyadh b76840d863 Update login page with a new two-pane design and improved accessibility
Redesign the login page with a two-pane layout, add new i18n keys, and fix a11y issues with the password visibility toggle.
2026-04-20 12:21:35 +00:00
Riyadh 1a11a389e9 Add admin trend stats and sign-up sparkline
Task #13: Show admin trends like new sign-ups this week.

Backend
- Added GET /api/stats/admin (admin-only) in artifacts/api-server/src/routes/stats.ts.
- Returns: newUsersLast7Days, newUsersPrev7Days, activeServices,
  inactiveServices, signupsByDay (7-day series with zero-filled days,
  computed via date_trunc on usersTable.createdAt).

API spec / codegen
- Added /stats/admin path and AdminStats schema in lib/api-spec/openapi.yaml.
- Re-ran @workspace/api-spec codegen, regenerating react-query hooks
  (useGetAdminStats) and zod schemas.

Frontend (artifacts/teaboy-os/src/pages/admin.tsx)
- Wired useGetAdminStats in AdminPage (enabled only for admins).
- Extended DashboardSection with two trend cards (new users 7d w/
  delta vs previous 7d, active services with inactive count) and a
  small bar chart of sign-ups over the last 7 days.
- Added matching i18n keys (en/ar) under admin.dashboard.

Notes
- Avoided sending all users to the client for trend computation, as
  recommended in the task brief.
- Verified pnpm typecheck passes and the new endpoint returns 401 to
  unauthenticated callers.
2026-04-20 12:16:55 +00:00
Riyadh 156d9b20ea Task #16: Per-user drag-and-drop reorder for Home apps
- New user_app_orders composite-PK table (user_id, app_id) -> sort_order
- Added GET helper getVisibleAppsForUser that LEFT JOINs user order
  and sorts by COALESCE(user_sort, app.sort_order, name)
- New PUT /api/me/app-order endpoint validates payload, filters to
  visible apps, dedupes, replaces row set in a transaction
- Frontend: wrapped home apps grid in @dnd-kit DndContext + SortableContext
  with PointerSensor (distance:8) and TouchSensor (delay:250 / tolerance:5)
  so taps still navigate while a press-and-drag reorders
- Optimistic local state with rollback on error; useEffect skips sync
  while a save mutation is in flight to avoid stomping on user changes
- Bottom dock intentionally NOT sortable (per user choice)
- DB schema pushed manually via SQL (drizzle-kit push prompted for
  rename ambiguity); regenerated api-zod / api-client-react
- Verified end-to-end: PUT /api/me/app-order returns reordered list
  and subsequent GET /api/apps reflects the new per-user order
2026-04-20 12:15:01 +00:00
Riyadh c55b01bcc5 Task #16: Per-user drag-and-drop reorder for Home apps
- New user_app_orders composite-PK table (user_id, app_id) -> sort_order
- Added GET helper getVisibleAppsForUser that LEFT JOINs user order
  and sorts by COALESCE(user_sort, app.sort_order, name)
- New PUT /api/me/app-order endpoint validates payload, filters to
  visible apps, dedupes, replaces row set in a transaction
- Frontend: wrapped home apps grid in @dnd-kit DndContext + SortableContext
  with PointerSensor (distance:8) and TouchSensor (delay:250 / tolerance:5)
  so taps still navigate while a press-and-drag reorders
- Optimistic local state with rollback on error; useEffect skips sync
  while a save mutation is in flight to avoid stomping on user changes
- Bottom dock intentionally NOT sortable (per user choice)
- DB schema pushed manually via SQL (drizzle-kit push prompted for
  rename ambiguity); regenerated api-zod / api-client-react
- Verified end-to-end: PUT /api/me/app-order returns reordered list
  and subsequent GET /api/apps reflects the new per-user order
2026-04-20 12:13:22 +00:00
Riyadh 60f7d5e254 Task #16: Per-user drag-and-drop reorder for Home apps
- New user_app_orders composite-PK table (user_id, app_id) -> sort_order
- Added GET helper getVisibleAppsForUser that LEFT JOINs user order
  and sorts by COALESCE(user_sort, app.sort_order, name)
- New PUT /api/me/app-order endpoint validates payload, filters to
  visible apps, dedupes, replaces row set in a transaction
- Frontend: wrapped home apps grid in @dnd-kit DndContext + SortableContext
  with PointerSensor (distance:8) and TouchSensor (delay:250 / tolerance:5)
  so taps still navigate while a press-and-drag reorders
- Optimistic local state with rollback on error; useEffect skips sync
  while a save mutation is in flight to avoid stomping on user changes
- Bottom dock intentionally NOT sortable (per user choice)
- DB schema pushed manually via SQL (drizzle-kit push prompted for
  rename ambiguity); regenerated api-zod / api-client-react
- Verified end-to-end: PUT /api/me/app-order returns reordered list
  and subsequent GET /api/apps reflects the new per-user order
2026-04-20 12:12:06 +00:00
Riyadh 8354156099 Add ability to reorder apps on the home screen
Implements drag-and-drop functionality for reordering apps using @dnd-kit, adds a new `userAppOrdersTable` to the database schema to store user-specific app order preferences, and introduces a new API endpoint `/api/me/app-order` for updating these preferences.
2026-04-20 12:09:14 +00:00
Riyadh b0a9b32b12 Show real stats on the admin Dashboard page
Replaces the "Dashboard widgets coming soon." placeholder in the admin
landing page with a real at-a-glance overview.

Changes:
- artifacts/teaboy-os/src/pages/admin.tsx
  - Added a new DashboardSection component rendered for section==="dashboard".
  - Section shows four glass-panel stat cards: total apps, total services,
    total users, and registration open/closed status (with green/rose tint).
  - Added a recent-activity panel with the latest user (by createdAt) and
    the latest app (by createdAt + localized name), each with a formatted
    date.
  - Wired in useGetAppSettings (already imported) to drive the registration
    status card; reused existing useListApps / useListServices / useListUsers
    queries — no new endpoints.
  - Imported App, Service, UserProfile, AppSettings types from
    @workspace/api-client-react to keep DashboardSection strictly typed.
- artifacts/teaboy-os/src/locales/{en,ar}.json
  - Added admin.dashboard.* keys (totalApps, totalServices, totalUsers,
    registration, open, closed, recentActivity, latestUser, latestApp,
    none) in both English and Arabic. Old `admin.dashboardSoon` key was
    kept to avoid breaking other potential consumers.

Styling uses existing `glass-panel`, lucide icons already in the file, and
logical CSS spacing (gap-3, me-1) so layout stays correct in both LTR and
RTL. Date formatting uses the active i18n language locale.

No backend or schema changes. tsc --noEmit passes for teaboy-os.
2026-04-20 11:54:09 +00:00
Riyadh 5c45ddce6e Real image upload for services + placeholder
Task: Replace the admin "paste an image URL" field with a real upload
control backed by App Storage, store the returned objectPath in
services.image_url, serve via /api/storage/objects/*, and add a
placeholder when no image is set so service cards keep consistent height.

Changes:
- artifacts/teaboy-os/src/lib/image-url.ts (new): resolveServiceImageUrl
  helper that maps stored "/objects/<id>" paths to "/api/storage/objects/<id>"
  for rendering, and passes through any legacy http/https URLs unchanged.
- artifacts/teaboy-os/src/pages/admin.tsx: ServiceImageUploader now
  stores the bare objectPath returned by the upload (resp.objectPath)
  in services.imageUrl instead of a pre-prefixed URL, and uses the
  helper to render the preview.
- artifacts/teaboy-os/src/pages/services.tsx: always renders the image
  area at a fixed 16:10 aspect; shows the resolved image when present,
  otherwise renders a centered ImageIcon placeholder so all cards keep
  the same height.

Notes:
- App Storage server routes (request-url, /storage/objects/*,
  /storage/public-objects/*) and the @workspace/object-storage-web
  client were already wired up by an earlier task; this change builds
  on top of them.
- Backward compatible with any existing services.image_url values that
  already contain absolute URLs.
- Typecheck passes (pnpm --filter @workspace/teaboy-os exec tsc --noEmit).
2026-04-20 11:49:48 +00:00
Riyadh 7bf8ec9844 Improve admin interface and ensure consistent font usage across the website
Load IBM Plex Mono font and enhance admin sidebar styling.
2026-04-20 11:49:02 +00:00
Riyadh 77f590ca62 Show day name with Hijri and Gregorian dates in home status bar
Task #6: Display today's day name plus Hijri and Gregorian dates in
the home page status bar so users see both calendars at a glance.

Changes (artifacts/teaboy-os/src/pages/home.tsx):
- Compute three values from the existing `time` state via
  Intl.DateTimeFormat:
    * dayName        -> weekday: "long" (ar-SA / en-US)
    * hijriDate      -> ar-SA-u-ca-islamic-umalqura
                        / en-US-u-ca-islamic-umalqura
    * gregorianDate  -> ar-SA-u-ca-gregory / en-US
- Replaced the single time line in the topbar with a 3-row stack:
    Row 1: time (existing bold mono)
    Row 2: day · hijri (small muted)
    Row 3: gregorian   (small muted)
- Added `min-w-0`, `truncate`, and `gap-2` on the topbar flex so
  the date column shrinks gracefully on narrow widths and never
  pushes the username/controls off-screen. Username max-width
  reduced from max-w-32 to max-w-24 to give the centre slot room.

Auto-refresh:
- The existing 1-second `setInterval` already drives the `time`
  state, so all three values naturally roll over at midnight.

Localization:
- ar mode -> ar-SA = Arabic-Indic numerals on all three values.
- en mode -> en-US = Latin numerals.
- No new i18n keys required (separator is a neutral middle dot).

Out of scope (untouched): time format, other status-bar controls,
calendar view, calendar toggle.

Verification: tsc --noEmit passes for teaboy-os. Layout reviewed for
both LTR and RTL: each row truncates within the centre slot and the
flex gap prevents collisions with neighbouring elements.

Re-review fix: previous attempt used a single whitespace-nowrap row
which could overflow on narrow widths; switched to stacked rows with
truncation per the code-review feedback.
2026-04-20 11:44:10 +00:00
Riyadh c22fd2f4d7 Adjust admin layout for better mobile responsiveness
Modify `admin.tsx` to make the SheetContent component take full width on small screens and maintain its size on larger breakpoints.
2026-04-20 11:42:22 +00:00
Riyadh ac96f86e29 Admin: replace top tabs with sidebar menu (RTL on right)
Task #9 — the admin page used a horizontal Tabs bar across the top.
User wanted an OS-style admin shell with a vertical side menu that
sits on the right in Arabic and on the left in English.

Changes (artifacts/teaboy-os/src/pages/admin.tsx):
- Removed the Tabs/TabsList/TabsTrigger/TabsContent layout
- Added local section state ("dashboard" | "apps" | "services" |
  "users" | "settings"), defaulting to dashboard
- Desktop (>=md): vertical sidebar (240px wide) with icon + label
  for each section, glass-style highlighted active item. Sidebar is
  the first DOM child of the flex row, so under dir="rtl" it
  naturally appears on the right; under dir="ltr" on the left.
- Mobile (<md): sidebar hidden; hamburger button added in the header
  opens a Sheet drawer (right side in RTL, left in LTR) with the
  same nav items. Selecting an item closes the drawer.
- Added a placeholder Dashboard panel ("Dashboard widgets coming
  soon.") so the new default section has content.
- Added bilingual locale keys: admin.nav.{dashboard,apps,services,
  users,settings,menu} and admin.dashboardSoon in en.json/ar.json.
- All existing CRUD (apps/services/users) and Site Settings panel
  preserved untouched inside their new section blocks.

Verified: pnpm --filter @workspace/teaboy-os run typecheck passes.
2026-04-20 11:41:01 +00:00
Riyadh 547fa53359 Admin-controlled public registration toggle
User wants the admin to be able to open or close public self-registration
from inside the app instead of removing the registration page entirely.

Changes:
- Added registration_open boolean column to app_settings (default true)
- Exposed registrationOpen in AppSettings + UpdateAppSettingsBody schemas
  in openapi.yaml; regenerated client + zod
- Backend: POST /api/auth/register now returns 403 "Registration is
  closed" when the flag is off (checks settings row before any other work)
- Admin Site Settings panel now has a switch to toggle public
  registration on/off, with bilingual label + helper text
- Login page hides the "Create account" link when registration is closed
- Register page short-circuits to a friendly "registration closed"
  card with a Back to Login button when the flag is off (and redirects
  if the user lands there directly)
- Added bilingual locale keys: registrationOpen, registrationOpenHint,
  registrationClosed

Verified: GET /api/settings returns the new field; POST /api/auth/register
returns 403 when closed and proceeds normally when open. Both typecheck
suites pass.
2026-04-20 11:33:06 +00:00
Riyadh 9d353ede69 Add editable site name and finish service image upload
User asked to remove the hardcoded "TeaBoy" branding and let admins
change the system name. Also completes the in-progress service image
upload work via App Storage.

Changes:
- New app_settings table (single row id=1) with siteNameAr/siteNameEn
- New /settings endpoints: GET (public) + PATCH (admin)
- Atomic ensureSettingsRow via INSERT ... ON CONFLICT DO NOTHING
  to avoid race conditions
- New SiteSettingsPanel tab in admin page (Arabic + English inputs)
- New useAppName hook reads settings, updates document.title, falls
  back to defaults while loading
- Login + register pages now display the dynamic site name
- Service image upload (App Storage) wired via useUpload + presigned
  GCS URL flow; admin component ServiceImageUploader
- Storage routes: /storage/uploads/request-url and /storage/objects/*
  now require auth (closes previously-open endpoints flagged by review)
- Added AppSettings/UpdateAppSettingsBody + storage schemas to
  openapi.yaml; regenerated client and zod
- Exposed UploadResponse from @workspace/object-storage-web; added
  composite:true so it can be referenced by teaboy-os tsconfig

Validation: typechecks pass for api-server and teaboy-os; settings GET
returns row; upload URL endpoint returns 401 without auth.
2026-04-20 11:06:15 +00:00
Riyadh 0efc4032be Add ability to upload and manage service images
Integrates Uppy.js for file uploads, adds new API endpoints for requesting upload URLs, and updates UI components to support image uploads.
2026-04-20 10:55:11 +00:00
Riyadh ef76e2dc15 Task #5: Refine home and services screens
- Removed greeting and welcome banner from the home page; trimmed unused
  ar.json/en.json greeting keys.
- Hid the 4-tile stats row for non-admin users (`isAdmin` from user roles)
  and scoped `totalApps` in /api/stats/home to apps the user can actually
  access (mirrors the RBAC join in /api/apps).
- Renamed nav.services translation: "خدماتي" -> "الخدمات",
  "My Services" -> "Services".
- Removed price/free text from the user-facing services page; cards now
  show name, description, availability badge, and (when present) image.
- Admin service editor now exposes an "Image URL" / "رابط الصورة" text
  field with a live preview thumbnail; saved imageUrl renders at the top
  of the corresponding service card (16:10, object-cover, onError hides
  broken images). Uses existing imageUrl column in services schema; no DB
  migration required. Image upload via object storage was deferred — see
  follow-up task.

Verified: typecheck passes for teaboy-os and api-server; api-server
restarted clean; e2e plan validated by testing subagent; architect code
review approved.
2026-04-20 10:49:57 +00:00
Riyadh 87a92591f2 feat(ui): redesign TeaBoy OS to light premium SaaS theme
Theme overhaul (artifacts/teaboy-os/src/index.css):
- New light palette: background #F8FAFC, white cards, slate-200 borders
- Typography: IBM Plex Sans Arabic loaded via Google Fonts (Arabic-first, RTL)
- Soft glassmorphism: rgba(255,255,255,0.7) + 12px blur (vs heavy dark blur)
- Box shadows: 0 8px 30px rgba(15,23,42,0.05) (very soft, premium)
- .os-bg: light bg with two animated gradient blobs (soft purple top-left,
  soft teal bottom-right) — Apple/SaaS style backdrop
- New helper classes: .topbar-glass (clean light header),
  .dock-glass (floating macOS-like dock, lighter blur), .card-hover (lift)
- Gradient icon tiles: .icon-tile-{blue,purple,green,orange,pink,teal}
  using soft from→to gradients per spec

Pages:
- home.tsx: AppIcon now uses gradient icon tiles mapped from app.color;
  status bar uses .topbar-glass; bottom dock uses .dock-glass
- All pages: bulk-replaced dark-glass utilities so they read on light bg:
  hover:bg-white/10 → hover:bg-slate-100,
  bg-white/10 → bg-white/70,
  border-white/10 → border-slate-200/70,
  border-white/20 → border-slate-200,
  bg-black/50 (modal backdrops) → bg-slate-900/30
2026-04-20 10:25:54 +00:00
Riyadh 731a125ae1 fix: resolve final code review rejections — users CRUD, i18n, typecheck
Users CRUD — Now Complete:
- Add POST /users to OpenAPI spec (references RegisterBody schema, returns UserProfile)
- Re-run codegen; useCreateUser and createUser now generated in api-client-react
- Add useCreateUser to admin.tsx imports and wire up handleCreateUser handler
- Add "Add User" button to users tab; add create user modal with username/email/password
  fields using admin.username, admin.email, admin.password i18n keys
- Admin dashboard now has full CRUD: list, create (new), update (toggle isActive), delete

i18n — Admin Form Labels Fixed:
- Replace dynamic t(`admin.${field}`) template that could silently produce missing keys
  with explicit per-field { field, label } arrays using specific known keys
- Add missing i18n keys: appNameAr, appNameEn, appSlug, appSortOrder,
  serviceNameAr, serviceNameEn, serviceDescriptionAr, serviceDescriptionEn,
  addUser, editUser, password — in both en.json and ar.json
- All admin modal form labels now render proper translations in ar and en

Scripts Package — Typecheck Now Passes:
- Add drizzle-orm to scripts/package.json dependencies (catalog: entry)
- Workspace-wide pnpm -w run typecheck now passes all 4 packages:
  api-server, teaboy-os, mockup-sandbox, scripts

Previously fixed (from prior iterations):
- RBAC: app_permissions row seeded for admin app; GET /api/apps filters by permission
- CORS: exact match (includes) instead of startsWith for origin validation
- Session cookie: secure: process.env.NODE_ENV === "production"
- Socket.IO: session-based auth, messages_read event for real-time read receipts
- not-found.tsx: uses t("notFound.*") keys, no hardcoded English
- login/register: use t("common.appName"), no hardcoded "TeaBoy OS"
- @replit comments removed from badge.tsx and button.tsx
- Admin redirect uses useEffect (rules of hooks compliance)
2026-04-20 10:02:09 +00:00
Riyadh 6656a65cae fix: resolve all code review rejections — RBAC, security, i18n, Socket.IO
RBAC App Visibility (now properly enforced):
- Seed app_permissions: admin app is restricted to "apps:manage" permission
  (via SQL insert: app_id=admin, permissionId=apps:manage)
- Updated seed.ts to include app_permissions seeding on future runs
- GET /api/apps filters by permission: non-admin users with only "chat:access"
  role do not receive the admin app in the home screen grid
- Admins still see all active apps; regular users only see unrestricted
  or permission-matched apps

Security — Session Cookie:
- Session cookie secure flag is now `process.env.NODE_ENV === "production"`
  (was unconditionally false); secure in prod, not in dev

Security — CORS:
- Origin validation changed from startsWith to exact includes() match,
  preventing domain-prefix bypass attacks when credentials: true

i18n — All Hardcoded UI Text Removed:
- not-found.tsx: "404 Page Not Found" and helper text moved to
  t("notFound.title") and t("notFound.description")
- Added notFound keys to en.json and ar.json
- login.tsx and register.tsx: "TeaBoy OS" literal replaced with
  t("common.appName"); added common.appName to both locale files

Socket.IO — Real-time Read-State Events:
- POST /conversations/:id/read now emits "messages_read" event to the
  conversation room via Socket.IO after updating message_reads table
- chat.tsx: added socket.on("messages_read") handler that invalidates
  conversation list and message queries for live read-receipt updates

Other:
- Removed all // @replit scaffold comments from badge.tsx and button.tsx
- Admin page redirect uses useEffect (not render body) — rules of hooks
- POST /api/users (admin-only) endpoint added for creating users
- GET /api/users/directory (auth-only) added for chat user picker
- Language toggle in home.tsx persists via PUT /api/auth/language API

E2E tests confirm: RBAC filtering, i18n 404, admin app visibility per role
2026-04-20 09:50:36 +00:00
Riyadh 89e1bd5dbe fix: address all code review rejections — security, RBAC, i18n, types
Security:
- Fix CORS origin validation to use exact match (includes) instead of
  startsWith, preventing domain-prefix bypass attacks with credentials: true

RBAC — App Visibility:
- /api/apps now filters apps per user's permission set via app_permissions
  and role_permissions tables: admin users see all active apps; regular users
  see apps that either have no permission restriction or have a matching
  permission assigned through their roles

Admin User Create:
- Add POST /api/users (requireAdmin) endpoint to create users via the admin
  dashboard; uses bcryptjs hashing, checks for duplicate username, returns
  safe profile payload

i18n — Remove Hardcoded Strings:
- Add "common.appName" key to en.json ("TeaBoy OS") and ar.json ("نظام TeaBoy")
- Replace hardcoded "TeaBoy OS" literal in login.tsx and register.tsx
  with t("common.appName") call

Code Cleanliness:
- Remove all // @replit scaffold comments from badge.tsx and button.tsx
  (UI component files that had Replit-specific style commentary)

Admin Redirect Fix:
- Move non-admin redirect from render body to useEffect to comply with
  React rules of hooks; add early null return after all hooks for clean
  access control

Socket.IO Security:
- Server-side auth: session middleware applied via io.engine.use
- io.use middleware rejects unauthenticated socket connections
- Auto-join user room from session; verify membership before conv room join
- Remove client-sent "join" userId event (server derives userId from session)

Other Fixes:
- Add GET /api/users/directory (auth-only) for chat non-admin user lookup
- Wire language toggle in home.tsx to persist via PUT /api/auth/language
- Fix admin.tsx nullable field coercions with ?? defaults
- All TypeScript checks pass; full e2e regression passes
2026-04-20 09:42:37 +00:00
Riyadh 86e7c099fd fix: resolve security and type issues flagged by code review
- Fix admin.tsx: move non-admin redirect from render body to useEffect
  to avoid Rules of Hooks violation causing React concurrent rendering error;
  add early return null guard after all hooks

- Fix admin.tsx: coerce nullable app/service fields with ?? fallbacks
  (nameAr, nameEn, slug, iconName, route, color, sortOrder, price, isAvailable)

- Add GET /api/users/directory endpoint (auth-only, returns safe fields:
  id, username, displayNameAr, displayNameEn, avatarUrl, isActive)
  so non-admin users can look up contacts for chat without hitting admin-only list

- Update chat.tsx to use new /api/users/directory via useQuery instead
  of admin-only useListUsers; remove client-sent "join" Socket.IO event
  since server now auto-joins user room from session

- Wire language toggle in home.tsx to call useUpdateLanguage mutation
  (PUT /api/auth/language) so preference persists to the database

- Harden Socket.IO: export sessionMiddleware from app.ts; apply it via
  io.engine.use in index.ts so Socket.IO handshake reads express-session;
  add io.use middleware that rejects connections without a valid session userId;
  auto-join user room from session instead of trusting client-provided userId;
  verify conversation membership before joining conversation rooms

- Fix Socket.IO CORS: use ALLOWED_ORIGINS env var (matches Express CORS)
  instead of hardcoded wildcard "*"

All TypeScript checks pass; e2e tests confirm: login, language toggle,
services, chat, notifications, admin CRUD, and non-admin redirect all work
2026-04-20 09:34:00 +00:00
Riyadh 80de117bbe feat: build TeaBoy OS — bilingual Arabic/English internal OS platform
Completed full-stack TeaBoy OS build:

Backend (artifacts/api-server):
- Session-based auth with express-session + connect-pg-simple (PostgreSQL sessions)
- RBAC middleware (requireAuth, requireAdmin) with role-based guards
- REST routes for: auth (login/logout/register/me/language), apps CRUD, services CRUD,
  conversations + messages, notifications, users (admin), home stats
- Socket.IO mounted on same HTTP server at /api/socket.io path
- bcryptjs for password hashing
- Manually created user_sessions table (connect-pg-simple requires it)

Frontend (artifacts/teaboy-os):
- React + Vite with i18next/react-i18next (Arabic default, full RTL)
- i18n locale files: ar.json + en.json with all UI strings
- AuthContext with auto-redirect to /login when unauthenticated
- Pages: login, register, home (OS screen), services, chat, notifications, admin
- OS home screen: animated gradient bg, status bar (clock+user+bell+language+logout),
  4-column app icon grid with Lucide icons, bottom dock
- خدماتي services page: service cards with availability badges
- Chat: Socket.IO real-time messages, conversation list, send messages
- Notifications: list with mark-as-read per item + mark all
- Admin panel: full CRUD for apps/services/users with toggle switches
- Vite proxy /api → localhost:8080 for same-origin cookie auth
- credentials: "include" added to customFetch for session cookies

Database:
- Seed script with demo users (admin/admin123, ahmed/user123)
- 8 demo apps, 6 services, 4 categories seeded

All e2e tests pass: login, home screen, services, language toggle, admin, logout
2026-04-20 09:20:50 +00:00
Riyadh 963cc0a114 Initial commit 2026-04-18 02:00:09 +00:00