create-fluxstack 1.9.1 → 1.12.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.
- package/.dockerignore +1 -2
- package/Dockerfile +8 -8
- package/LIVE_COMPONENTS_REVIEW.md +781 -0
- package/LLMD/INDEX.md +64 -0
- package/LLMD/MAINTENANCE.md +197 -0
- package/LLMD/MIGRATION.md +156 -0
- package/LLMD/config/.gitkeep +1 -0
- package/LLMD/config/declarative-system.md +268 -0
- package/LLMD/config/environment-vars.md +327 -0
- package/LLMD/config/runtime-reload.md +401 -0
- package/LLMD/core/.gitkeep +1 -0
- package/LLMD/core/build-system.md +599 -0
- package/LLMD/core/framework-lifecycle.md +229 -0
- package/LLMD/core/plugin-system.md +451 -0
- package/LLMD/patterns/.gitkeep +1 -0
- package/LLMD/patterns/anti-patterns.md +297 -0
- package/LLMD/patterns/project-structure.md +264 -0
- package/LLMD/patterns/type-safety.md +440 -0
- package/LLMD/reference/.gitkeep +1 -0
- package/LLMD/reference/cli-commands.md +250 -0
- package/LLMD/reference/plugin-hooks.md +357 -0
- package/LLMD/reference/routing.md +39 -0
- package/LLMD/reference/troubleshooting.md +364 -0
- package/LLMD/resources/.gitkeep +1 -0
- package/LLMD/resources/controllers.md +465 -0
- package/LLMD/resources/live-components.md +703 -0
- package/LLMD/resources/live-rooms.md +482 -0
- package/LLMD/resources/live-upload.md +130 -0
- package/LLMD/resources/plugins-external.md +617 -0
- package/LLMD/resources/routes-eden.md +254 -0
- package/README.md +37 -17
- package/app/client/index.html +0 -1
- package/app/client/src/App.tsx +109 -156
- package/app/client/src/components/AppLayout.tsx +68 -0
- package/app/client/src/components/BackButton.tsx +13 -0
- package/app/client/src/components/DemoPage.tsx +20 -0
- package/app/client/src/components/LiveUploadWidget.tsx +204 -0
- package/app/client/src/lib/eden-api.ts +85 -65
- package/app/client/src/live/ChatDemo.tsx +107 -0
- package/app/client/src/live/CounterDemo.tsx +206 -0
- package/app/client/src/live/FormDemo.tsx +119 -0
- package/app/client/src/live/RoomChatDemo.tsx +242 -0
- package/app/client/src/live/UploadDemo.tsx +21 -0
- package/app/client/src/main.tsx +13 -10
- package/app/client/src/pages/ApiTestPage.tsx +108 -0
- package/app/client/src/pages/HomePage.tsx +76 -0
- package/app/client/src/vite-env.d.ts +1 -1
- package/app/server/app.ts +1 -4
- package/app/server/controllers/users.controller.ts +36 -44
- package/app/server/index.ts +24 -107
- package/app/server/live/LiveChat.ts +77 -0
- package/app/server/live/LiveCounter.ts +67 -0
- package/app/server/live/LiveForm.ts +63 -0
- package/app/server/live/LiveLocalCounter.ts +32 -0
- package/app/server/live/LiveRoomChat.ts +285 -0
- package/app/server/live/LiveUpload.ts +81 -0
- package/app/server/live/register-components.ts +19 -19
- package/app/server/routes/index.ts +3 -1
- package/app/server/routes/room.routes.ts +117 -0
- package/app/server/routes/users.routes.ts +35 -27
- package/app/shared/types/index.ts +14 -2
- package/config/app.config.ts +2 -62
- package/config/client.config.ts +2 -95
- package/config/database.config.ts +2 -99
- package/config/fluxstack.config.ts +25 -45
- package/config/index.ts +57 -38
- package/config/monitoring.config.ts +2 -114
- package/config/plugins.config.ts +2 -80
- package/config/server.config.ts +2 -68
- package/config/services.config.ts +2 -130
- package/config/system/app.config.ts +29 -0
- package/config/system/build.config.ts +49 -0
- package/config/system/client.config.ts +68 -0
- package/config/system/database.config.ts +17 -0
- package/config/system/fluxstack.config.ts +114 -0
- package/config/{logger.config.ts → system/logger.config.ts} +3 -1
- package/config/system/monitoring.config.ts +114 -0
- package/config/system/plugins.config.ts +84 -0
- package/config/{runtime.config.ts → system/runtime.config.ts} +1 -1
- package/config/system/server.config.ts +68 -0
- package/config/system/services.config.ts +46 -0
- package/config/{system.config.ts → system/system.config.ts} +1 -1
- package/core/build/bundler.ts +4 -1
- package/core/build/flux-plugins-generator.ts +325 -325
- package/core/build/index.ts +159 -27
- package/core/build/live-components-generator.ts +70 -3
- package/core/build/optimizer.ts +235 -235
- package/core/cli/command-registry.ts +6 -4
- package/core/cli/commands/build.ts +79 -0
- package/core/cli/commands/create.ts +54 -0
- package/core/cli/commands/dev.ts +101 -0
- package/core/cli/commands/help.ts +34 -0
- package/core/cli/commands/index.ts +34 -0
- package/core/cli/commands/make-plugin.ts +90 -0
- package/core/cli/commands/plugin-add.ts +197 -0
- package/core/cli/commands/plugin-deps.ts +2 -2
- package/core/cli/commands/plugin-list.ts +208 -0
- package/core/cli/commands/plugin-remove.ts +170 -0
- package/core/cli/generators/component.ts +769 -769
- package/core/cli/generators/controller.ts +1 -1
- package/core/cli/generators/index.ts +146 -146
- package/core/cli/generators/interactive.ts +227 -227
- package/core/cli/generators/plugin.ts +2 -2
- package/core/cli/generators/prompts.ts +82 -82
- package/core/cli/generators/route.ts +6 -6
- package/core/cli/generators/service.ts +2 -2
- package/core/cli/generators/template-engine.ts +4 -3
- package/core/cli/generators/types.ts +2 -2
- package/core/cli/generators/utils.ts +191 -191
- package/core/cli/index.ts +115 -558
- package/core/cli/plugin-discovery.ts +2 -2
- package/core/client/LiveComponentsProvider.tsx +63 -17
- package/core/client/api/eden.ts +183 -0
- package/core/client/api/index.ts +11 -0
- package/core/client/components/Live.tsx +104 -0
- package/core/client/fluxstack.ts +1 -9
- package/core/client/hooks/AdaptiveChunkSizer.ts +215 -0
- package/core/client/hooks/state-validator.ts +1 -1
- package/core/client/hooks/useAuth.ts +48 -48
- package/core/client/hooks/useChunkedUpload.ts +170 -69
- package/core/client/hooks/useLiveChunkedUpload.ts +87 -0
- package/core/client/hooks/useLiveComponent.ts +800 -0
- package/core/client/hooks/useLiveUpload.ts +71 -0
- package/core/client/hooks/useRoom.ts +409 -0
- package/core/client/hooks/useRoomProxy.ts +382 -0
- package/core/client/index.ts +18 -51
- package/core/client/standalone-entry.ts +8 -0
- package/core/client/standalone.ts +74 -53
- package/core/client/state/createStore.ts +192 -192
- package/core/client/state/index.ts +14 -14
- package/core/config/index.ts +70 -291
- package/core/config/schema.ts +42 -723
- package/core/framework/client.ts +131 -131
- package/core/framework/index.ts +7 -7
- package/core/framework/server.ts +227 -47
- package/core/framework/types.ts +2 -2
- package/core/index.ts +23 -4
- package/core/live/ComponentRegistry.ts +7 -3
- package/core/live/types.ts +77 -0
- package/core/plugins/built-in/index.ts +134 -131
- package/core/plugins/built-in/live-components/commands/create-live-component.ts +242 -1074
- package/core/plugins/built-in/live-components/index.ts +1 -1
- package/core/plugins/built-in/monitoring/index.ts +111 -47
- package/core/plugins/built-in/static/index.ts +1 -1
- package/core/plugins/built-in/swagger/index.ts +68 -265
- package/core/plugins/built-in/vite/index.ts +94 -306
- package/core/plugins/built-in/vite/vite-dev.ts +82 -0
- package/core/plugins/config.ts +9 -7
- package/core/plugins/dependency-manager.ts +31 -1
- package/core/plugins/discovery.ts +19 -7
- package/core/plugins/executor.ts +2 -2
- package/core/plugins/index.ts +203 -203
- package/core/plugins/manager.ts +27 -39
- package/core/plugins/module-resolver.ts +19 -8
- package/core/plugins/registry.ts +309 -21
- package/core/plugins/types.ts +106 -55
- package/core/server/framework.ts +66 -43
- package/core/server/index.ts +15 -16
- package/core/server/live/ComponentRegistry.ts +91 -75
- package/core/server/live/FileUploadManager.ts +41 -31
- package/core/server/live/LiveComponentPerformanceMonitor.ts +1 -1
- package/core/server/live/LiveRoomManager.ts +261 -0
- package/core/server/live/RoomEventBus.ts +234 -0
- package/core/server/live/RoomStateManager.ts +172 -0
- package/core/server/live/StateSignature.ts +643 -643
- package/core/server/live/WebSocketConnectionManager.ts +30 -19
- package/core/server/live/auto-generated-components.ts +41 -26
- package/core/server/live/index.ts +14 -0
- package/core/server/live/websocket-plugin.ts +233 -72
- package/core/server/middleware/elysia-helpers.ts +7 -2
- package/core/server/middleware/errorHandling.ts +1 -1
- package/core/server/middleware/index.ts +31 -31
- package/core/server/plugins/database.ts +180 -180
- package/core/server/plugins/static-files-plugin.ts +69 -260
- package/core/server/plugins/swagger.ts +33 -33
- package/core/server/rooms/RoomBroadcaster.ts +357 -0
- package/core/server/rooms/RoomSystem.ts +463 -0
- package/core/server/rooms/index.ts +13 -0
- package/core/server/services/BaseService.ts +1 -1
- package/core/server/services/ServiceContainer.ts +1 -1
- package/core/server/services/index.ts +8 -8
- package/core/templates/create-project.ts +12 -12
- package/core/testing/index.ts +9 -9
- package/core/testing/setup.ts +73 -73
- package/core/types/api.ts +168 -168
- package/core/types/build.ts +219 -218
- package/core/types/config.ts +56 -26
- package/core/types/index.ts +4 -4
- package/core/types/plugin.ts +107 -99
- package/core/types/types.ts +490 -14
- package/core/utils/build-logger.ts +324 -324
- package/core/utils/config-schema.ts +480 -480
- package/core/utils/env.ts +2 -8
- package/core/utils/errors/codes.ts +114 -114
- package/core/utils/errors/handlers.ts +36 -1
- package/core/utils/errors/index.ts +49 -5
- package/core/utils/errors/middleware.ts +113 -113
- package/core/utils/helpers.ts +6 -16
- package/core/utils/index.ts +17 -17
- package/core/utils/logger/colors.ts +114 -114
- package/core/utils/logger/config.ts +13 -9
- package/core/utils/logger/formatter.ts +82 -82
- package/core/utils/logger/group-logger.ts +101 -101
- package/core/utils/logger/index.ts +6 -1
- package/core/utils/logger/stack-trace.ts +3 -1
- package/core/utils/logger/startup-banner.ts +82 -66
- package/core/utils/logger/winston-logger.ts +152 -152
- package/core/utils/monitoring/index.ts +211 -211
- package/core/utils/sync-version.ts +66 -66
- package/core/utils/version.ts +1 -1
- package/create-fluxstack.ts +8 -7
- package/eslint.config.js +23 -23
- package/package.json +14 -15
- package/plugins/crypto-auth/cli/make-protected-route.command.ts +1 -1
- package/plugins/crypto-auth/client/CryptoAuthClient.ts +302 -302
- package/plugins/crypto-auth/client/components/index.ts +11 -11
- package/plugins/crypto-auth/client/index.ts +11 -11
- package/plugins/crypto-auth/config/index.ts +1 -1
- package/plugins/crypto-auth/index.ts +4 -4
- package/plugins/crypto-auth/package.json +65 -65
- package/plugins/crypto-auth/server/AuthMiddleware.ts +1 -1
- package/plugins/crypto-auth/server/CryptoAuthService.ts +185 -185
- package/plugins/crypto-auth/server/index.ts +21 -21
- package/plugins/crypto-auth/server/middlewares/cryptoAuthAdmin.ts +3 -3
- package/plugins/crypto-auth/server/middlewares/cryptoAuthOptional.ts +1 -1
- package/plugins/crypto-auth/server/middlewares/cryptoAuthPermissions.ts +2 -2
- package/plugins/crypto-auth/server/middlewares/cryptoAuthRequired.ts +2 -2
- package/plugins/crypto-auth/server/middlewares/helpers.ts +1 -1
- package/plugins/crypto-auth/server/middlewares/index.ts +22 -22
- package/plugins/crypto-auth/server/middlewares.ts +19 -19
- package/tsconfig.api-strict.json +16 -0
- package/tsconfig.json +10 -14
- package/{app/client/tsconfig.node.json → tsconfig.node.json} +1 -1
- package/types/global.d.ts +29 -29
- package/types/vitest.d.ts +8 -8
- package/vite.config.ts +38 -62
- package/vitest.config.live.ts +10 -9
- package/vitest.config.ts +29 -17
- package/workspace.json +5 -5
- package/app/client/README.md +0 -69
- package/app/client/SIMPLIFICATION.md +0 -140
- package/app/client/frontend-only.ts +0 -12
- package/app/client/tsconfig.app.json +0 -44
- package/app/client/tsconfig.json +0 -7
- package/app/client/zustand-setup.md +0 -65
- package/app/server/backend-only.ts +0 -18
- package/app/server/live/LiveClockComponent.ts +0 -215
- package/app/server/routes/env-test.ts +0 -110
- package/core/client/hooks/index.ts +0 -7
- package/core/client/hooks/useHybridLiveComponent.ts +0 -631
- package/core/client/hooks/useWebSocket.ts +0 -373
- package/core/config/env.ts +0 -546
- package/core/config/loader.ts +0 -522
- package/core/config/runtime-config.ts +0 -327
- package/core/config/validator.ts +0 -540
- package/core/server/backend-entry.ts +0 -51
- package/core/server/standalone.ts +0 -106
- package/core/utils/regenerate-files.ts +0 -69
- package/fluxstack.config.ts +0 -354
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
// 🔥 CounterDemo - Contador isolado e compartilhado
|
|
2
|
+
|
|
3
|
+
import { useMemo } from 'react'
|
|
4
|
+
import { Live } from '@/core/client'
|
|
5
|
+
import { LiveCounter } from '@server/live/LiveCounter'
|
|
6
|
+
import { LiveLocalCounter } from '@server/live/LiveLocalCounter'
|
|
7
|
+
|
|
8
|
+
export function CounterDemo() {
|
|
9
|
+
const isolatedRoom = useMemo(() => {
|
|
10
|
+
if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {
|
|
11
|
+
return `local-${crypto.randomUUID()}`
|
|
12
|
+
}
|
|
13
|
+
return `local-${Math.random().toString(36).slice(2)}`
|
|
14
|
+
}, [])
|
|
15
|
+
|
|
16
|
+
const sharedCounter = Live.use(LiveCounter, {
|
|
17
|
+
room: 'global-counter',
|
|
18
|
+
initialState: LiveCounter.defaultState
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const isolatedCounter = Live.use(LiveCounter, {
|
|
22
|
+
room: isolatedRoom,
|
|
23
|
+
initialState: LiveCounter.defaultState,
|
|
24
|
+
persistState: false
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const localCounter = Live.use(LiveLocalCounter, {
|
|
28
|
+
initialState: LiveLocalCounter.defaultState,
|
|
29
|
+
persistState: false
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
const renderCounter = (
|
|
33
|
+
title: string,
|
|
34
|
+
description: string,
|
|
35
|
+
counter: ReturnType<typeof Live.use>
|
|
36
|
+
) => {
|
|
37
|
+
const handleIncrement = async () => {
|
|
38
|
+
await counter.increment()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const handleDecrement = async () => {
|
|
42
|
+
await counter.decrement()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const handleReset = async () => {
|
|
46
|
+
await counter.reset()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<div className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-2xl p-8 max-w-md w-full">
|
|
51
|
+
<h2 className="text-2xl font-bold text-white mb-2 text-center">
|
|
52
|
+
{title}
|
|
53
|
+
</h2>
|
|
54
|
+
|
|
55
|
+
<p className="text-gray-400 text-sm text-center mb-6">
|
|
56
|
+
{description}
|
|
57
|
+
</p>
|
|
58
|
+
|
|
59
|
+
<div className="flex justify-center gap-4 mb-6">
|
|
60
|
+
<div className={`flex items-center gap-2 px-3 py-1 rounded-full text-xs ${
|
|
61
|
+
counter.$connected
|
|
62
|
+
? 'bg-emerald-500/20 text-emerald-300'
|
|
63
|
+
: 'bg-red-500/20 text-red-300'
|
|
64
|
+
}`}>
|
|
65
|
+
<div className={`w-2 h-2 rounded-full ${
|
|
66
|
+
counter.$connected ? 'bg-emerald-400' : 'bg-red-400'
|
|
67
|
+
}`} />
|
|
68
|
+
{counter.$connected ? 'Conectado' : 'Desconectado'}
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<div className="flex items-center gap-2 px-3 py-1 rounded-full text-xs bg-blue-500/20 text-blue-300">
|
|
72
|
+
<span>👥</span>
|
|
73
|
+
{counter.$state.connectedUsers} usuário(s)
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<div className="text-center mb-8">
|
|
78
|
+
<div className="text-8xl font-bold bg-gradient-to-r from-blue-400 via-purple-400 to-pink-400 bg-clip-text text-transparent">
|
|
79
|
+
{counter.$state.count}
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
{counter.$state.lastUpdatedBy && (
|
|
83
|
+
<p className="text-gray-500 text-sm mt-2">
|
|
84
|
+
Última atualização: {counter.$state.lastUpdatedBy}
|
|
85
|
+
</p>
|
|
86
|
+
)}
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<div className="flex gap-4 justify-center">
|
|
90
|
+
<button
|
|
91
|
+
onClick={handleDecrement}
|
|
92
|
+
disabled={counter.$loading}
|
|
93
|
+
className="w-14 h-14 flex items-center justify-center text-3xl bg-red-500/20 hover:bg-red-500/30 border border-red-500/30 text-red-300 rounded-xl transition-all disabled:opacity-50"
|
|
94
|
+
>
|
|
95
|
+
−
|
|
96
|
+
</button>
|
|
97
|
+
|
|
98
|
+
<button
|
|
99
|
+
onClick={handleReset}
|
|
100
|
+
disabled={counter.$loading}
|
|
101
|
+
className="px-6 h-14 flex items-center justify-center text-sm bg-gray-500/20 hover:bg-gray-500/30 border border-gray-500/30 text-gray-300 rounded-xl transition-all disabled:opacity-50"
|
|
102
|
+
>
|
|
103
|
+
Reset
|
|
104
|
+
</button>
|
|
105
|
+
|
|
106
|
+
<button
|
|
107
|
+
onClick={handleIncrement}
|
|
108
|
+
disabled={counter.$loading}
|
|
109
|
+
className="w-14 h-14 flex items-center justify-center text-3xl bg-emerald-500/20 hover:bg-emerald-500/30 border border-emerald-500/30 text-emerald-300 rounded-xl transition-all disabled:opacity-50"
|
|
110
|
+
>
|
|
111
|
+
+
|
|
112
|
+
</button>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
{counter.$loading && (
|
|
116
|
+
<div className="flex justify-center mt-4">
|
|
117
|
+
<div className="w-5 h-5 border-2 border-purple-500 border-t-transparent rounded-full animate-spin" />
|
|
118
|
+
</div>
|
|
119
|
+
)}
|
|
120
|
+
|
|
121
|
+
<div className="mt-8 pt-6 border-t border-white/10">
|
|
122
|
+
<p className="text-gray-500 text-xs text-center">
|
|
123
|
+
✨ Usando <code className="text-purple-400">Room Events</code>
|
|
124
|
+
</p>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const renderLocalCounter = () => {
|
|
131
|
+
const handleIncrement = async () => {
|
|
132
|
+
await localCounter.increment()
|
|
133
|
+
}
|
|
134
|
+
const handleDecrement = async () => {
|
|
135
|
+
await localCounter.decrement()
|
|
136
|
+
}
|
|
137
|
+
const handleReset = async () => {
|
|
138
|
+
await localCounter.reset()
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<div className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-2xl p-8 max-w-md w-full">
|
|
143
|
+
<h2 className="text-2xl font-bold text-white mb-2 text-center">
|
|
144
|
+
Contador Local (sem Room)
|
|
145
|
+
</h2>
|
|
146
|
+
<p className="text-gray-400 text-sm text-center mb-6">
|
|
147
|
+
Estado local do componente, sem eventos de sala.
|
|
148
|
+
</p>
|
|
149
|
+
|
|
150
|
+
<div className="text-center mb-8">
|
|
151
|
+
<div className="text-8xl font-bold bg-gradient-to-r from-amber-400 via-orange-400 to-rose-400 bg-clip-text text-transparent">
|
|
152
|
+
{localCounter.$state.count}
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
<div className="flex gap-4 justify-center">
|
|
157
|
+
<button
|
|
158
|
+
onClick={handleDecrement}
|
|
159
|
+
disabled={localCounter.$loading}
|
|
160
|
+
className="w-14 h-14 flex items-center justify-center text-3xl bg-red-500/20 hover:bg-red-500/30 border border-red-500/30 text-red-300 rounded-xl transition-all disabled:opacity-50"
|
|
161
|
+
>
|
|
162
|
+
−
|
|
163
|
+
</button>
|
|
164
|
+
|
|
165
|
+
<button
|
|
166
|
+
onClick={handleReset}
|
|
167
|
+
disabled={localCounter.$loading}
|
|
168
|
+
className="px-6 h-14 flex items-center justify-center text-sm bg-gray-500/20 hover:bg-gray-500/30 border border-gray-500/30 text-gray-300 rounded-xl transition-all disabled:opacity-50"
|
|
169
|
+
>
|
|
170
|
+
Reset
|
|
171
|
+
</button>
|
|
172
|
+
|
|
173
|
+
<button
|
|
174
|
+
onClick={handleIncrement}
|
|
175
|
+
disabled={localCounter.$loading}
|
|
176
|
+
className="w-14 h-14 flex items-center justify-center text-3xl bg-emerald-500/20 hover:bg-emerald-500/30 border border-emerald-500/30 text-emerald-300 rounded-xl transition-all disabled:opacity-50"
|
|
177
|
+
>
|
|
178
|
+
+
|
|
179
|
+
</button>
|
|
180
|
+
</div>
|
|
181
|
+
|
|
182
|
+
{localCounter.$loading && (
|
|
183
|
+
<div className="flex justify-center mt-4">
|
|
184
|
+
<div className="w-5 h-5 border-2 border-purple-500 border-t-transparent rounded-full animate-spin" />
|
|
185
|
+
</div>
|
|
186
|
+
)}
|
|
187
|
+
</div>
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return (
|
|
192
|
+
<div className="flex flex-col lg:flex-row gap-6 items-start justify-center">
|
|
193
|
+
{renderLocalCounter()}
|
|
194
|
+
{renderCounter(
|
|
195
|
+
'Contador Isolado',
|
|
196
|
+
'Cada aba tem seu próprio valor (room único).',
|
|
197
|
+
isolatedCounter
|
|
198
|
+
)}
|
|
199
|
+
{renderCounter(
|
|
200
|
+
'Contador Compartilhado',
|
|
201
|
+
'Abra em várias abas - todos veem o mesmo valor!',
|
|
202
|
+
sharedCounter
|
|
203
|
+
)}
|
|
204
|
+
</div>
|
|
205
|
+
)
|
|
206
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// 🔥 FormDemo - Exemplo de Live Component
|
|
2
|
+
import { Live } from '@/core/client'
|
|
3
|
+
import { LiveForm } from '@server/live/LiveForm'
|
|
4
|
+
|
|
5
|
+
export function FormDemo() {
|
|
6
|
+
// ✨ Usa defaultState do backend automaticamente
|
|
7
|
+
const form = Live.use(LiveForm)
|
|
8
|
+
|
|
9
|
+
// Sucesso
|
|
10
|
+
if (form.submitted) {
|
|
11
|
+
return (
|
|
12
|
+
<div className="p-6 bg-green-500/20 border border-green-500/30 rounded-xl text-center">
|
|
13
|
+
<div className="text-4xl mb-3">✅</div>
|
|
14
|
+
<h2 className="text-xl font-bold text-white mb-2">Enviado!</h2>
|
|
15
|
+
<p className="text-gray-300">Obrigado, <span className="text-green-400">{form.name}</span>!</p>
|
|
16
|
+
<p className="text-gray-400 text-sm mt-2">
|
|
17
|
+
Enviado em: {form.submittedAt ? new Date(form.submittedAt).toLocaleString() : '-'}
|
|
18
|
+
</p>
|
|
19
|
+
<button
|
|
20
|
+
onClick={() => form.reset()}
|
|
21
|
+
className="mt-4 px-4 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600 transition-all"
|
|
22
|
+
>
|
|
23
|
+
Novo Formulário
|
|
24
|
+
</button>
|
|
25
|
+
</div>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div className="p-6 bg-white/10 backdrop-blur-sm border border-white/20 rounded-xl">
|
|
31
|
+
<div className="flex items-center justify-between mb-6">
|
|
32
|
+
<h2 className="text-xl font-bold text-white">Live Form</h2>
|
|
33
|
+
<span className={`px-3 py-1 rounded-full text-xs ${
|
|
34
|
+
form.$connected ? 'bg-green-500/20 text-green-300' : 'bg-red-500/20 text-red-300'
|
|
35
|
+
}`}>
|
|
36
|
+
{form.$connected ? '🟢 Conectado' : '🔴 Desconectado'}
|
|
37
|
+
</span>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<div className="space-y-4">
|
|
41
|
+
{/* Nome - sync on blur */}
|
|
42
|
+
<div>
|
|
43
|
+
<label className="block text-gray-300 text-sm mb-1">
|
|
44
|
+
Nome <span className="text-purple-400 text-xs">(sync: blur)</span>
|
|
45
|
+
</label>
|
|
46
|
+
<input
|
|
47
|
+
{...form.$field('name', { syncOn: 'blur' })}
|
|
48
|
+
placeholder="Seu nome"
|
|
49
|
+
className="w-full px-4 py-3 bg-white/5 border border-white/20 rounded-lg text-white placeholder-gray-500 focus:border-purple-500 focus:outline-none"
|
|
50
|
+
/>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
{/* Email - sync on change with debounce */}
|
|
54
|
+
<div>
|
|
55
|
+
<label className="block text-gray-300 text-sm mb-1">
|
|
56
|
+
Email <span className="text-blue-400 text-xs">(sync: 500ms)</span>
|
|
57
|
+
</label>
|
|
58
|
+
<input
|
|
59
|
+
{...form.$field('email', { syncOn: 'change', debounce: 500 })}
|
|
60
|
+
type="email"
|
|
61
|
+
placeholder="seu@email.com"
|
|
62
|
+
className="w-full px-4 py-3 bg-white/5 border border-white/20 rounded-lg text-white placeholder-gray-500 focus:border-purple-500 focus:outline-none"
|
|
63
|
+
/>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
{/* Mensagem - sync on blur */}
|
|
67
|
+
<div>
|
|
68
|
+
<label className="block text-gray-300 text-sm mb-1">
|
|
69
|
+
Mensagem <span className="text-orange-400 text-xs">(sync: blur)</span>
|
|
70
|
+
</label>
|
|
71
|
+
<textarea
|
|
72
|
+
{...form.$field('message', { syncOn: 'blur' })}
|
|
73
|
+
rows={3}
|
|
74
|
+
placeholder="Sua mensagem..."
|
|
75
|
+
className="w-full px-4 py-3 bg-white/5 border border-white/20 rounded-lg text-white placeholder-gray-500 focus:border-purple-500 focus:outline-none resize-none"
|
|
76
|
+
/>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
{/* Botões */}
|
|
80
|
+
<div className="flex gap-2">
|
|
81
|
+
<button
|
|
82
|
+
onClick={async () => {
|
|
83
|
+
try {
|
|
84
|
+
await form.$sync()
|
|
85
|
+
await form.submit()
|
|
86
|
+
} catch (err: any) {
|
|
87
|
+
alert(err.message || 'Erro ao enviar')
|
|
88
|
+
}
|
|
89
|
+
}}
|
|
90
|
+
disabled={!form.$connected}
|
|
91
|
+
className="flex-1 px-4 py-3 bg-gradient-to-r from-purple-500 to-pink-500 text-white rounded-lg font-medium hover:shadow-lg hover:shadow-purple-500/30 transition-all disabled:opacity-50"
|
|
92
|
+
>
|
|
93
|
+
{form.$loading ? 'Enviando...' : 'Enviar'}
|
|
94
|
+
</button>
|
|
95
|
+
<button
|
|
96
|
+
onClick={() => form.reset()}
|
|
97
|
+
className="px-4 py-3 bg-gray-600 text-white rounded-lg hover:bg-gray-500 transition-all"
|
|
98
|
+
>
|
|
99
|
+
Limpar
|
|
100
|
+
</button>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
{/* Legenda */}
|
|
105
|
+
<div className="mt-4 p-3 bg-white/5 rounded-lg text-xs text-gray-400 space-y-1">
|
|
106
|
+
<p><span className="text-purple-400">blur:</span> Sincroniza ao sair do campo</p>
|
|
107
|
+
<p><span className="text-blue-400">500ms:</span> Sincroniza 500ms após parar de digitar</p>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
{/* Debug */}
|
|
111
|
+
<details className="mt-4">
|
|
112
|
+
<summary className="text-gray-400 text-sm cursor-pointer">Debug State (servidor)</summary>
|
|
113
|
+
<pre className="mt-2 p-3 bg-black/40 rounded-lg text-xs text-green-400 overflow-auto">
|
|
114
|
+
{JSON.stringify(form.$state, null, 2)}
|
|
115
|
+
</pre>
|
|
116
|
+
</details>
|
|
117
|
+
</div>
|
|
118
|
+
)
|
|
119
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
// 🔥 RoomChatDemo - Demo do chat com múltiplas salas
|
|
2
|
+
//
|
|
3
|
+
// Demonstra o uso do sistema $room para:
|
|
4
|
+
// - Entrar/sair de múltiplas salas
|
|
5
|
+
// - Enviar mensagens para sala ativa
|
|
6
|
+
// - Ver quem está digitando
|
|
7
|
+
// - Trocar entre salas
|
|
8
|
+
|
|
9
|
+
import { useState, useEffect, useRef, useMemo } from 'react'
|
|
10
|
+
import { Live } from '@/core/client'
|
|
11
|
+
import { LiveRoomChat } from '@server/live/LiveRoomChat'
|
|
12
|
+
|
|
13
|
+
// Salas disponíveis
|
|
14
|
+
const AVAILABLE_ROOMS = [
|
|
15
|
+
{ id: 'geral', name: '💬 Geral' },
|
|
16
|
+
{ id: 'tech', name: '💻 Tecnologia' },
|
|
17
|
+
{ id: 'random', name: '🎲 Random' },
|
|
18
|
+
{ id: 'vip', name: '⭐ VIP' }
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
export function RoomChatDemo() {
|
|
22
|
+
const [text, setText] = useState('')
|
|
23
|
+
const messagesEndRef = useRef<HTMLDivElement>(null)
|
|
24
|
+
|
|
25
|
+
// Username aleatório
|
|
26
|
+
const defaultUsername = useMemo(() => {
|
|
27
|
+
const adjectives = ['Happy', 'Cool', 'Fast', 'Smart', 'Brave']
|
|
28
|
+
const nouns = ['Panda', 'Tiger', 'Eagle', 'Wolf', 'Bear']
|
|
29
|
+
const adj = adjectives[Math.floor(Math.random() * adjectives.length)]
|
|
30
|
+
const noun = nouns[Math.floor(Math.random() * nouns.length)]
|
|
31
|
+
const num = Math.floor(Math.random() * 100)
|
|
32
|
+
return `${adj}${noun}${num}`
|
|
33
|
+
}, [])
|
|
34
|
+
|
|
35
|
+
// Live component - estado sincronizado automaticamente
|
|
36
|
+
const chat = Live.use(LiveRoomChat, {
|
|
37
|
+
initialState: { ...LiveRoomChat.defaultState, username: defaultUsername }
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
// Mensagens e typing vêm diretamente do estado sincronizado
|
|
41
|
+
const activeRoom = chat.$state.activeRoom
|
|
42
|
+
const activeMessages = activeRoom ? (chat.$state.messages[activeRoom] || []) : []
|
|
43
|
+
const activeTyping = activeRoom ? (chat.$state.typingUsers[activeRoom] || []) : []
|
|
44
|
+
|
|
45
|
+
// Auto scroll quando mensagens mudam
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
|
|
48
|
+
}, [activeMessages.length])
|
|
49
|
+
|
|
50
|
+
// Handlers
|
|
51
|
+
const handleJoinRoom = async (roomId: string, roomName: string) => {
|
|
52
|
+
if (chat.$rooms.includes(roomId)) {
|
|
53
|
+
await chat.switchRoom({ roomId })
|
|
54
|
+
} else {
|
|
55
|
+
await chat.joinRoom({ roomId, roomName })
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const handleLeaveRoom = async (roomId: string) => {
|
|
60
|
+
await chat.leaveRoom({ roomId })
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const handleSendMessage = async () => {
|
|
64
|
+
if (!text.trim() || !chat.$state.activeRoom) return
|
|
65
|
+
await chat.sendMessage({ text })
|
|
66
|
+
setText('')
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const handleTyping = () => {
|
|
70
|
+
chat.startTyping({})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<div className="flex h-[600px] bg-gray-900 rounded-2xl overflow-hidden border border-white/10">
|
|
75
|
+
{/* Sidebar - Lista de Salas */}
|
|
76
|
+
<div className="w-64 bg-gray-800/50 border-r border-white/10 flex flex-col">
|
|
77
|
+
{/* Header */}
|
|
78
|
+
<div className="p-4 border-b border-white/10">
|
|
79
|
+
<h2 className="text-lg font-bold text-white mb-2">💬 Room Chat</h2>
|
|
80
|
+
<div className="flex items-center gap-2">
|
|
81
|
+
<div className={`w-2 h-2 rounded-full ${chat.$connected ? 'bg-emerald-400' : 'bg-red-400'}`} />
|
|
82
|
+
<span className="text-sm text-gray-400">{chat.$state.username}</span>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
{/* Salas Disponíveis */}
|
|
87
|
+
<div className="flex-1 overflow-auto p-2">
|
|
88
|
+
<p className="text-xs text-gray-500 px-2 py-1">SALAS</p>
|
|
89
|
+
{AVAILABLE_ROOMS.map(room => {
|
|
90
|
+
const isJoined = chat.$rooms.includes(room.id)
|
|
91
|
+
const isActive = chat.$state.activeRoom === room.id
|
|
92
|
+
const unreadCount = 0 // TODO: implementar contagem
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<div
|
|
96
|
+
key={room.id}
|
|
97
|
+
onClick={() => handleJoinRoom(room.id, room.name)}
|
|
98
|
+
className={`
|
|
99
|
+
flex items-center justify-between px-3 py-2 rounded-lg cursor-pointer mb-1
|
|
100
|
+
transition-all group
|
|
101
|
+
${isActive
|
|
102
|
+
? 'bg-purple-500/20 text-purple-300'
|
|
103
|
+
: isJoined
|
|
104
|
+
? 'bg-white/5 text-gray-300 hover:bg-white/10'
|
|
105
|
+
: 'text-gray-500 hover:bg-white/5 hover:text-gray-400'
|
|
106
|
+
}
|
|
107
|
+
`}
|
|
108
|
+
>
|
|
109
|
+
<span className="flex items-center gap-2">
|
|
110
|
+
{room.name}
|
|
111
|
+
{isJoined && (
|
|
112
|
+
<span className="w-1.5 h-1.5 rounded-full bg-emerald-400" />
|
|
113
|
+
)}
|
|
114
|
+
</span>
|
|
115
|
+
|
|
116
|
+
{isJoined && !isActive && (
|
|
117
|
+
<button
|
|
118
|
+
onClick={(e) => {
|
|
119
|
+
e.stopPropagation()
|
|
120
|
+
handleLeaveRoom(room.id)
|
|
121
|
+
}}
|
|
122
|
+
className="opacity-0 group-hover:opacity-100 text-red-400 hover:text-red-300 text-xs"
|
|
123
|
+
>
|
|
124
|
+
✕
|
|
125
|
+
</button>
|
|
126
|
+
)}
|
|
127
|
+
</div>
|
|
128
|
+
)
|
|
129
|
+
})}
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
{/* Salas ativas */}
|
|
133
|
+
<div className="p-3 border-t border-white/10">
|
|
134
|
+
<p className="text-xs text-gray-500 mb-1">
|
|
135
|
+
Em {chat.$rooms.length} sala(s)
|
|
136
|
+
</p>
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
{/* Chat Area */}
|
|
141
|
+
<div className="flex-1 flex flex-col">
|
|
142
|
+
{activeRoom ? (
|
|
143
|
+
<>
|
|
144
|
+
{/* Header da sala */}
|
|
145
|
+
<div className="px-4 py-3 border-b border-white/10 flex items-center justify-between">
|
|
146
|
+
<div>
|
|
147
|
+
<h3 className="text-white font-semibold">
|
|
148
|
+
{chat.$state.rooms.find(r => r.id === activeRoom)?.name || activeRoom}
|
|
149
|
+
</h3>
|
|
150
|
+
<p className="text-xs text-gray-500">
|
|
151
|
+
{activeMessages.length} mensagens
|
|
152
|
+
</p>
|
|
153
|
+
</div>
|
|
154
|
+
<button
|
|
155
|
+
onClick={() => handleLeaveRoom(activeRoom)}
|
|
156
|
+
className="px-3 py-1 text-sm bg-red-500/20 text-red-300 rounded-lg hover:bg-red-500/30 transition-all"
|
|
157
|
+
>
|
|
158
|
+
Sair
|
|
159
|
+
</button>
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
{/* Mensagens */}
|
|
163
|
+
<div className="flex-1 overflow-auto p-4 space-y-3">
|
|
164
|
+
{activeMessages.length === 0 ? (
|
|
165
|
+
<div className="text-center text-gray-500 py-8">
|
|
166
|
+
<p>Nenhuma mensagem ainda</p>
|
|
167
|
+
<p className="text-sm">Seja o primeiro a enviar!</p>
|
|
168
|
+
</div>
|
|
169
|
+
) : (
|
|
170
|
+
activeMessages.map(msg => (
|
|
171
|
+
<div
|
|
172
|
+
key={msg.id}
|
|
173
|
+
className={`flex flex-col ${msg.user === chat.$state.username ? 'items-end' : 'items-start'}`}
|
|
174
|
+
>
|
|
175
|
+
<div
|
|
176
|
+
className={`
|
|
177
|
+
max-w-[80%] rounded-2xl px-4 py-2
|
|
178
|
+
${msg.user === chat.$state.username
|
|
179
|
+
? 'bg-purple-500/30 text-purple-100'
|
|
180
|
+
: 'bg-white/10 text-gray-200'
|
|
181
|
+
}
|
|
182
|
+
`}
|
|
183
|
+
>
|
|
184
|
+
<p className="text-xs text-gray-400 mb-1">{msg.user}</p>
|
|
185
|
+
<p>{msg.text}</p>
|
|
186
|
+
</div>
|
|
187
|
+
<span className="text-xs text-gray-600 mt-1">
|
|
188
|
+
{new Date(msg.timestamp).toLocaleTimeString()}
|
|
189
|
+
</span>
|
|
190
|
+
</div>
|
|
191
|
+
))
|
|
192
|
+
)}
|
|
193
|
+
<div ref={messagesEndRef} />
|
|
194
|
+
</div>
|
|
195
|
+
|
|
196
|
+
{/* Typing indicator */}
|
|
197
|
+
{activeTyping.length > 0 && (
|
|
198
|
+
<div className="px-4 py-1 text-xs text-gray-500">
|
|
199
|
+
{activeTyping.filter(u => u !== chat.$state.username).join(', ')} está digitando...
|
|
200
|
+
</div>
|
|
201
|
+
)}
|
|
202
|
+
|
|
203
|
+
{/* Input */}
|
|
204
|
+
<div className="p-4 border-t border-white/10">
|
|
205
|
+
<div className="flex gap-2">
|
|
206
|
+
<input
|
|
207
|
+
value={text}
|
|
208
|
+
onChange={(e) => {
|
|
209
|
+
setText(e.target.value)
|
|
210
|
+
handleTyping()
|
|
211
|
+
}}
|
|
212
|
+
onKeyDown={(e) => {
|
|
213
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
214
|
+
e.preventDefault()
|
|
215
|
+
handleSendMessage()
|
|
216
|
+
}
|
|
217
|
+
}}
|
|
218
|
+
placeholder="Digite uma mensagem..."
|
|
219
|
+
className="flex-1 px-4 py-2 rounded-xl bg-white/10 border border-white/20 text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-purple-500/50"
|
|
220
|
+
/>
|
|
221
|
+
<button
|
|
222
|
+
onClick={handleSendMessage}
|
|
223
|
+
disabled={!text.trim()}
|
|
224
|
+
className="px-6 py-2 rounded-xl bg-purple-500/30 text-purple-200 hover:bg-purple-500/40 transition-all disabled:opacity-50 disabled:cursor-not-allowed"
|
|
225
|
+
>
|
|
226
|
+
Enviar
|
|
227
|
+
</button>
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
</>
|
|
231
|
+
) : (
|
|
232
|
+
<div className="flex-1 flex items-center justify-center text-gray-500">
|
|
233
|
+
<div className="text-center">
|
|
234
|
+
<p className="text-4xl mb-4">👈</p>
|
|
235
|
+
<p>Selecione uma sala para começar</p>
|
|
236
|
+
</div>
|
|
237
|
+
</div>
|
|
238
|
+
)}
|
|
239
|
+
</div>
|
|
240
|
+
</div>
|
|
241
|
+
)
|
|
242
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import { LiveUploadWidget } from '../components/LiveUploadWidget'
|
|
3
|
+
import { useLiveUpload } from '@/core/client'
|
|
4
|
+
|
|
5
|
+
export function UploadDemo() {
|
|
6
|
+
const [lastUrl, setLastUrl] = useState<string | null>(null)
|
|
7
|
+
const { live } = useLiveUpload({
|
|
8
|
+
onComplete: (response) => setLastUrl(response.fileUrl || null)
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<div className="flex flex-col gap-4 max-w-xl w-full mx-auto">
|
|
13
|
+
<LiveUploadWidget live={live} />
|
|
14
|
+
{lastUrl && (
|
|
15
|
+
<div className="text-xs text-gray-500 text-center">
|
|
16
|
+
Ultimo arquivo: {lastUrl}
|
|
17
|
+
</div>
|
|
18
|
+
)}
|
|
19
|
+
</div>
|
|
20
|
+
)
|
|
21
|
+
}
|
package/app/client/src/main.tsx
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import { StrictMode } from 'react'
|
|
2
|
-
import { createRoot } from 'react-dom/client'
|
|
3
|
-
import '
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import { StrictMode } from 'react'
|
|
2
|
+
import { createRoot } from 'react-dom/client'
|
|
3
|
+
import { BrowserRouter } from 'react-router'
|
|
4
|
+
import './index.css'
|
|
5
|
+
import App from './App.tsx'
|
|
6
|
+
|
|
7
|
+
createRoot(document.getElementById('root')!).render(
|
|
8
|
+
<StrictMode>
|
|
9
|
+
<BrowserRouter>
|
|
10
|
+
<App />
|
|
11
|
+
</BrowserRouter>
|
|
12
|
+
</StrictMode>,
|
|
13
|
+
)
|