create-fluxstack 1.17.1 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/LLMD/resources/live-auth.md +462 -465
  2. package/app/client/.live-stubs/LiveAdminPanel.js +15 -0
  3. package/app/client/.live-stubs/LiveCounter.js +9 -0
  4. package/app/client/.live-stubs/LiveForm.js +11 -0
  5. package/app/client/.live-stubs/LiveLocalCounter.js +8 -0
  6. package/app/client/.live-stubs/LivePingPong.js +10 -0
  7. package/app/client/.live-stubs/LiveRoomChat.js +11 -0
  8. package/app/client/.live-stubs/LiveSharedCounter.js +10 -0
  9. package/app/client/.live-stubs/LiveUpload.js +15 -0
  10. package/app/client/src/App.tsx +45 -3
  11. package/app/client/src/components/AppLayout.tsx +10 -1
  12. package/app/client/src/components/ErrorBoundary.tsx +117 -0
  13. package/app/client/src/components/LiveErrorBoundary.tsx +87 -0
  14. package/app/client/src/components/LiveUploadWidget.tsx +10 -14
  15. package/app/client/src/lib/eden-api.ts +6 -0
  16. package/app/client/src/lib/plugin-hooks.ts +82 -0
  17. package/app/client/src/live/AuthDemo.tsx +0 -1
  18. package/app/client/src/live/FormDemo.tsx +1 -1
  19. package/app/client/src/live/PingPongDemo.tsx +4 -1
  20. package/app/client/src/live/RoomChatDemo.tsx +90 -50
  21. package/app/client/src/live/SharedCounterDemo.tsx +5 -0
  22. package/app/server/auth/AuthManager.ts +24 -0
  23. package/app/server/auth/contracts.ts +12 -1
  24. package/app/server/auth/errors.ts +84 -0
  25. package/app/server/auth/guards/TokenGuard.ts +5 -2
  26. package/app/server/auth/index.ts +1 -1
  27. package/app/server/auth/providers/InMemoryProvider.ts +1 -1
  28. package/app/server/index.ts +3 -4
  29. package/app/server/live/LiveAdminPanel.ts +8 -8
  30. package/app/server/live/LiveForm.ts +1 -1
  31. package/app/server/live/LiveProtectedChat.ts +5 -5
  32. package/app/server/live/LiveRoomChat.ts +50 -28
  33. package/app/server/live/LiveUpload.ts +17 -3
  34. package/app/server/live/auto-generated-components.ts +26 -0
  35. package/app/server/live/rooms/ChatRoom.ts +17 -2
  36. package/app/server/routes/auth.routes.ts +29 -20
  37. package/app/server/routes/index.ts +9 -0
  38. package/app/server/routes/room.routes.ts +6 -6
  39. package/config/index.ts +3 -3
  40. package/config/system/app.config.ts +1 -1
  41. package/config/system/auth.config.ts +1 -1
  42. package/config/system/build.config.ts +8 -6
  43. package/config/system/client.config.ts +6 -4
  44. package/config/system/database.config.ts +1 -1
  45. package/config/system/logger.config.ts +1 -1
  46. package/config/system/monitoring.config.ts +6 -4
  47. package/config/system/plugins.config.ts +1 -1
  48. package/config/system/runtime.config.ts +1 -1
  49. package/config/system/server.config.ts +1 -1
  50. package/config/system/services.config.ts +1 -1
  51. package/config/system/session.config.ts +3 -3
  52. package/config/system/system.config.ts +1 -1
  53. package/core/build/vite-plugins.ts +3 -2
  54. package/core/cli/generators/plugin.ts +1 -1
  55. package/core/config/index.ts +8 -1
  56. package/core/framework/server.ts +7 -3
  57. package/core/index.ts +1 -1
  58. package/core/plugins/index.ts +1 -1
  59. package/core/plugins/manager.ts +5 -1
  60. package/core/plugins/types.ts +17 -1
  61. package/core/server/index.ts +5 -2
  62. package/core/server/live/index.ts +8 -71
  63. package/core/server/plugin-client-hooks.ts +97 -0
  64. package/core/types/types.ts +1 -0
  65. package/core/utils/version.ts +1 -1
  66. package/create-fluxstack.ts +1 -1
  67. package/package.json +8 -5
  68. package/src/client/components/ui/StatusBadge.tsx +23 -0
  69. package/core/utils/config-schema.ts +0 -480
  70. package/core/utils/env.ts +0 -305
  71. package/plugins/crypto-auth/README.md +0 -788
  72. package/plugins/crypto-auth/ai-context.md +0 -1282
  73. package/plugins/crypto-auth/cli/make-protected-route.command.ts +0 -383
  74. package/plugins/crypto-auth/client/CryptoAuthClient.ts +0 -302
  75. package/plugins/crypto-auth/client/components/AuthProvider.tsx +0 -131
  76. package/plugins/crypto-auth/client/components/LoginButton.tsx +0 -138
  77. package/plugins/crypto-auth/client/components/ProtectedRoute.tsx +0 -89
  78. package/plugins/crypto-auth/client/components/index.ts +0 -12
  79. package/plugins/crypto-auth/client/index.ts +0 -12
  80. package/plugins/crypto-auth/config/index.ts +0 -34
  81. package/plugins/crypto-auth/index.ts +0 -173
  82. package/plugins/crypto-auth/package.json +0 -66
  83. package/plugins/crypto-auth/server/AuthMiddleware.ts +0 -181
  84. package/plugins/crypto-auth/server/CryptoAuthLiveProvider.ts +0 -58
  85. package/plugins/crypto-auth/server/CryptoAuthService.ts +0 -186
  86. package/plugins/crypto-auth/server/index.ts +0 -25
  87. package/plugins/crypto-auth/server/middlewares/cryptoAuthAdmin.ts +0 -66
  88. package/plugins/crypto-auth/server/middlewares/cryptoAuthOptional.ts +0 -26
  89. package/plugins/crypto-auth/server/middlewares/cryptoAuthPermissions.ts +0 -77
  90. package/plugins/crypto-auth/server/middlewares/cryptoAuthRequired.ts +0 -45
  91. package/plugins/crypto-auth/server/middlewares/helpers.ts +0 -155
  92. package/plugins/crypto-auth/server/middlewares/index.ts +0 -22
  93. package/plugins/crypto-auth/server/middlewares.ts +0 -19
@@ -9,7 +9,14 @@
9
9
 
10
10
  import type { FluxStackConfig } from '@config'
11
11
  import { fluxStackConfig } from '@config'
12
- import { helpers } from '../utils/env'
12
+ import { env } from '@fluxstack/config'
13
+
14
+ const nodeEnv = (): string => env.get('NODE_ENV', 'development')
15
+ const helpers = {
16
+ isDevelopment: () => nodeEnv() === 'development',
17
+ isProduction: () => nodeEnv() === 'production',
18
+ isTest: () => nodeEnv() === 'test',
19
+ }
13
20
  import { logger } from '../utils/logger'
14
21
 
15
22
  // ============================================================================
@@ -7,11 +7,12 @@ import { fluxStackConfig } from "@config"
7
7
  import { getEnvironmentInfo } from "@core/config"
8
8
  import { logger, type Logger } from "@core/utils/logger"
9
9
  import { displayStartupBanner, type StartupInfo } from "@core/utils/logger/startup-banner"
10
- import { componentRegistry } from "@core/server/live"
10
+ import { liveServer } from "@core/server/live"
11
11
  import { FluxStackError } from "@core/utils/errors"
12
12
  import { createTimer, formatBytes, isProduction, isDevelopment } from "@core/utils/helpers"
13
13
  import { createHash } from "crypto"
14
14
  import { createPluginUtils } from "@core/plugins/config"
15
+ import { pluginClientHooks } from "@core/server/plugin-client-hooks"
15
16
  import type { Plugin } from "@core/plugins"
16
17
 
17
18
  export class FluxStackFramework {
@@ -142,7 +143,10 @@ export class FluxStackFramework {
142
143
  config: fullConfig,
143
144
  logger: pluginLogger,
144
145
  app: this.app,
145
- utils: pluginUtils
146
+ utils: pluginUtils,
147
+ clientHooks: {
148
+ register: (hookName: string, jsCode: string) => pluginClientHooks.register(hookName, jsCode)
149
+ }
146
150
  }
147
151
 
148
152
  // Initialize plugin manager
@@ -826,7 +830,7 @@ export class FluxStackFramework {
826
830
  vitePort: this.cfg.client?.port,
827
831
  viteEmbedded: vitePluginActive, // Vite is embedded when plugin is active
828
832
  swaggerPath: '/swagger', // TODO: Get from swagger plugin config
829
- liveComponents: componentRegistry.getRegisteredComponentNames()
833
+ liveComponents: liveServer?.registry.getRegisteredComponentNames() ?? []
830
834
  }
831
835
 
832
836
  // Display banner if enabled
package/core/index.ts CHANGED
@@ -24,7 +24,7 @@ export * from './cli/generators'
24
24
 
25
25
  // Plugin system (avoid wildcard to prevent conflicts)
26
26
  export { PluginRegistry } from './plugins/registry'
27
- export { PluginDiscovery, pluginDiscovery } from './plugins/discovery'
27
+ export { PluginDiscovery } from './plugins/discovery'
28
28
  export { PluginManager } from './plugins/manager'
29
29
  export { PluginUtils } from './plugins'
30
30
 
@@ -34,7 +34,7 @@ export { PluginRegistry } from './registry'
34
34
  export type { PluginRegistryConfig } from './registry'
35
35
 
36
36
  // Plugin discovery
37
- export { PluginDiscovery, pluginDiscovery } from './discovery'
37
+ export { PluginDiscovery } from './discovery'
38
38
  export type { PluginDiscoveryConfig } from './discovery'
39
39
 
40
40
  // Plugin configuration management
@@ -24,6 +24,7 @@ import type { Logger } from "@core/utils/logger"
24
24
  import { PluginRegistry } from "./registry"
25
25
  import { createPluginUtils } from "./config"
26
26
  import { FluxStackError } from "@core/utils/errors"
27
+ import { pluginClientHooks } from "@core/server/plugin-client-hooks"
27
28
  import { EventEmitter } from "events"
28
29
 
29
30
  /**
@@ -505,7 +506,10 @@ export class PluginManager extends EventEmitter {
505
506
  logger: this.logger.child ? this.logger.child({ plugin: plugin.name }) : this.logger,
506
507
  app: this.app,
507
508
  utils: createPluginUtils(this.logger),
508
- registry: this.registry
509
+ registry: this.registry,
510
+ clientHooks: {
511
+ register: (hookName: string, jsCode: string) => pluginClientHooks.register(hookName, jsCode)
512
+ }
509
513
  }
510
514
 
511
515
  this.contexts.set(plugin.name, context)
@@ -33,12 +33,28 @@ export type PluginHook =
33
33
 
34
34
  export type PluginPriority = 'highest' | 'high' | 'normal' | 'low' | 'lowest' | number
35
35
 
36
+ export interface PluginClientHooksAPI {
37
+ /**
38
+ * Register JavaScript code to be executed on the client at a specific hook point.
39
+ *
40
+ * Built-in hook points:
41
+ * - 'onEdenInit' — runs after the Eden Treaty client is created
42
+ * - 'onLiveConnect' — runs when the LiveComponents WebSocket connects
43
+ *
44
+ * @param hookName - The hook point name
45
+ * @param jsCode - JavaScript code string to execute on the client
46
+ */
47
+ register(hookName: string, jsCode: string): void
48
+ }
49
+
36
50
  export interface PluginContext {
37
51
  config: FluxStackConfig
38
52
  logger: Logger
39
53
  app: unknown // Elysia app
40
54
  utils: PluginUtils
41
55
  registry?: unknown // Plugin registry reference
56
+ /** Register client-side JS hooks that plugins can inject */
57
+ clientHooks: PluginClientHooksAPI
42
58
  }
43
59
 
44
60
  export interface PluginUtils {
@@ -190,7 +206,7 @@ export namespace FluxStack {
190
206
  * @example
191
207
  * // ✅ New way (recommended):
192
208
  * // plugins/my-plugin/config/index.ts
193
- * import { defineConfig, config } from '@core/utils/config-schema'
209
+ * import { defineConfig, config } from '@fluxstack/config'
194
210
  * export const myConfig = defineConfig({ ... })
195
211
  *
196
212
  * // ❌ Old way (deprecated):
@@ -1,15 +1,18 @@
1
1
  // FluxStack framework exports
2
2
  export { FluxStackFramework } from "../framework/server"
3
- export { vitePlugin, staticPlugin } from "../plugins/built-in"
3
+ export { vitePlugin } from "../plugins/built-in"
4
4
  export { swaggerPlugin } from "../plugins/built-in/swagger"
5
5
  export { PluginRegistry } from "../plugins/registry"
6
6
  export * from "../types"
7
7
 
8
8
  // Live Components exports
9
- export { liveComponentsPlugin, componentRegistry } from "./live"
9
+ export { liveComponentsPlugin, liveServer, registerAuthProvider } from "./live"
10
10
  export { LiveComponent } from "../types/types"
11
11
 
12
12
  // Static Files Plugin
13
13
  export { staticFilesPlugin } from "./plugins/static-files-plugin"
14
14
 
15
+ // Plugin Client Hooks
16
+ export { pluginClientHooks } from "./plugin-client-hooks"
17
+
15
18
  export * from "../types/types"
@@ -24,6 +24,7 @@ export { AuthenticatedContext, AnonymousContext, ANONYMOUS_CONTEXT } from '@flux
24
24
  export type {
25
25
  LiveAuthProvider,
26
26
  LiveAuthCredentials,
27
+ LiveAuthSession,
27
28
  LiveAuthUser,
28
29
  LiveAuthContext,
29
30
  LiveComponentAuth,
@@ -32,78 +33,14 @@ export type {
32
33
  LiveAuthResult,
33
34
  } from '@fluxstack/live'
34
35
 
35
- // Backward-compatible singleton accessors
36
- // These lazily access the LiveServer instance created by the plugin
37
- import { liveServer, pendingAuthProviders } from './websocket-plugin'
36
+ // Register auth provider — buffers if LiveServer not yet initialized
37
+ import { liveServer as _ls, pendingAuthProviders } from './websocket-plugin'
38
38
  import type { LiveAuthProvider as _LiveAuthProvider } from '@fluxstack/live'
39
- import type { ComponentRegistry as _ComponentRegistry } from '@fluxstack/live'
40
- import type { WebSocketConnectionManager as _WebSocketConnectionManager } from '@fluxstack/live'
41
- import type { RoomStateManager as _RoomStateManager } from '@fluxstack/live'
42
- import type { LiveRoomManager as _LiveRoomManager } from '@fluxstack/live'
43
- import type { RoomEventBus as _RoomEventBus } from '@fluxstack/live'
44
- import type { FileUploadManager as _FileUploadManager } from '@fluxstack/live'
45
- import type { PerformanceMonitor as _PerformanceMonitor } from '@fluxstack/live'
46
- import type { StateSignatureManager as _StateSignatureManager } from '@fluxstack/live'
47
- import type { LiveAuthManager as _LiveAuthManager } from '@fluxstack/live'
48
39
 
49
- function requireLiveServer() {
50
- if (!liveServer) {
51
- throw new Error(
52
- 'LiveComponents plugin not initialized. ' +
53
- 'Ensure the live-components plugin is loaded before accessing Live singletons.'
54
- )
40
+ export function registerAuthProvider(provider: _LiveAuthProvider) {
41
+ if (_ls) {
42
+ _ls.useAuth(provider)
43
+ } else {
44
+ pendingAuthProviders.push(provider)
55
45
  }
56
- return liveServer
57
46
  }
58
-
59
- /**
60
- * Backward-compatible liveAuthManager.
61
- * Buffers register() calls that happen before the plugin setup(),
62
- * then delegates to liveServer.authManager once available.
63
- * @deprecated Access via liveServer.authManager instead
64
- */
65
- export const liveAuthManager: Pick<_LiveAuthManager, 'authenticate' | 'hasProviders' | 'authorizeRoom' | 'authorizeAction' | 'authorizeComponent'> & { register: (provider: _LiveAuthProvider) => void } = {
66
- register(provider: _LiveAuthProvider) {
67
- if (liveServer) {
68
- liveServer.useAuth(provider)
69
- } else {
70
- pendingAuthProviders.push(provider)
71
- }
72
- },
73
- get authenticate() { return requireLiveServer().authManager.authenticate.bind(requireLiveServer().authManager) },
74
- get hasProviders() { return requireLiveServer().authManager.hasProviders.bind(requireLiveServer().authManager) },
75
- get authorizeRoom() { return requireLiveServer().authManager.authorizeRoom.bind(requireLiveServer().authManager) },
76
- get authorizeAction() { return requireLiveServer().authManager.authorizeAction.bind(requireLiveServer().authManager) },
77
- get authorizeComponent() { return requireLiveServer().authManager.authorizeComponent.bind(requireLiveServer().authManager) },
78
- }
79
-
80
- /** Helper to create a typed lazy proxy that delegates to a LiveServer property */
81
- function createLazyProxy<T extends object>(accessor: () => T): T {
82
- return new Proxy({} as T, {
83
- get(_, prop) { return (accessor() as Record<string | symbol, unknown>)[prop] }
84
- })
85
- }
86
-
87
- /** @deprecated Access via liveServer.registry instead */
88
- export const componentRegistry = createLazyProxy<_ComponentRegistry>(() => requireLiveServer().registry)
89
-
90
- /** @deprecated Access via liveServer.connectionManager instead */
91
- export const connectionManager = createLazyProxy<_WebSocketConnectionManager>(() => requireLiveServer().connectionManager)
92
-
93
- /** @deprecated Access via liveServer.roomManager instead */
94
- export const liveRoomManager = createLazyProxy<_LiveRoomManager>(() => requireLiveServer().roomManager)
95
-
96
- /** @deprecated Access via liveServer.roomEvents instead */
97
- export const roomEvents = createLazyProxy<_RoomEventBus>(() => requireLiveServer().roomEvents)
98
-
99
- /** @deprecated Access via liveServer.fileUploadManager instead */
100
- export const fileUploadManager = createLazyProxy<_FileUploadManager>(() => requireLiveServer().fileUploadManager)
101
-
102
- /** @deprecated Access via liveServer.performanceMonitor instead */
103
- export const performanceMonitor = createLazyProxy<_PerformanceMonitor>(() => requireLiveServer().performanceMonitor)
104
-
105
- /** @deprecated Access via liveServer.stateSignature instead */
106
- export const stateSignature = createLazyProxy<_StateSignatureManager>(() => requireLiveServer().stateSignature)
107
-
108
- // Room state backward compat
109
- export const roomState = createLazyProxy<_LiveRoomManager>(() => requireLiveServer().roomManager)
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Plugin Client Hook Registry
3
+ *
4
+ * Singleton registry where server-side plugins register JavaScript code
5
+ * to be executed on the client. The client fetches these hooks via
6
+ * GET /api/__plugins/client-hooks and executes them at the right moment
7
+ * (e.g., when Eden Treaty initializes, when LiveComponents WebSocket connects).
8
+ *
9
+ * Plugins call `pluginClientHooks.register(hookName, jsCode)` during their
10
+ * setup phase to inject client-side behavior.
11
+ *
12
+ * Built-in hook points:
13
+ * - 'onEdenInit' — runs after the Eden Treaty client is created
14
+ * - 'onLiveConnect' — runs when the LiveComponents WebSocket connects
15
+ */
16
+
17
+ class PluginClientHookRegistry {
18
+ private hooks = new Map<string, string[]>()
19
+ private _cachedSignature: string | null = null
20
+
21
+ /**
22
+ * Register a JavaScript code string to be executed on the client
23
+ * for the given hook name.
24
+ *
25
+ * @param hookName - The hook point name (e.g., 'onEdenInit', 'onLiveConnect')
26
+ * @param jsCode - JavaScript code string to execute on the client
27
+ */
28
+ register(hookName: string, jsCode: string): void {
29
+ const existing = this.hooks.get(hookName)
30
+ if (existing) {
31
+ existing.push(jsCode)
32
+ } else {
33
+ this.hooks.set(hookName, [jsCode])
34
+ }
35
+ this._cachedSignature = null // invalidate on change
36
+ }
37
+
38
+ /**
39
+ * Get all registered hooks as a plain object.
40
+ * Used by the HTTP endpoint to serialize the response.
41
+ */
42
+ getHooks(): Record<string, string[]> {
43
+ const result: Record<string, string[]> = {}
44
+ for (const [name, codes] of this.hooks) {
45
+ result[name] = [...codes]
46
+ }
47
+ return result
48
+ }
49
+
50
+ /**
51
+ * Get code strings for a specific hook name.
52
+ */
53
+ getHook(name: string): string[] {
54
+ return this.hooks.get(name) ? [...this.hooks.get(name)!] : []
55
+ }
56
+
57
+ /**
58
+ * Get HMAC-SHA256 signature of the hooks payload.
59
+ * Uses a per-process secret (random on startup) so the signature
60
+ * is only valid for this server instance.
61
+ *
62
+ * The client receives this signature via WebSocket (CONNECTION_ESTABLISHED)
63
+ * and verifies it against the HTTP response to prevent injection.
64
+ */
65
+ getSignature(): string {
66
+ if (this._cachedSignature) return this._cachedSignature
67
+ const { createHmac, randomBytes } = require('crypto')
68
+ if (!this._secret) this._secret = randomBytes(32)
69
+ const payload = JSON.stringify(this.getHooks())
70
+ this._cachedSignature = createHmac('sha256', this._secret).update(payload).digest('hex') as string
71
+ return this._cachedSignature!
72
+ }
73
+
74
+ /** @internal Per-process secret for HMAC signing */
75
+ private _secret: Buffer | null = null
76
+
77
+ /**
78
+ * Get hooks + signature for the HTTP response.
79
+ */
80
+ getSignedResponse(): { hooks: Record<string, string[]>; signature: string } {
81
+ return {
82
+ hooks: this.getHooks(),
83
+ signature: this.getSignature(),
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Clear all registered hooks. Useful for testing.
89
+ */
90
+ clear(): void {
91
+ this.hooks.clear()
92
+ this._cachedSignature = null
93
+ }
94
+ }
95
+
96
+ export const pluginClientHooks = new PluginClientHookRegistry()
97
+ export type { PluginClientHookRegistry }
@@ -48,6 +48,7 @@ export type {
48
48
  LiveActionAuthMap,
49
49
  LiveAuthProvider,
50
50
  LiveAuthCredentials,
51
+ LiveAuthSession,
51
52
  LiveAuthUser,
52
53
  LiveAuthResult,
53
54
  } from '@fluxstack/live'
@@ -3,4 +3,4 @@
3
3
  * Single source of truth for version number
4
4
  * Auto-synced with package.json
5
5
  */
6
- export const FLUXSTACK_VERSION = '1.17.1'
6
+ export const FLUXSTACK_VERSION = '1.18.0'
@@ -418,7 +418,7 @@ FluxStack includes several built-in plugins that are ready to use:
418
418
 
419
419
  \`\`\`typescript
420
420
  // app/server/index.ts
421
- import { loggerPlugin, swaggerPlugin, staticPlugin } from "@core/server"
421
+ import { swaggerPlugin } from "@core/server"
422
422
 
423
423
  // Add built-in plugins
424
424
  app.use(loggerPlugin)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-fluxstack",
3
- "version": "1.17.1",
3
+ "version": "1.18.0",
4
4
  "description": "⚡ Revolutionary full-stack TypeScript framework with Declarative Config System, Elysia + React + Bun",
5
5
  "keywords": [
6
6
  "framework",
@@ -75,12 +75,15 @@
75
75
  "vitest": "^3.2.4"
76
76
  },
77
77
  "dependencies": {
78
- "@fluxstack/live": "^0.3.1",
79
- "@fluxstack/live-elysia": "^0.2.1",
80
- "@fluxstack/live-client": "^0.3.1",
81
- "@fluxstack/live-react": "^0.3.1",
82
78
  "@elysiajs/eden": "^1.3.2",
83
79
  "@elysiajs/swagger": "^1.3.1",
80
+ "@fluxstack/config": "^1.0.0",
81
+ "@fluxstack/live": "^0.5.1",
82
+ "@fluxstack/live-client": "^0.5.1",
83
+ "@fluxstack/live-elysia": "^0.5.1",
84
+ "@fluxstack/live-react": "^0.5.1",
85
+ "@fluxstack/plugin-crypto-auth": "^1.0.0",
86
+ "@fluxstack/plugin-csrf-protection": "^1.1.0",
84
87
  "@vitejs/plugin-react": "^4.6.0",
85
88
  "chalk": "^5.3.0",
86
89
  "commander": "^12.1.0",
@@ -0,0 +1,23 @@
1
+ type StatusBadgeProps = {
2
+ status: 'online' | 'offline' | 'away'
3
+ size?: 'sm' | 'md' | 'lg'
4
+ }
5
+
6
+ export default function StatusBadge({ status, size = 'md' }: StatusBadgeProps) {
7
+ const statusClasses = {
8
+ online: 'bg-green-100 text-green-700',
9
+ offline: 'bg-gray-100 text-gray-700',
10
+ away: 'bg-yellow-100 text-yellow-700',
11
+ }
12
+ const sizeClasses = {
13
+ sm: 'px-2 py-0.5 text-xs',
14
+ md: 'px-2.5 py-1 text-sm',
15
+ lg: 'px-3 py-1.5 text-base',
16
+ }
17
+
18
+ return (
19
+ <span className={`inline-flex items-center rounded-full font-medium ${statusClasses[status]} ${sizeClasses[size]}`}>
20
+ {status}
21
+ </span>
22
+ )
23
+ }