create-fluxstack 1.0.12 → 1.0.14

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 (215) hide show
  1. package/.env.example +29 -29
  2. package/app/client/README.md +69 -69
  3. package/app/client/index.html +14 -13
  4. package/app/client/src/App.tsx +157 -524
  5. package/app/client/src/components/ErrorBoundary.tsx +107 -0
  6. package/app/client/src/components/ErrorDisplay.css +365 -0
  7. package/app/client/src/components/ErrorDisplay.tsx +258 -0
  8. package/app/client/src/components/FluxStackConfig.tsx +1321 -0
  9. package/app/client/src/components/HybridLiveCounter.tsx +140 -0
  10. package/app/client/src/components/LiveClock.tsx +286 -0
  11. package/app/client/src/components/MainLayout.tsx +390 -0
  12. package/app/client/src/components/SidebarNavigation.tsx +391 -0
  13. package/app/client/src/components/StateDemo.tsx +178 -0
  14. package/app/client/src/components/SystemMonitor.tsx +1038 -0
  15. package/app/client/src/components/Teste.tsx +104 -0
  16. package/app/client/src/components/UserProfile.tsx +809 -0
  17. package/app/client/src/hooks/useAuth.ts +39 -0
  18. package/app/client/src/hooks/useNotifications.ts +56 -0
  19. package/app/client/src/lib/eden-api.ts +189 -53
  20. package/app/client/src/lib/errors.ts +340 -0
  21. package/app/client/src/lib/hooks/useErrorHandler.ts +258 -0
  22. package/app/client/src/lib/index.ts +45 -0
  23. package/app/client/src/main.tsx +3 -2
  24. package/app/client/src/pages/ApiDocs.tsx +182 -0
  25. package/app/client/src/pages/Demo.tsx +174 -0
  26. package/app/client/src/pages/HybridLive.tsx +263 -0
  27. package/app/client/src/pages/Overview.tsx +155 -0
  28. package/app/client/src/store/README.md +43 -0
  29. package/app/client/src/store/index.ts +16 -0
  30. package/app/client/src/store/slices/uiSlice.ts +151 -0
  31. package/app/client/src/store/slices/userSlice.ts +161 -0
  32. package/app/client/src/test/README.md +257 -0
  33. package/app/client/src/test/setup.ts +70 -0
  34. package/app/client/src/test/types.ts +12 -0
  35. package/app/client/src/vite-env.d.ts +1 -1
  36. package/app/client/tsconfig.app.json +44 -43
  37. package/app/client/tsconfig.json +7 -7
  38. package/app/client/tsconfig.node.json +25 -25
  39. package/app/client/zustand-setup.md +65 -0
  40. package/app/server/controllers/users.controller.ts +68 -68
  41. package/app/server/index.ts +9 -1
  42. package/app/server/live/CounterComponent.ts +191 -0
  43. package/app/server/live/FluxStackConfig.ts +529 -0
  44. package/app/server/live/LiveClockComponent.ts +214 -0
  45. package/app/server/live/SidebarNavigation.ts +156 -0
  46. package/app/server/live/SystemMonitor.ts +594 -0
  47. package/app/server/live/SystemMonitorIntegration.ts +151 -0
  48. package/app/server/live/TesteComponent.ts +87 -0
  49. package/app/server/live/UserProfileComponent.ts +135 -0
  50. package/app/server/live/register-components.ts +28 -0
  51. package/app/server/middleware/auth.ts +136 -0
  52. package/app/server/middleware/errorHandling.ts +250 -0
  53. package/app/server/middleware/index.ts +10 -0
  54. package/app/server/middleware/rateLimit.ts +193 -0
  55. package/app/server/middleware/requestLogging.ts +215 -0
  56. package/app/server/middleware/validation.ts +270 -0
  57. package/app/server/routes/index.ts +14 -2
  58. package/app/server/routes/upload.ts +92 -0
  59. package/app/server/routes/users.routes.ts +2 -9
  60. package/app/server/services/NotificationService.ts +302 -0
  61. package/app/server/services/UserService.ts +222 -0
  62. package/app/server/services/index.ts +46 -0
  63. package/core/cli/commands/plugin-deps.ts +263 -0
  64. package/core/cli/generators/README.md +339 -0
  65. package/core/cli/generators/component.ts +770 -0
  66. package/core/cli/generators/controller.ts +299 -0
  67. package/core/cli/generators/index.ts +144 -0
  68. package/core/cli/generators/interactive.ts +228 -0
  69. package/core/cli/generators/prompts.ts +83 -0
  70. package/core/cli/generators/route.ts +513 -0
  71. package/core/cli/generators/service.ts +465 -0
  72. package/core/cli/generators/template-engine.ts +154 -0
  73. package/core/cli/generators/types.ts +71 -0
  74. package/core/cli/generators/utils.ts +192 -0
  75. package/core/cli/index.ts +69 -0
  76. package/core/cli/plugin-discovery.ts +16 -85
  77. package/core/client/fluxstack.ts +17 -0
  78. package/core/client/hooks/index.ts +7 -0
  79. package/core/client/hooks/state-validator.ts +130 -0
  80. package/core/client/hooks/useAuth.ts +49 -0
  81. package/core/client/hooks/useChunkedUpload.ts +258 -0
  82. package/core/client/hooks/useHybridLiveComponent.ts +967 -0
  83. package/core/client/hooks/useWebSocket.ts +373 -0
  84. package/core/client/index.ts +47 -0
  85. package/core/client/state/createStore.ts +193 -0
  86. package/core/client/state/index.ts +15 -0
  87. package/core/config/env-dynamic.ts +1 -1
  88. package/core/config/env.ts +2 -1
  89. package/core/config/loader.ts +8 -32
  90. package/core/config/runtime-config.ts +3 -3
  91. package/core/config/schema.ts +84 -49
  92. package/core/framework/server.ts +30 -0
  93. package/core/index.ts +25 -0
  94. package/core/live/ComponentRegistry.ts +399 -0
  95. package/core/live/types.ts +164 -0
  96. package/core/plugins/built-in/live-components/commands/create-live-component.ts +1201 -0
  97. package/core/plugins/built-in/live-components/index.ts +27 -0
  98. package/core/plugins/built-in/logger/index.ts +1 -1
  99. package/core/plugins/built-in/monitoring/index.ts +1 -1
  100. package/core/plugins/built-in/static/index.ts +1 -1
  101. package/core/plugins/built-in/swagger/index.ts +1 -1
  102. package/core/plugins/built-in/vite/index.ts +1 -1
  103. package/core/plugins/dependency-manager.ts +384 -0
  104. package/core/plugins/index.ts +5 -1
  105. package/core/plugins/manager.ts +7 -3
  106. package/core/plugins/registry.ts +88 -10
  107. package/core/plugins/types.ts +11 -11
  108. package/core/server/framework.ts +43 -0
  109. package/core/server/index.ts +11 -1
  110. package/core/server/live/ComponentRegistry.ts +1017 -0
  111. package/core/server/live/FileUploadManager.ts +272 -0
  112. package/core/server/live/LiveComponentPerformanceMonitor.ts +930 -0
  113. package/core/server/live/SingleConnectionManager.ts +0 -0
  114. package/core/server/live/StateSignature.ts +644 -0
  115. package/core/server/live/WebSocketConnectionManager.ts +688 -0
  116. package/core/server/live/websocket-plugin.ts +435 -0
  117. package/core/server/middleware/errorHandling.ts +141 -0
  118. package/core/server/middleware/index.ts +16 -0
  119. package/core/server/plugins/static-files-plugin.ts +232 -0
  120. package/core/server/services/BaseService.ts +95 -0
  121. package/core/server/services/ServiceContainer.ts +144 -0
  122. package/core/server/services/index.ts +9 -0
  123. package/core/templates/create-project.ts +46 -2
  124. package/core/testing/index.ts +10 -0
  125. package/core/testing/setup.ts +74 -0
  126. package/core/types/build.ts +38 -14
  127. package/core/types/types.ts +319 -0
  128. package/core/utils/env-runtime.ts +7 -0
  129. package/core/utils/errors/handlers.ts +264 -39
  130. package/core/utils/errors/index.ts +528 -18
  131. package/core/utils/errors/middleware.ts +114 -0
  132. package/core/utils/logger/formatters.ts +222 -0
  133. package/core/utils/logger/index.ts +167 -48
  134. package/core/utils/logger/middleware.ts +253 -0
  135. package/core/utils/logger/performance.ts +384 -0
  136. package/core/utils/logger/transports.ts +365 -0
  137. package/create-fluxstack.ts +296 -296
  138. package/fluxstack.config.ts +17 -1
  139. package/package-template.json +66 -66
  140. package/package.json +31 -6
  141. package/public/README.md +16 -0
  142. package/vite.config.ts +29 -14
  143. package/.claude/settings.local.json +0 -74
  144. package/.github/workflows/ci-build-tests.yml +0 -480
  145. package/.github/workflows/dependency-management.yml +0 -324
  146. package/.github/workflows/release-validation.yml +0 -355
  147. package/.kiro/specs/fluxstack-architecture-optimization/design.md +0 -700
  148. package/.kiro/specs/fluxstack-architecture-optimization/requirements.md +0 -127
  149. package/.kiro/specs/fluxstack-architecture-optimization/tasks.md +0 -330
  150. package/CLAUDE.md +0 -200
  151. package/Dockerfile +0 -58
  152. package/Dockerfile.backend +0 -52
  153. package/Dockerfile.frontend +0 -54
  154. package/README-Docker.md +0 -85
  155. package/ai-context/00-QUICK-START.md +0 -86
  156. package/ai-context/README.md +0 -88
  157. package/ai-context/development/eden-treaty-guide.md +0 -362
  158. package/ai-context/development/patterns.md +0 -382
  159. package/ai-context/development/plugins-guide.md +0 -572
  160. package/ai-context/examples/crud-complete.md +0 -626
  161. package/ai-context/project/architecture.md +0 -399
  162. package/ai-context/project/overview.md +0 -213
  163. package/ai-context/recent-changes/eden-treaty-refactor.md +0 -281
  164. package/ai-context/recent-changes/type-inference-fix.md +0 -223
  165. package/ai-context/reference/environment-vars.md +0 -384
  166. package/ai-context/reference/troubleshooting.md +0 -407
  167. package/app/client/src/components/TestPage.tsx +0 -453
  168. package/bun.lock +0 -1063
  169. package/bunfig.toml +0 -16
  170. package/core/__tests__/integration.test.ts +0 -227
  171. package/core/build/index.ts +0 -186
  172. package/core/config/__tests__/config-loader.test.ts +0 -591
  173. package/core/config/__tests__/config-merger.test.ts +0 -657
  174. package/core/config/__tests__/env-converter.test.ts +0 -372
  175. package/core/config/__tests__/env-processor.test.ts +0 -431
  176. package/core/config/__tests__/env.test.ts +0 -452
  177. package/core/config/__tests__/integration.test.ts +0 -418
  178. package/core/config/__tests__/loader.test.ts +0 -331
  179. package/core/config/__tests__/schema.test.ts +0 -129
  180. package/core/config/__tests__/validator.test.ts +0 -318
  181. package/core/framework/__tests__/server.test.ts +0 -233
  182. package/core/plugins/__tests__/built-in.test.ts.disabled +0 -366
  183. package/core/plugins/__tests__/manager.test.ts +0 -398
  184. package/core/plugins/__tests__/monitoring.test.ts +0 -401
  185. package/core/plugins/__tests__/registry.test.ts +0 -335
  186. package/core/utils/__tests__/errors.test.ts +0 -139
  187. package/core/utils/__tests__/helpers.test.ts +0 -297
  188. package/core/utils/__tests__/logger.test.ts +0 -141
  189. package/create-test-app.ts +0 -156
  190. package/docker-compose.microservices.yml +0 -75
  191. package/docker-compose.simple.yml +0 -57
  192. package/docker-compose.yml +0 -71
  193. package/eslint.config.js +0 -23
  194. package/flux-cli.ts +0 -214
  195. package/nginx-lb.conf +0 -37
  196. package/publish.sh +0 -63
  197. package/run-clean.ts +0 -26
  198. package/run-env-tests.ts +0 -313
  199. package/tailwind.config.js +0 -34
  200. package/tests/__mocks__/api.ts +0 -56
  201. package/tests/fixtures/users.ts +0 -69
  202. package/tests/integration/api/users.routes.test.ts +0 -221
  203. package/tests/setup.ts +0 -29
  204. package/tests/unit/app/client/App-simple.test.tsx +0 -56
  205. package/tests/unit/app/client/App.test.tsx.skip +0 -237
  206. package/tests/unit/app/client/eden-api.test.ts +0 -186
  207. package/tests/unit/app/client/simple.test.tsx +0 -23
  208. package/tests/unit/app/controllers/users.controller.test.ts +0 -150
  209. package/tests/unit/core/create-project.test.ts.skip +0 -95
  210. package/tests/unit/core/framework.test.ts +0 -144
  211. package/tests/unit/core/plugins/logger.test.ts.skip +0 -268
  212. package/tests/unit/core/plugins/vite.test.ts.disabled +0 -188
  213. package/tests/utils/test-helpers.ts +0 -61
  214. package/vitest.config.ts +0 -50
  215. package/workspace.json +0 -6
@@ -0,0 +1,809 @@
1
+ import React, { useState } from 'react';
2
+ import { useHybridLiveComponent, useChunkedUpload } from 'fluxstack';
3
+ import {
4
+ FaUser,
5
+ FaEdit,
6
+ FaMapMarkerAlt,
7
+ FaCalendarAlt,
8
+ FaUsers,
9
+ FaHeart,
10
+ FaNewspaper,
11
+ FaBell,
12
+ FaSun,
13
+ FaMoon,
14
+ FaCircle,
15
+ FaUserPlus,
16
+ FaCheck,
17
+ FaTimes,
18
+ FaEnvelope,
19
+ FaCamera,
20
+ FaUpload,
21
+ FaSpinner
22
+ } from 'react-icons/fa';
23
+
24
+ interface UserProfileState {
25
+ name: string;
26
+ email: string;
27
+ avatar: string;
28
+ status: 'online' | 'away' | 'busy' | 'offline';
29
+ bio: string;
30
+ location: string;
31
+ joinedDate: string;
32
+ followers: number;
33
+ following: number;
34
+ posts: number;
35
+ isEditing: boolean;
36
+ lastActivity: string;
37
+ theme: 'light' | 'dark';
38
+ notifications: number;
39
+ }
40
+
41
+ const initialState: UserProfileState = {
42
+ name: "Loading...",
43
+ email: "",
44
+ avatar: "",
45
+ status: "offline",
46
+ bio: "",
47
+ location: "",
48
+ joinedDate: "",
49
+ followers: 0,
50
+ following: 0,
51
+ posts: 0,
52
+ isEditing: false,
53
+ lastActivity: "",
54
+ theme: "light",
55
+ notifications: 0
56
+ };
57
+
58
+ export function UserProfile() {
59
+ const { state, call, connected, status, componentId, sendMessageAndWait, error } = useHybridLiveComponent<UserProfileState>('UserProfile', initialState, {
60
+ debug: true // Enable debug logs to track re-hydration
61
+ });
62
+
63
+ const chunkedUpload = useChunkedUpload(componentId || '', {
64
+ sendMessageAndWait,
65
+ onProgress: (progress, bytesUploaded, totalBytes) => {
66
+ console.log(`📊 Upload progress: ${progress}% (${bytesUploaded}/${totalBytes} bytes)`)
67
+ },
68
+ onComplete: async (response) => {
69
+ console.log('✅ Chunked upload completed:', response.fileUrl)
70
+ if (response.fileUrl) {
71
+ await call('updateAvatar', { imageUrl: response.fileUrl })
72
+ setShowPhotoUpload(false)
73
+ }
74
+ },
75
+ onError: (error) => {
76
+ console.error('❌ Chunked upload error:', error)
77
+ alert(`Erro no upload: ${error}`)
78
+ }
79
+ });
80
+
81
+ const [editForm, setEditForm] = useState({
82
+ name: state.name,
83
+ bio: state.bio,
84
+ location: state.location
85
+ });
86
+ const [showPhotoUpload, setShowPhotoUpload] = useState(false);
87
+ const [dragOver, setDragOver] = useState(false);
88
+ const fileInputRef = React.useRef<HTMLInputElement>(null);
89
+
90
+ React.useEffect(() => {
91
+ setEditForm({
92
+ name: state.name,
93
+ bio: state.bio,
94
+ location: state.location
95
+ });
96
+ }, [state.name, state.bio, state.location]);
97
+
98
+ const getStatusColor = (status: string) => {
99
+ switch (status) {
100
+ case 'online': return '#10b981';
101
+ case 'away': return '#f59e0b';
102
+ case 'busy': return '#ef4444';
103
+ case 'offline': return '#6b7280';
104
+ default: return '#6b7280';
105
+ }
106
+ };
107
+
108
+ const getStatusText = (status: string) => {
109
+ switch (status) {
110
+ case 'online': return 'Online';
111
+ case 'away': return 'Ausente';
112
+ case 'busy': return 'Ocupado';
113
+ case 'offline': return 'Offline';
114
+ default: return 'Desconhecido';
115
+ }
116
+ };
117
+
118
+ const handleSaveEdit = async () => {
119
+ await call('updateProfile', editForm);
120
+ await call('toggleEdit');
121
+ };
122
+
123
+ const handleCancelEdit = async () => {
124
+ setEditForm({
125
+ name: state.name,
126
+ bio: state.bio,
127
+ location: state.location
128
+ });
129
+ await call('toggleEdit');
130
+ };
131
+
132
+ const handleFileUpload = async (file: File) => {
133
+ if (!file) return;
134
+
135
+ // Check if Live Component is connected
136
+ if (!connected || status !== 'synced') {
137
+ alert('Aguarde a conexão do Live Component ser estabelecida...')
138
+ return
139
+ }
140
+
141
+ console.log('🚀 Starting chunked WebSocket upload for:', file.name)
142
+ await chunkedUpload.uploadFile(file)
143
+ };
144
+
145
+ const handleDrop = (e: React.DragEvent) => {
146
+ e.preventDefault();
147
+ setDragOver(false);
148
+
149
+ const files = Array.from(e.dataTransfer.files);
150
+ if (files.length > 0) {
151
+ handleFileUpload(files[0]);
152
+ }
153
+ };
154
+
155
+ const handleDragOver = (e: React.DragEvent) => {
156
+ e.preventDefault();
157
+ setDragOver(true);
158
+ };
159
+
160
+ const handleDragLeave = (e: React.DragEvent) => {
161
+ e.preventDefault();
162
+ setDragOver(false);
163
+ };
164
+
165
+ const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
166
+ const files = e.target.files;
167
+ if (files && files.length > 0) {
168
+ handleFileUpload(files[0]);
169
+ }
170
+ };
171
+
172
+ // Show connection status
173
+ if (!connected || status !== 'synced') {
174
+ const getStatusMessage = () => {
175
+ switch (status) {
176
+ case 'connecting':
177
+ return '🔄 Conectando ao UserProfile...'
178
+ case 'reconnecting':
179
+ return '🔄 Reconectando componente...'
180
+ case 'mounting':
181
+ return '🚀 Montando componente...'
182
+ case 'loading':
183
+ return '⏳ Carregando...'
184
+ case 'error':
185
+ return '❌ Erro de conexão'
186
+ default:
187
+ return '🔄 Preparando componente...'
188
+ }
189
+ }
190
+
191
+ return (
192
+ <div style={{
193
+ padding: '2rem',
194
+ textAlign: 'center',
195
+ background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
196
+ color: 'white',
197
+ borderRadius: '16px',
198
+ margin: '1rem'
199
+ }}>
200
+ <FaUser size={32} style={{ marginBottom: '1rem' }} />
201
+ <p>{getStatusMessage()}</p>
202
+ {error && (
203
+ <p style={{ fontSize: '0.9rem', marginTop: '0.5rem', opacity: 0.8 }}>
204
+ {error}
205
+ </p>
206
+ )}
207
+ </div>
208
+ );
209
+ }
210
+
211
+ const isDark = state.theme === 'dark';
212
+ const bgColor = isDark ? '#1f2937' : '#ffffff';
213
+ const textColor = isDark ? '#f9fafb' : '#111827';
214
+ const secondaryColor = isDark ? '#9ca3af' : '#6b7280';
215
+ const borderColor = isDark ? '#374151' : '#e5e7eb';
216
+
217
+ return (
218
+ <div style={{
219
+ backgroundColor: bgColor,
220
+ color: textColor,
221
+ borderRadius: '20px',
222
+ padding: '2rem',
223
+ margin: '1rem',
224
+ boxShadow: isDark
225
+ ? '0 25px 50px -12px rgba(0, 0, 0, 0.5)'
226
+ : '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
227
+ border: `1px solid ${borderColor}`,
228
+ maxWidth: '450px',
229
+ position: 'relative',
230
+ overflow: 'hidden'
231
+ }}>
232
+ {/* Header with Status and Theme Toggle */}
233
+ <div style={{
234
+ display: 'flex',
235
+ justifyContent: 'space-between',
236
+ alignItems: 'center',
237
+ marginBottom: '1.5rem'
238
+ }}>
239
+ <div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
240
+ <FaCircle
241
+ size={12}
242
+ style={{ color: getStatusColor(state.status) }}
243
+ />
244
+ <span style={{ fontSize: '0.875rem', color: secondaryColor }}>
245
+ {getStatusText(state.status)} • {status === 'synced' ? '🟢' : '🔴'} Live
246
+ </span>
247
+ </div>
248
+
249
+ <div style={{ display: 'flex', gap: '0.5rem' }}>
250
+ {state.notifications > 0 && (
251
+ <button
252
+ onClick={() => call('clearNotifications')}
253
+ style={{
254
+ background: '#ef4444',
255
+ color: 'white',
256
+ border: 'none',
257
+ borderRadius: '50%',
258
+ width: '28px',
259
+ height: '28px',
260
+ display: 'flex',
261
+ alignItems: 'center',
262
+ justifyContent: 'center',
263
+ cursor: 'pointer',
264
+ fontSize: '0.75rem',
265
+ position: 'relative'
266
+ }}
267
+ >
268
+ <FaBell size={12} />
269
+ <span style={{
270
+ position: 'absolute',
271
+ top: '-5px',
272
+ right: '-5px',
273
+ background: '#dc2626',
274
+ color: 'white',
275
+ borderRadius: '50%',
276
+ width: '16px',
277
+ height: '16px',
278
+ fontSize: '10px',
279
+ display: 'flex',
280
+ alignItems: 'center',
281
+ justifyContent: 'center'
282
+ }}>
283
+ {state.notifications}
284
+ </span>
285
+ </button>
286
+ )}
287
+
288
+ <button
289
+ onClick={() => call('toggleTheme')}
290
+ style={{
291
+ background: 'transparent',
292
+ color: textColor,
293
+ border: `1px solid ${borderColor}`,
294
+ borderRadius: '8px',
295
+ padding: '0.5rem',
296
+ cursor: 'pointer',
297
+ display: 'flex',
298
+ alignItems: 'center'
299
+ }}
300
+ >
301
+ {isDark ? <FaSun size={14} /> : <FaMoon size={14} />}
302
+ </button>
303
+ </div>
304
+ </div>
305
+
306
+ {/* Avatar and Basic Info */}
307
+ <div style={{ textAlign: 'center', marginBottom: '1.5rem' }}>
308
+ <div style={{ position: 'relative', display: 'inline-block', marginBottom: '1rem' }}>
309
+ {state.avatar ? (
310
+ <img
311
+ src={state.avatar}
312
+ alt={state.name}
313
+ style={{
314
+ width: '100px',
315
+ height: '100px',
316
+ borderRadius: '50%',
317
+ border: `4px solid ${getStatusColor(state.status)}`,
318
+ objectFit: 'cover'
319
+ }}
320
+ />
321
+ ) : (
322
+ <div
323
+ style={{
324
+ width: '100px',
325
+ height: '100px',
326
+ borderRadius: '50%',
327
+ border: `4px solid ${getStatusColor(state.status)}`,
328
+ backgroundColor: '#f0f0f0',
329
+ display: 'flex',
330
+ alignItems: 'center',
331
+ justifyContent: 'center',
332
+ fontSize: '2rem',
333
+ color: '#999'
334
+ }}
335
+ >
336
+ 👤
337
+ </div>
338
+ )}
339
+ <button
340
+ onClick={() => call('toggleStatus')}
341
+ style={{
342
+ position: 'absolute',
343
+ bottom: '0',
344
+ right: '0',
345
+ background: getStatusColor(state.status),
346
+ border: `2px solid ${bgColor}`,
347
+ borderRadius: '50%',
348
+ width: '24px',
349
+ height: '24px',
350
+ cursor: 'pointer',
351
+ display: 'flex',
352
+ alignItems: 'center',
353
+ justifyContent: 'center'
354
+ }}
355
+ >
356
+ <FaCircle size={8} color="white" />
357
+ </button>
358
+
359
+ <button
360
+ onClick={() => setShowPhotoUpload(true)}
361
+ style={{
362
+ position: 'absolute',
363
+ top: '0',
364
+ right: '0',
365
+ background: isDark ? '#4f46e5' : '#6366f1',
366
+ border: `2px solid ${bgColor}`,
367
+ borderRadius: '50%',
368
+ width: '32px',
369
+ height: '32px',
370
+ cursor: 'pointer',
371
+ display: 'flex',
372
+ alignItems: 'center',
373
+ justifyContent: 'center',
374
+ boxShadow: '0 4px 8px rgba(0,0,0,0.2)'
375
+ }}
376
+ >
377
+ <FaCamera size={14} color="white" />
378
+ </button>
379
+ </div>
380
+
381
+ {state.isEditing ? (
382
+ <div style={{ marginBottom: '1rem' }}>
383
+ <input
384
+ type="text"
385
+ value={editForm.name}
386
+ onChange={(e) => setEditForm({ ...editForm, name: e.target.value })}
387
+ style={{
388
+ background: isDark ? '#374151' : '#f9fafb',
389
+ color: textColor,
390
+ border: `1px solid ${borderColor}`,
391
+ borderRadius: '8px',
392
+ padding: '0.5rem',
393
+ marginBottom: '0.5rem',
394
+ width: '100%',
395
+ fontSize: '1.25rem',
396
+ fontWeight: 'bold',
397
+ textAlign: 'center'
398
+ }}
399
+ />
400
+ </div>
401
+ ) : (
402
+ <h2 style={{ margin: '0 0 0.5rem 0', fontSize: '1.5rem' }}>
403
+ {state.name}
404
+ </h2>
405
+ )}
406
+
407
+ <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '0.5rem', marginBottom: '0.5rem' }}>
408
+ <FaEnvelope size={14} style={{ color: secondaryColor }} />
409
+ <span style={{ color: secondaryColor, fontSize: '0.875rem' }}>
410
+ {state.email}
411
+ </span>
412
+ </div>
413
+ </div>
414
+
415
+ {/* Bio */}
416
+ <div style={{ marginBottom: '1.5rem' }}>
417
+ {state.isEditing ? (
418
+ <textarea
419
+ value={editForm.bio}
420
+ onChange={(e) => setEditForm({ ...editForm, bio: e.target.value })}
421
+ placeholder="Conte um pouco sobre você..."
422
+ style={{
423
+ background: isDark ? '#374151' : '#f9fafb',
424
+ color: textColor,
425
+ border: `1px solid ${borderColor}`,
426
+ borderRadius: '8px',
427
+ padding: '0.75rem',
428
+ width: '100%',
429
+ minHeight: '80px',
430
+ resize: 'vertical',
431
+ fontSize: '0.875rem'
432
+ }}
433
+ />
434
+ ) : (
435
+ <p style={{
436
+ color: secondaryColor,
437
+ fontSize: '0.875rem',
438
+ lineHeight: '1.5',
439
+ textAlign: 'center',
440
+ margin: '0',
441
+ fontStyle: state.bio ? 'normal' : 'italic'
442
+ }}>
443
+ {state.bio || 'Nenhuma biografia ainda...'}
444
+ </p>
445
+ )}
446
+ </div>
447
+
448
+ {/* Location */}
449
+ <div style={{
450
+ display: 'flex',
451
+ alignItems: 'center',
452
+ justifyContent: 'center',
453
+ gap: '0.5rem',
454
+ marginBottom: '1.5rem'
455
+ }}>
456
+ <FaMapMarkerAlt size={14} style={{ color: secondaryColor }} />
457
+ {state.isEditing ? (
458
+ <input
459
+ type="text"
460
+ value={editForm.location}
461
+ onChange={(e) => setEditForm({ ...editForm, location: e.target.value })}
462
+ placeholder="Sua localização"
463
+ style={{
464
+ background: 'transparent',
465
+ color: textColor,
466
+ border: 'none',
467
+ borderBottom: `1px solid ${borderColor}`,
468
+ padding: '0.25rem',
469
+ fontSize: '0.875rem',
470
+ textAlign: 'center',
471
+ width: '150px'
472
+ }}
473
+ />
474
+ ) : (
475
+ <span style={{ color: secondaryColor, fontSize: '0.875rem' }}>
476
+ {state.location || 'Localização não informada'}
477
+ </span>
478
+ )}
479
+ </div>
480
+
481
+ {/* Stats */}
482
+ <div style={{
483
+ display: 'grid',
484
+ gridTemplateColumns: '1fr 1fr 1fr',
485
+ gap: '1rem',
486
+ marginBottom: '1.5rem',
487
+ padding: '1rem',
488
+ background: isDark ? '#374151' : '#f9fafb',
489
+ borderRadius: '12px'
490
+ }}>
491
+ <div style={{ textAlign: 'center' }}>
492
+ <div style={{ fontSize: '1.25rem', fontWeight: 'bold', color: textColor }}>
493
+ {state.posts}
494
+ </div>
495
+ <div style={{ fontSize: '0.75rem', color: secondaryColor, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '0.25rem' }}>
496
+ <FaNewspaper size={10} /> Posts
497
+ </div>
498
+ </div>
499
+ <div style={{ textAlign: 'center' }}>
500
+ <div style={{ fontSize: '1.25rem', fontWeight: 'bold', color: textColor }}>
501
+ {state.followers.toLocaleString()}
502
+ </div>
503
+ <div style={{ fontSize: '0.75rem', color: secondaryColor, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '0.25rem' }}>
504
+ <FaUsers size={10} /> Seguidores
505
+ </div>
506
+ </div>
507
+ <div style={{ textAlign: 'center' }}>
508
+ <div style={{ fontSize: '1.25rem', fontWeight: 'bold', color: textColor }}>
509
+ {state.following}
510
+ </div>
511
+ <div style={{ fontSize: '0.75rem', color: secondaryColor, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '0.25rem' }}>
512
+ <FaHeart size={10} /> Seguindo
513
+ </div>
514
+ </div>
515
+ </div>
516
+
517
+ {/* Action Buttons */}
518
+ {state.isEditing ? (
519
+ <div style={{ display: 'flex', gap: '0.75rem' }}>
520
+ <button
521
+ onClick={handleSaveEdit}
522
+ style={{
523
+ flex: 1,
524
+ background: '#10b981',
525
+ color: 'white',
526
+ border: 'none',
527
+ borderRadius: '12px',
528
+ padding: '0.75rem',
529
+ cursor: 'pointer',
530
+ display: 'flex',
531
+ alignItems: 'center',
532
+ justifyContent: 'center',
533
+ gap: '0.5rem',
534
+ fontWeight: '500'
535
+ }}
536
+ >
537
+ <FaCheck size={14} /> Salvar
538
+ </button>
539
+ <button
540
+ onClick={handleCancelEdit}
541
+ style={{
542
+ flex: 1,
543
+ background: '#ef4444',
544
+ color: 'white',
545
+ border: 'none',
546
+ borderRadius: '12px',
547
+ padding: '0.75rem',
548
+ cursor: 'pointer',
549
+ display: 'flex',
550
+ alignItems: 'center',
551
+ justifyContent: 'center',
552
+ gap: '0.5rem',
553
+ fontWeight: '500'
554
+ }}
555
+ >
556
+ <FaTimes size={14} /> Cancelar
557
+ </button>
558
+ </div>
559
+ ) : (
560
+ <div style={{ display: 'flex', gap: '0.75rem' }}>
561
+ <button
562
+ onClick={() => call('toggleEdit')}
563
+ style={{
564
+ flex: 1,
565
+ background: isDark ? '#4f46e5' : '#6366f1',
566
+ color: 'white',
567
+ border: 'none',
568
+ borderRadius: '12px',
569
+ padding: '0.75rem',
570
+ cursor: 'pointer',
571
+ display: 'flex',
572
+ alignItems: 'center',
573
+ justifyContent: 'center',
574
+ gap: '0.5rem',
575
+ fontWeight: '500'
576
+ }}
577
+ >
578
+ <FaEdit size={14} /> Editar
579
+ </button>
580
+ <button
581
+ onClick={() => call('followUser')}
582
+ style={{
583
+ flex: 1,
584
+ background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
585
+ color: 'white',
586
+ border: 'none',
587
+ borderRadius: '12px',
588
+ padding: '0.75rem',
589
+ cursor: 'pointer',
590
+ display: 'flex',
591
+ alignItems: 'center',
592
+ justifyContent: 'center',
593
+ gap: '0.5rem',
594
+ fontWeight: '500'
595
+ }}
596
+ >
597
+ <FaUserPlus size={14} /> Seguir
598
+ </button>
599
+ </div>
600
+ )}
601
+
602
+ {/* Footer Info */}
603
+ <div style={{
604
+ marginTop: '1.5rem',
605
+ paddingTop: '1rem',
606
+ borderTop: `1px solid ${borderColor}`,
607
+ display: 'flex',
608
+ alignItems: 'center',
609
+ justifyContent: 'center',
610
+ gap: '0.5rem'
611
+ }}>
612
+ <FaCalendarAlt size={12} style={{ color: secondaryColor }} />
613
+ <span style={{ fontSize: '0.75rem', color: secondaryColor }}>
614
+ Membro desde {state.joinedDate}
615
+ </span>
616
+ </div>
617
+
618
+ {/* Photo Upload Modal */}
619
+ {showPhotoUpload && (
620
+ <div style={{
621
+ position: 'fixed',
622
+ top: 0,
623
+ left: 0,
624
+ right: 0,
625
+ bottom: 0,
626
+ background: 'rgba(0, 0, 0, 0.7)',
627
+ display: 'flex',
628
+ alignItems: 'center',
629
+ justifyContent: 'center',
630
+ zIndex: 1000
631
+ }}>
632
+ <div style={{
633
+ background: bgColor,
634
+ borderRadius: '20px',
635
+ padding: '2rem',
636
+ width: '90%',
637
+ maxWidth: '400px',
638
+ position: 'relative',
639
+ border: `1px solid ${borderColor}`,
640
+ boxShadow: '0 25px 50px -12px rgba(0, 0, 0, 0.5)'
641
+ }}>
642
+ {/* Close button */}
643
+ <button
644
+ onClick={() => setShowPhotoUpload(false)}
645
+ style={{
646
+ position: 'absolute',
647
+ top: '1rem',
648
+ right: '1rem',
649
+ background: 'transparent',
650
+ border: 'none',
651
+ color: secondaryColor,
652
+ cursor: 'pointer',
653
+ padding: '0.5rem'
654
+ }}
655
+ >
656
+ <FaTimes size={16} />
657
+ </button>
658
+
659
+ <h3 style={{
660
+ color: textColor,
661
+ marginBottom: '1.5rem',
662
+ textAlign: 'center',
663
+ fontSize: '1.25rem'
664
+ }}>
665
+ 📸 Atualizar Foto do Perfil
666
+ </h3>
667
+
668
+ {/* Drag and Drop Area */}
669
+ <div
670
+ onDrop={handleDrop}
671
+ onDragOver={handleDragOver}
672
+ onDragLeave={handleDragLeave}
673
+ style={{
674
+ border: `2px dashed ${dragOver ? '#6366f1' : borderColor}`,
675
+ borderRadius: '12px',
676
+ padding: '2rem',
677
+ textAlign: 'center',
678
+ marginBottom: '1rem',
679
+ background: dragOver
680
+ ? (isDark ? '#312e81' : '#f0f0ff')
681
+ : (isDark ? '#374151' : '#f9fafb'),
682
+ transition: 'all 0.3s ease',
683
+ cursor: 'pointer'
684
+ }}
685
+ onClick={() => fileInputRef.current?.click()}
686
+ >
687
+ {chunkedUpload.uploading ? (
688
+ <div style={{ color: textColor }}>
689
+ <div
690
+ style={{
691
+ color: '#6366f1',
692
+ marginBottom: '1rem',
693
+ display: 'inline-block',
694
+ animation: 'spin 1s linear infinite'
695
+ }}
696
+ >
697
+ <FaSpinner size={32} />
698
+ </div>
699
+ <p style={{ margin: 0, fontSize: '0.875rem', marginBottom: '0.5rem' }}>
700
+ Enviando por chunks via WebSocket...
701
+ </p>
702
+ <div style={{
703
+ width: '100%',
704
+ backgroundColor: borderColor,
705
+ borderRadius: '4px',
706
+ overflow: 'hidden',
707
+ marginBottom: '0.5rem'
708
+ }}>
709
+ <div style={{
710
+ width: `${chunkedUpload.progress}%`,
711
+ height: '8px',
712
+ backgroundColor: '#6366f1',
713
+ transition: 'width 0.2s ease-in-out'
714
+ }}></div>
715
+ </div>
716
+ <p style={{ margin: 0, fontSize: '0.75rem', color: secondaryColor }}>
717
+ {chunkedUpload.progress.toFixed(1)}% - {Math.round(chunkedUpload.bytesUploaded / 1024)}KB de {Math.round(chunkedUpload.totalBytes / 1024)}KB
718
+ </p>
719
+ <style>{`
720
+ @keyframes spin {
721
+ from { transform: rotate(0deg); }
722
+ to { transform: rotate(360deg); }
723
+ }
724
+ `}</style>
725
+ </div>
726
+ ) : (
727
+ <div style={{ color: textColor }}>
728
+ <FaUpload
729
+ size={32}
730
+ style={{
731
+ color: dragOver ? '#6366f1' : secondaryColor,
732
+ marginBottom: '1rem'
733
+ }}
734
+ />
735
+ <p style={{ margin: '0 0 0.5rem 0', fontWeight: '500' }}>
736
+ {dragOver ? 'Solte a imagem aqui!' : 'Arraste uma imagem ou clique para selecionar'}
737
+ </p>
738
+ <p style={{
739
+ margin: 0,
740
+ fontSize: '0.75rem',
741
+ color: secondaryColor
742
+ }}>
743
+ JPEG, PNG, WebP, GIF • Máximo 5MB
744
+ </p>
745
+ </div>
746
+ )}
747
+ </div>
748
+
749
+ {/* File Input */}
750
+ <input
751
+ ref={fileInputRef}
752
+ type="file"
753
+ accept="image/jpeg,image/jpg,image/png,image/webp,image/gif"
754
+ onChange={handleFileSelect}
755
+ style={{ display: 'none' }}
756
+ />
757
+
758
+ {/* Action Buttons */}
759
+ <div style={{ display: 'flex', gap: '0.75rem' }}>
760
+ <button
761
+ onClick={() => fileInputRef.current?.click()}
762
+ disabled={chunkedUpload.uploading}
763
+ style={{
764
+ flex: 1,
765
+ background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
766
+ color: 'white',
767
+ border: 'none',
768
+ borderRadius: '12px',
769
+ padding: '0.75rem',
770
+ cursor: chunkedUpload.uploading ? 'not-allowed' : 'pointer',
771
+ display: 'flex',
772
+ alignItems: 'center',
773
+ justifyContent: 'center',
774
+ gap: '0.5rem',
775
+ fontWeight: '500',
776
+ opacity: chunkedUpload.uploading ? 0.6 : 1
777
+ }}
778
+ >
779
+ <FaCamera size={14} /> Escolher Arquivo
780
+ </button>
781
+
782
+ <button
783
+ onClick={() => setShowPhotoUpload(false)}
784
+ disabled={chunkedUpload.uploading}
785
+ style={{
786
+ flex: 1,
787
+ background: 'transparent',
788
+ color: textColor,
789
+ border: `1px solid ${borderColor}`,
790
+ borderRadius: '12px',
791
+ padding: '0.75rem',
792
+ cursor: chunkedUpload.uploading ? 'not-allowed' : 'pointer',
793
+ display: 'flex',
794
+ alignItems: 'center',
795
+ justifyContent: 'center',
796
+ gap: '0.5rem',
797
+ fontWeight: '500',
798
+ opacity: chunkedUpload.uploading ? 0.6 : 1
799
+ }}
800
+ >
801
+ <FaTimes size={14} /> Cancelar
802
+ </button>
803
+ </div>
804
+ </div>
805
+ </div>
806
+ )}
807
+ </div>
808
+ );
809
+ }