create-fluxstack 1.10.1 → 1.12.1
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/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 +107 -150
- 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 -60
- 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 +161 -0
- package/app/client/src/live/UploadDemo.tsx +21 -0
- package/app/client/src/main.tsx +4 -1
- package/app/client/src/pages/ApiTestPage.tsx +108 -0
- package/app/client/src/pages/HomePage.tsx +76 -0
- package/app/server/app.ts +1 -4
- package/app/server/controllers/users.controller.ts +36 -44
- package/app/server/index.ts +25 -35
- 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 +127 -0
- package/app/server/live/LiveUpload.ts +81 -0
- 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/flux-plugins-generator.ts +325 -325
- package/core/build/index.ts +39 -27
- package/core/build/live-components-generator.ts +3 -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 -686
- package/core/cli/plugin-discovery.ts +2 -2
- package/core/client/LiveComponentsProvider.tsx +60 -8
- 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 -215
- package/core/client/hooks/state-validator.ts +1 -1
- package/core/client/hooks/useAuth.ts +48 -48
- package/core/client/hooks/useChunkedUpload.ts +85 -35
- 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 +17 -68
- 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 +47 -40
- package/core/framework/types.ts +2 -2
- package/core/index.ts +23 -4
- package/core/live/ComponentRegistry.ts +3 -3
- package/core/live/types.ts +77 -0
- package/core/plugins/built-in/index.ts +134 -134
- package/core/plugins/built-in/live-components/commands/create-live-component.ts +242 -1066
- 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 +85 -185
- package/core/plugins/built-in/vite/vite-dev.ts +10 -16
- 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 +255 -19
- package/core/plugins/types.ts +20 -53
- package/core/server/framework.ts +66 -43
- package/core/server/index.ts +15 -15
- package/core/server/live/ComponentRegistry.ts +78 -71
- package/core/server/live/FileUploadManager.ts +23 -10
- 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 +21 -9
- package/core/server/live/index.ts +14 -0
- package/core/server/live/websocket-plugin.ts +214 -67
- 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 -69
- package/core/server/plugins/swagger.ts +1 -1
- 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 -219
- package/core/types/config.ts +56 -26
- package/core/types/index.ts +4 -4
- package/core/types/plugin.ts +107 -107
- package/core/types/types.ts +353 -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 -82
- 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/package.json +12 -13
- 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/tsconfig.api-strict.json +16 -0
- package/tsconfig.json +48 -52
- package/{app/client/tsconfig.node.json → tsconfig.node.json} +25 -25
- 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/app/client/README.md +0 -69
- package/app/client/SIMPLIFICATION.md +0 -140
- package/app/client/frontend-only.ts +0 -12
- package/app/client/src/live/FileUploadExample.tsx +0 -359
- package/app/client/src/live/MinimalLiveClock.tsx +0 -47
- package/app/client/src/live/QuickUploadTest.tsx +0 -193
- package/app/client/tsconfig.app.json +0 -45
- 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/live/LiveFileUploadComponent.ts +0 -77
- package/app/server/routes/env-test.ts +0 -110
- package/core/client/hooks/index.ts +0 -7
- package/core/client/hooks/useHybridLiveComponent.ts +0 -685
- package/core/client/hooks/useTypedLiveComponent.ts +0 -133
- package/core/client/hooks/useWebSocket.ts +0 -361
- 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,68 @@
|
|
|
1
|
+
import { Link, Outlet, useLocation } from 'react-router'
|
|
2
|
+
import { FaBook, FaGithub } from 'react-icons/fa'
|
|
3
|
+
|
|
4
|
+
const navItems = [
|
|
5
|
+
{ to: '/', label: 'Home' },
|
|
6
|
+
{ to: '/counter', label: 'Counter' },
|
|
7
|
+
{ to: '/form', label: 'Form' },
|
|
8
|
+
{ to: '/upload', label: 'Upload' },
|
|
9
|
+
{ to: '/chat', label: 'Chat' },
|
|
10
|
+
{ to: '/room-chat', label: 'Room Chat' },
|
|
11
|
+
{ to: '/api-test', label: 'API Test' }
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
export function AppLayout() {
|
|
15
|
+
const location = useLocation()
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-purple-900 to-slate-900">
|
|
19
|
+
<header className="sticky top-0 z-10 backdrop-blur-md bg-slate-900/60 border-b border-white/10">
|
|
20
|
+
<div className="container mx-auto px-6 py-4 flex flex-wrap items-center justify-between gap-4">
|
|
21
|
+
<div className="text-white font-semibold tracking-wide">
|
|
22
|
+
FluxStack
|
|
23
|
+
</div>
|
|
24
|
+
<nav className="flex flex-wrap items-center gap-2">
|
|
25
|
+
{navItems.map((item) => {
|
|
26
|
+
const active = location.pathname === item.to
|
|
27
|
+
return (
|
|
28
|
+
<Link
|
|
29
|
+
key={item.to}
|
|
30
|
+
to={item.to}
|
|
31
|
+
className={`px-3 py-1.5 rounded-lg text-sm transition-all ${
|
|
32
|
+
active
|
|
33
|
+
? 'bg-white/15 text-white'
|
|
34
|
+
: 'text-gray-300 hover:bg-white/10'
|
|
35
|
+
}`}
|
|
36
|
+
>
|
|
37
|
+
{item.label}
|
|
38
|
+
</Link>
|
|
39
|
+
)
|
|
40
|
+
})}
|
|
41
|
+
</nav>
|
|
42
|
+
<div className="flex items-center gap-2">
|
|
43
|
+
<a
|
|
44
|
+
href="/swagger"
|
|
45
|
+
target="_blank"
|
|
46
|
+
rel="noopener noreferrer"
|
|
47
|
+
className="inline-flex items-center gap-2 px-3 py-1.5 bg-white/10 border border-white/20 text-white rounded-lg text-sm hover:bg-white/20 transition-all"
|
|
48
|
+
>
|
|
49
|
+
<FaBook />
|
|
50
|
+
Docs
|
|
51
|
+
</a>
|
|
52
|
+
<a
|
|
53
|
+
href="https://github.com/MarcosBrendonDePaula/FluxStack"
|
|
54
|
+
target="_blank"
|
|
55
|
+
rel="noopener noreferrer"
|
|
56
|
+
className="inline-flex items-center gap-2 px-3 py-1.5 bg-white/10 border border-white/20 text-white rounded-lg text-sm hover:bg-white/20 transition-all"
|
|
57
|
+
>
|
|
58
|
+
<FaGithub />
|
|
59
|
+
GitHub
|
|
60
|
+
</a>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
</header>
|
|
64
|
+
|
|
65
|
+
<Outlet />
|
|
66
|
+
</div>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { useNavigate } from 'react-router'
|
|
2
|
+
|
|
3
|
+
export function BackButton() {
|
|
4
|
+
const navigate = useNavigate()
|
|
5
|
+
return (
|
|
6
|
+
<button
|
|
7
|
+
onClick={() => navigate(-1)}
|
|
8
|
+
className="px-4 py-2 bg-white/10 backdrop-blur-sm border border-white/20 text-white rounded-lg font-medium hover:bg-white/20 transition-all"
|
|
9
|
+
>
|
|
10
|
+
← Voltar
|
|
11
|
+
</button>
|
|
12
|
+
)
|
|
13
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
import { BackButton } from './BackButton'
|
|
3
|
+
|
|
4
|
+
export function DemoPage({ children, note }: { children: ReactNode; note?: ReactNode }) {
|
|
5
|
+
return (
|
|
6
|
+
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-purple-900 to-slate-900 flex flex-col items-center justify-center px-4">
|
|
7
|
+
<div className="mb-8">
|
|
8
|
+
<BackButton />
|
|
9
|
+
</div>
|
|
10
|
+
<div className="w-full max-w-6xl">
|
|
11
|
+
{children}
|
|
12
|
+
</div>
|
|
13
|
+
{note && (
|
|
14
|
+
<p className="mt-6 text-gray-400 text-sm max-w-md text-center">
|
|
15
|
+
{note}
|
|
16
|
+
</p>
|
|
17
|
+
)}
|
|
18
|
+
</div>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { useEffect, useMemo, useState } from 'react'
|
|
2
|
+
import { useLiveChunkedUpload } from '@/core/client'
|
|
3
|
+
import type { LiveChunkedUploadOptions } from '@/core/client'
|
|
4
|
+
import type { FileUploadCompleteResponse } from '@core/types/types'
|
|
5
|
+
type LiveUploadActions = {
|
|
6
|
+
$componentId: string | null
|
|
7
|
+
$connected: boolean
|
|
8
|
+
$state: {
|
|
9
|
+
status: 'idle' | 'uploading' | 'complete' | 'error'
|
|
10
|
+
progress: number
|
|
11
|
+
fileName: string
|
|
12
|
+
fileSize: number
|
|
13
|
+
fileType: string
|
|
14
|
+
fileUrl: string
|
|
15
|
+
bytesUploaded: number
|
|
16
|
+
totalBytes: number
|
|
17
|
+
error: string | null
|
|
18
|
+
}
|
|
19
|
+
$error?: string | null
|
|
20
|
+
startUpload: (payload: { fileName: string; fileSize: number; fileType: string }) => Promise<any>
|
|
21
|
+
updateProgress: (payload: { progress: number; bytesUploaded: number; totalBytes: number }) => Promise<any>
|
|
22
|
+
completeUpload: (payload: { fileUrl: string }) => Promise<any>
|
|
23
|
+
failUpload: (payload: { error: string }) => Promise<any>
|
|
24
|
+
reset: () => Promise<any>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface LiveUploadWidgetProps {
|
|
28
|
+
live: LiveUploadActions
|
|
29
|
+
title?: string
|
|
30
|
+
description?: string
|
|
31
|
+
allowPreview?: boolean
|
|
32
|
+
options?: LiveChunkedUploadOptions
|
|
33
|
+
onComplete?: (response: FileUploadCompleteResponse) => void
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function LiveUploadWidget({
|
|
37
|
+
live,
|
|
38
|
+
title = 'Upload em Chunks',
|
|
39
|
+
description = 'Envio via WebSocket com Live Components e reatividade server-side.',
|
|
40
|
+
allowPreview = true,
|
|
41
|
+
options,
|
|
42
|
+
onComplete
|
|
43
|
+
}: LiveUploadWidgetProps) {
|
|
44
|
+
// live is expected to be a LiveUpload-compatible component
|
|
45
|
+
const [selectedFile, setSelectedFile] = useState<File | null>(null)
|
|
46
|
+
const [localError, setLocalError] = useState<string | null>(null)
|
|
47
|
+
const [previewUrl, setPreviewUrl] = useState<string | null>(null)
|
|
48
|
+
|
|
49
|
+
const mergedOptions = useMemo<LiveChunkedUploadOptions>(() => {
|
|
50
|
+
return {
|
|
51
|
+
allowedTypes: [],
|
|
52
|
+
maxFileSize: 500 * 1024 * 1024,
|
|
53
|
+
adaptiveChunking: true,
|
|
54
|
+
fileUrlResolver: (fileUrl) => fileUrl.startsWith('/uploads/') ? `/api${fileUrl}` : fileUrl,
|
|
55
|
+
onComplete,
|
|
56
|
+
...options
|
|
57
|
+
}
|
|
58
|
+
}, [options, onComplete])
|
|
59
|
+
|
|
60
|
+
const {
|
|
61
|
+
uploading,
|
|
62
|
+
bytesUploaded,
|
|
63
|
+
totalBytes,
|
|
64
|
+
uploadFile,
|
|
65
|
+
cancelUpload,
|
|
66
|
+
reset
|
|
67
|
+
} = useLiveChunkedUpload(live, mergedOptions)
|
|
68
|
+
|
|
69
|
+
const canUpload = live.$connected && !!live.$componentId && !uploading
|
|
70
|
+
|
|
71
|
+
const handleSelectFile = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
72
|
+
const file = e.target.files?.[0] ?? null
|
|
73
|
+
setSelectedFile(file)
|
|
74
|
+
setLocalError(null)
|
|
75
|
+
|
|
76
|
+
if (previewUrl) {
|
|
77
|
+
URL.revokeObjectURL(previewUrl)
|
|
78
|
+
setPreviewUrl(null)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (allowPreview && file && file.type.startsWith('image/')) {
|
|
82
|
+
setPreviewUrl(URL.createObjectURL(file))
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
return () => {
|
|
88
|
+
if (previewUrl) {
|
|
89
|
+
URL.revokeObjectURL(previewUrl)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}, [previewUrl])
|
|
93
|
+
|
|
94
|
+
const handleStartUpload = async () => {
|
|
95
|
+
if (!selectedFile) {
|
|
96
|
+
setLocalError('Selecione um arquivo primeiro.')
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!live.$connected || !live.$componentId) {
|
|
101
|
+
setLocalError('WebSocket ainda nao conectou. Tente novamente em alguns segundos.')
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
setLocalError(null)
|
|
106
|
+
await uploadFile(selectedFile)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const handleReset = async () => {
|
|
110
|
+
setSelectedFile(null)
|
|
111
|
+
setLocalError(null)
|
|
112
|
+
if (previewUrl) {
|
|
113
|
+
URL.revokeObjectURL(previewUrl)
|
|
114
|
+
setPreviewUrl(null)
|
|
115
|
+
}
|
|
116
|
+
await reset()
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const resolvedUrl = live.$state.fileUrl
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<div className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-2xl p-8 max-w-xl w-full mx-auto">
|
|
123
|
+
<h2 className="text-2xl font-bold text-white mb-2 text-center">
|
|
124
|
+
{title}
|
|
125
|
+
</h2>
|
|
126
|
+
|
|
127
|
+
<p className="text-gray-400 text-sm text-center mb-6">
|
|
128
|
+
{description}
|
|
129
|
+
</p>
|
|
130
|
+
|
|
131
|
+
<div className="space-y-4">
|
|
132
|
+
<input
|
|
133
|
+
type="file"
|
|
134
|
+
onChange={handleSelectFile}
|
|
135
|
+
className="w-full text-sm text-gray-300 file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:bg-white/10 file:text-white hover:file:bg-white/20"
|
|
136
|
+
disabled={!live.$connected}
|
|
137
|
+
/>
|
|
138
|
+
|
|
139
|
+
<div className="flex gap-3">
|
|
140
|
+
<button
|
|
141
|
+
onClick={handleStartUpload}
|
|
142
|
+
disabled={!canUpload || !selectedFile}
|
|
143
|
+
className="flex-1 px-4 py-2 rounded-lg bg-emerald-500/20 border border-emerald-500/30 text-emerald-300 hover:bg-emerald-500/30 transition-all disabled:opacity-50"
|
|
144
|
+
>
|
|
145
|
+
Iniciar Upload
|
|
146
|
+
</button>
|
|
147
|
+
<button
|
|
148
|
+
onClick={cancelUpload}
|
|
149
|
+
disabled={!uploading}
|
|
150
|
+
className="px-4 py-2 rounded-lg bg-red-500/20 border border-red-500/30 text-red-300 hover:bg-red-500/30 transition-all disabled:opacity-50"
|
|
151
|
+
>
|
|
152
|
+
Cancelar
|
|
153
|
+
</button>
|
|
154
|
+
<button
|
|
155
|
+
onClick={handleReset}
|
|
156
|
+
className="px-4 py-2 rounded-lg bg-white/10 border border-white/20 text-white hover:bg-white/20 transition-all"
|
|
157
|
+
>
|
|
158
|
+
Reset
|
|
159
|
+
</button>
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
{(localError || live.$state.error || live.$error) && (
|
|
163
|
+
<div className="text-sm text-red-300 bg-red-500/10 border border-red-500/30 rounded-lg px-3 py-2">
|
|
164
|
+
{localError || live.$state.error || live.$error}
|
|
165
|
+
</div>
|
|
166
|
+
)}
|
|
167
|
+
|
|
168
|
+
<div className="bg-black/40 border border-white/10 rounded-xl p-4">
|
|
169
|
+
<div className="flex justify-between text-xs text-gray-400 mb-2">
|
|
170
|
+
<span>Status: {live.$state.status}</span>
|
|
171
|
+
<span>{Math.round(live.$state.progress)}%</span>
|
|
172
|
+
</div>
|
|
173
|
+
<div className="w-full h-3 bg-white/10 rounded-full overflow-hidden">
|
|
174
|
+
<div
|
|
175
|
+
className="h-full bg-gradient-to-r from-blue-500 to-purple-500 transition-all"
|
|
176
|
+
style={{ width: `${live.$state.progress}%` }}
|
|
177
|
+
/>
|
|
178
|
+
</div>
|
|
179
|
+
<div className="flex justify-between text-xs text-gray-500 mt-2">
|
|
180
|
+
<span>{live.$state.fileName || 'Nenhum arquivo selecionado'}</span>
|
|
181
|
+
<span>{bytesUploaded > 0 ? `${Math.round(bytesUploaded / 1024)} KB` : ''}{totalBytes > 0 ? ` / ${Math.round(totalBytes / 1024)} KB` : ''}</span>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
{previewUrl && (
|
|
186
|
+
<div className="bg-white/5 border border-white/10 rounded-xl p-4">
|
|
187
|
+
<div className="text-xs text-gray-400 mb-2">Preview</div>
|
|
188
|
+
<img
|
|
189
|
+
src={previewUrl}
|
|
190
|
+
alt="Preview"
|
|
191
|
+
className="max-h-48 w-full object-contain rounded-lg border border-white/10"
|
|
192
|
+
/>
|
|
193
|
+
</div>
|
|
194
|
+
)}
|
|
195
|
+
|
|
196
|
+
{resolvedUrl && live.$state.status === 'complete' && (
|
|
197
|
+
<div className="bg-emerald-500/10 border border-emerald-500/30 rounded-xl p-4 text-sm text-emerald-200">
|
|
198
|
+
Upload concluido: <a className="underline" href={resolvedUrl} target="_blank" rel="noopener noreferrer">abrir arquivo</a>
|
|
199
|
+
</div>
|
|
200
|
+
)}
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
)
|
|
204
|
+
}
|
|
@@ -1,74 +1,99 @@
|
|
|
1
|
-
// Eden Treaty API Client - Full Type Inference
|
|
2
|
-
import { treaty } from '@elysiajs/eden'
|
|
3
|
-
import type { App } from '../../../server/app'
|
|
4
|
-
|
|
5
1
|
/**
|
|
6
|
-
*
|
|
2
|
+
* Eden Treaty API Client
|
|
3
|
+
* Configured for this application
|
|
4
|
+
*
|
|
5
|
+
* This file extends the core Eden client with app-specific customizations.
|
|
6
|
+
* Modify the options below to change API behavior across the entire app.
|
|
7
7
|
*/
|
|
8
|
-
export const getBaseUrl = () => {
|
|
9
|
-
if (typeof window === 'undefined') return 'http://localhost:3000'
|
|
10
|
-
|
|
11
|
-
// Always use current origin - works for both dev and production
|
|
12
|
-
return window.location.origin
|
|
13
|
-
}
|
|
14
8
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
*/
|
|
18
|
-
const getAuthToken = (): string | null => {
|
|
19
|
-
if (typeof window === 'undefined') return null
|
|
20
|
-
return localStorage.getItem('accessToken')
|
|
21
|
-
}
|
|
9
|
+
import { createEdenClient, getErrorMessage } from '@/core/client/api'
|
|
10
|
+
import type { App } from '@server/app'
|
|
22
11
|
|
|
23
12
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
13
|
+
* API client with full type inference from server routes
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* // GET request
|
|
18
|
+
* const { data, error } = await api.users.get()
|
|
19
|
+
*
|
|
20
|
+
* // POST request
|
|
21
|
+
* const { data, error } = await api.users.post({
|
|
22
|
+
* name: 'John',
|
|
23
|
+
* email: 'john@example.com'
|
|
24
|
+
* })
|
|
25
|
+
*
|
|
26
|
+
* // With query params
|
|
27
|
+
* const { data } = await api.users.get({ query: { page: 1, limit: 10 } })
|
|
28
|
+
*
|
|
29
|
+
* // With path params
|
|
30
|
+
* const { data } = await api.users({ id: 123 }).get()
|
|
31
|
+
* ```
|
|
26
32
|
*/
|
|
27
|
-
export const api =
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
},
|
|
33
|
+
export const api = createEdenClient<App>({
|
|
34
|
+
// ┌─────────────────────────────────────────────────────────────────┐
|
|
35
|
+
// │ CUSTOMIZATION EXAMPLES │
|
|
36
|
+
// │ Uncomment and modify the options below as needed │
|
|
37
|
+
// └─────────────────────────────────────────────────────────────────┘
|
|
33
38
|
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
if (import.meta.env.DEV) {
|
|
38
|
-
console.log(`🌐 ${init?.method ?? 'GET'} ${url}`)
|
|
39
|
-
}
|
|
39
|
+
// ── Authentication ──────────────────────────────────────────────────
|
|
40
|
+
// Custom token key (default: 'accessToken')
|
|
41
|
+
// tokenKey: 'authToken',
|
|
40
42
|
|
|
41
|
-
|
|
43
|
+
// Custom token getter (e.g., from a state manager)
|
|
44
|
+
// getAuthToken: () => {
|
|
45
|
+
// return useAuthStore.getState().token
|
|
46
|
+
// },
|
|
42
47
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
48
|
+
// ── Unauthorized Handler ────────────────────────────────────────────
|
|
49
|
+
// Redirect to login on 401
|
|
50
|
+
// onUnauthorized: () => {
|
|
51
|
+
// localStorage.removeItem('accessToken')
|
|
52
|
+
// window.location.href = '/login'
|
|
53
|
+
// },
|
|
46
54
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
55
|
+
// Show toast notification on 401
|
|
56
|
+
// onUnauthorized: () => {
|
|
57
|
+
// toast.error('Session expired. Please login again.')
|
|
58
|
+
// router.push('/login')
|
|
59
|
+
// },
|
|
53
60
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
61
|
+
// ── Base URL ────────────────────────────────────────────────────────
|
|
62
|
+
// Custom base URL (e.g., for different environments)
|
|
63
|
+
// getBaseUrl: () => {
|
|
64
|
+
// if (import.meta.env.PROD) return 'https://api.myapp.com'
|
|
65
|
+
// return 'http://localhost:3000'
|
|
66
|
+
// },
|
|
57
67
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
// Eden Treaty error format
|
|
63
|
-
if (error && typeof error === 'object' && 'value' in error) {
|
|
64
|
-
const edenError = error as { value?: { message?: string; userMessage?: string } }
|
|
65
|
-
return edenError.value?.userMessage || edenError.value?.message || 'An error occurred'
|
|
66
|
-
}
|
|
68
|
+
// ── Logging ─────────────────────────────────────────────────────────
|
|
69
|
+
// Force enable/disable logging (default: auto based on DEV mode)
|
|
70
|
+
// enableLogging: true,
|
|
71
|
+
})
|
|
67
72
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
return error.message
|
|
71
|
-
}
|
|
73
|
+
// Re-export utility for convenience
|
|
74
|
+
export { getErrorMessage }
|
|
72
75
|
|
|
73
|
-
|
|
74
|
-
|
|
76
|
+
// ┌─────────────────────────────────────────────────────────────────────┐
|
|
77
|
+
// │ ADVANCED: Creating Multiple API Clients │
|
|
78
|
+
// │ │
|
|
79
|
+
// │ You can create additional clients for different purposes: │
|
|
80
|
+
// └─────────────────────────────────────────────────────────────────────┘
|
|
81
|
+
//
|
|
82
|
+
// // Public API (no auth)
|
|
83
|
+
// export const publicApi = createEdenClient<App>({
|
|
84
|
+
// getAuthToken: () => null,
|
|
85
|
+
// })
|
|
86
|
+
//
|
|
87
|
+
// // Admin API (different token)
|
|
88
|
+
// export const adminApi = createEdenClient<App>({
|
|
89
|
+
// tokenKey: 'adminToken',
|
|
90
|
+
// onUnauthorized: () => {
|
|
91
|
+
// window.location.href = '/admin/login'
|
|
92
|
+
// },
|
|
93
|
+
// })
|
|
94
|
+
//
|
|
95
|
+
// // External API (different base URL)
|
|
96
|
+
// import type { ExternalApp } from './external-types'
|
|
97
|
+
// export const externalApi = createEdenClient<ExternalApp>({
|
|
98
|
+
// getBaseUrl: () => 'https://external-api.com',
|
|
99
|
+
// })
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { useEffect, useMemo, useRef, useState } from 'react'
|
|
2
|
+
import { Live } from '@/core/client'
|
|
3
|
+
import { LiveChat } from '@server/live/LiveChat'
|
|
4
|
+
|
|
5
|
+
export function ChatDemo() {
|
|
6
|
+
const [text, setText] = useState('')
|
|
7
|
+
const [user, setUser] = useState('')
|
|
8
|
+
const containerRef = useRef<HTMLDivElement | null>(null)
|
|
9
|
+
const wasNearBottomRef = useRef(true)
|
|
10
|
+
const defaultUser = useMemo(() => {
|
|
11
|
+
if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {
|
|
12
|
+
return `user-${crypto.randomUUID().slice(0, 6)}`
|
|
13
|
+
}
|
|
14
|
+
return `user-${Math.random().toString(36).slice(2, 8)}`
|
|
15
|
+
}, [])
|
|
16
|
+
|
|
17
|
+
const chat = Live.use(LiveChat, {
|
|
18
|
+
room: 'global-chat',
|
|
19
|
+
initialState: LiveChat.defaultState,
|
|
20
|
+
persistState: false
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const handleSend = async () => {
|
|
24
|
+
if (!text.trim()) return
|
|
25
|
+
const finalUser = user.trim() || defaultUser
|
|
26
|
+
await chat.sendMessage({ user: finalUser, text })
|
|
27
|
+
setText('')
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
const el = containerRef.current
|
|
32
|
+
if (!el) return
|
|
33
|
+
|
|
34
|
+
if (wasNearBottomRef.current) {
|
|
35
|
+
el.scrollTop = el.scrollHeight
|
|
36
|
+
}
|
|
37
|
+
}, [chat.$state.messages.length])
|
|
38
|
+
|
|
39
|
+
const handleScroll = () => {
|
|
40
|
+
const el = containerRef.current
|
|
41
|
+
if (!el) return
|
|
42
|
+
const distance = el.scrollHeight - (el.scrollTop + el.clientHeight)
|
|
43
|
+
wasNearBottomRef.current = distance < 80
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-2xl p-8 w-full mx-auto">
|
|
48
|
+
<h2 className="text-2xl font-bold text-white mb-2 text-center">Chat Compartilhado</h2>
|
|
49
|
+
<p className="text-gray-400 text-sm text-center mb-4">
|
|
50
|
+
Sala global em tempo real. Abra em várias abas para testar.
|
|
51
|
+
</p>
|
|
52
|
+
|
|
53
|
+
<div className="flex flex-wrap items-center gap-2 mb-4 text-xs">
|
|
54
|
+
<span className={`px-3 py-1 rounded-full ${chat.$connected ? 'bg-emerald-500/20 text-emerald-300' : 'bg-red-500/20 text-red-300'}`}>
|
|
55
|
+
{chat.$connected ? 'Conectado' : 'Desconectado'}
|
|
56
|
+
</span>
|
|
57
|
+
<span className="text-gray-400">Você: {user.trim() || defaultUser}</span>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<div className="mb-4">
|
|
61
|
+
<label className="block text-xs text-gray-400 mb-2">Seu nome</label>
|
|
62
|
+
<input
|
|
63
|
+
value={user}
|
|
64
|
+
onChange={(e) => setUser(e.target.value)}
|
|
65
|
+
placeholder={`Ex: ${defaultUser}`}
|
|
66
|
+
className="w-full px-4 py-2 rounded-lg bg-white/10 border border-white/20 text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-purple-500/50"
|
|
67
|
+
/>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<div
|
|
71
|
+
ref={containerRef}
|
|
72
|
+
onScroll={handleScroll}
|
|
73
|
+
className="bg-black/40 border border-white/10 rounded-xl p-5 h-[28rem] overflow-auto space-y-3"
|
|
74
|
+
>
|
|
75
|
+
{chat.$state.messages.length === 0 && (
|
|
76
|
+
<div className="text-gray-500 text-sm text-center">Nenhuma mensagem ainda</div>
|
|
77
|
+
)}
|
|
78
|
+
{chat.$state.messages.map((m) => (
|
|
79
|
+
<div key={m.id} className="text-sm">
|
|
80
|
+
<span className="text-purple-300 font-semibold">{m.user}</span>
|
|
81
|
+
<span className="text-gray-500 text-xs ml-2">{new Date(m.timestamp).toLocaleTimeString()}</span>
|
|
82
|
+
<div className="text-gray-200">{m.text}</div>
|
|
83
|
+
</div>
|
|
84
|
+
))}
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<div className="mt-4 flex gap-2">
|
|
88
|
+
<input
|
|
89
|
+
value={text}
|
|
90
|
+
onChange={(e) => setText(e.target.value)}
|
|
91
|
+
onKeyDown={(e) => {
|
|
92
|
+
if (e.key === 'Enter') void handleSend()
|
|
93
|
+
}}
|
|
94
|
+
placeholder="Digite uma mensagem..."
|
|
95
|
+
className="flex-1 px-4 py-2 rounded-lg bg-white/10 border border-white/20 text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-purple-500/50"
|
|
96
|
+
/>
|
|
97
|
+
<button
|
|
98
|
+
onClick={handleSend}
|
|
99
|
+
disabled={!chat.$connected}
|
|
100
|
+
className="px-4 py-2 rounded-lg bg-purple-500/20 border border-purple-500/30 text-purple-200 hover:bg-purple-500/30 transition-all disabled:opacity-50"
|
|
101
|
+
>
|
|
102
|
+
Enviar
|
|
103
|
+
</button>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
)
|
|
107
|
+
}
|