diff --git a/src/App.jsx b/src/App.jsx index 6a56178..09fa25e 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -30,6 +30,24 @@ export default function App() { setWallError(next.error || ""); }, []); + const handlePostDeleted = useCallback((postId) => { + setWallPosts((prev) => prev.filter((p) => (p.id ?? p._id) !== postId)); + if (selectedPost && (selectedPost.id ?? selectedPost._id) === postId) { + setSelectedPost(null); + } + }, [selectedPost]); + + const handlePostUpdated = useCallback((updatedPost) => { + if (!updatedPost) return; + setWallPosts((prev) => + prev.map((p) => + (p.id ?? p._id) === (updatedPost.id ?? updatedPost._id) + ? { ...p, ...updatedPost } + : p + ) + ); + }, []); + // charge le thème au démarrage useEffect(() => { if (typeof window === "undefined") return; @@ -186,6 +204,8 @@ export default function App() { error={wallError} selectedPost={selectedPost} onSelectPost={setSelectedPost} + onPostDeleted={handlePostDeleted} + onPostUpdated={handlePostUpdated} /> {/* CHAT PANEL SUPPRIMÉ */} diff --git a/src/components/Friends/Friends.css b/src/components/Friends/Friends.css index 7f39724..896cd6b 100644 --- a/src/components/Friends/Friends.css +++ b/src/components/Friends/Friends.css @@ -225,3 +225,117 @@ background: rgba(248,113,113,0.25); border-color: rgba(248,113,113,0.7); } + +/* Add Friend Form */ +.friends-add-form { + display: flex; + gap: .25rem; + margin-bottom: .4rem; +} + +.friends-add-input { + flex: 1; + padding: .35rem .5rem; + border-radius: 8px; + border: 1px solid rgba(71, 85, 105, 0.5); + background: rgba(15, 23, 42, 0.8); + color: #e5e7eb; + font-size: .75rem; +} + +.friends-add-input::placeholder { + color: #64748b; +} + +.friends-add-input:focus { + outline: none; + border-color: rgba(56, 189, 248, 0.6); +} + +.friends-add-btn { + width: 32px; + height: 32px; + border-radius: 8px; + border: 1px solid rgba(56, 189, 248, 0.4); + background: rgba(56, 189, 248, 0.15); + color: #7dd3fc; + font-size: .8rem; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s; +} + +.friends-add-btn:hover:not(:disabled) { + background: rgba(56, 189, 248, 0.25); + border-color: rgba(56, 189, 248, 0.7); +} + +.friends-add-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.friends-add-error { + font-size: .7rem; + color: #fca5a5; + padding: .2rem .3rem; + margin-bottom: .3rem; +} + +.friends-add-success { + font-size: .7rem; + color: #4ade80; + padding: .2rem .3rem; + margin-bottom: .3rem; +} + +/* Suggestions dropdown */ +.friends-add-wrap { + position: relative; + margin-bottom: .4rem; +} + +.friends-suggestions { + position: absolute; + top: 100%; + left: 0; + right: 0; + background: rgba(15, 23, 42, 0.98); + border: 1px solid rgba(56, 189, 248, 0.4); + border-radius: 8px; + margin-top: 4px; + max-height: 180px; + overflow-y: auto; + z-index: 10; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); +} + +.friends-suggestion-item { + display: flex; + align-items: center; + gap: .4rem; + padding: .4rem .5rem; + cursor: pointer; + font-size: .75rem; + color: #e5e7eb; + transition: background 0.15s; +} + +.friends-suggestion-item:hover { + background: rgba(56, 189, 248, 0.15); +} + +.friends-suggestion-item .friend-avatar { + width: 24px; + height: 24px; + font-size: .65rem; +} + +.friends-suggestions-loading { + font-size: .7rem; + color: #64748b; + padding: .3rem .4rem; + text-align: center; +} diff --git a/src/components/Friends/FriendsList.jsx b/src/components/Friends/FriendsList.jsx index 250c137..d69c316 100644 --- a/src/components/Friends/FriendsList.jsx +++ b/src/components/Friends/FriendsList.jsx @@ -1,12 +1,14 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useRef } from 'react'; import { fetchFriends, fetchFriendRequests, acceptFriendRequest, rejectFriendRequest, removeFriend, + sendFriendRequest, } from '../../api/client'; import { useAuth } from '../../auth/AuthContext'; +import { recoSearch } from '../../services/recoSearch'; import './Friends.css'; export default function FriendsList({ onSelectUser, onClose }) { @@ -16,6 +18,19 @@ export default function FriendsList({ onSelectUser, onClose }) { const [requests, setRequests] = useState([]); const [loading, setLoading] = useState(true); + // Add friend state + const [addUsername, setAddUsername] = useState(''); + const [addLoading, setAddLoading] = useState(false); + const [addError, setAddError] = useState(''); + const [addSuccess, setAddSuccess] = useState(''); + + // Autocomplete state + const [suggestions, setSuggestions] = useState([]); + const [showSuggestions, setShowSuggestions] = useState(false); + const [suggestionsLoading, setSuggestionsLoading] = useState(false); + const searchTimeoutRef = useRef(null); + const inputRef = useRef(null); + useEffect(() => { if (!authenticated) return; @@ -33,6 +48,58 @@ export default function FriendsList({ onSelectUser, onClose }) { loadData(); }, [authenticated]); + // Search for users with debounce + const searchUsers = async (query) => { + if (!query || query.length < 2) { + setSuggestions([]); + setShowSuggestions(false); + return; + } + + setSuggestionsLoading(true); + try { + const results = await recoSearch(query, { limit: 8 }); + // Filter only profiles + const profiles = results.filter( + (r) => r.type === 'profile' || r.kind === 'profile' + ); + setSuggestions(profiles); + setShowSuggestions(profiles.length > 0); + } catch { + setSuggestions([]); + } + setSuggestionsLoading(false); + }; + + const handleInputChange = (e) => { + const value = e.target.value; + setAddUsername(value); + + // Debounce search + if (searchTimeoutRef.current) { + clearTimeout(searchTimeoutRef.current); + } + searchTimeoutRef.current = setTimeout(() => { + searchUsers(value); + }, 300); + }; + + const handleSelectSuggestion = (username) => { + setAddUsername(username); + setSuggestions([]); + setShowSuggestions(false); + inputRef.current?.focus(); + }; + + // Cleanup timeout on unmount + useEffect(() => { + return () => { + if (searchTimeoutRef.current) { + clearTimeout(searchTimeoutRef.current); + } + }; + }, []); + const handleAccept = async (username) => { const res = await acceptFriendRequest(username); if (res?.ok) { @@ -58,6 +125,28 @@ export default function FriendsList({ onSelectUser, onClose }) { } }; + const handleAddFriend = async (e) => { + e.preventDefault(); + const uname = addUsername.trim(); + if (!uname) return; + + setAddLoading(true); + setAddError(''); + setAddSuccess(''); + + const res = await sendFriendRequest(uname); + setAddLoading(false); + + if (res?.ok) { + setAddSuccess(`Friend request sent to ${uname}`); + setAddUsername(''); + setTimeout(() => setAddSuccess(''), 3000); + } else { + setAddError(res?.error || res?.text || 'Could not send request'); + setTimeout(() => setAddError(''), 4000); + } + }; + if (!authenticated) { return (