create-elit 3.2.6 → 3.2.8
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/dist/index.js +37 -319
- package/dist/templates/README.md +23 -0
- package/dist/templates/elit.config.ts +59 -0
- package/dist/templates/package.json +14 -0
- package/dist/templates/public/favicon.svg +22 -0
- package/dist/templates/public/index.html +14 -0
- package/dist/templates/src/client.ts +15 -0
- package/dist/templates/src/components/Footer.ts +20 -0
- package/dist/templates/src/components/Header.ts +70 -0
- package/dist/templates/src/components/index.ts +2 -0
- package/dist/templates/src/main.ts +22 -0
- package/dist/templates/src/pages/ChatListPage.ts +144 -0
- package/dist/templates/src/pages/ChatPage.ts +186 -0
- package/dist/templates/src/pages/ForgotPasswordPage.ts +110 -0
- package/dist/templates/src/pages/HomePage.ts +166 -0
- package/dist/templates/src/pages/LoginPage.ts +182 -0
- package/dist/templates/src/pages/PrivateChatPage.ts +268 -0
- package/dist/templates/src/pages/ProfilePage.ts +342 -0
- package/dist/templates/src/pages/RegisterPage.ts +230 -0
- package/dist/templates/src/router.ts +30 -0
- package/dist/templates/src/server.ts +595 -0
- package/dist/templates/src/styles.ts +1181 -0
- package/dist/templates/tsconfig.json +23 -0
- package/package.json +2 -3
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import { div, h2, h3, p, button, span, input, label } from 'elit/el';
|
|
2
|
+
import { createState, reactive, reactiveAs } from 'elit/state';
|
|
3
|
+
import type { Router } from 'elit';
|
|
4
|
+
|
|
5
|
+
export function ProfilePage(router: Router) {
|
|
6
|
+
const isEditing = createState(false);
|
|
7
|
+
const name = createState('');
|
|
8
|
+
const email = createState('');
|
|
9
|
+
const bio = createState('');
|
|
10
|
+
const location = createState('');
|
|
11
|
+
const website = createState('');
|
|
12
|
+
const isLoading = createState(false);
|
|
13
|
+
const isLoaded = createState(false);
|
|
14
|
+
const error = createState('');
|
|
15
|
+
|
|
16
|
+
const stats = {
|
|
17
|
+
projects: 0,
|
|
18
|
+
followers: 0,
|
|
19
|
+
following: 0,
|
|
20
|
+
stars: 0
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// Load profile data
|
|
24
|
+
const loadProfile = async () => {
|
|
25
|
+
const token = localStorage.getItem('token');
|
|
26
|
+
if (!token) {
|
|
27
|
+
router.push('/login');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const response = await fetch('/api/profile', {
|
|
33
|
+
method: 'GET',
|
|
34
|
+
headers: {
|
|
35
|
+
'Authorization': `Bearer ${token}`
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
if (response.status === 401) {
|
|
41
|
+
localStorage.removeItem('token');
|
|
42
|
+
localStorage.removeItem('user');
|
|
43
|
+
router.push('/login');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
throw new Error('Failed to load profile');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const data = await response.json();
|
|
50
|
+
const user = data.user;
|
|
51
|
+
|
|
52
|
+
name.value = user.name;
|
|
53
|
+
email.value = user.email;
|
|
54
|
+
bio.value = user.bio;
|
|
55
|
+
location.value = user.location;
|
|
56
|
+
website.value = user.website;
|
|
57
|
+
stats.projects = user.stats.projects;
|
|
58
|
+
stats.followers = user.stats.followers;
|
|
59
|
+
stats.following = user.stats.following;
|
|
60
|
+
stats.stars = user.stats.stars;
|
|
61
|
+
|
|
62
|
+
isLoaded.value = true;
|
|
63
|
+
} catch (err) {
|
|
64
|
+
error.value = 'Failed to load profile';
|
|
65
|
+
isLoaded.value = true;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Load profile on mount
|
|
70
|
+
loadProfile();
|
|
71
|
+
|
|
72
|
+
const handleSave = async () => {
|
|
73
|
+
isLoading.value = true;
|
|
74
|
+
error.value = '';
|
|
75
|
+
|
|
76
|
+
const token = localStorage.getItem('token');
|
|
77
|
+
if (!token) {
|
|
78
|
+
error.value = 'Not authenticated';
|
|
79
|
+
isLoading.value = false;
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const response = await fetch('/api/profile', {
|
|
85
|
+
method: 'PUT',
|
|
86
|
+
headers: {
|
|
87
|
+
'Content-Type': 'application/json',
|
|
88
|
+
'Authorization': `Bearer ${token}`
|
|
89
|
+
},
|
|
90
|
+
body: JSON.stringify({
|
|
91
|
+
name: name.value,
|
|
92
|
+
bio: bio.value,
|
|
93
|
+
location: location.value,
|
|
94
|
+
website: website.value
|
|
95
|
+
})
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if (!response.ok) {
|
|
99
|
+
throw new Error('Failed to update profile');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const data = await response.json();
|
|
103
|
+
const updatedUser = data.user;
|
|
104
|
+
|
|
105
|
+
// Update local storage
|
|
106
|
+
localStorage.setItem('user', JSON.stringify(updatedUser));
|
|
107
|
+
|
|
108
|
+
isLoading.value = false;
|
|
109
|
+
isEditing.value = false;
|
|
110
|
+
} catch (err) {
|
|
111
|
+
error.value = 'Failed to save profile';
|
|
112
|
+
isLoading.value = false;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const handleCancel = () => {
|
|
117
|
+
// Reset to original values from localStorage
|
|
118
|
+
const user = JSON.parse(localStorage.getItem('user') || '{}');
|
|
119
|
+
if (user.name) {
|
|
120
|
+
name.value = user.name;
|
|
121
|
+
}
|
|
122
|
+
if (user.email) {
|
|
123
|
+
email.value = user.email;
|
|
124
|
+
}
|
|
125
|
+
if (user.bio) {
|
|
126
|
+
bio.value = user.bio;
|
|
127
|
+
}
|
|
128
|
+
if (user.location) {
|
|
129
|
+
location.value = user.location;
|
|
130
|
+
}
|
|
131
|
+
if (user.website) {
|
|
132
|
+
website.value = user.website;
|
|
133
|
+
}
|
|
134
|
+
isEditing.value = false;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const handleLogout = () => {
|
|
138
|
+
localStorage.removeItem('token');
|
|
139
|
+
localStorage.removeItem('user');
|
|
140
|
+
// Dispatch custom event to notify Header
|
|
141
|
+
window.dispatchEvent(new Event('elit:storage'));
|
|
142
|
+
router.push('/');
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
return div({ className: 'profile-page' },
|
|
146
|
+
// Error display
|
|
147
|
+
reactive(error, (err) => err ? div({ className: 'auth-error' }, err) : null),
|
|
148
|
+
|
|
149
|
+
// Profile Header
|
|
150
|
+
div({ className: 'profile-header-section' },
|
|
151
|
+
div({ className: 'profile-cover' }),
|
|
152
|
+
div({ className: 'profile-avatar-section' },
|
|
153
|
+
div({ className: 'profile-avatar' },
|
|
154
|
+
reactive(name, (nm) => nm ? nm.charAt(0).toUpperCase() : '?')
|
|
155
|
+
),
|
|
156
|
+
button({ className: 'avatar-edit-button' }, '📷')
|
|
157
|
+
),
|
|
158
|
+
),
|
|
159
|
+
|
|
160
|
+
// Profile Content
|
|
161
|
+
reactive(isLoaded, (loaded) => {
|
|
162
|
+
if (!loaded) {
|
|
163
|
+
return div({ className: 'profile-content' }, p('Loading...'));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return div({ className: 'profile-content' },
|
|
167
|
+
// Left column
|
|
168
|
+
div({ className: 'profile-sidebar' },
|
|
169
|
+
div({ className: 'profile-card' },
|
|
170
|
+
reactive(isEditing, (editing) => {
|
|
171
|
+
if (editing) {
|
|
172
|
+
return div({ className: 'edit-form' },
|
|
173
|
+
div({ className: 'form-group' },
|
|
174
|
+
label({ className: 'form-label' }, 'Display Name'),
|
|
175
|
+
input({
|
|
176
|
+
type: 'text',
|
|
177
|
+
className: 'form-input',
|
|
178
|
+
value: name.value,
|
|
179
|
+
oninput: (e: Event) => { name.value = (e.target as HTMLInputElement).value; }
|
|
180
|
+
})
|
|
181
|
+
),
|
|
182
|
+
div({ className: 'form-group' },
|
|
183
|
+
label({ className: 'form-label' }, 'Email'),
|
|
184
|
+
input({
|
|
185
|
+
type: 'email',
|
|
186
|
+
className: 'form-input',
|
|
187
|
+
value: email.value,
|
|
188
|
+
disabled: true
|
|
189
|
+
})
|
|
190
|
+
)
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
return div({ className: 'profile-info-display' },
|
|
194
|
+
reactive(name, (nm) => h3({ className: 'profile-name' }, nm)),
|
|
195
|
+
reactive(email, (em) => p({ className: 'profile-email' }, em))
|
|
196
|
+
);
|
|
197
|
+
}),
|
|
198
|
+
|
|
199
|
+
div({ className: 'profile-meta' },
|
|
200
|
+
div({ className: 'meta-item' },
|
|
201
|
+
span({ className: 'meta-icon' }, '📍'),
|
|
202
|
+
reactive(isEditing, (editing) => {
|
|
203
|
+
if (editing) {
|
|
204
|
+
return input({
|
|
205
|
+
type: 'text',
|
|
206
|
+
className: 'form-input form-input-sm',
|
|
207
|
+
value: location.value,
|
|
208
|
+
oninput: (e: Event) => { location.value = (e.target as HTMLInputElement).value; }
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
return span({ className: 'meta-text' }, location.value || 'No location');
|
|
212
|
+
})
|
|
213
|
+
),
|
|
214
|
+
div({ className: 'meta-item' },
|
|
215
|
+
span({ className: 'meta-icon' }, '🔗'),
|
|
216
|
+
reactive(isEditing, (editing) => {
|
|
217
|
+
if (editing) {
|
|
218
|
+
return input({
|
|
219
|
+
type: 'url',
|
|
220
|
+
className: 'form-input form-input-sm',
|
|
221
|
+
value: website.value,
|
|
222
|
+
oninput: (e: Event) => { website.value = (e.target as HTMLInputElement).value; }
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
return span({ className: 'meta-text' }, website.value || 'No website')
|
|
226
|
+
})
|
|
227
|
+
)
|
|
228
|
+
),
|
|
229
|
+
|
|
230
|
+
// Use reactiveAs with a stable div wrapper to handle structure changes
|
|
231
|
+
reactiveAs('div', isEditing, (editing) => {
|
|
232
|
+
if (editing) {
|
|
233
|
+
return div({ className: 'edit-actions' },
|
|
234
|
+
button({
|
|
235
|
+
className: 'btn btn-secondary',
|
|
236
|
+
onclick: () => {
|
|
237
|
+
console.log('Cancel button clicked');
|
|
238
|
+
const user = JSON.parse(localStorage.getItem('user') || '{}');
|
|
239
|
+
console.log('User from localStorage:', user);
|
|
240
|
+
name.value = user.name || '';
|
|
241
|
+
email.value = user.email || '';
|
|
242
|
+
bio.value = user.bio || '';
|
|
243
|
+
location.value = user.location || '';
|
|
244
|
+
website.value = user.website || '';
|
|
245
|
+
console.log('States reset, setting isEditing to false');
|
|
246
|
+
isEditing.value = false;
|
|
247
|
+
console.log('isEditing.value:', isEditing.value);
|
|
248
|
+
},
|
|
249
|
+
disabled: isLoading.value
|
|
250
|
+
}, 'Cancel'),
|
|
251
|
+
button({
|
|
252
|
+
className: 'btn btn-primary',
|
|
253
|
+
onclick: handleSave,
|
|
254
|
+
disabled: isLoading.value
|
|
255
|
+
}, isLoading.value ? 'Saving...' : 'Save Changes')
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
return button({
|
|
259
|
+
className: 'btn btn-primary btn-block',
|
|
260
|
+
onclick: () => isEditing.value = true
|
|
261
|
+
}, 'Edit Profile');
|
|
262
|
+
}, { className: 'profile-action-wrapper' })
|
|
263
|
+
)
|
|
264
|
+
),
|
|
265
|
+
|
|
266
|
+
// Right column
|
|
267
|
+
div({ className: 'profile-main' },
|
|
268
|
+
// Stats Card
|
|
269
|
+
div({ className: 'stats-grid' },
|
|
270
|
+
div({ className: 'stat-card' },
|
|
271
|
+
span({ className: 'stat-icon' }, '📁'),
|
|
272
|
+
div({ className: 'stat-info' },
|
|
273
|
+
span({ className: 'stat-value' }, stats.projects.toString()),
|
|
274
|
+
span({ className: 'stat-label' }, 'Projects')
|
|
275
|
+
)
|
|
276
|
+
),
|
|
277
|
+
div({ className: 'stat-card' },
|
|
278
|
+
span({ className: 'stat-icon' }, '⭐'),
|
|
279
|
+
div({ className: 'stat-info' },
|
|
280
|
+
span({ className: 'stat-value' }, stats.stars.toString()),
|
|
281
|
+
span({ className: 'stat-label' }, 'Stars')
|
|
282
|
+
)
|
|
283
|
+
),
|
|
284
|
+
div({ className: 'stat-card' },
|
|
285
|
+
span({ className: 'stat-icon' }, '👥'),
|
|
286
|
+
div({ className: 'stat-info' },
|
|
287
|
+
span({ className: 'stat-value' }, stats.followers.toString()),
|
|
288
|
+
span({ className: 'stat-label' }, 'Followers')
|
|
289
|
+
)
|
|
290
|
+
),
|
|
291
|
+
div({ className: 'stat-card' },
|
|
292
|
+
span({ className: 'stat-icon' }, '👣'),
|
|
293
|
+
div({ className: 'stat-info' },
|
|
294
|
+
span({ className: 'stat-value' }, stats.following.toString()),
|
|
295
|
+
span({ className: 'stat-label' }, 'Following')
|
|
296
|
+
)
|
|
297
|
+
)
|
|
298
|
+
),
|
|
299
|
+
|
|
300
|
+
// About Card
|
|
301
|
+
div({ className: 'profile-card' },
|
|
302
|
+
h3({ className: 'card-title' }, 'About'),
|
|
303
|
+
reactive(isEditing, (editing) => {
|
|
304
|
+
if (editing) {
|
|
305
|
+
return div({ className: 'form-group' },
|
|
306
|
+
label({ className: 'form-label' }, 'Bio'),
|
|
307
|
+
input({
|
|
308
|
+
type: 'text',
|
|
309
|
+
className: 'form-input',
|
|
310
|
+
value: bio.value,
|
|
311
|
+
oninput: (e: Event) => { bio.value = (e.target as HTMLInputElement).value; }
|
|
312
|
+
})
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
return reactive(bio, (b) => p({ className: 'profile-bio' }, b || 'No bio yet'));
|
|
316
|
+
})
|
|
317
|
+
),
|
|
318
|
+
|
|
319
|
+
// Activity Card
|
|
320
|
+
div({ className: 'profile-card' },
|
|
321
|
+
h3({ className: 'card-title' }, 'Recent Activity'),
|
|
322
|
+
div({ className: 'activity-list' },
|
|
323
|
+
div({ className: 'activity-item' },
|
|
324
|
+
span({ className: 'activity-icon' }, '🚀'),
|
|
325
|
+
div({ className: 'activity-content' },
|
|
326
|
+
p({ className: 'activity-title' }, 'Account created'),
|
|
327
|
+
p({ className: 'activity-time' }, 'Just now')
|
|
328
|
+
)
|
|
329
|
+
)
|
|
330
|
+
)
|
|
331
|
+
),
|
|
332
|
+
|
|
333
|
+
// Actions
|
|
334
|
+
div({ className: 'profile-actions' },
|
|
335
|
+
button({ className: 'btn btn-outline btn-block' }, 'View Public Profile'),
|
|
336
|
+
button({ className: 'btn btn-secondary btn-block', onclick: handleLogout }, 'Logout')
|
|
337
|
+
)
|
|
338
|
+
)
|
|
339
|
+
);
|
|
340
|
+
})
|
|
341
|
+
);
|
|
342
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { div, h1, h2, p, input, label, button, form, span } from 'elit/el';
|
|
2
|
+
import { createState, reactive } from 'elit/state';
|
|
3
|
+
import type { Router } from 'elit';
|
|
4
|
+
|
|
5
|
+
export function RegisterPage(router: Router) {
|
|
6
|
+
// Check if already logged in
|
|
7
|
+
const token = localStorage.getItem('token');
|
|
8
|
+
if (token) {
|
|
9
|
+
router.push('/profile');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const name = createState('');
|
|
13
|
+
const email = createState('');
|
|
14
|
+
const password = createState('');
|
|
15
|
+
const confirmPassword = createState('');
|
|
16
|
+
const error = createState('');
|
|
17
|
+
const isLoading = createState(false);
|
|
18
|
+
|
|
19
|
+
const handleRegister = async (e: Event) => {
|
|
20
|
+
e.preventDefault();
|
|
21
|
+
|
|
22
|
+
if (!name.value || !email.value || !password.value || !confirmPassword.value) {
|
|
23
|
+
error.value = 'Please fill in all fields';
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!email.value.includes('@')) {
|
|
28
|
+
error.value = 'Please enter a valid email';
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (password.value.length < 6) {
|
|
33
|
+
error.value = 'Password must be at least 6 characters';
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (password.value !== confirmPassword.value) {
|
|
38
|
+
error.value = 'Passwords do not match';
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
isLoading.value = true;
|
|
43
|
+
error.value = '';
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const response = await fetch('/api/auth/register', {
|
|
47
|
+
method: 'POST',
|
|
48
|
+
headers: {
|
|
49
|
+
'Content-Type': 'application/json'
|
|
50
|
+
},
|
|
51
|
+
body: JSON.stringify({
|
|
52
|
+
name: name.value,
|
|
53
|
+
email: email.value,
|
|
54
|
+
password: password.value
|
|
55
|
+
})
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const data = await response.json();
|
|
59
|
+
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
error.value = data.error || 'Registration failed';
|
|
62
|
+
isLoading.value = false;
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Store token and user data
|
|
67
|
+
localStorage.setItem('token', data.token);
|
|
68
|
+
localStorage.setItem('user', JSON.stringify(data.user));
|
|
69
|
+
|
|
70
|
+
// Dispatch custom event to notify other components (like Header)
|
|
71
|
+
window.dispatchEvent(new Event('elit:storage'));
|
|
72
|
+
|
|
73
|
+
isLoading.value = false;
|
|
74
|
+
router.push('/profile');
|
|
75
|
+
} catch (err) {
|
|
76
|
+
isLoading.value = false;
|
|
77
|
+
error.value = 'Network error. Please try again.';
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
return div({ className: 'auth-page' },
|
|
82
|
+
div({ className: 'auth-container' },
|
|
83
|
+
// Left side - branding
|
|
84
|
+
div({ className: 'auth-branding' },
|
|
85
|
+
div({ className: 'branding-content' },
|
|
86
|
+
h1({ className: 'branding-title' }, 'Join Us Today'),
|
|
87
|
+
p({ className: 'branding-description' },
|
|
88
|
+
'Create your account and start building amazing applications in minutes.'
|
|
89
|
+
),
|
|
90
|
+
div({ className: 'branding-features' },
|
|
91
|
+
div({ className: 'branding-feature' },
|
|
92
|
+
span({ className: 'feature-icon' }, '✓'),
|
|
93
|
+
span({ className: 'feature-text' }, 'Free forever for personal projects')
|
|
94
|
+
),
|
|
95
|
+
div({ className: 'branding-feature' },
|
|
96
|
+
span({ className: 'feature-icon' }, '✓'),
|
|
97
|
+
span({ className: 'feature-text' }, 'No credit card required')
|
|
98
|
+
),
|
|
99
|
+
div({ className: 'branding-feature' },
|
|
100
|
+
span({ className: 'feature-icon' }, '✓'),
|
|
101
|
+
span({ className: 'feature-text' }, 'Instant setup & deployment')
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
),
|
|
106
|
+
|
|
107
|
+
// Right side - form
|
|
108
|
+
div({ className: 'auth-form-wrapper' },
|
|
109
|
+
div({ className: 'auth-form-card' },
|
|
110
|
+
div({ className: 'auth-header' },
|
|
111
|
+
h2({ className: 'auth-title' }, 'Create Account'),
|
|
112
|
+
p({ className: 'auth-subtitle' }, 'Sign up to get started with your free account')
|
|
113
|
+
),
|
|
114
|
+
|
|
115
|
+
reactive(error, (err) => err ? div({ className: 'auth-error' }, err) : null),
|
|
116
|
+
|
|
117
|
+
form({ onsubmit: handleRegister },
|
|
118
|
+
div({ className: 'form-group' },
|
|
119
|
+
label({ htmlFor: 'name', className: 'form-label' }, 'Full Name'),
|
|
120
|
+
div({ className: 'input-wrapper' },
|
|
121
|
+
span({ className: 'input-icon' }, '👤'),
|
|
122
|
+
input({
|
|
123
|
+
type: 'text',
|
|
124
|
+
id: 'name',
|
|
125
|
+
className: 'form-input',
|
|
126
|
+
placeholder: 'John Doe',
|
|
127
|
+
value: name.value,
|
|
128
|
+
oninput: (e: Event) => {
|
|
129
|
+
name.value = (e.target as HTMLInputElement).value;
|
|
130
|
+
error.value = '';
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
)
|
|
134
|
+
),
|
|
135
|
+
|
|
136
|
+
div({ className: 'form-group' },
|
|
137
|
+
label({ htmlFor: 'email', className: 'form-label' }, 'Email Address'),
|
|
138
|
+
div({ className: 'input-wrapper' },
|
|
139
|
+
span({ className: 'input-icon' }, '📧'),
|
|
140
|
+
input({
|
|
141
|
+
type: 'email',
|
|
142
|
+
id: 'email',
|
|
143
|
+
className: 'form-input',
|
|
144
|
+
placeholder: 'your@email.com',
|
|
145
|
+
value: email.value,
|
|
146
|
+
oninput: (e: Event) => {
|
|
147
|
+
email.value = (e.target as HTMLInputElement).value;
|
|
148
|
+
error.value = '';
|
|
149
|
+
}
|
|
150
|
+
})
|
|
151
|
+
)
|
|
152
|
+
),
|
|
153
|
+
|
|
154
|
+
div({ className: 'form-group' },
|
|
155
|
+
label({ htmlFor: 'password', className: 'form-label' }, 'Password'),
|
|
156
|
+
div({ className: 'input-wrapper' },
|
|
157
|
+
span({ className: 'input-icon' }, '🔒'),
|
|
158
|
+
input({
|
|
159
|
+
type: 'password',
|
|
160
|
+
id: 'password',
|
|
161
|
+
className: 'form-input',
|
|
162
|
+
placeholder: '••••••••',
|
|
163
|
+
value: password.value,
|
|
164
|
+
oninput: (e: Event) => {
|
|
165
|
+
password.value = (e.target as HTMLInputElement).value;
|
|
166
|
+
error.value = '';
|
|
167
|
+
}
|
|
168
|
+
})
|
|
169
|
+
)
|
|
170
|
+
),
|
|
171
|
+
|
|
172
|
+
div({ className: 'form-group' },
|
|
173
|
+
label({ htmlFor: 'confirmPassword', className: 'form-label' }, 'Confirm Password'),
|
|
174
|
+
div({ className: 'input-wrapper' },
|
|
175
|
+
span({ className: 'input-icon' }, '🔒'),
|
|
176
|
+
input({
|
|
177
|
+
type: 'password',
|
|
178
|
+
id: 'confirmPassword',
|
|
179
|
+
className: 'form-input',
|
|
180
|
+
placeholder: '••••••••',
|
|
181
|
+
value: confirmPassword.value,
|
|
182
|
+
oninput: (e: Event) => {
|
|
183
|
+
confirmPassword.value = (e.target as HTMLInputElement).value;
|
|
184
|
+
error.value = '';
|
|
185
|
+
}
|
|
186
|
+
})
|
|
187
|
+
)
|
|
188
|
+
),
|
|
189
|
+
|
|
190
|
+
div({ className: 'form-options' },
|
|
191
|
+
label({ className: 'checkbox-label' },
|
|
192
|
+
input({ type: 'checkbox', className: 'checkbox' }),
|
|
193
|
+
span({ className: 'checkbox-text' }, 'I agree to the Terms of Service and Privacy Policy')
|
|
194
|
+
)
|
|
195
|
+
),
|
|
196
|
+
|
|
197
|
+
button({
|
|
198
|
+
type: 'submit',
|
|
199
|
+
className: 'btn btn-primary btn-block btn-lg',
|
|
200
|
+
disabled: isLoading.value
|
|
201
|
+
}, isLoading.value ? 'Creating account...' : 'Create Account')
|
|
202
|
+
),
|
|
203
|
+
|
|
204
|
+
div({ className: 'auth-divider' }, 'OR'),
|
|
205
|
+
|
|
206
|
+
div({ className: 'social-login' },
|
|
207
|
+
button({ className: 'social-button' },
|
|
208
|
+
span({ className: 'social-icon' }, 'G'),
|
|
209
|
+
'Sign up with Google'
|
|
210
|
+
),
|
|
211
|
+
button({ className: 'social-button' },
|
|
212
|
+
span({ className: 'social-icon' }, 'Gh'),
|
|
213
|
+
'Sign up with GitHub'
|
|
214
|
+
)
|
|
215
|
+
),
|
|
216
|
+
|
|
217
|
+
div({ className: 'auth-footer' },
|
|
218
|
+
p({ className: 'footer-text' },
|
|
219
|
+
'Already have an account? ',
|
|
220
|
+
button({
|
|
221
|
+
className: 'link-button-inline',
|
|
222
|
+
onclick: () => router.push('/login')
|
|
223
|
+
}, 'Sign in')
|
|
224
|
+
)
|
|
225
|
+
)
|
|
226
|
+
)
|
|
227
|
+
)
|
|
228
|
+
)
|
|
229
|
+
);
|
|
230
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createRouter, createRouterView, type RouteParams, type Router } from 'elit';
|
|
2
|
+
import { HomePage } from './pages/HomePage';
|
|
3
|
+
import { LoginPage } from './pages/LoginPage';
|
|
4
|
+
import { RegisterPage } from './pages/RegisterPage';
|
|
5
|
+
import { ProfilePage } from './pages/ProfilePage';
|
|
6
|
+
import { ForgotPasswordPage } from './pages/ForgotPasswordPage';
|
|
7
|
+
import { ChatPage } from './pages/ChatPage';
|
|
8
|
+
import { ChatListPage } from './pages/ChatListPage';
|
|
9
|
+
import { PrivateChatPage } from './pages/PrivateChatPage';
|
|
10
|
+
|
|
11
|
+
// Initialize router
|
|
12
|
+
export const router = createRouter({
|
|
13
|
+
mode: 'hash',
|
|
14
|
+
base: '/',
|
|
15
|
+
routes: []
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// Define routes
|
|
19
|
+
const routes = [
|
|
20
|
+
{ path: '/', component: () => HomePage(router) },
|
|
21
|
+
{ path: '/login', component: () => LoginPage(router) },
|
|
22
|
+
{ path: '/register', component: () => RegisterPage(router) },
|
|
23
|
+
{ path: '/profile', component: () => ProfilePage(router) },
|
|
24
|
+
{ path: '/forgot-password', component: () => ForgotPasswordPage(router) },
|
|
25
|
+
{ path: '/chat', component: () => ChatPage(router) },
|
|
26
|
+
{ path: '/chat/list', component: () => ChatListPage(router) },
|
|
27
|
+
{ path: '/chat/dm/:userId', component: (params: RouteParams) => PrivateChatPage(router, params.userId as string) }
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
export const RouterView = createRouterView(router, { mode: 'hash', routes });
|