بنقرة واحدة
bedrud-fe-platform
Frontend foundation — routing, API client, build/dev ports, auth hooks, types.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Frontend foundation — routing, API client, build/dev ports, auth hooks, types.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
Admin endpoints — users, rooms, queue, settings, invite tokens, webhooks, overview, recordings.
Auth endpoints — JWT flow, local/OAuth/passkey/verify/password-reset/avatar/preferences.
Room endpoints — CRUD, join, moderation, presence, chat upload, stage stubs, recording.
Complete Bedrud API endpoint reference. Dense index of every route, middleware, status code; deep detail in leaf skills.
All DTO definitions, source file index, Swagger reference.
Auth service + middleware — JWT, passkeys, OAuth, session store, rate limiting, recordings gate.
| name | bedrud-fe-platform |
| description | Frontend foundation — routing, API client, build/dev ports, auth hooks, types. |
| license | Apache License |
React 19 SPA. apps/web/. TanStack Router (file-based) + TanStack Query 5 + TailwindCSS v4 + Bun + Biome.
Path aliases: #/* → ./src/*, @/* → ./src/*. Prefer #/*. Never relative ../src/*.
Owns: routes map, lib/api.ts, platform hooks, Vite/tsconfig/biome, vendor resolve aliases.
Does not own: Zustand store internals → bedrud-fe-state; meeting runtime/whiteboard → bedrud-fe-meeting; admin UI → bedrud-fe-admin; shadcn/UI chrome → bedrud-fe-ui-foundation.
src/router.tsxgetRouter() — TanStack Router.
| Option | Value |
|---|---|
scrollRestoration | true |
defaultPreload | 'intent' |
defaultPreloadStaleTime | 0 |
defaultNotFoundComponent | <ErrorPage variant="not-found" /> |
defaultErrorComponent | msg starts with 404 → not-found; else <ErrorPage variant="server" /> |
Registers router type on @tanstack/react-router.
src/routes/__root.tsx — App shellcreateRootRoute + shellComponent: RootDocumentviewport-fit=cover), title Bedrud, styles.css, favicons, manifest.json, inline theme script (reads localStorage key theme before paint)QueryClient: retry: 1, staleTime: 30_000ErrorBoundary → IntlProvider (en + #/locales/en.json) → QueryClientProvider + Sonner Toaster#main-contentapplyTheme on store change + OS prefers-color-scheme listeneruseAuthStore.getState().initialize() (non-blocking; protected routes await in beforeLoad)src/routeTree.gen.tsAuto-generated by TanStack Router. Never edit. ~32 route modules (1 root + 5 layouts + leaves).
scripts/embed.mjsProd embed bridge: Vite build → SSR shell → copy client assets → server/frontend/ (index.html + shell.html). Invoked via bun run build:embed.
vite.config.ts)| Constant | Port | Role |
|---|---|---|
DEV_PORT_WEB | 7070 | Vite / TanStack Start |
DEV_PORT_API | 7071 | Go API (make dev) |
DEV_PORT_LIVEKIT | 7072 | Embedded LiveKit (local only) |
DEV_PORT_DEVTOOLS | 7074 | TanStack devtools event bus |
| Path | Target | Notes |
|---|---|---|
/api | http://localhost:7071 | API |
/uploads | http://localhost:7071 | Chat/media uploads via API |
/livekit | http://127.0.0.1:7072 | Direct to LiveKit — ws: true, changeOrigin: true, rewrite strips /livekit prefix |
Do not chain /livekit through the Go API — double WS proxy breaks /rtc/v1/validate.
Remote debug (BEDRUD_ALLOWED_HOSTS non-empty): HMR disabled (prevents LiveKit PC teardown). Bind host: BEDRUD_DEV_BIND_HOST. Allowed hosts: localhost, 127.0.0.1, + env list.
resolve.alias:
/^@\/(.*)$/ → src/$1
/^#\/(.*)$/ → src/$1
...excalidrawAliases(appRoot) // see Vendor aliases
optimizeDeps.include:
@livekit/components-react, @livekit/krisp-noise-filter, livekit-client,
roughjs, jotai, perfect-freehand, yjs, y-protocols/sync, lib0/encoding, lib0/decoding
| Chunk | Match |
|---|---|
meeting-context | /components/meeting/MeetingContext |
react-vendor | react / react-dom |
tanstack-vendor | @tanstack/ |
livekit-client-vendor | livekit-client |
livekit-components-vendor | @livekit/components-react |
charts-vendor | recharts / d3- |
ui-vendor | @radix-ui/ |
markdown-vendor | react-markdown, remark, unified, rehype, hast, mdast, micromark, vfile |
excalidraw-vendor | /src/vendor/excalidraw/ |
vendor | remaining node_modules except krisp, rnnoise-wasm, mermaid / @excalidraw/mermaid-* / markdown-to-text (kept lazy) |
chunkSizeWarningLimit: 6000.
Plugins: @tanstack/devtools-vite, @tailwindcss/vite, @tanstack/react-start/plugin/vite, @vitejs/plugin-react.
Vitest: jsdom, src/**/*.test.ts(x).
Source: src/vendor/excalidraw/aliases.ts — platform owns Vite/tsconfig wiring. Whiteboard UI/runtime stays in meeting skill.
excalidrawAliases(appRoot) maps workspace packages under src/vendor/excalidraw/packages/:
| Import | Resolves to |
|---|---|
@excalidraw/common (+ /*) | packages/common/src |
@excalidraw/element (+ /*) | packages/element/src |
@excalidraw/math (+ /*) | packages/math/src |
@excalidraw/utils (+ /*) | packages/utils/src |
@excalidraw/fractional-indexing | packages/fractional-indexing/src |
@excalidraw/laser-pointer | packages/laser-pointer/src |
@excalidraw/excalidraw | packages/excalidraw/index.tsx |
@excalidraw/excalidraw/* | packages/excalidraw/* |
@excalidraw/excalidraw/types | packages/excalidraw/types.ts |
@excalidraw/excalidraw/element/types | packages/element/src/types.ts |
Mirrored in tsconfig.json paths. Biome excludes src/vendor.
__root__ QueryClient, theme, Intl, auth init
├── / landing + guest join (no forced auth redirect)
├── /$ splat → ErrorPage not-found
├── /new auth → POST /api/room/create → /m/$meetId
├── /auth layout (split-screen); redirect /dashboard if tokens
│ ├── /auth/ guest join form
│ ├── /auth/login email/pass + passkey + OAuth; ?redirect=
│ ├── /auth/register name/email/pass + invite token
│ ├── /auth/callback OAuth cookie → GET /api/auth/me
│ ├── /auth/verify email verify (?token|status|reason)
│ ├── /auth/forgot-password POST /api/auth/forgot-password
│ └── /auth/reset-password ?token → POST reset
├── /dashboard layout sidebar+topbar; require tokens
│ ├── /dashboard/ My Rooms + Recent
│ ├── /dashboard/archived/$roomId archived room detail (placeholder/TODO)
│ ├── /dashboard/settings settings layout tabs
│ │ ├── /dashboard/settings/ profile
│ │ ├── /dashboard/settings/security
│ │ ├── /dashboard/settings/audio
│ │ └── /dashboard/settings/video
│ └── /dashboard/admin admin layout; admin|superadmin|moderator
│ ├── /dashboard/admin/ overview KPIs
│ ├── /dashboard/admin/queue
│ ├── /dashboard/admin/rooms
│ ├── /dashboard/admin/rooms/$roomId
│ ├── /dashboard/admin/rooms/events
│ ├── /dashboard/admin/users
│ ├── /dashboard/admin/users/$userId
│ ├── /dashboard/admin/users/recent-signups
│ ├── /dashboard/admin/recordings placeholder ("future release")
│ └── /dashboard/admin/settings blocks moderator; admin/superadmin only
└── /m/$meetId live meeting (meeting skill)
| File | Path | Type | Guard / loader | Purpose |
|---|---|---|---|---|
__root.tsx | (root) | Layout | — | Shell, providers, theme, auth init |
index.tsx | / | Leaf | beforeLoad: init auth only; loader: me if tokens | Landing + guest join |
$.tsx | /$ | Leaf | — | Catch-all not-found |
new.tsx | /new | Leaf | auth required; loader creates room | Instant meeting |
auth.tsx | /auth | Layout | tokens → /dashboard | Auth chrome + <Outlet /> |
auth.index.tsx | /auth/ | Leaf | — | Guest join |
auth.login.tsx | /auth/login | Leaf | search redirect? | Login |
auth.register.tsx | /auth/register | Leaf | — | Register |
auth.callback.tsx | /auth/callback | Leaf | — | OAuth completion |
auth.verify.tsx | /auth/verify | Leaf | search token/status/reason | Email verification |
auth.forgot-password.tsx | /auth/forgot-password | Leaf | — | Request reset email |
auth.reset-password.tsx | /auth/reset-password | Leaf | search token | Set new password |
dashboard.tsx | /dashboard | Layout | no tokens → /auth; loader /api/auth/me | App chrome |
dashboard.index.tsx | /dashboard/ | Leaf | — | Rooms list + recent |
dashboard/archived_.$roomId.tsx | /dashboard/archived/$roomId | Leaf | — | Archived room (stub UI) |
dashboard/settings.tsx | /dashboard/settings | Layout | — | Settings tabs |
dashboard/settings/index.tsx | /dashboard/settings/ | Leaf | — | Profile |
dashboard/settings/security.tsx | /dashboard/settings/security | Leaf | — | Password |
dashboard/settings/audio.tsx | /dashboard/settings/audio | Leaf | — | Audio prefs |
dashboard/settings/video.tsx | /dashboard/settings/video | Leaf | — | Video prefs |
dashboard/admin.tsx | /dashboard/admin | Layout | loader: me + allow admin/superadmin/moderator; else /dashboard | Admin shell + AdminContext (read-only for mods) |
dashboard/admin/index.tsx | /dashboard/admin/ | Leaf | — | Overview |
dashboard/admin/queue.tsx | /dashboard/admin/queue | Leaf | — | Queue stats |
dashboard/admin/rooms.tsx | /dashboard/admin/rooms | Leaf | — | Rooms table |
dashboard/admin/rooms_.$roomId.tsx | /dashboard/admin/rooms/$roomId | Leaf | — | Room detail |
dashboard/admin/rooms_.events.tsx | /dashboard/admin/rooms/events | Leaf | — | Room events |
dashboard/admin/users.tsx | /dashboard/admin/users | Leaf | — | Users table |
dashboard/admin/users_.$userId.tsx | /dashboard/admin/users/$userId | Leaf | — | User detail |
dashboard/admin/users_.recent-signups.tsx | /dashboard/admin/users/recent-signups | Leaf | — | Recent signups |
dashboard/admin/recordings.tsx | /dashboard/admin/recordings | Leaf | — | Placeholder |
dashboard/admin/settings.tsx | /dashboard/admin/settings | Leaf | beforeLoad: block moderator / non-admin | System settings tabs |
m.$meetId.tsx | /m/$meetId | Leaf | join flow in-page | Live meeting |
Counts: 32 route modules · 5 layouts (__root, auth, dashboard, settings, admin) · rest leaves.
| Area | Rule |
|---|---|
/auth/* layout | Tokens present → /dashboard |
/ | Does not redirect authenticated users (landing stays public) |
/dashboard | No tokens → /auth; loader loads user |
/new | No tokens → /auth/login?redirect=/new |
/dashboard/admin | Need isSuperAdmin or access admin or moderator |
/dashboard/admin/settings | Moderator blocked; need admin or superadmin |
src/lib/api.tsapi.get<T>(path, opts?)
api.post<T>(path, body?, opts?)
api.put<T>(path, body?, opts?)
api.delete<T>(path, opts?)
export const API_URL // same as BASE_URL
export class ApiError extends Error // status, body, parsedBody, message
| Concern | Behavior |
|---|---|
| Base URL | VITE_API_URL or '' so dev hits relative /api → Vite proxy → :7071 |
| Auth | Authorization: Bearer <accessToken> when present |
| CSRF fallback | No bearer → X-CSRF-Token from <meta name="csrf-token"> or csrf_token cookie |
| Credentials | include always (HTTP-only refresh cookie) |
| Body | JSON stringify unless FormData (then no forced Content-Type) |
| 401 refresh | If request had access token: singleton POST /api/auth/refresh → update tokens → retry once. Fail → clear() + location.replace('/auth'). Skip on refresh path / unauthenticated 401 / already-retried |
| Errors | Non-OK → ApiError with user message from JSON error or message |
Refresh response keys: snake_case access_token, refresh_token.
| File | Export | Notes |
|---|---|---|
handle-auth-success.ts | useHandleAuthSuccess(), AuthResponse | setTokens + setUser (isSuperAdmin/isAdmin from accesses) + navigate (redirectTo or /dashboard) |
webauthn.ts | base64ToBuffer, bufferToBase64, request/creation option types | Passkey encode helpers |
jwt-user.ts | decodeBedrudJwt(token) | { userId, accesses } via jwt-decode |
use-public-settings.ts | getPublicSettings, refreshPublicSettings, usePublicSettings | Module cache of GET /api/auth/settings |
| File | Export | Notes |
|---|---|---|
use-admin-overview.ts | useAdminOverview(), AdminOverview + nested types | GET /api/admin/overview, poll 60s |
use-queue-stats.ts | useQueueStats(), QueueStats, FailedJobSummary | GET /api/admin/queue, poll 10s |
| File | Export | Notes |
|---|---|---|
utils.ts | cn(...) | clsx + twMerge |
errors.ts | getErrorMessage(error, fallback) | Strip status prefix; parse JSON message/error/detail |
i18n.ts | useI18n, FormattedMessage, messages, defaultLocale | Thin react-intl wrap (en) |
user-preferences.ts | parseUserPreferences, patchUserPreferences | Server prefs blob merge → PUT-style preferences API |
useLongPress.ts | useLongPress(cb, ms=500) | Touch long-press; cancel if drift >10px |
text-direction.ts | textStartsRtl, textDirectionFor | RTL heuristics |
Not platform: meeting-only modules in lib/ (livekit-publish, meeting-sounds, rnnoise-processor, audio-processor.service, pin/PTT helpers, etc.) and components/meeting/chat/useChatPersistence → meeting skill. Stores → bedrud-fe-state.
| Type | File | Shape |
|---|---|---|
AuthTokens | lib/auth.store.ts | { accessToken: string, refreshToken: string | null } |
User | lib/user.store.ts | { id, email, name, provider, isSuperAdmin, isAdmin, accesses: string[] | null, avatarUrl? } |
Theme | lib/theme.store.ts | 'light' | 'dark' | 'system' |
AudioPreferences | lib/audio-preferences.store.ts | NS mode, echo, AGC, gain, gate, muted beep + interval, pushToTalkEnabled, pushToTalkKey |
NoiseSuppressionMode | same | 'none' | 'browser' | 'rnnoise' | 'krisp' |
VideoPreferences | lib/video-preferences.store.ts | { mirrorWebcam: boolean } |
RecentRoom | lib/recent-rooms.store.ts | { name, joinedAt } |
AuthResponse | lib/handle-auth-success.ts | { user, tokens } (login/passkey payloads) |
// lib/use-public-settings.ts
PublicSettings: {
serverName: string
registrationEnabled: boolean
tokenRegistrationOnly: boolean
guestLoginEnabled: boolean
passkeysEnabled: boolean
oauthProviders: string[]
requireEmailVerification: boolean
chatMaxMessageCount: number
chatMessageTTLHours: number
recordingsEnabled: boolean // TODO feature flag surface
}
src/types/admin.ts| Export | Purpose |
|---|---|
AdminUser | id, email, name, provider, isActive, accesses, createdAt |
AdminRoom | room DTO + optional settings / owner / activity fields |
ROLE_OPTS | superadmin · admin · moderator · user · guest |
ROLE_ACCESS_MAP | role → accesses[] |
detectRole, getRoleLabel | map accesses → role label |
See use-admin-overview.ts (AdminOverview, health, KPIs, activity, composition, attention items, signups, events, instance) and use-queue-stats.ts (pending/active/done/failed rates + email queue fields).
Chat/meeting message types live under meeting components — not this skill.
tsconfig.jsonreact-jsx, ESNext, moduleResolution: bundler#/*, @/*, full @excalidraw/* mirrornoUnusedLocals, noUnusedParameters, noFallthroughCasesInSwitch, noUncheckedSideEffectImportsverbatimModuleSyntax, allowImportingTsExtensions, noEmitvite/client, @types/reactbiome.jsondist, node_modules, src/routeTree.gen.ts, src/vendor, **/*.csscomponents.jsonshadcn: default style, CSS vars, slate base, Lucide. Aliases: @/components, @/lib/utils, @/components/ui.
package.json scripts| Script | Command |
|---|---|
dev | vite dev |
build | bun --bun vite build |
build:embed | node scripts/embed.mjs |
check | biome + tsc --noEmit |
lint / lint:fix / format | Biome |
typecheck | tsc --noEmit |
test | vitest run |
Key runtime deps: react 19, livekit-client 2.x, @livekit/components-react, zustand 5, TanStack router/start/query, zod 4, tailwindcss 4, recharts, yjs, sonner, react-intl.
router.tsx → routeTree.gen.ts
__root.tsx → QueryClient, theme.store, auth.store.initialize, Intl, ErrorBoundary
routes/* → lib/api.ts → auth.store (Bearer + refresh)
→ handle-auth-success / webauthn / use-public-settings
→ use-admin-overview / use-queue-stats (admin leaves)
→ types/admin.ts (admin tables)
vite.config → ports 7070/7071/7072, proxy, excalidrawAliases, manualChunks