@underpostnet/underpost 2.97.1 → 2.97.5
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/README.md +2 -2
- package/cli.md +3 -1
- package/conf.js +2 -0
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
- package/package.json +1 -1
- package/src/api/core/core.service.js +0 -5
- package/src/api/default/default.service.js +7 -5
- package/src/api/document/document.model.js +1 -1
- package/src/api/document/document.router.js +5 -0
- package/src/api/document/document.service.js +105 -47
- package/src/api/file/file.model.js +112 -4
- package/src/api/file/file.ref.json +42 -0
- package/src/api/file/file.service.js +380 -32
- package/src/api/user/user.model.js +38 -1
- package/src/api/user/user.router.js +96 -63
- package/src/api/user/user.service.js +81 -48
- package/src/cli/db.js +424 -166
- package/src/cli/index.js +8 -0
- package/src/cli/repository.js +1 -1
- package/src/cli/run.js +1 -0
- package/src/cli/ssh.js +10 -10
- package/src/client/components/core/Account.js +327 -36
- package/src/client/components/core/AgGrid.js +3 -0
- package/src/client/components/core/Auth.js +9 -3
- package/src/client/components/core/Chat.js +2 -2
- package/src/client/components/core/Content.js +159 -78
- package/src/client/components/core/CssCore.js +16 -12
- package/src/client/components/core/FileExplorer.js +115 -8
- package/src/client/components/core/Input.js +204 -11
- package/src/client/components/core/LogIn.js +42 -20
- package/src/client/components/core/Modal.js +138 -24
- package/src/client/components/core/Panel.js +69 -31
- package/src/client/components/core/PanelForm.js +262 -77
- package/src/client/components/core/PublicProfile.js +888 -0
- package/src/client/components/core/Router.js +117 -15
- package/src/client/components/core/SearchBox.js +329 -13
- package/src/client/components/core/SignUp.js +26 -7
- package/src/client/components/core/SocketIo.js +6 -3
- package/src/client/components/core/Translate.js +98 -0
- package/src/client/components/core/Validator.js +15 -0
- package/src/client/components/core/windowGetDimensions.js +6 -6
- package/src/client/components/default/MenuDefault.js +59 -12
- package/src/client/components/default/RoutesDefault.js +1 -0
- package/src/client/services/core/core.service.js +163 -1
- package/src/client/services/default/default.management.js +451 -64
- package/src/client/services/default/default.service.js +13 -6
- package/src/client/services/file/file.service.js +43 -16
- package/src/client/services/user/user.service.js +13 -9
- package/src/db/DataBaseProvider.js +1 -1
- package/src/db/mongo/MongooseDB.js +1 -1
- package/src/index.js +1 -1
- package/src/mailer/MailerProvider.js +4 -4
- package/src/runtime/express/Express.js +2 -1
- package/src/runtime/lampp/Lampp.js +2 -2
- package/src/server/auth.js +3 -6
- package/src/server/data-query.js +449 -0
- package/src/server/object-layer.js +0 -3
- package/src/ws/IoInterface.js +2 -2
|
@@ -0,0 +1,888 @@
|
|
|
1
|
+
import { renderWave } from './Css.js';
|
|
2
|
+
import { Translate } from './Translate.js';
|
|
3
|
+
import { s, htmls } from './VanillaJs.js';
|
|
4
|
+
import { UserService } from '../../services/user/user.service.js';
|
|
5
|
+
import { ThemeEvents, darkTheme, subThemeManager, lightenHex, darkenHex } from './Css.js';
|
|
6
|
+
import { Modal } from './Modal.js';
|
|
7
|
+
import { getId } from './CommonJs.js';
|
|
8
|
+
import { setPath, getProxyPath, getQueryParams, extractUsernameFromPath, RouterEvents } from './Router.js';
|
|
9
|
+
|
|
10
|
+
const PublicProfile = {
|
|
11
|
+
Data: {},
|
|
12
|
+
currentUsername: null, // Track the currently displayed username for back/forward navigation
|
|
13
|
+
|
|
14
|
+
Update: async function (options = { idModal: '', user: {} }) {
|
|
15
|
+
const { idModal, user } = options;
|
|
16
|
+
const username = user.username || 'Unknown User';
|
|
17
|
+
|
|
18
|
+
// Check if modal exists and is registered in Modal.Data
|
|
19
|
+
if (!Modal.Data[idModal]) {
|
|
20
|
+
return await this.Render(options);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
// Track the username we're updating to
|
|
25
|
+
this.currentUsername = username;
|
|
26
|
+
|
|
27
|
+
// Ensure modal is in correct state
|
|
28
|
+
this._ensureModalState(idModal);
|
|
29
|
+
|
|
30
|
+
// Show loading state using Modal.writeHTML
|
|
31
|
+
const loadingHtml = this._getLoadingHtml(username);
|
|
32
|
+
Modal.writeHTML({ idModal, html: loadingHtml });
|
|
33
|
+
|
|
34
|
+
// Clean up existing profile data to avoid conflicts
|
|
35
|
+
this._cleanupProfileData({ idModal });
|
|
36
|
+
|
|
37
|
+
// Re-render the profile content with new user
|
|
38
|
+
const newContent = await this.Render({ ...options, disableUpdate: true });
|
|
39
|
+
|
|
40
|
+
// Update modal content using Modal.writeHTML with smooth transition
|
|
41
|
+
Modal.writeHTML({ idModal, html: newContent });
|
|
42
|
+
Modal.zIndexSync({ idModal });
|
|
43
|
+
|
|
44
|
+
if (Modal.mobileModal()) setTimeout(() => s(`.btn-close-modal-menu`).click());
|
|
45
|
+
|
|
46
|
+
this._addTransitionEffect(idModal);
|
|
47
|
+
return newContent;
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.error('Error updating profile:', error);
|
|
50
|
+
|
|
51
|
+
// Show error state using Modal.writeHTML
|
|
52
|
+
const errorHtml = this._getErrorHtml(username, error.message);
|
|
53
|
+
Modal.writeHTML({ idModal, html: errorHtml });
|
|
54
|
+
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
_getLoadingHtml: function (username) {
|
|
60
|
+
return html`
|
|
61
|
+
<div class="profile-loading-container">
|
|
62
|
+
<div class="profile-loading-spinner"></div>
|
|
63
|
+
<p class="profile-loading-text">Loading profile for @${username}...</p>
|
|
64
|
+
<style>
|
|
65
|
+
.profile-loading-container {
|
|
66
|
+
display: flex;
|
|
67
|
+
justify-content: center;
|
|
68
|
+
align-items: center;
|
|
69
|
+
height: 400px;
|
|
70
|
+
flex-direction: column;
|
|
71
|
+
gap: 20px;
|
|
72
|
+
}
|
|
73
|
+
.profile-loading-spinner {
|
|
74
|
+
width: 40px;
|
|
75
|
+
height: 40px;
|
|
76
|
+
border: 4px solid #f3f3f3;
|
|
77
|
+
border-top: 4px solid #3498db;
|
|
78
|
+
border-radius: 50%;
|
|
79
|
+
animation: profile-spin 1s linear infinite;
|
|
80
|
+
}
|
|
81
|
+
.profile-loading-text {
|
|
82
|
+
margin: 0;
|
|
83
|
+
color: #666;
|
|
84
|
+
font-size: 14px;
|
|
85
|
+
}
|
|
86
|
+
@keyframes profile-spin {
|
|
87
|
+
0% {
|
|
88
|
+
transform: rotate(0deg);
|
|
89
|
+
}
|
|
90
|
+
100% {
|
|
91
|
+
transform: rotate(360deg);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
</style>
|
|
95
|
+
</div>
|
|
96
|
+
`;
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
_addTransitionEffect: function (idModal) {
|
|
100
|
+
// Add smooth transition effect to modal content
|
|
101
|
+
const modalContent = s(`.html-${idModal}`);
|
|
102
|
+
if (modalContent) {
|
|
103
|
+
modalContent.style.transition = 'opacity 0.3s ease-in-out';
|
|
104
|
+
modalContent.style.opacity = '0';
|
|
105
|
+
|
|
106
|
+
// Fade in after a brief delay
|
|
107
|
+
setTimeout(() => {
|
|
108
|
+
modalContent.style.opacity = '1';
|
|
109
|
+
}, 50);
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
_getErrorHtml: function (username, errorMessage) {
|
|
114
|
+
return html`
|
|
115
|
+
<div class="profile-error-container">
|
|
116
|
+
<div class="profile-error-icon">
|
|
117
|
+
<i class="fas fa-exclamation-triangle"></i>
|
|
118
|
+
</div>
|
|
119
|
+
<h3 class="profile-error-title">Failed to load profile</h3>
|
|
120
|
+
<p class="profile-error-message">Could not load profile for @${username}</p>
|
|
121
|
+
<p class="profile-error-details">${errorMessage}</p>
|
|
122
|
+
<button class="profile-error-retry btn-retry-profile" onclick="location.reload()">
|
|
123
|
+
<i class="fas fa-redo"></i> Retry
|
|
124
|
+
</button>
|
|
125
|
+
<style>
|
|
126
|
+
.profile-error-container {
|
|
127
|
+
display: flex;
|
|
128
|
+
justify-content: center;
|
|
129
|
+
align-items: center;
|
|
130
|
+
height: 400px;
|
|
131
|
+
flex-direction: column;
|
|
132
|
+
gap: 15px;
|
|
133
|
+
text-align: center;
|
|
134
|
+
padding: 20px;
|
|
135
|
+
}
|
|
136
|
+
.profile-error-icon {
|
|
137
|
+
font-size: 48px;
|
|
138
|
+
color: #e74c3c;
|
|
139
|
+
}
|
|
140
|
+
.profile-error-title {
|
|
141
|
+
margin: 0;
|
|
142
|
+
color: #333;
|
|
143
|
+
font-size: 18px;
|
|
144
|
+
}
|
|
145
|
+
.profile-error-message {
|
|
146
|
+
margin: 0;
|
|
147
|
+
color: #666;
|
|
148
|
+
font-size: 14px;
|
|
149
|
+
}
|
|
150
|
+
.profile-error-details {
|
|
151
|
+
margin: 0;
|
|
152
|
+
color: #999;
|
|
153
|
+
font-size: 12px;
|
|
154
|
+
font-style: italic;
|
|
155
|
+
}
|
|
156
|
+
.profile-error-retry {
|
|
157
|
+
padding: 8px 16px;
|
|
158
|
+
background: #3498db;
|
|
159
|
+
color: white;
|
|
160
|
+
border: none;
|
|
161
|
+
border-radius: 4px;
|
|
162
|
+
cursor: pointer;
|
|
163
|
+
font-size: 14px;
|
|
164
|
+
transition: background-color 0.3s;
|
|
165
|
+
}
|
|
166
|
+
.profile-error-retry:hover {
|
|
167
|
+
background: #2980b9;
|
|
168
|
+
}
|
|
169
|
+
</style>
|
|
170
|
+
</div>
|
|
171
|
+
`;
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
_cleanupProfileData: function ({ idModal }) {
|
|
175
|
+
delete ThemeEvents[`error-state-${idModal}`];
|
|
176
|
+
delete ThemeEvents[`profile-${idModal}`];
|
|
177
|
+
delete this.Data[idModal];
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
_ensureModalState: function (idModal) {
|
|
181
|
+
// Ensure modal is in the correct state for content updates
|
|
182
|
+
if (Modal.Data[idModal]) {
|
|
183
|
+
// Reset any modal-specific states that might interfere
|
|
184
|
+
Modal.Data[idModal].updated = true;
|
|
185
|
+
Modal.Data[idModal].lastUpdated = Date.now();
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
Render: async function (
|
|
190
|
+
options = {
|
|
191
|
+
idModal: '',
|
|
192
|
+
user: {},
|
|
193
|
+
},
|
|
194
|
+
) {
|
|
195
|
+
let {
|
|
196
|
+
user: { _id: userId, username },
|
|
197
|
+
} = options;
|
|
198
|
+
const idModal = options.idModal || getId();
|
|
199
|
+
const profileId = `public-profile-${userId}`;
|
|
200
|
+
const waveAnimationId = `${profileId}-wave`;
|
|
201
|
+
const profileImageClass = `${profileId}-image`;
|
|
202
|
+
const profileContainerId = `${profileId}-container`;
|
|
203
|
+
const cardId = `${profileId}-card`;
|
|
204
|
+
|
|
205
|
+
if (!options.disableUpdate) {
|
|
206
|
+
const queryParams = getQueryParams();
|
|
207
|
+
const usernameFromPath = extractUsernameFromPath();
|
|
208
|
+
const cid = usernameFromPath || queryParams.cid || username;
|
|
209
|
+
const existingModal = s(`.${idModal}`);
|
|
210
|
+
if (existingModal && Modal.Data[idModal]) {
|
|
211
|
+
await PublicProfile.Update({
|
|
212
|
+
idModal,
|
|
213
|
+
user: { username: cid },
|
|
214
|
+
});
|
|
215
|
+
return;
|
|
216
|
+
} else username = cid;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Initialize data structure (Modal.Data pattern)
|
|
220
|
+
if (!PublicProfile.Data[profileId]) {
|
|
221
|
+
PublicProfile.Data[profileId] = {
|
|
222
|
+
userData: null,
|
|
223
|
+
colors: {},
|
|
224
|
+
updated: true,
|
|
225
|
+
lastUpdated: Date.now(),
|
|
226
|
+
};
|
|
227
|
+
} // Setup observer callback using Modal.Data pattern
|
|
228
|
+
|
|
229
|
+
// Get primary theme color for secondary colors
|
|
230
|
+
const getPrimaryThemeColor = () => {
|
|
231
|
+
const primaryColor = darkTheme ? subThemeManager.darkColor : subThemeManager.lightColor;
|
|
232
|
+
return primaryColor || (darkTheme ? '#3498db' : '#3498db');
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// Theme-aware color palette with subtheme secondary colors
|
|
236
|
+
const getThemeColors = () => {
|
|
237
|
+
const isDark = darkTheme;
|
|
238
|
+
const primaryColor = getPrimaryThemeColor();
|
|
239
|
+
const primaryColorLight = lightenHex(primaryColor, 0.8);
|
|
240
|
+
const primaryColorDark = darkenHex(primaryColor, 0.75);
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
// Background colors
|
|
244
|
+
bgPrimary: isDark ? 'rgba(25, 25, 30, 0.95)' : 'rgba(255, 255, 255, 0.95)',
|
|
245
|
+
bgSecondary: isDark ? 'rgba(35, 35, 40, 0.95)' : 'rgba(248, 249, 250, 0.95)',
|
|
246
|
+
bgGradient: isDark
|
|
247
|
+
? `linear-gradient(135deg, ${darkenHex(primaryColor, 0.9)}, ${darkenHex(primaryColor, 0.95)})`
|
|
248
|
+
: `linear-gradient(135deg, ${lightenHex(primaryColor, 0.92)}, ${lightenHex(primaryColor, 0.88)})`,
|
|
249
|
+
|
|
250
|
+
// Text colors
|
|
251
|
+
textPrimary: isDark ? '#e8e8e8' : '#1a1a1a',
|
|
252
|
+
textSecondary: isDark ? '#b0b0b0' : '#555555',
|
|
253
|
+
textTertiary: isDark ? '#808080' : '#999999',
|
|
254
|
+
|
|
255
|
+
// Border and divider
|
|
256
|
+
border: isDark ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.08)',
|
|
257
|
+
divider: isDark ? 'rgba(255, 255, 255, 0.08)' : 'rgba(0, 0, 0, 0.08)',
|
|
258
|
+
|
|
259
|
+
// Badge with subtheme
|
|
260
|
+
badgeBg: isDark ? `${darkenHex(primaryColor, 0.85)}33` : `${lightenHex(primaryColor, 0.92)}66`,
|
|
261
|
+
badgeBorder: isDark ? `${primaryColorLight}4d` : `${primaryColor}26`,
|
|
262
|
+
badgeText: isDark ? primaryColorLight : primaryColor,
|
|
263
|
+
|
|
264
|
+
// Button with subtheme gradient
|
|
265
|
+
buttonGradient: isDark
|
|
266
|
+
? `linear-gradient(135deg, ${primaryColorLight}, ${primaryColor})`
|
|
267
|
+
: `linear-gradient(135deg, ${primaryColor}, ${primaryColorDark})`,
|
|
268
|
+
buttonShadow: isDark ? `${primaryColor}40` : `${primaryColor}4d`,
|
|
269
|
+
buttonShadowHover: isDark ? `${primaryColor}66` : `${primaryColor}80`,
|
|
270
|
+
|
|
271
|
+
// Error
|
|
272
|
+
error: isDark ? '#ff6b6b' : '#e74c3c',
|
|
273
|
+
|
|
274
|
+
// Shadows
|
|
275
|
+
shadowSmall: isDark ? 'rgba(0, 0, 0, 0.3)' : 'rgba(0, 0, 0, 0.08)',
|
|
276
|
+
shadowMedium: isDark ? 'rgba(0, 0, 0, 0.4)' : 'rgba(0, 0, 0, 0.12)',
|
|
277
|
+
shadowLarge: isDark ? 'rgba(0, 0, 0, 0.5)' : 'rgba(0, 0, 0, 0.15)',
|
|
278
|
+
|
|
279
|
+
// Primary colors
|
|
280
|
+
primaryColor,
|
|
281
|
+
primaryColorLight,
|
|
282
|
+
primaryColorDark,
|
|
283
|
+
};
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
// Update error state theme colors dynamically
|
|
287
|
+
const updateErrorStateTheme = () => {
|
|
288
|
+
const errorStateEl = s(`.${profileContainerId}-error-state`);
|
|
289
|
+
if (!errorStateEl) return;
|
|
290
|
+
|
|
291
|
+
const colors = getThemeColors();
|
|
292
|
+
const primaryColor = getPrimaryThemeColor();
|
|
293
|
+
|
|
294
|
+
errorStateEl.style.background = `linear-gradient(135deg, ${colors.bgPrimary} 0%, ${colors.bgSecondary} 100%)`;
|
|
295
|
+
|
|
296
|
+
// Update icon container
|
|
297
|
+
const iconContainer = errorStateEl.querySelector('div[style*="border-radius: 50%"]');
|
|
298
|
+
if (iconContainer) {
|
|
299
|
+
iconContainer.style.background = `${colors.error}15`;
|
|
300
|
+
iconContainer.style.borderColor = `${colors.error}30`;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Update icon
|
|
304
|
+
const icon = errorStateEl.querySelector('i');
|
|
305
|
+
if (icon) {
|
|
306
|
+
icon.style.color = colors.error;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Update heading
|
|
310
|
+
const heading = errorStateEl.querySelector('h2');
|
|
311
|
+
if (heading) {
|
|
312
|
+
heading.style.color = colors.textPrimary;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Update description
|
|
316
|
+
const description = errorStateEl.querySelector('p');
|
|
317
|
+
if (description) {
|
|
318
|
+
description.style.color = colors.textSecondary;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Update buttons
|
|
322
|
+
const buttons = errorStateEl.querySelectorAll('a');
|
|
323
|
+
buttons.forEach((btn, index) => {
|
|
324
|
+
if (index === 0) {
|
|
325
|
+
// Home button
|
|
326
|
+
btn.style.background = colors.buttonGradient;
|
|
327
|
+
btn.style.boxShadow = `0 4px 12px ${colors.buttonShadow}`;
|
|
328
|
+
} else {
|
|
329
|
+
// Go Back button
|
|
330
|
+
btn.style.color = colors.primaryColor;
|
|
331
|
+
btn.style.borderColor = `${colors.primaryColor}40`;
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
// Register theme change handler
|
|
337
|
+
ThemeEvents[`error-state-${idModal}`] = updateErrorStateTheme;
|
|
338
|
+
setTimeout(updateErrorStateTheme);
|
|
339
|
+
|
|
340
|
+
const renderErrorState = (message, icon = 'fa-exclamation-circle', description = '') => {
|
|
341
|
+
const colors = getThemeColors();
|
|
342
|
+
return html`
|
|
343
|
+
<div
|
|
344
|
+
class="${profileContainerId}-error-state"
|
|
345
|
+
style="
|
|
346
|
+
display: flex;
|
|
347
|
+
flex-direction: column;
|
|
348
|
+
align-items: center;
|
|
349
|
+
justify-content: center;
|
|
350
|
+
padding: 60px 40px;
|
|
351
|
+
background: linear-gradient(135deg, ${colors.bgPrimary} 0%, ${colors.bgSecondary} 100%);
|
|
352
|
+
border-radius: 20px;
|
|
353
|
+
text-align: center;
|
|
354
|
+
"
|
|
355
|
+
>
|
|
356
|
+
<!-- Icon Container -->
|
|
357
|
+
<div
|
|
358
|
+
style="
|
|
359
|
+
width: 120px;
|
|
360
|
+
height: 120px;
|
|
361
|
+
border-radius: 50%;
|
|
362
|
+
background: ${colors.error}15;
|
|
363
|
+
border: 2px solid ${colors.error}30;
|
|
364
|
+
display: flex;
|
|
365
|
+
align-items: center;
|
|
366
|
+
justify-content: center;
|
|
367
|
+
margin-bottom: 32px;
|
|
368
|
+
"
|
|
369
|
+
>
|
|
370
|
+
<i
|
|
371
|
+
class="fa-solid ${icon}"
|
|
372
|
+
style="
|
|
373
|
+
font-size: 56px;
|
|
374
|
+
color: ${colors.error};
|
|
375
|
+
opacity: 0.9;
|
|
376
|
+
"
|
|
377
|
+
></i>
|
|
378
|
+
</div>
|
|
379
|
+
|
|
380
|
+
<!-- Main Message -->
|
|
381
|
+
<h2
|
|
382
|
+
style="
|
|
383
|
+
margin: 0 0 16px 0;
|
|
384
|
+
font-size: 24px;
|
|
385
|
+
font-weight: 600;
|
|
386
|
+
color: ${colors.textPrimary};
|
|
387
|
+
letter-spacing: -0.5px;
|
|
388
|
+
"
|
|
389
|
+
>
|
|
390
|
+
${message}
|
|
391
|
+
</h2>
|
|
392
|
+
|
|
393
|
+
<!-- Description/Documentation -->
|
|
394
|
+
${description
|
|
395
|
+
? html`<p
|
|
396
|
+
style="
|
|
397
|
+
margin: 0 0 32px 0;
|
|
398
|
+
font-size: 16px;
|
|
399
|
+
color: ${colors.textSecondary};
|
|
400
|
+
line-height: 1.6;
|
|
401
|
+
max-width: 500px;
|
|
402
|
+
"
|
|
403
|
+
>
|
|
404
|
+
${description}
|
|
405
|
+
</p>`
|
|
406
|
+
: ''}
|
|
407
|
+
|
|
408
|
+
<!-- Helpful Actions -->
|
|
409
|
+
<div
|
|
410
|
+
style="
|
|
411
|
+
display: flex;
|
|
412
|
+
gap: 12px;
|
|
413
|
+
flex-wrap: wrap;
|
|
414
|
+
justify-content: center;
|
|
415
|
+
margin-top: 24px;
|
|
416
|
+
"
|
|
417
|
+
>
|
|
418
|
+
<a
|
|
419
|
+
href="/"
|
|
420
|
+
style="
|
|
421
|
+
padding: 12px 28px;
|
|
422
|
+
background: ${colors.buttonGradient};
|
|
423
|
+
color: white;
|
|
424
|
+
text-decoration: none;
|
|
425
|
+
border-radius: 8px;
|
|
426
|
+
font-size: 14px;
|
|
427
|
+
font-weight: 500;
|
|
428
|
+
transition: all 0.3s ease;
|
|
429
|
+
box-shadow: 0 4px 12px ${colors.buttonShadow};
|
|
430
|
+
cursor: pointer;
|
|
431
|
+
display: inline-flex;
|
|
432
|
+
align-items: center;
|
|
433
|
+
gap: 8px;
|
|
434
|
+
"
|
|
435
|
+
onmouseover="this.style.transform='translateY(-2px)'; this.style.boxShadow='0 6px 16px ${colors.buttonShadowHover}';"
|
|
436
|
+
onmouseout="this.style.transform='translateY(0)'; this.style.boxShadow='0 4px 12px ${colors.buttonShadow}';"
|
|
437
|
+
>
|
|
438
|
+
<i class="fa-solid fa-home"></i>
|
|
439
|
+
${Translate.Render('go-home')}
|
|
440
|
+
</a>
|
|
441
|
+
<a
|
|
442
|
+
href="javascript:history.back()"
|
|
443
|
+
style="
|
|
444
|
+
padding: 12px 28px;
|
|
445
|
+
background: transparent;
|
|
446
|
+
color: ${colors.primaryColor};
|
|
447
|
+
text-decoration: none;
|
|
448
|
+
border: 2px solid ${colors.primaryColor}40;
|
|
449
|
+
border-radius: 8px;
|
|
450
|
+
font-size: 14px;
|
|
451
|
+
font-weight: 500;
|
|
452
|
+
transition: all 0.3s ease;
|
|
453
|
+
cursor: pointer;
|
|
454
|
+
display: inline-flex;
|
|
455
|
+
align-items: center;
|
|
456
|
+
gap: 8px;
|
|
457
|
+
"
|
|
458
|
+
onmouseover="this.style.background='${colors.primaryColor}08'; this.style.borderColor='${colors.primaryColor}60';"
|
|
459
|
+
onmouseout="this.style.background='transparent'; this.style.borderColor='${colors.primaryColor}40';"
|
|
460
|
+
>
|
|
461
|
+
<i class="fa-solid fa-arrow-left"></i>
|
|
462
|
+
${Translate.Render('go-back')}
|
|
463
|
+
</a>
|
|
464
|
+
</div>
|
|
465
|
+
</div>
|
|
466
|
+
`;
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
if (!userId && !username) {
|
|
470
|
+
return renderErrorState(
|
|
471
|
+
Translate.Render('user-not-found'),
|
|
472
|
+
'fa-user-slash',
|
|
473
|
+
"The user you're looking for could not be found. Please check the username and try again.",
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Fetch public user data
|
|
478
|
+
let userData = null;
|
|
479
|
+
try {
|
|
480
|
+
const result = await UserService.get({ id: `u/${username}` });
|
|
481
|
+
setTimeout(() => {
|
|
482
|
+
Modal.Data[idModal].onObserverListener['profile-card-observer'] = () => {
|
|
483
|
+
const modalHeight = s(`.${idModal}`).offsetHeight;
|
|
484
|
+
|
|
485
|
+
if (s(`.${profileContainerId}`)) s(`.${profileContainerId}`).style.height = `${modalHeight - 110}px`;
|
|
486
|
+
if (s(`.${profileContainerId}-error-state`))
|
|
487
|
+
s(`.${profileContainerId}-error-state`).style.height = `${modalHeight - 160}px`;
|
|
488
|
+
};
|
|
489
|
+
Modal.Data[idModal].onObserverListener['profile-card-observer']();
|
|
490
|
+
});
|
|
491
|
+
if (result.status === 'success') {
|
|
492
|
+
userData = result.data;
|
|
493
|
+
PublicProfile.Data[profileId].userData = userData;
|
|
494
|
+
|
|
495
|
+
// Track the currently displayed username for back/forward navigation
|
|
496
|
+
PublicProfile.currentUsername = userData.username || username;
|
|
497
|
+
|
|
498
|
+
// Update browser history to show clean URL after successful data fetch
|
|
499
|
+
if (userData.username) {
|
|
500
|
+
const cleanPath = `${getProxyPath()}u/${username}`;
|
|
501
|
+
setPath(cleanPath, { replace: true });
|
|
502
|
+
}
|
|
503
|
+
} else {
|
|
504
|
+
if (result.message && result.message.toLowerCase().match('private'))
|
|
505
|
+
return renderErrorState(
|
|
506
|
+
Translate.Render('profile-is-private'),
|
|
507
|
+
'fa-lock',
|
|
508
|
+
'This user has chosen to keep their profile private. Respect their privacy and check back later if they change their settings.',
|
|
509
|
+
);
|
|
510
|
+
else
|
|
511
|
+
return renderErrorState(
|
|
512
|
+
Translate.Render('user-not-found'),
|
|
513
|
+
'fa-user-slash',
|
|
514
|
+
'This user profile does not exist or has been removed. Please verify the username.',
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
} catch (error) {
|
|
518
|
+
console.error('Error fetching public profile:', error);
|
|
519
|
+
return renderErrorState(
|
|
520
|
+
Translate.Render('error-loading-profile'),
|
|
521
|
+
'fa-circle-exclamation',
|
|
522
|
+
'We encountered an issue loading this profile. Please try again in a moment or contact support if the problem persists.',
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// If user doesn't have public profile enabled
|
|
527
|
+
if (!userData.publicProfile) {
|
|
528
|
+
return renderErrorState(
|
|
529
|
+
Translate.Render('profile-is-private'),
|
|
530
|
+
'fa-lock',
|
|
531
|
+
'This user has chosen to keep their profile private. Respect their privacy and check back later if they change their settings.',
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Function to update profile card styles based on theme changes
|
|
536
|
+
const updateProfileCardTheme = () => {
|
|
537
|
+
const container = s(`.${profileContainerId}`);
|
|
538
|
+
if (!container) return;
|
|
539
|
+
|
|
540
|
+
const colors = getThemeColors();
|
|
541
|
+
PublicProfile.Data[profileId].colors = colors;
|
|
542
|
+
|
|
543
|
+
// Update container background
|
|
544
|
+
container.style.background = colors.bgGradient;
|
|
545
|
+
|
|
546
|
+
// Update card
|
|
547
|
+
const card = s(`.${cardId}`);
|
|
548
|
+
if (card) {
|
|
549
|
+
card.style.backgroundColor = colors.bgPrimary;
|
|
550
|
+
card.style.borderColor = colors.border;
|
|
551
|
+
card.style.boxShadow = `0 12px 48px ${colors.shadowMedium}`;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// Update text colors
|
|
555
|
+
const username_el = s(`.${profileId}-username`);
|
|
556
|
+
if (username_el) username_el.style.color = colors.textPrimary;
|
|
557
|
+
|
|
558
|
+
const description_els = document.querySelectorAll(`.${profileId}-description`);
|
|
559
|
+
description_els.forEach((el) => {
|
|
560
|
+
el.style.color = colors.textSecondary;
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
const badge_el = s(`.${profileId}-badge`);
|
|
564
|
+
if (badge_el) {
|
|
565
|
+
badge_el.style.backgroundColor = colors.badgeBg;
|
|
566
|
+
badge_el.style.borderColor = colors.badgeBorder;
|
|
567
|
+
const badgeText = badge_el.querySelector('span');
|
|
568
|
+
if (badgeText) badgeText.style.color = colors.badgeText;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
const divider = s(`.${profileId}-divider`);
|
|
572
|
+
if (divider) {
|
|
573
|
+
divider.style.background = `linear-gradient(90deg, transparent, ${colors.divider}, transparent)`;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const button = s(`.${profileId}-button`);
|
|
577
|
+
if (button) {
|
|
578
|
+
button.style.background = colors.buttonGradient;
|
|
579
|
+
button.style.boxShadow = `0 6px 16px ${colors.buttonShadow}`;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
const imageContainer = s(`.${profileId}-image-container`);
|
|
583
|
+
if (imageContainer) {
|
|
584
|
+
imageContainer.style.borderColor = colors.bgPrimary;
|
|
585
|
+
imageContainer.style.backgroundColor = colors.bgSecondary;
|
|
586
|
+
}
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
// Register theme change listener
|
|
590
|
+
ThemeEvents[`profile-${idModal}`] = updateProfileCardTheme;
|
|
591
|
+
|
|
592
|
+
// Schedule rendering of profile image after DOM is ready
|
|
593
|
+
setTimeout(async () => {
|
|
594
|
+
if (userData.profileImageId) {
|
|
595
|
+
try {
|
|
596
|
+
const imageSrc = `${getProxyPath()}api/file/blob/${userData.profileImageId}`;
|
|
597
|
+
const imgElement = s(`.${profileImageClass}`);
|
|
598
|
+
if (imgElement) {
|
|
599
|
+
imgElement.src = imageSrc;
|
|
600
|
+
imgElement.style.opacity = '1';
|
|
601
|
+
const loadingEl = s(`.${profileImageClass}-loading`);
|
|
602
|
+
if (loadingEl) {
|
|
603
|
+
loadingEl.style.display = 'none';
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
} catch (error) {
|
|
607
|
+
console.warn('Could not load profile image:', error);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
const colors = getThemeColors();
|
|
613
|
+
PublicProfile.Data[profileId].colors = colors;
|
|
614
|
+
|
|
615
|
+
return html`
|
|
616
|
+
<!-- Hero Section with Wave Background -->
|
|
617
|
+
<div
|
|
618
|
+
class="${profileContainerId}"
|
|
619
|
+
style="
|
|
620
|
+
position: relative;
|
|
621
|
+
background: ${colors.bgGradient};
|
|
622
|
+
overflow: visible;
|
|
623
|
+
display: flex;
|
|
624
|
+
align-items: flex-start;
|
|
625
|
+
justify-content: center;
|
|
626
|
+
padding-top: 40px;
|
|
627
|
+
padding-bottom: 20px;
|
|
628
|
+
padding-left: 20px;
|
|
629
|
+
padding-right: 20px;
|
|
630
|
+
transition: background 0.3s ease-in-out;
|
|
631
|
+
"
|
|
632
|
+
>
|
|
633
|
+
<!-- Wave Animation Background - Fixed Height -->
|
|
634
|
+
<div
|
|
635
|
+
class="${waveAnimationId}"
|
|
636
|
+
style="
|
|
637
|
+
position: absolute;
|
|
638
|
+
top: 0;
|
|
639
|
+
left: 0;
|
|
640
|
+
width: 100%;
|
|
641
|
+
height: 100%;
|
|
642
|
+
opacity: 0.5;
|
|
643
|
+
overflow: hidden;
|
|
644
|
+
"
|
|
645
|
+
>
|
|
646
|
+
${renderWave({ id: waveAnimationId })}
|
|
647
|
+
</div>
|
|
648
|
+
|
|
649
|
+
<!-- Profile Card Container - Half Overlapping -->
|
|
650
|
+
<div
|
|
651
|
+
class="${cardId}"
|
|
652
|
+
style="
|
|
653
|
+
position: relative;
|
|
654
|
+
top: 60px;
|
|
655
|
+
background: ${colors.bgPrimary};
|
|
656
|
+
backdrop-filter: blur(10px);
|
|
657
|
+
border-radius: 20px;
|
|
658
|
+
padding: 48px 32px 40px;
|
|
659
|
+
max-width: 520px;
|
|
660
|
+
width: 100%;
|
|
661
|
+
box-shadow: 0 12px 48px ${colors.shadowMedium};
|
|
662
|
+
border: 1px solid ${colors.border};
|
|
663
|
+
transition: all 0.3s ease-in-out;
|
|
664
|
+
margin-top: -40px;
|
|
665
|
+
"
|
|
666
|
+
>
|
|
667
|
+
<!-- Profile Image Container -->
|
|
668
|
+
<div
|
|
669
|
+
style="
|
|
670
|
+
display: flex;
|
|
671
|
+
justify-content: center;
|
|
672
|
+
margin-bottom: 36px;
|
|
673
|
+
"
|
|
674
|
+
>
|
|
675
|
+
<div
|
|
676
|
+
class="${profileId}-image-container"
|
|
677
|
+
style="
|
|
678
|
+
position: relative;
|
|
679
|
+
width: 160px;
|
|
680
|
+
height: 160px;
|
|
681
|
+
flex-shrink: 0;
|
|
682
|
+
border-radius: 50%;
|
|
683
|
+
border: 6px solid ${colors.bgPrimary};
|
|
684
|
+
background-color: ${colors.bgSecondary};
|
|
685
|
+
transition: all 0.3s ease-in-out;
|
|
686
|
+
overflow: hidden;
|
|
687
|
+
box-shadow: 0 12px 40px ${colors.shadowLarge};
|
|
688
|
+
"
|
|
689
|
+
>
|
|
690
|
+
<img
|
|
691
|
+
class="${profileImageClass}"
|
|
692
|
+
style="
|
|
693
|
+
width: 100%;
|
|
694
|
+
height: 100%;
|
|
695
|
+
border-radius: 50%;
|
|
696
|
+
opacity: 0;
|
|
697
|
+
transition: opacity 0.4s ease-in-out;
|
|
698
|
+
object-fit: cover;
|
|
699
|
+
"
|
|
700
|
+
alt="${userData.username}"
|
|
701
|
+
/>
|
|
702
|
+
<div
|
|
703
|
+
class="${profileImageClass}-loading"
|
|
704
|
+
style="
|
|
705
|
+
position: absolute;
|
|
706
|
+
top: 0;
|
|
707
|
+
left: 0;
|
|
708
|
+
width: 100%;
|
|
709
|
+
height: 100%;
|
|
710
|
+
border-radius: 50%;
|
|
711
|
+
display: flex;
|
|
712
|
+
align-items: center;
|
|
713
|
+
justify-content: center;
|
|
714
|
+
color: ${colors.textTertiary};
|
|
715
|
+
font-size: 48px;
|
|
716
|
+
transition: display 0.3s ease-in-out;
|
|
717
|
+
background: linear-gradient(135deg, ${colors.bgSecondary}, ${colors.bgPrimary});
|
|
718
|
+
"
|
|
719
|
+
>
|
|
720
|
+
<i class="fa-solid fa-user" style="opacity: 0.4;"></i>
|
|
721
|
+
</div>
|
|
722
|
+
</div>
|
|
723
|
+
</div>
|
|
724
|
+
|
|
725
|
+
<!-- Content Section -->
|
|
726
|
+
<div style="text-align: center;">
|
|
727
|
+
<!-- Username -->
|
|
728
|
+
<h1
|
|
729
|
+
class="${profileId}-username"
|
|
730
|
+
style="
|
|
731
|
+
margin: 0 0 12px 0;
|
|
732
|
+
font-size: 28px;
|
|
733
|
+
font-weight: 700;
|
|
734
|
+
color: ${colors.textPrimary};
|
|
735
|
+
letter-spacing: -0.5px;
|
|
736
|
+
transition: color 0.3s ease-in-out;
|
|
737
|
+
"
|
|
738
|
+
>
|
|
739
|
+
${userData.username}
|
|
740
|
+
</h1>
|
|
741
|
+
|
|
742
|
+
<!-- Brief Description / Bio -->
|
|
743
|
+
<div style="margin: 16px 0 28px 0;">
|
|
744
|
+
${userData.briefDescription
|
|
745
|
+
? html`<p
|
|
746
|
+
class="${profileId}-description"
|
|
747
|
+
style="
|
|
748
|
+
margin: 0;
|
|
749
|
+
font-size: 14px;
|
|
750
|
+
color: ${colors.textSecondary};
|
|
751
|
+
line-height: 1.6;
|
|
752
|
+
font-weight: 500;
|
|
753
|
+
transition: color 0.3s ease-in-out;
|
|
754
|
+
"
|
|
755
|
+
>
|
|
756
|
+
${userData.briefDescription}
|
|
757
|
+
</p>`
|
|
758
|
+
: html`<p
|
|
759
|
+
class="${profileId}-description"
|
|
760
|
+
style="
|
|
761
|
+
margin: 0;
|
|
762
|
+
font-size: 14px;
|
|
763
|
+
color: ${colors.textTertiary};
|
|
764
|
+
font-style: italic;
|
|
765
|
+
transition: color 0.3s ease-in-out;
|
|
766
|
+
"
|
|
767
|
+
>
|
|
768
|
+
${Translate.Render('no-description')}
|
|
769
|
+
</p>`}
|
|
770
|
+
</div>
|
|
771
|
+
|
|
772
|
+
<!-- Member Since Badge -->
|
|
773
|
+
<div
|
|
774
|
+
class="${profileId}-badge"
|
|
775
|
+
style="
|
|
776
|
+
display: inline-block;
|
|
777
|
+
background: ${colors.badgeBg};
|
|
778
|
+
border: 1px solid ${colors.badgeBorder};
|
|
779
|
+
border-radius: 24px;
|
|
780
|
+
padding: 8px 16px;
|
|
781
|
+
margin-bottom: 28px;
|
|
782
|
+
transition: all 0.3s ease-in-out;
|
|
783
|
+
"
|
|
784
|
+
>
|
|
785
|
+
<span
|
|
786
|
+
style="
|
|
787
|
+
font-size: 12px;
|
|
788
|
+
color: ${colors.badgeText};
|
|
789
|
+
font-weight: 600;
|
|
790
|
+
letter-spacing: 0.5px;
|
|
791
|
+
transition: color 0.3s ease-in-out;
|
|
792
|
+
"
|
|
793
|
+
>
|
|
794
|
+
<i class="fa-solid fa-calendar" style="margin-right: 6px;"></i>
|
|
795
|
+
${Translate.Render('member-since')}: ${new Date(userData.createdAt).toLocaleDateString()}
|
|
796
|
+
</span>
|
|
797
|
+
</div>
|
|
798
|
+
|
|
799
|
+
<!-- Divider -->
|
|
800
|
+
<div
|
|
801
|
+
class="${profileId}-divider"
|
|
802
|
+
style="
|
|
803
|
+
margin: 24px 0;
|
|
804
|
+
height: 1px;
|
|
805
|
+
background: linear-gradient(
|
|
806
|
+
90deg,
|
|
807
|
+
transparent,
|
|
808
|
+
${colors.divider},
|
|
809
|
+
transparent
|
|
810
|
+
);
|
|
811
|
+
transition: background 0.3s ease-in-out;
|
|
812
|
+
"
|
|
813
|
+
></div>
|
|
814
|
+
|
|
815
|
+
<!-- Action Links -->
|
|
816
|
+
<div style="display: flex; justify-content: center; gap: 12px; flex-wrap: wrap;">
|
|
817
|
+
<a
|
|
818
|
+
class="${profileId}-button"
|
|
819
|
+
href="javascript:void(0)"
|
|
820
|
+
title="${Translate.Render('view-profile')}"
|
|
821
|
+
style="
|
|
822
|
+
display: inline-flex;
|
|
823
|
+
align-items: center;
|
|
824
|
+
justify-content: center;
|
|
825
|
+
width: 50px;
|
|
826
|
+
height: 50px;
|
|
827
|
+
border-radius: 50%;
|
|
828
|
+
background: ${colors.buttonGradient};
|
|
829
|
+
color: white;
|
|
830
|
+
text-decoration: none;
|
|
831
|
+
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
832
|
+
cursor: pointer;
|
|
833
|
+
box-shadow: 0 6px 16px ${colors.buttonShadow};
|
|
834
|
+
font-size: 18px;
|
|
835
|
+
border: none;
|
|
836
|
+
"
|
|
837
|
+
onmouseover="
|
|
838
|
+
this.style.transform = 'translateY(-4px) scale(1.1)';
|
|
839
|
+
this.style.boxShadow = '0 10px 24px ${colors.buttonShadowHover}';
|
|
840
|
+
"
|
|
841
|
+
onmouseout="
|
|
842
|
+
this.style.transform = 'translateY(0) scale(1)';
|
|
843
|
+
this.style.boxShadow = '0 6px 16px ${colors.buttonShadow}';
|
|
844
|
+
"
|
|
845
|
+
>
|
|
846
|
+
<i class="fa-solid fa-user"></i>
|
|
847
|
+
</a>
|
|
848
|
+
</div>
|
|
849
|
+
</div>
|
|
850
|
+
</div>
|
|
851
|
+
</div>
|
|
852
|
+
`;
|
|
853
|
+
},
|
|
854
|
+
|
|
855
|
+
Router: async function (options = { idModal: '' }) {
|
|
856
|
+
const idModal = options.idModal || 'modal-public-profile';
|
|
857
|
+
// Register RouterEvents listener for back/forward navigation between profiles
|
|
858
|
+
// This ensures the profile updates when the user navigates through browser history
|
|
859
|
+
// Note: route id is 'u', modal id is 'modal-public-profile', button class is 'main-btn-public-profile'
|
|
860
|
+
RouterEvents[`${idModal}-navigation`] = async ({ route }) => {
|
|
861
|
+
if (route === 'u') {
|
|
862
|
+
const usernameFromPath = extractUsernameFromPath();
|
|
863
|
+
const queryParams = getQueryParams();
|
|
864
|
+
const cid = usernameFromPath || queryParams.cid;
|
|
865
|
+
|
|
866
|
+
if (!cid) return;
|
|
867
|
+
|
|
868
|
+
// Check if modal exists (could be behind another view modal like settings)
|
|
869
|
+
if (s(`.${idModal}`) && Modal.Data[idModal]) {
|
|
870
|
+
// Modal exists - bring to front and update if username changed
|
|
871
|
+
const currentUsername = PublicProfile.currentUsername;
|
|
872
|
+
if (currentUsername !== cid)
|
|
873
|
+
await PublicProfile.Update({
|
|
874
|
+
idModal,
|
|
875
|
+
user: { username: cid },
|
|
876
|
+
});
|
|
877
|
+
} else {
|
|
878
|
+
// Modal doesn't exist - open it
|
|
879
|
+
if (s('.main-btn-public-profile')) {
|
|
880
|
+
s('.main-btn-public-profile').click();
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
};
|
|
885
|
+
},
|
|
886
|
+
};
|
|
887
|
+
|
|
888
|
+
export { PublicProfile };
|