Skip to content

Commit 06551ef

Browse files
committed
👀 feat: Implement initial client-side views including file upload, history, and general layout components.
1 parent 1d9cfe1 commit 06551ef

File tree

8 files changed

+97
-138
lines changed

8 files changed

+97
-138
lines changed

‎src/client/views/components/header.ejs‎

Lines changed: 55 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,22 @@
2828
</button>
2929

3030
<div class="relative">
31-
<button type="button" data-dropdown-toggle="user-menu" data-dropdown-placement="bottom-end" class="flex items-center gap-2 rounded-full border border-slate-200 bg-white p-0 shadow-sm hover:border-slate-300" aria-expanded="false" aria-haspopup="true">
32-
<img id="user-avatar" src="https://ui-avatars.com/api/?name=User&background=0f766e&color=fff" alt="Avatar" class="w-9 h-9 rounded-full">
33-
<!-- <svg class="w-4 h-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
34-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
35-
</svg> -->
31+
<button type="button" id="avatar-container" data-dropdown-toggle="user-menu" data-dropdown-placement="bottom-end" class="flex items-center gap-2 rounded-full border border-slate-200 bg-white p-0 shadow-sm hover:border-slate-300" aria-expanded="false" aria-haspopup="true">
32+
<img id="user-avatar" src="https://ui-avatars.com/api/?name=User&background=0f766e&color=fff" alt="Avatar" class="w-9 h-9 rounded-full object-cover">
33+
<input type="file" id="avatar-input" class="hidden" accept="image/jpeg,image/png,image/webp">
3634
</button>
3735

3836
<div id="user-menu" class="hidden absolute right-0 mt-2 w-64 rounded-2xl border border-slate-200 bg-white p-3 shadow-lg">
3937
<div class="flex items-center gap-3 border-b border-slate-100 pb-3">
40-
<img id="user-avatar-menu" src="https://ui-avatars.com/api/?name=User&background=0f766e&color=fff" alt="Avatar" class="w-10 h-10 rounded-full">
38+
<div class="relative group">
39+
<img id="user-avatar-menu" src="https://ui-avatars.com/api/?name=User&background=0f766e&color=fff" alt="Avatar" class="w-10 h-10 rounded-full object-cover">
40+
<button id="change-avatar-btn" class="absolute inset-0 bg-black/40 rounded-full flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity text-white" title="Change Avatar">
41+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
42+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"></path>
43+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 13a3 3 0 11-6 0 3 3 0 016 0z"></path>
44+
</svg>
45+
</button>
46+
</div>
4147
<div>
4248
<p id="user-name" class="text-sm font-semibold text-slate-900">User</p>
4349
<p id="user-email" class="text-xs text-slate-500">user@stackdev.cloud</p>
@@ -62,62 +68,58 @@
6268
</div>
6369
</header>
6470

65-
<script>
66-
(function () {
67-
const logoutButton = document.getElementById("logout-button");
71+
<script type="module">
72+
import { authProvider } from '/utils/auth.js';
73+
74+
document.addEventListener("DOMContentLoaded", () => {
6875
const userName = document.getElementById("user-name");
6976
const userEmail = document.getElementById("user-email");
7077
const userAvatar = document.getElementById("user-avatar");
7178
const userAvatarMenu = document.getElementById("user-avatar-menu");
79+
const avatarInput = document.getElementById("avatar-input");
80+
const changeAvatarBtn = document.getElementById("change-avatar-btn");
81+
const logoutButton = document.getElementById("logout-button");
7282
73-
const readCookie = (name) => {
74-
return document.cookie
75-
.split(";")
76-
.map((cookie) => cookie.trim())
77-
.find((cookie) => cookie.startsWith(`${name}=`))
78-
?.split("=")[1];
79-
};
80-
81-
const clearCookie = (name) => {
82-
document.cookie = `${name}=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax`;
83-
};
83+
authProvider.subscribe((user) => {
84+
if (user) {
85+
if (userName) userName.textContent = user.name;
86+
if (userEmail) userEmail.textContent = user.email || "";
87+
88+
const defaultAvatar = `https://ui-avatars.com/api/?name=${encodeURIComponent(user.name)}&background=0f766e&color=fff`;
89+
const avatarUrl = user.avatar || defaultAvatar;
8490
85-
try {
86-
const storedUser = localStorage.getItem("user");
87-
if (storedUser) {
88-
const parsed = JSON.parse(storedUser);
89-
if (parsed?.name && userName) userName.textContent = parsed.name;
90-
if (parsed?.email && userEmail) userEmail.textContent = parsed.email;
91-
if (parsed?.name && userAvatar && userAvatarMenu) {
92-
const avatarUrl = `https://ui-avatars.com/api/?name=${encodeURIComponent(parsed.name)}&background=0f766e&color=fff`;
93-
userAvatar.src = avatarUrl;
94-
userAvatarMenu.src = avatarUrl;
95-
}
91+
if (userAvatar) userAvatar.src = avatarUrl;
92+
if (userAvatarMenu) userAvatarMenu.src = avatarUrl;
9693
}
97-
} catch (error) {
98-
// Ignore localStorage errors
99-
}
94+
});
10095
101-
if (!logoutButton) return;
96+
if (changeAvatarBtn && avatarInput) {
97+
changeAvatarBtn.addEventListener("click", (e) => {
98+
e.stopPropagation();
99+
avatarInput.click();
100+
});
102101
103-
logoutButton.addEventListener("click", async () => {
104-
const token = localStorage.getItem("accessToken") || readCookie("accessToken");
102+
avatarInput.addEventListener("change", async (e) => {
103+
const file = e.target.files[0];
104+
if (!file) return;
105105
106-
if (token) {
107-
await fetch("/api/auth/logout", {
108-
method: "POST",
109-
headers: {
110-
Authorization: `Bearer ${token}`
111-
}
112-
}).catch(() => null);
113-
}
106+
try {
107+
changeAvatarBtn.disabled = true;
108+
await authProvider.uploadAvatar(file);
109+
} catch (err) {
110+
console.error("Upload error:", err);
111+
alert(err.message || "Failed to upload avatar");
112+
} finally {
113+
changeAvatarBtn.disabled = false;
114+
avatarInput.value = "";
115+
}
116+
});
117+
}
114118
115-
localStorage.removeItem("accessToken");
116-
localStorage.removeItem("refreshToken");
117-
localStorage.removeItem("user");
118-
clearCookie("accessToken");
119-
clearCookie("refreshToken");
120-
window.location.href = "/login";
121-
});
122-
})();
119+
if (logoutButton) {
120+
logoutButton.addEventListener("click", () => {
121+
authProvider.logout();
122+
});
123+
}
124+
});
123125
</script>

‎src/client/views/layouts/main.ejs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
<%- include('../pages/' + (typeof page !== 'undefined' ? page : 'dashboard')); %>
2727
</div>
2828
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js" crossorigin="anonymous"></script>
29+
<script type="module" src="/utils/auth.js"></script>
2930
<script type="module" src="/utils/dropdown.js"></script>
3031
<script type="module" src="/utils/sidebar.js"></script>
3132
</body>

‎src/client/views/pages/detail.ejs‎

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,9 @@
416416
</div>
417417
</main>
418418

419-
<script>
419+
<script type="module">
420+
import { authProvider } from '/utils/auth.js';
421+
420422
const loadBtn = document.getElementById('load-btn');
421423
const loadingState = document.getElementById('loading-state');
422424
const emptyState = document.getElementById('empty-state');
@@ -439,18 +441,7 @@
439441
let zoomLevel = 100;
440442
let rotation = 0;
441443
442-
const getAccessToken = () => {
443-
const readCookie = (name) => {
444-
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
445-
if (match) return match[2];
446-
};
447-
return localStorage.getItem("accessToken") || readCookie("accessToken") || "";
448-
};
449-
450-
const getAuthHeaders = () => {
451-
const token = getAccessToken();
452-
return token ? { 'Authorization': `Bearer ${token}` } : {};
453-
};
444+
// Auth helpers removed as they are handled by AuthProvider
454445
455446
// File type mappings
456447
const imageExts = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp', 'ico', 'tiff', 'tif', 'avif'];
@@ -628,7 +619,7 @@
628619
try {
629620
const headRes = await fetch(fileUrl, {
630621
method: 'HEAD',
631-
headers: getAuthHeaders()
622+
headers: authProvider.getAuthHeaders()
632623
});
633624
if (headRes.ok) {
634625
fileSize = parseInt(headRes.headers.get('content-length') || '0');
@@ -685,7 +676,7 @@
685676
pdfPreviewContainer.classList.remove('hidden');
686677
} else if (isCode || isText) {
687678
const res = await fetch(fileUrl, {
688-
headers: getAuthHeaders()
679+
headers: authProvider.getAuthHeaders()
689680
});
690681
if (!res.ok) throw new Error('Failed to load file');
691682
const text = await res.text();

‎src/client/views/pages/my-file.ejs‎

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -177,18 +177,7 @@
177177
</div>
178178
179179
<script type="module">
180-
const getAccessToken = () => {
181-
const readCookie = (name) => {
182-
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
183-
if (match) return match[2];
184-
};
185-
return localStorage.getItem("accessToken") || readCookie("accessToken") || "";
186-
};
187-
188-
const getAuthHeaders = () => {
189-
const token = getAccessToken();
190-
return token ? { 'Authorization': `Bearer ${token}` } : {};
191-
};
180+
import { authProvider } from '/utils/auth.js';
192181
193182
// Initialize delete action handlers
194183
const initDeleteActions = () => {
@@ -208,7 +197,7 @@
208197
try {
209198
const res = await fetch(`/api/file/delete/${filename}`, {
210199
method: 'DELETE',
211-
headers: getAuthHeaders()
200+
headers: authProvider.getAuthHeaders()
212201
});
213202
214203
if (res.ok) {

‎src/client/views/pages/recent.ejs‎

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,9 @@
159159
</div>
160160
</main>
161161

162-
<script>
162+
<script type="module">
163+
import { authProvider } from '/utils/auth.js';
164+
163165
// Elements
164166
const recentSearch = document.getElementById('recent-search');
165167
const recentFilter = document.getElementById('recent-filter');
@@ -210,18 +212,7 @@
210212
return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${sizes[i]}`;
211213
};
212214
213-
const getAccessToken = () => {
214-
const readCookie = (name) => {
215-
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
216-
if (match) return match[2];
217-
};
218-
return localStorage.getItem("accessToken") || readCookie("accessToken") || "";
219-
};
220-
221-
const getAuthHeaders = () => {
222-
const token = getAccessToken();
223-
return token ? { 'Authorization': `Bearer ${token}` } : {};
224-
};
215+
// Auth helpers removed as they are handled by AuthProvider
225216
226217
const timeAgo = (isoString) => {
227218
if (!isoString) return '—';
@@ -538,7 +529,7 @@
538529
try {
539530
const res = await fetch(`/api/file/delete/${encodeURIComponent(filename)}`, {
540531
method: 'DELETE',
541-
headers: getAuthHeaders()
532+
headers: authProvider.getAuthHeaders()
542533
});
543534
if (res.ok) {
544535
loadFiles();
@@ -571,7 +562,7 @@
571562
const loadFiles = async () => {
572563
try {
573564
const response = await fetch('/api/database/files', {
574-
headers: getAuthHeaders()
565+
headers: authProvider.getAuthHeaders()
575566
});
576567
if (!response.ok) throw new Error('Failed to load files');
577568
const data = await response.json();

‎src/client/views/pages/upload-history.ejs‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,9 @@
103103
</div>
104104
</main>
105105

106-
<script>
106+
<script type="module">
107+
import { authProvider } from '/utils/auth.js';
108+
107109
const historyTable = document.getElementById('history-table');
108110
const historySearch = document.getElementById('history-search');
109111
const historyFilter = document.getElementById('history-filter');
@@ -254,7 +256,9 @@
254256
const loadHistory = async () => {
255257
if (!historyTable) return;
256258
try {
257-
const response = await fetch('/api/database/files');
259+
const response = await fetch('/api/database/files', {
260+
headers: authProvider.getAuthHeaders()
261+
});
258262
if (!response.ok) {
259263
throw new Error('Failed to load uploads');
260264
}
@@ -320,7 +324,10 @@
320324
log('Attempting to delete file:', filename);
321325
if (filename && confirm('Are you sure you want to delete this file?')) {
322326
try {
323-
const res = await fetch(`/api/file/delete/${filename}`, { method: 'DELETE' });
327+
const res = await fetch(`/api/file/delete/${filename}`, {
328+
method: 'DELETE',
329+
headers: authProvider.getAuthHeaders()
330+
});
324331
if (res.ok) {
325332
loadHistory();
326333
} else {

‎src/client/views/pages/upload.ejs‎

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,9 @@
152152
</div>
153153
</main>
154154

155-
<script>
155+
<script type="module">
156+
import { authProvider } from '/utils/auth.js';
157+
156158
const uploadInput = document.getElementById('upload-input');
157159
const uploadList = document.getElementById('upload-list');
158160
const clearButton = document.getElementById('clear-upload');
@@ -189,18 +191,7 @@
189191
return new Date(isoString).toLocaleDateString();
190192
};
191193
192-
const getAccessToken = () => {
193-
const readCookie = (name) => {
194-
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
195-
if (match) return match[2];
196-
};
197-
return localStorage.getItem("accessToken") || readCookie("accessToken") || "";
198-
};
199-
200-
const getAuthHeaders = () => {
201-
const token = getAccessToken();
202-
return token ? { 'Authorization': `Bearer ${token}` } : {};
203-
};
194+
// Auth helpers removed as they are handled by AuthProvider
204195
205196
const renderRecentUploads = (files) => {
206197
if (!recentUploads) return;
@@ -228,7 +219,7 @@
228219
if (!recentUploads) return;
229220
try {
230221
const response = await fetch('/api/database/files', {
231-
headers: getAuthHeaders()
222+
headers: authProvider.getAuthHeaders()
232223
});
233224
if (!response.ok) {
234225
throw new Error('Failed to load uploads');
@@ -385,9 +376,9 @@
385376
xhr.open('POST', uploadForm.action, true);
386377
387378
// Add Authorization header
388-
const token = getAccessToken();
389-
if (token) {
390-
xhr.setRequestHeader('Authorization', `Bearer ${token}`);
379+
const headers = authProvider.getAuthHeaders();
380+
if (headers.Authorization) {
381+
xhr.setRequestHeader('Authorization', headers.Authorization);
391382
}
392383
393384
if (storagePath && storagePath.value.trim()) {

0 commit comments

Comments
 (0)