function formatDate(dateStr) { const d = new Date(dateStr); return d.toLocaleDateString('fr-FR', { year: 'numeric', month: 'long', day: 'numeric' }); } function showError(message) { document.getElementById('error').innerHTML = `
${escapeHtml(message)}
`; } function statTile(value, label) { return `
${value}
${escapeHtml(label)}
`; } // Shown for a registered feed instead of its full URL, consistent with the // admin calendar-management panel — the URL itself is a secret (whoever has // it can read that person's calendar), so the list view doesn't re-display // the plaintext on every load. function maskIcsUrl(url) { try { const u = new URL(url); return `${u.hostname}/••••`; } catch { return '••••'; } } function myFeedRowTemplate(feed) { return `
${feed.label ? `${escapeHtml(feed.label)} — ` : ''}${escapeHtml(maskIcsUrl(feed.icsUrl))}
`; } async function loadMyFeeds() { const feeds = await api.get('/api/calendar/my-feeds'); const container = document.getElementById('my-feeds-list'); container.innerHTML = feeds.length ? feeds.map(myFeedRowTemplate).join('') : '

Aucun calendrier configuré.

'; container.querySelectorAll('.remove-my-feed-btn').forEach((btn) => { btn.addEventListener('click', () => onRemoveMyFeed(parseInt(btn.dataset.feedId, 10))); }); } async function onAddMyFeed(e) { e.preventDefault(); const form = e.target; const label = form.label.value.trim(); const icsUrl = form.icsUrl.value.trim(); const submitBtn = form.querySelector('button[type="submit"]'); submitBtn.disabled = true; submitBtn.textContent = 'Vérification…'; try { await api.post('/api/calendar/my-feeds', { label, icsUrl }); form.reset(); await loadMyFeeds(); } catch (err) { showError(err.message); } finally { submitBtn.disabled = false; submitBtn.textContent = '+ Ajouter'; } } async function onRemoveMyFeed(feedId) { try { await api.del(`/api/calendar/my-feeds/${feedId}`); await loadMyFeeds(); } catch (err) { showError(err.message); } } async function loadIcsFeedUrl() { const { path } = await api.get('/api/users/me/ics-feed-url'); const httpsUrl = `${window.location.origin}${path}`; const webcalUrl = `webcal://${window.location.host}${path}`; document.getElementById('ics-feed-url').value = webcalUrl; document.getElementById('ics-feed-open-link').href = webcalUrl; document.getElementById('copy-ics-feed-btn').dataset.url = httpsUrl; } async function onCopyIcsFeedUrl(e) { const btn = e.target; try { await navigator.clipboard.writeText(btn.dataset.url); const original = btn.textContent; btn.textContent = 'Copié !'; setTimeout(() => { btn.textContent = original; }, 1500); } catch (err) { showError("Impossible de copier le lien : " + err.message); } } async function onSaveDisplayName(e) { e.preventDefault(); const form = e.target; const displayName = form.displayName.value.trim(); try { await api.patch('/api/users/me/display-name', { displayName }); window.location.reload(); } catch (err) { showError(err.message); } } async function onResetDisplayName() { try { await api.patch('/api/users/me/display-name', { displayName: '' }); window.location.reload(); } catch (err) { showError(err.message); } } (async function init() { await initNav(null); try { const profile = await api.get('/api/users/me/profile'); const container = document.getElementById('content'); container.innerHTML = `
${avatarHtml(profile, 'avatar-lg')}

${escapeHtml(profile.name)}${profile.isAdmin ? ' admin' : ''}

${profile.username ? `
@${escapeHtml(profile.username)}
` : ''} ${profile.email ? `
${escapeHtml(profile.email)}
` : ''}
Membre depuis ${formatDate(profile.createdAt)}

Nom affiché

Par défaut, le nom fourni par Authentik est utilisé. Vous pouvez le remplacer par un nom personnalisé ci-dessous — cela n'affecte pas votre compte Authentik.

${profile.hasCustomName ? '' : ''}
${profile.groups && profile.groups.length ? `

Groupes Authentik

${profile.groups.map((g) => `${escapeHtml(g)}`).join('')}
` : ''}

Votre activité

${statTile(profile.stats.songsAdded, 'Morceaux ajoutés')} ${statTile(profile.stats.suggestionsProposed, 'Suggestions proposées')} ${statTile(profile.stats.votesCast, 'Votes exprimés')}

Mes calendriers

Ajoutez le lien ICS (iCal) d'un ou plusieurs de vos calendriers (Google, Outlook, Apple...) pour apparaître dans les disponibilités du groupe sur la page Disponibilités. L'application ne conserve jamais le contenu de vos calendriers — seul un statut disponible/occupé par créneau est déduit et enregistré.

Chargement…

Comment trouver mon lien ICS ?

Répétitions dans mon calendrier

Abonnez-vous à ce lien depuis Google Calendar, Apple Calendar ou Outlook pour voir les répétitions (proposées et confirmées) directement dans votre calendrier personnel, mises à jour automatiquement. Ce lien est personnel, ne le partagez pas.

S'abonner

Identité gérée par Authentik — pour changer votre email ou mot de passe, ${profile.authentikAccountUrl ? `rendez-vous sur votre compte Authentik.` : 'rendez-vous sur votre compte Authentik.'}

`; document.getElementById('display-name-form').addEventListener('submit', onSaveDisplayName); const resetBtn = document.getElementById('reset-display-name-btn'); if (resetBtn) resetBtn.addEventListener('click', onResetDisplayName); document.getElementById('add-my-feed-form').addEventListener('submit', onAddMyFeed); document.getElementById('copy-ics-feed-btn').addEventListener('click', onCopyIcsFeedUrl); await loadMyFeeds(); await loadIcsFeedUrl(); } catch (err) { showError(err.message); } })();