+
Add Post
👤
📎 Tap to attach a photo or video
Loading your family feed...
.eq('post_id', postId); if (error) throw error; const post = posts.find(p => p.id === postId); if (post) { post.feed_reactions = reactions; } } catch (error) { console.error('Error loading reactions:', error); } } async function loadPostComments(postId) { try { const { data: comments, error } = await _supabase .from('feed_comments') .select(` *, author:profiles!feed_comments_author_id_fkey(full_name, photo_id_url) `) .eq('post_id', postId) .order('created_at', { ascending: true }); if (error) throw error; const post = posts.find(p => p.id === postId); if (post) { post.feed_comments = [{ count: comments.length }]; displayComments(postId, comments); } } catch (error) { console.error('Error loading comments:', error); } } function displayComments(postId, comments) { const commentsContainer = document.getElementById(`comments-${postId}`); if (!commentsContainer) return; if (comments.length === 0) { commentsContainer.innerHTML = '

No comments yet

'; return; } commentsContainer.innerHTML = comments.map(comment => `
${comment.author?.full_name}
${comment.author?.full_name || 'Unknown'}
${comment.content}
`).join(''); } function toggleComments(postId) { const commentsContainer = document.getElementById(`comments-${postId}`); if (commentsContainer) { commentsContainer.classList.toggle('expanded'); } } async function toggleReaction(postId, emoji) { try { // Check if user already reacted const existingReaction = posts.find(p => p.id === postId)?.feed_reactions?.find(r => r.emoji === emoji && r.user_id === currentUser.id); if (existingReaction) { // Remove reaction const { error } = await _supabase .from('feed_reactions') .delete() .eq('post_id', postId) .eq('user_id', currentUser.id) .eq('emoji', emoji); if (error) throw error; } else { // Add reaction const { error } = await _supabase .from('feed_reactions') .insert({ post_id: postId, user_id: currentUser.id, emoji: emoji }); if (error) throw error; } // Reload post reactions await loadPostReactions(postId); const post = posts.find(p => p.id === postId); if (post) { const postElement = document.getElementById(`post-${postId}`); if (postElement) { const reactionsBar = postElement.querySelector('.reactions-bar'); if (reactionsBar) { reactionsBar.innerHTML = createReactionsHTML(post); } } } } catch (error) { console.error('Error toggling reaction:', error); showError('Failed to update reaction. Please try again.'); } } async function flagPost(postId) { if (!confirm('Are you sure you want to flag this post for moderation?')) return; try { const { error } = await _supabase .from('feed_posts') .update({ is_flagged: true, flagged_by: currentUser.id, flagged_at: new Date().toISOString() }) .eq('id', postId); if (error) throw error; // Reload posts await loadPosts(); } catch (error) { console.error('Error flagging post:', error); showError('Failed to flag post. Please try again.'); } } async function deletePost(postId) { if (!confirm('Are you sure you want to delete this post? This action cannot be undone.')) return; try { const { error } = await _supabase .from('feed_posts') .delete() .eq('id', postId); if (error) throw error; // Remove post from UI const postElement = document.getElementById(`post-${postId}`); if (postElement) { postElement.remove(); } // Remove from posts array posts = posts.filter(p => p.id !== postId); } catch (error) { console.error('Error deleting post:', error); showError('Failed to delete post. Please try again.'); } } // Photo upload handlers function handlePhotoUpload(event) { const file = event.target.files[0]; if (!file) return; if (!file.type.startsWith('image/')) { showError('Please select an image file.'); return; } const reader = new FileReader(); reader.onload = function(e) { document.getElementById('photo-preview-img').src = e.target.result; document.getElementById('photo-preview').style.display = 'block'; document.querySelector('.file-upload-area').style.display = 'none'; }; reader.readAsDataURL(file); // Store file for posting window.currentPhotoFile = file; } async function postPhoto() { if (!window.currentPhotoFile) return; showLoading(true); try { // Upload to Supabase storage const fileName = `feed-photo-${currentUser.id}-${Date.now()}`; const { data: uploadData, error: uploadError } = await _supabase.storage .from('documents') .upload(fileName, window.currentPhotoFile); if (uploadError) throw uploadError; // Get public URL const { data: { publicUrl } } = _supabase.storage .from('documents') .getPublicUrl(fileName); // Create post const caseId = userCases.length > 0 ? userCases[0].id : null; const { error: postError } = await _supabase .from('feed_posts') .insert({ case_id: caseId, author_id: currentUser.id, post_type: 'photo', media_url: publicUrl, content_text: document.getElementById('photo-caption').value }); if (postError) throw postError; // Reset and reload resetPhotoComposer(); await loadPosts(); } catch (error) { console.error('Error posting photo:', error); showError('Failed to post photo. Please try again.'); } finally { showLoading(false); } } function resetPhotoComposer() { document.getElementById('photo-preview').style.display = 'none'; document.querySelector('.file-upload-area').style.display = 'block'; document.getElementById('photo-caption').value = ''; document.getElementById('photo-file-input').value = ''; window.currentPhotoFile = null; } // Video upload handlers function handleVideoUpload(event) { const file = event.target.files[0]; if (!file) return; if (!file.type.startsWith('video/')) { showError('Please select a video file.'); return; } // Check file size (max 50MB) if (file.size > 50 * 1024 * 1024) { showError('Video file must be less than 50MB.'); return; } const reader = new FileReader(); reader.onload = function(e) { const video = document.getElementById('video-preview-video'); video.src = e.target.result; document.getElementById('video-preview').style.display = 'block'; document.querySelector('.file-upload-area').style.display = 'none'; }; reader.readAsDataURL(file); // Store file for posting window.currentVideoFile = file; } async function postVideo() { if (!window.currentVideoFile) return; showLoading(true); try { // Upload to Supabase storage const fileName = `feed-video-${currentUser.id}-${Date.now()}`; const { data: uploadData, error: uploadError } = await _supabase.storage .from('documents') .upload(fileName, window.currentVideoFile); if (uploadError) throw uploadError; // Get public URL const { data: { publicUrl } } = _supabase.storage .from('documents') .getPublicUrl(fileName); // Create post const caseId = userCases.length > 0 ? userCases[0].id : null; const { error: postError } = await _supabase .from('feed_posts') .insert({ case_id: caseId, author_id: currentUser.id, post_type: 'video', media_url: publicUrl, content_text: document.getElementById('video-caption').value }); if (postError) throw postError; // Reset and reload resetVideoComposer(); await loadPosts(); } catch (error) { console.error('Error posting video:', error); showError('Failed to post video. Please try again.'); } finally { showLoading(false); } } function resetVideoComposer() { document.getElementById('video-preview').style.display = 'none'; document.querySelector('.file-upload-area').style.display = 'block'; document.getElementById('video-caption').value = ''; document.getElementById('video-file-input').value = ''; window.currentVideoFile = null; } // Note posting function postNote() { const content = document.getElementById('note-text').value.trim(); if (!content) { showError('Please enter a note to post.'); return; } showLoading(true); try { const caseId = userCases.length > 0 ? userCases[0].id : null; const { error } = await _supabase .from('feed_posts') .insert({ case_id: caseId, author_id: currentUser.id, post_type: 'note', content_text: content }); if (error) throw error; // Reset and reload document.getElementById('note-text').value = ''; updateCharCount(); await loadPosts(); } catch (error) { console.error('Error posting note:', error); showError('Failed to post note. Please try again.'); } finally { showLoading(false); } } // Voice recording function toggleRecording() { if (isRecording) { stopRecording(); } else { startRecording(); } } function startRecording() { navigator.mediaDevices.getUserMedia({ audio: true }) .then(stream => { mediaRecorder = new MediaRecorder(stream); recordedChunks = []; mediaRecorder.ondataavailable = event => { if (event.data.size > 0) { recordedChunks.push(event.data); } }; mediaRecorder.onstop = () => { const blob = new Blob(recordedChunks, { type: 'audio/mpeg' }); const audioURL = URL.createObjectURL(blob); document.getElementById('voice-audio').src = audioURL; document.getElementById('voice-preview').style.display = 'block'; document.querySelector('.voice-recorder').style.display = 'none'; window.currentVoiceBlob = blob; }; mediaRecorder.start(); isRecording = true; updateRecordingUI(); startRecordingTimer(); }) .catch(error => { console.error('Error accessing microphone:', error); showError('Unable to access microphone. Please check permissions.'); }); } function stopRecording() { if (mediaRecorder && mediaRecorder.state !== 'inactive') { mediaRecorder.stop(); isRecording = false; updateRecordingUI(); stopRecordingTimer(); } } function updateRecordingUI() { const recordBtn = document.getElementById('record-btn'); const recordText = recordBtn.querySelector('.record-text'); if (isRecording) { recordBtn.classList.add('recording'); recordText.textContent = 'Stop Recording'; } else { recordBtn.classList.remove('recording'); recordText.textContent = 'Record Voice'; } } let recordingStartTime = null; let recordingInterval = null; function startRecordingTimer() { recordingStartTime = Date.now(); recordingInterval = setInterval(updateRecordingTimer, 100); } function stopRecordingTimer() { if (recordingInterval) { clearInterval(recordingInterval); recordingInterval = null; } recordingStartTime = null; document.getElementById('recording-timer').style.display = 'none'; } function updateRecordingTimer() { if (recordingStartTime) { const elapsed = Date.now() - recordingStartTime; const minutes = Math.floor(elapsed / 60000); const seconds = Math.floor((elapsed % 60000) / 1000); const display = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; document.getElementById('recording-timer').textContent = display; document.getElementById('recording-timer').style.display = 'block'; } } async function postVoice() { if (!window.currentVoiceBlob) return; showLoading(true); try { // Upload to Supabase storage const fileName = `feed-voice-${currentUser.id}-${Date.now()}.mp3`; const { data: uploadData, error: uploadError } = await _supabase.storage .from('documents') .upload(fileName, window.currentVoiceBlob); if (uploadError) throw uploadError; // Get public URL const { data: { publicUrl } } = _supabase.storage .from('documents') .getPublicUrl(fileName); // Create post const caseId = userCases.length > 0 ? userCases[0].id : null; const { error } = await _supabase .from('feed_posts') .insert({ case_id: caseId, author_id: currentUser.id, post_type: 'voice', media_url: publicUrl }); if (error) throw error; // Reset and reload resetVoiceComposer(); await loadPosts(); } catch (error) { console.error('Error posting voice:', error); showError('Failed to post voice. Please try again.'); } finally { showLoading(false); } } function resetVoiceComposer() { document.getElementById('voice-preview').style.display = 'none'; document.querySelector('.voice-recorder').style.display = 'block'; window.currentVoiceBlob = null; } // Utility functions function formatRole(role) { const roleMap = { 'requesting_parent': 'Requesting Parent', 'other_parent': 'Other Parent', 'supervisor': 'Supervisor', 'case_manager': 'Case Manager', 'attorney': 'Attorney', 'foster_worker': 'Foster Worker' }; return roleMap[role] || role?.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()) || 'Unknown'; } function formatTime(dateString) { const date = new Date(dateString); const now = new Date(); const diff = now - date; const hours = Math.floor(diff / (1000 * 60 * 60)); const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)); if (hours < 1) { return `${minutes}m ago`; } else if (hours < 24) { return `${hours}h ago`; } else { return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); } } function showEmptyState() { document.getElementById('feed-posts').innerHTML = `
📭

No posts yet

Be the first to share something!

`; } function showLoading(show) { document.getElementById('loading-overlay').style.display = show ? 'flex' : 'none'; } function showError(message) { alert(message); // Simple error display - could be enhanced with toast notification } function signOutUser() { _supabase.auth.signOut().then(() => { window.location.href = '../index.html'; }); } // Character counter for notes document.addEventListener('DOMContentLoaded', async () => { const noteTextarea = document.getElementById('note-text'); if (noteTextarea) { noteTextarea.addEventListener('input', updateCharCount); } }); async function updateCharCount() { const textarea = document.getElementById('note-text'); const charCount = textarea.value.length; const charCountElement = document.getElementById('note-char-count'); if (charCountElement) { charCountElement.textContent = `${charCount}/500`; } } // Emoji picker (simplified) async function toggleEmojiPicker() { const emojis = ['😊', '😂', '❤️', '👍', '🎉', '😢', '😮', '🙏', '💪', '🌟']; const emojiHTML = emojis.map(emoji => `${emoji}` ).join(''); // Simple emoji picker - in a real app this would be more sophisticated const picker = document.createElement('div'); picker.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 20px; border-radius: 12px; box-shadow: 0 8px 32px rgba(0,0,0,0.2); z-index: 10000; `; picker.innerHTML = emojiHTML; // Close on click outside picker.addEventListener('click', (e) => { if (e.target === picker) { document.body.removeChild(picker); } }); document.body.appendChild(picker); } async function addEmoji(emoji) { const textarea = document.getElementById('note-text'); textarea.value += emoji; await updateCharCount(); textarea.focus(); }