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
This commit is contained in:
riyadhafraa
2026-04-20 09:20:50 +00:00
parent 593ce6a418
commit 001e9023b2
115 changed files with 14720 additions and 79 deletions
+1 -1
View File
@@ -360,7 +360,7 @@ export async function customFetch<T = unknown>(
const requestInfo = { method, url: resolveUrl(input) };
const response = await fetch(input, { ...init, method, headers });
const response = await fetch(input, { ...init, method, headers, credentials: "include" });
if (!response.ok) {
const errorData = await parseErrorBody(response, method);
@@ -2,9 +2,265 @@
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Api
* API specification
* TeaBoy OS API specification
* OpenAPI spec version: 0.1.0
*/
export interface HealthStatus {
status: string;
}
export interface ErrorResponse {
error: string;
}
export interface SuccessResponse {
success: boolean;
}
export interface RegisterBody {
username: string;
email: string;
password: string;
/** @nullable */
displayNameAr?: string | null;
/** @nullable */
displayNameEn?: string | null;
preferredLanguage?: string;
}
export interface LoginBody {
username: string;
password: string;
}
export interface UpdateLanguageBody {
language: string;
}
export interface AuthUser {
id: number;
username: string;
email: string;
/** @nullable */
displayNameAr?: string | null;
/** @nullable */
displayNameEn?: string | null;
preferredLanguage: string;
/** @nullable */
avatarUrl?: string | null;
isActive: boolean;
roles: string[];
createdAt: string;
}
export interface UserProfile {
id: number;
username: string;
email: string;
/** @nullable */
displayNameAr?: string | null;
/** @nullable */
displayNameEn?: string | null;
preferredLanguage: string;
/** @nullable */
avatarUrl?: string | null;
isActive: boolean;
roles: string[];
createdAt: string;
}
export interface UpdateUserBody {
/** @nullable */
displayNameAr?: string | null;
/** @nullable */
displayNameEn?: string | null;
isActive?: boolean;
preferredLanguage?: string;
}
export interface App {
id: number;
slug: string;
nameAr: string;
nameEn: string;
/** @nullable */
descriptionAr?: string | null;
/** @nullable */
descriptionEn?: string | null;
iconName: string;
route: string;
color: string;
isActive: boolean;
isSystem: boolean;
sortOrder: number;
createdAt: string;
updatedAt: string;
}
export interface CreateAppBody {
slug: string;
nameAr: string;
nameEn: string;
/** @nullable */
descriptionAr?: string | null;
/** @nullable */
descriptionEn?: string | null;
iconName: string;
route: string;
color: string;
isActive?: boolean;
isSystem?: boolean;
sortOrder?: number;
}
export interface UpdateAppBody {
nameAr?: string;
nameEn?: string;
/** @nullable */
descriptionAr?: string | null;
/** @nullable */
descriptionEn?: string | null;
iconName?: string;
route?: string;
color?: string;
isActive?: boolean;
sortOrder?: number;
}
export interface Service {
id: number;
/** @nullable */
categoryId?: number | null;
nameAr: string;
nameEn: string;
/** @nullable */
descriptionAr?: string | null;
/** @nullable */
descriptionEn?: string | null;
/** @nullable */
imageUrl?: string | null;
/** @nullable */
price?: string | null;
isAvailable: boolean;
sortOrder: number;
createdAt: string;
updatedAt: string;
}
export interface CreateServiceBody {
/** @nullable */
categoryId?: number | null;
nameAr: string;
nameEn: string;
/** @nullable */
descriptionAr?: string | null;
/** @nullable */
descriptionEn?: string | null;
/** @nullable */
imageUrl?: string | null;
/** @nullable */
price?: string | null;
isAvailable?: boolean;
sortOrder?: number;
}
export interface UpdateServiceBody {
/** @nullable */
categoryId?: number | null;
nameAr?: string;
nameEn?: string;
/** @nullable */
descriptionAr?: string | null;
/** @nullable */
descriptionEn?: string | null;
/** @nullable */
imageUrl?: string | null;
/** @nullable */
price?: string | null;
isAvailable?: boolean;
sortOrder?: number;
}
export interface ServiceCategory {
id: number;
nameAr: string;
nameEn: string;
sortOrder: number;
createdAt: string;
}
export interface ParticipantInfo {
id: number;
username: string;
/** @nullable */
displayNameAr?: string | null;
/** @nullable */
displayNameEn?: string | null;
/** @nullable */
avatarUrl?: string | null;
isAdmin: boolean;
}
export interface MessageWithSender {
id: number;
conversationId: number;
senderId: number;
content: string;
sender: ParticipantInfo;
createdAt: string;
updatedAt: string;
}
export interface ConversationWithDetails {
id: number;
/** @nullable */
nameAr?: string | null;
/** @nullable */
nameEn?: string | null;
isGroup: boolean;
createdBy: number;
participants: ParticipantInfo[];
lastMessage?: MessageWithSender | null;
unreadCount: number;
createdAt: string;
updatedAt: string;
}
export interface CreateConversationBody {
participantIds: number[];
/** @nullable */
nameAr?: string | null;
/** @nullable */
nameEn?: string | null;
isGroup?: boolean;
}
export interface SendMessageBody {
content: string;
}
export interface Notification {
id: number;
userId: number;
titleAr: string;
titleEn: string;
/** @nullable */
bodyAr?: string | null;
/** @nullable */
bodyEn?: string | null;
type: string;
isRead: boolean;
/** @nullable */
relatedId?: number | null;
/** @nullable */
relatedType?: string | null;
createdAt: string;
}
export interface HomeStats {
totalApps: number;
totalServices: number;
unreadNotifications: number;
unreadMessages: number;
totalUsers: number;
}
File diff suppressed because it is too large Load Diff