WorkNest is a production-grade, multi-tenant ERP frontend built with React, Vite, TypeScript, and Tailwind CSS. It provides the tenant workspace for day-to-day operations and a separate platform console for internal administration.
The application is designed for SaaS teams that need a single frontend for employees, managers, HR, and tenant administrators without leaking platform-level capabilities into the tenant experience.
WorkNest solves the problem of fragmented workplace operations by combining core business workflows into one tenant-aware interface.
Each company is isolated as a tenant. The frontend communicates with tenant-scoped APIs, renders features according to the active user permissions, and keeps platform administration separate from tenant usage.
The current client covers authentication, dashboards, employees, teams, projects, tasks, attendance, leave, announcements, notifications, chat, analytics, and settings.
- Tenant-aware authentication and session bootstrap
- Permission-based route guards and UI rendering
- Employee, team, project, and task management
- Attendance and leave workflows
- Announcements with pinned content and CRUD support
- System notifications shown in the topbar
- Team and HR chat messaging
- Dashboard and analytics views
- File uploads for profile images, task attachments, leave documents, and project files
- Separate public, tenant, and platform route shells
| Layer | Tools |
|---|---|
| Framework | React 19, Vite |
| Language | TypeScript |
| Routing | React Router |
| Data Fetching | TanStack Query |
| HTTP | Axios |
| Styling | Tailwind CSS v4, token-driven CSS variables |
| State | Zustand |
| Realtime | STOMP over WebSocket |
| Storage | Supabase-backed file storage contract |
| Icons | Lucide React |
| Testing | Vitest, Testing Library, jsdom |
Application composition lives here: providers, layouts, route guards, and the router entry point. This is where the app shell is assembled and where tenant, platform, and public boundaries are enforced.
Feature logic is grouped by domain. Each module owns its types, services, access rules, components, and page-level workflows. This keeps announcements, chat, tasks, leave, and other business domains isolated from each other.
Shared UI components live here. These are reusable primitives and composite components used across modules, such as buttons, dialogs, inputs, headers, nav surfaces, and section cards.
Infrastructure code lives here: HTTP clients, response parsers, auth/token handling, realtime subscriptions, and file upload helpers. Shared API concerns are centralized so modules stay focused on business behavior.
Reusable hooks expose cross-cutting behavior such as auth state, permissions, theme, page metadata, toast state, and queries.
WorkNest uses JWT-based authentication with access and refresh token handling. The frontend boots the session on load, restores the authenticated user when tokens are available, and refreshes expired access tokens through the shared Axios client.
The current client persists session state client-side and sends bearer tokens on API requests. The auth layer is structured so it can evolve toward HttpOnly cookie refresh handling when the backend contract is finalized.
Authorization is permission-based, not role-only.
PLATFORM_ADMINbelongs to the platform console, not the tenant workspaceTENANT_ADMINcan manage tenant-wide workspace content and admin workflowsHRcan manage HR-related areas such as announcements and people workflows where permittedEMPLOYEEreceives only the permissions granted by the tenant policy
The UI checks permissions through the usePermission hook, and protected routes use PermissionGuard to block access before the page renders. This same model is used for module entry points and management flows.
For announcements, the backend also returns item-level flags such as canEdit and canDelete, allowing the UI to render action controls only when the current user is allowed to act on a specific record.
API communication is handled by a shared Axios client that:
- Sets the API base URL from environment configuration
- Attaches the bearer access token automatically
- Adds tenant-scoped headers for tenant endpoints
- Retries failed requests after token refresh
- Normalizes API errors into user-friendly messages
Announcements are workspace content, not system alerts.
They are authored by authorized tenant users, currently TENANT_ADMIN and HR, and are visible in /app/announcements. Announcements support a title, rich text content, pinned status, and record-level actions such as edit and delete when the backend allows them.
Notifications are different.
- Announcements are deliberate content created by users with permission
- Notifications are system-generated events tied to entities such as tasks, leave requests, or announcements
- Announcements appear as browsable content in the announcements module
- Notifications appear in the topbar and notification center as operational updates
The routing model reflects that split:
/app/announcementsfor the announcements list/app/announcements/newfor creation/app/announcements/:idfor details/app/announcements/:id/editfor editing
Management routes are protected by announcement management guards, and the card component renders edit/delete controls inline when the current announcement can be modified.
WorkNest supports file uploads for profile images, task attachments, leave documents, and project files.
The frontend upload flow is multipart-based:
- The UI collects a file from the user
- The upload service validates type and size on the client
- The file is sent to the backend upload endpoint as
multipart/form-data - The backend persists the file in storage, typically Supabase Storage in production
- The backend returns a public or resolved URL plus metadata
- The frontend stores that returned URL in the relevant domain record
This keeps the browser free from storage secrets and lets the backend control bucket structure, access rules, and URL normalization.
Example usage:
import { uploadImageFiles, uploadDocumentFiles } from "@/services/uploads/fileUploadService";
const images = await uploadImageFiles(files, { folder: "profiles" });
const documents = await uploadDocumentFiles(files, { folder: "leave-documents" });Recommended environment variables for storage integration:
VITE_SUPABASE_URL=
VITE_SUPABASE_ANON_KEY=If your deployment uses backend-mediated uploads only, these values can remain unused by the browser bundle, but they are still useful to document the storage contract and deployment environment.
Create a local environment file at the project root.
.env.localRequired variables:
VITE_API_BASE_URL=http://localhost:8080Storage-related variables:
VITE_SUPABASE_URL=
VITE_SUPABASE_ANON_KEY=Optional realtime variables:
VITE_WS_URL=ws://localhost:8080/ws
VITE_REALTIME_DISABLED=false
VITE_CHAT_TOPICS=/topic/chat.global,/user/queue/chat
VITE_NOTIFICATIONS_TOPICS=/topic/notifications.global,/user/queue/notificationsNotes:
- If
VITE_WS_URLis not set, the websocket URL can be derived from the API base URL in the realtime layer - If
VITE_REALTIME_DISABLEDistrue, STOMP subscriptions stay inactive - Topic variables accept comma-separated destination strings
npm install
npm run dev
npm run buildUseful local checks:
npm run lint
npm run test:run- The app uses lazy-loaded routes and manual chunking, which helps keep initial load cost under control
- Watch Vite bundle warnings during builds, especially when shared UI or icon imports expand vendor chunks
- API availability is a hard dependency for authenticated workspace pages, because most modules fetch data from tenant-scoped endpoints
- The Axios client handles token refresh and error normalization centrally, so most modules should not implement their own network retry logic
- File uploads should remain backend-mediated so storage credentials stay out of the browser bundle
- Chat delete or removal synchronization is not fully exposed in the current client, so delete-related changes may still require a refresh until realtime coverage is extended
- The browser currently relies on the backend API for uploads rather than talking directly to Supabase from the UI
- Some permission checks are still driven by backend-provided flags on a per-record basis, so UI action visibility depends on API responses
- Move refresh handling fully to HttpOnly cookie sessions when the backend contract is ready
- Expand realtime coverage for chat state changes and delete events
- Add optimistic updates where mutation safety is well defined
- Introduce stronger upload metadata validation and file scanning on the backend
- Add more granular analytics caching and prefetching with TanStack Query
- Extend auditability for high-impact tenant actions such as announcements and approvals
The project uses Vitest and Testing Library with a jsdom test environment.
npm run test
npm run test:runThis project is licensed under the MIT License. See LICENSE for the full text.