Add remove friend, sort friends by recently added, inline request sent message

This commit is contained in:
Sterling Archer
2026-06-18 15:35:42 -07:00
parent 05d5bbd01f
commit 76da4b73af
+37 -4
View File
@@ -1805,10 +1805,15 @@ function loadFriends() {
friendsList = []; friendsList = [];
for (const fuid of allFriendUids) { for (const fuid of allFriendUids) {
const req = [...sentSnap.docs, ...receivedSnap.docs].find(d =>
(d.data().fromUid === currentUser.uid && d.data().toUid === fuid) ||
(d.data().toUid === currentUser.uid && d.data().fromUid === fuid)
);
const addedAt = req?.data()?.createdAt?.toMillis?.() || 0;
const unsubProfile = onSnapshot(doc(db, 'profiles', fuid), profileSnap => { const unsubProfile = onSnapshot(doc(db, 'profiles', fuid), profileSnap => {
if (!profileSnap.exists()) return; if (!profileSnap.exists()) return;
const idx = friendsList.findIndex(f => f.uid === fuid); const idx = friendsList.findIndex(f => f.uid === fuid);
const data = { uid: fuid, ...profileSnap.data() }; const data = { uid: fuid, addedAt, ...profileSnap.data() };
if (idx >= 0) { if (idx >= 0) {
friendsList[idx] = data; friendsList[idx] = data;
} else { } else {
@@ -1900,7 +1905,8 @@ function renderFriendsPanel() {
if (friendsList.length === 0) { if (friendsList.length === 0) {
fml.innerHTML = `<div class="empty-state" style="padding:24px 0"><div class="es-icon">🤝</div><h3>No friends yet</h3><p>Send a request above to add your first StepMate.</p></div>`; fml.innerHTML = `<div class="empty-state" style="padding:24px 0"><div class="es-icon">🤝</div><h3>No friends yet</h3><p>Send a request above to add your first StepMate.</p></div>`;
} else { } else {
fml.innerHTML = friendsList.map(f => { const sortedByAdded = [...friendsList].sort((a,b) => (b.addedAt || 0) - (a.addedAt || 0));
fml.innerHTML = sortedByAdded.map(f => {
const steps = getStepsForPeriod(f.stepsByDate, currentTab); const steps = getStepsForPeriod(f.stepsByDate, currentTab);
return ` return `
<div class="friend-card"> <div class="friend-card">
@@ -1909,6 +1915,7 @@ function renderFriendsPanel() {
<div class="friend-name">${esc(f.displayName || f.email)}</div> <div class="friend-name">${esc(f.displayName || f.email)}</div>
<div class="friend-steps-sub">${steps.toLocaleString()} steps today</div> <div class="friend-steps-sub">${steps.toLocaleString()} steps today</div>
</div> </div>
<button class="btn btn-ghost btn-sm" style="color:var(--danger);border-color:var(--danger);flex-shrink:0" onclick="removeFriend('${f.uid}','${esc(f.displayName || f.email)}')">Remove</button>
</div>`; </div>`;
}).join(''); }).join('');
} }
@@ -1927,8 +1934,10 @@ function updateBadge() {
window.sendFriendRequest = async function() { window.sendFriendRequest = async function() {
const emailInput = document.getElementById('add-friend-email'); const emailInput = document.getElementById('add-friend-email');
const errEl = document.getElementById('add-friend-error'); const errEl = document.getElementById('add-friend-error');
const btn = document.querySelector('#panel-friends .add-friend-row .btn');
const email = emailInput.value.trim().toLowerCase(); const email = emailInput.value.trim().toLowerCase();
errEl.style.display = 'none'; errEl.style.display = 'none';
errEl.style.color = '';
if (!email || !/\S+@\S+\.\S+/.test(email)) { if (!email || !/\S+@\S+\.\S+/.test(email)) {
errEl.textContent = 'Please enter a valid email.'; errEl.textContent = 'Please enter a valid email.';
@@ -1941,17 +1950,21 @@ window.sendFriendRequest = async function() {
return; return;
} }
btn.disabled = true;
btn.innerHTML = '<span class="spinner"></span>';
const usersQ = query(collection(db, 'users'), where('email', '==', email)); const usersQ = query(collection(db, 'users'), where('email', '==', email));
const snap = await getDocs(usersQ); const snap = await getDocs(usersQ);
if (snap.empty) { if (snap.empty) {
errEl.textContent = 'No StepMates account found for that email.'; errEl.textContent = 'No StepMates account found for that email.';
errEl.style.display = 'block'; errEl.style.display = 'block';
btn.disabled = false;
btn.innerHTML = 'Send Request';
return; return;
} }
const toUser = snap.docs[0]; const toUser = snap.docs[0];
const toUid = toUser.id; const toUid = toUser.id;
const toData = toUser.data();
const existingQ = query(collection(db, 'friendRequests'), const existingQ = query(collection(db, 'friendRequests'),
where('fromUid', '==', currentUser.uid), where('fromUid', '==', currentUser.uid),
@@ -1961,6 +1974,8 @@ window.sendFriendRequest = async function() {
if (!existingSnap.empty) { if (!existingSnap.empty) {
errEl.textContent = 'You already sent a request to this person.'; errEl.textContent = 'You already sent a request to this person.';
errEl.style.display = 'block'; errEl.style.display = 'block';
btn.disabled = false;
btn.innerHTML = 'Send Request';
return; return;
} }
@@ -1975,7 +1990,12 @@ window.sendFriendRequest = async function() {
}); });
emailInput.value = ''; emailInput.value = '';
showToast('Friend request sent!', 'success'); btn.disabled = false;
btn.innerHTML = 'Send Request';
errEl.textContent = '✓ Friend request sent!';
errEl.style.color = 'var(--success)';
errEl.style.display = 'block';
setTimeout(() => { errEl.style.display = 'none'; }, 4000);
await loadFriends(); await loadFriends();
} }
@@ -1985,6 +2005,19 @@ window.respondRequest = async function(requestId, status) {
await loadFriends(); await loadFriends();
} }
window.removeFriend = async function(friendUid, friendName) {
if (!confirm(`Remove ${friendName} from your friends?`)) return;
const uid = currentUser.uid;
const frRef = collection(db, 'friendRequests');
const q1 = query(frRef, where('fromUid', '==', uid), where('toUid', '==', friendUid));
const q2 = query(frRef, where('fromUid', '==', friendUid), where('toUid', '==', uid));
const [s1, s2] = await Promise.all([getDocs(q1), getDocs(q2)]);
const deletes = [...s1.docs, ...s2.docs].map(d => updateDoc(doc(db, 'friendRequests', d.id), { status: 'removed' }));
await Promise.all(deletes);
showToast(`${friendName} removed`, '');
await loadFriends();
}
// ───────────────────────────────────────────── // ─────────────────────────────────────────────
// PANEL SWITCHING // PANEL SWITCHING
// ───────────────────────────────────────────── // ─────────────────────────────────────────────