Commit Graph

15 Commits

Author SHA1 Message Date
Riyadh b225ea8e46 Task #55: Movable, resizable home clock with Latin digits
- Refactor AnalogClockWidget: Latin 1-12 digits (was Roman), `size`
  (px) prop with scale-based interior, optional `showLabels`.
- Add useHomeClockSize (compact|large) and useHomeClockPosition hooks
  in clock.tsx — both persist to localStorage and broadcast a custom
  event so consumers stay in sync within the tab.
- home.tsx: introduce SortableClockTile that participates in the same
  dnd-kit grid as app icons via id `__clock`. App ids now prefixed
  `app-` so they don't collide. Long-press (500ms) toggles
  compact↔large; large = col-span-2 row-span-2. Custom pointer
  handlers chain with dnd-kit's listener so drag still works.
- handleDragEnd computes new app order excluding the clock and only
  fires updateOrder when app order actually changed.
- Remove the standalone AnalogClockWidget block above the apps grid;
  clock now lives inside the grid.
- When homeClockVisible is false, the clock is also excluded from
  sortable items (no dnd-kit warnings).
- e2e test passed: in-grid placement, Latin digits, long-press
  resize, drag-reorder, position persists across reload.
2026-04-21 13:03:05 +00:00
Riyadh 3734d1e99f Task #55: Movable, resizable home clock with Latin digits
- Refactor AnalogClockWidget: Latin 1-12 digits (was Roman), `size`
  (px) prop with scale-based interior, optional `showLabels`.
- Add useHomeClockSize (compact|large) and useHomeClockPosition hooks
  in clock.tsx — both persist to localStorage and broadcast a custom
  event so consumers stay in sync within the tab.
- home.tsx: introduce SortableClockTile that participates in the same
  dnd-kit grid as app icons via id `__clock`. App ids now prefixed
  `app-` so they don't collide. Long-press (500ms) toggles
  compact↔large; large = col-span-2 row-span-2. Custom pointer
  handlers chain with dnd-kit's listener so drag still works.
- handleDragEnd computes new app order excluding the clock and only
  fires updateOrder when app order actually changed.
- Remove the standalone AnalogClockWidget block above the apps grid;
  clock now lives inside the grid.
- When homeClockVisible is false, the clock is also excluded from
  sortable items (no dnd-kit warnings).
- e2e test passed: in-grid placement, Latin digits, long-press
  resize, drag-reorder, position persists across reload.
2026-04-21 12:56:33 +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 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 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 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 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 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 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 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 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 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