Files
ShooterHub/frontend/js/profile.js
2026-04-02 11:24:30 +02:00

183 lines
7.3 KiB
JavaScript

// ── Profile page logic ────────────────────────────────────────────────────────
let rigs = [];
// ── Toast ─────────────────────────────────────────────────────────────────────
function showToast(msg, type = 'success') {
const el = document.createElement('div');
el.className = `toast align-items-center text-bg-${type} border-0 show`;
el.innerHTML = `<div class="d-flex"><div class="toast-body">${msg}</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button></div>`;
document.getElementById('toastContainer').appendChild(el);
setTimeout(() => el.remove(), 3500);
}
function showAlert(id, msg, type = 'danger') {
const el = document.getElementById(id);
el.className = `alert alert-${type} mt-3`;
el.textContent = msg;
el.classList.remove('d-none');
}
function hideAlert(id) {
document.getElementById(id).classList.add('d-none');
}
// ── Load profile ─────────────────────────────────────────────────────────────
async function loadProfile() {
try {
const user = await apiGet('/users/profile/');
document.getElementById('firstName').value = user.first_name || '';
document.getElementById('lastName').value = user.last_name || '';
document.getElementById('email').value = user.email;
document.getElementById('profileUsername').textContent = '@' + user.username;
if (user.avatar_url) {
document.getElementById('avatarWrap').innerHTML =
`<img src="${user.avatar_url}" class="avatar-img mx-auto d-block" alt="avatar">`;
} else {
const initials = ((user.first_name?.[0] || '') + (user.last_name?.[0] || '') ||
user.username?.[0] || '?').toUpperCase();
document.getElementById('avatarInitials').textContent = initials;
}
} catch(e) {
showToast('Failed to load profile', 'danger');
}
}
// ── Save profile info ─────────────────────────────────────────────────────────
document.getElementById('profileForm').addEventListener('submit', async e => {
e.preventDefault();
hideAlert('profileAlert');
const btn = document.getElementById('saveProfileBtn');
btn.disabled = true;
try {
await apiPatch('/users/profile/', {
first_name: document.getElementById('firstName').value.trim(),
last_name: document.getElementById('lastName').value.trim(),
});
showAlert('profileAlert', 'Profile saved!', 'success');
showToast('Profile updated!');
} catch(e) {
showAlert('profileAlert', formatErrors(e.data));
} finally {
btn.disabled = false;
}
});
// ── Avatar upload ─────────────────────────────────────────────────────────────
document.getElementById('avatarInput').addEventListener('change', async e => {
const file = e.target.files[0];
if (!file) return;
const alertEl = document.getElementById('avatarAlert');
alertEl.classList.add('d-none');
try {
// Step 1: upload image to /api/photos/upload/
const form = new FormData();
form.append('file', file);
const photo = await apiPost('/photos/upload/', form);
// Step 2: set it as the user's avatar
const updated = await apiPatch('/users/profile/', { avatar: photo.id });
if (updated.avatar_url) {
document.getElementById('avatarWrap').innerHTML =
`<img src="${updated.avatar_url}" class="avatar-img mx-auto d-block" alt="avatar">`;
}
showToast('Avatar updated!');
} catch(e) {
alertEl.textContent = (e.data && formatErrors(e.data)) || e.message || 'Upload failed. Please try a smaller image.';
alertEl.classList.remove('d-none');
}
});
// ── Change password ───────────────────────────────────────────────────────────
document.getElementById('passwordForm').addEventListener('submit', async e => {
e.preventDefault();
hideAlert('passwordAlert');
const p1 = document.getElementById('newPassword1').value;
const p2 = document.getElementById('newPassword2').value;
if (p1 !== p2) {
showAlert('passwordAlert', 'New passwords do not match.');
return;
}
try {
await apiPost('/auth/password/change/', {
old_password: document.getElementById('oldPassword').value,
new_password1: p1,
new_password2: p2,
});
showAlert('passwordAlert', 'Password changed successfully!', 'success');
document.getElementById('passwordForm').reset();
showToast('Password changed!');
} catch(e) {
showAlert('passwordAlert', formatErrors(e.data));
}
});
// ── Rigs table ────────────────────────────────────────────────────────────────
function renderRigs() {
const tbody = document.getElementById('rigsBody');
const wrap = document.getElementById('rigsTableWrap');
const empty = document.getElementById('rigsEmpty');
document.getElementById('rigsSpinner').classList.add('d-none');
if (!rigs.length) {
empty.classList.remove('d-none');
return;
}
wrap.classList.remove('d-none');
tbody.innerHTML = rigs.map(rig => `
<tr>
<td class="fw-semibold">${rig.name}</td>
<td>${rig.rig_items.length} item${rig.rig_items.length === 1 ? '' : 's'}</td>
<td>
<span class="badge ${rig.is_public ? 'bg-success' : 'bg-secondary'}">
${rig.is_public ? 'Public' : 'Private'}
</span>
</td>
<td>
<button class="btn btn-sm ${rig.is_public ? 'btn-outline-secondary' : 'btn-outline-success'}"
onclick="toggleRig(${rig.id})">
<i class="bi bi-${rig.is_public ? 'lock' : 'globe2'} me-1"></i>
${rig.is_public ? 'Make private' : 'Make public'}
</button>
</td>
</tr>
`).join('');
}
async function loadRigs() {
try {
rigs = await apiGet('/rigs/');
renderRigs();
} catch(e) {
document.getElementById('rigsSpinner').classList.add('d-none');
showToast('Failed to load rigs', 'danger');
}
}
async function toggleRig(id) {
const rig = rigs.find(r => r.id === id);
if (!rig) return;
try {
const updated = await apiPatch(`/rigs/${id}/`, { is_public: !rig.is_public });
const idx = rigs.findIndex(r => r.id === id);
if (idx >= 0) rigs[idx] = { ...rigs[idx], is_public: updated.is_public };
renderRigs();
showToast(`Rig is now ${updated.is_public ? 'public' : 'private'}.`);
} catch(e) {
showToast('Failed to update rig.', 'danger');
}
}
// ── Init ──────────────────────────────────────────────────────────────────────
loadProfile();
loadRigs();