// screens-profile.jsx — profile + reviews + my profile
// Helpers : adapte un user qui peut venir du mock (objects bilingues) OU de l'API (strings)
function userName(u, lang) {
if (!u) return '';
if (typeof u.name === 'object' && u.name !== null) return u.name[lang] || u.name.fr || '';
return u.name || '';
}
function userLocation(u, lang) {
if (!u) return '';
if (u.city) return u.city;
if (typeof u.location === 'object' && u.location !== null) return u.location[lang] || u.location.fr || '';
return u.location || '';
}
function userBio(u, lang) {
if (!u) return '';
if (typeof u.bio === 'object' && u.bio !== null) return u.bio[lang] || u.bio.fr || '';
return u.bio || '';
}
function userPhoto(u) {
return (u && (u.profile_photo || u.avatar)) || null;
}
function memberSinceLabel(u, lang) {
if (!u || !u.created_at) return null;
const d = new Date(u.created_at);
if (isNaN(d.getTime())) return null;
const year = d.getFullYear();
return lang === 'ka' ? `წევრი ${year}-დან` : `Membre depuis ${year}`;
}
function ProfileScreen({ user, lang, t, onBack, onOpenChat, onLeaveReview, isMe = false, onOpenReferral, onLogout, onOpenMyListings, onOpenFavorites, onOpenAbout, onOpenContact, onOpenPrivacy, onOpenTerms, onOpenDeleteAccount, onOpenBlocked }) {
const [showReport, setShowReport] = React.useState(false);
const [showBlock, setShowBlock] = React.useState(false);
// En mode "isMe", on utilise toujours le user réel du localStorage (ignore le prop user mock)
const realUser = isMe
? (window.TrustaAuth && window.TrustaAuth.getUser()) || null
: user;
if (!realUser) {
return (
{lang === 'ka' ? 'იტვირთება…' : 'Chargement…'}
);
}
const name = userName(realUser, lang);
const location = userLocation(realUser, lang);
const photo = userPhoto(realUser);
const bio = userBio(realUser, lang);
const memberSince = memberSinceLabel(realUser, lang);
const verified = !!realUser.verified;
// Stats : seulement si le user a des données réelles (pour l'instant aucune)
const hasStats = (realUser.missions != null && realUser.missions > 0)
|| (realUser.recommend != null);
const hasReviews = realUser.reviewCount && realUser.reviewCount > 0;
const hasRating = realUser.rating != null && realUser.rating > 0;
const handleLogout = async () => {
if (!window.TrustaAPI) return;
try { await window.TrustaAPI.logout(); } catch (e) {}
if (onLogout) onLogout();
else window.location.reload();
};
return (
{!isMe && realUser && realUser.id && (
)}
} />
{name || (lang === 'ka' ? 'უსახელო' : 'Sans nom')}
{verified && (
)}
{(location || memberSince) && (
{location && (
<>
{location}
>
)}
{location && memberSince && ' · '}
{memberSince}
)}
{hasRating && (
{realUser.rating}
({realUser.reviewCount} {t('reviews')})
)}
{/* Stats — seulement si le user a des données réelles */}
{hasStats && (
{[
{ val: realUser.missions || 0, label: t('missions_done') },
{ val: realUser.years || 0, label: t('years_active') },
{ val: (realUser.recommend || 0) + '%', label: t('recommendation') },
].map((s, i) => (
0 ? `1px solid ${COLORS.borderSoft}` : 'none' }}>
{s.val}
{s.label}
))}
)}
{/* Bio */}
{bio && (
{bio}
)}
{/* Empty state pour profil neuf */}
{isMe && !bio && !hasReviews && !hasStats && (
{lang === 'ka'
? 'შენი პროფილი ჯერ ცარიელია. შექმენი განცხადება, რომ თემამ გაგიცნოს.'
: 'Ton profil est encore vide. Crée une annonce pour que la communauté te découvre.'}
)}
{/* Reviews — seulement si vraies reviews */}
{hasReviews && window.REVIEWS && window.REVIEWS[realUser.id] && (
{t('reviews_section')}
{t('view_all')}
{window.REVIEWS[realUser.id].slice(0, 2).map((r, i) => {
const author = window.MEMBERS && window.MEMBERS[r.author];
if (!author) return null;
return (
{userName(author, lang)}
{r.date}
{r.text[lang]}
);
})}
)}
{!isMe && (
setShowReport(true)} style={{
display: 'flex', alignItems: 'center', gap: 6,
background: 'transparent', border: 'none', cursor: 'pointer',
color: COLORS.text2, fontFamily: 'Inter', fontSize: 13, fontWeight: 500,
padding: '6px 10px',
}}>
🚩
{lang === 'ka' ? 'სიგნალი' : 'Signaler'}
setShowBlock(true)} style={{
display: 'flex', alignItems: 'center', gap: 6,
background: 'transparent', border: 'none', cursor: 'pointer',
color: COLORS.red, fontFamily: 'Inter', fontSize: 13, fontWeight: 500,
padding: '6px 10px',
}}>
🚫
{lang === 'ka' ? 'დაბლოკვა' : 'Bloquer'}
)}
{isMe && (
{lang === 'ka' ? 'ჩემი განცხადებები' : 'Mes annonces'}
{lang === 'ka' ? 'ფავორიტები' : 'Favoris'}
{t('invite_friends')}
{lang === 'ka' ? 'გასვლა' : 'Déconnexion'}
{/* Section légale & support (pages requises App Store / Google Play) */}
{lang === 'ka' ? 'ინფორმაცია' : 'Informations'}
{onOpenAbout && (
{lang === 'ka' ? 'ჩვენს შესახებ' : 'À propos'}
)}
{onOpenContact && (
{lang === 'ka' ? 'კონტაქტი' : 'Contact / Aide'}
)}
{onOpenPrivacy && (
{lang === 'ka' ? 'კონფიდენციალურობა' : 'Confidentialité'}
)}
{onOpenTerms && (
{lang === 'ka' ? 'პირობები' : 'Conditions d\'utilisation'}
)}
{onOpenBlocked && (
{lang === 'ka' ? 'დაბლოკილი მომხმარებლები' : 'Utilisateurs bloqués'}
)}
{/* Suppression de compte — obligatoire Apple/Google. Rouge pour signaler la criticité */}
{onOpenDeleteAccount && (
{lang === 'ka' ? 'ანგარიშის წაშლა' : 'Supprimer mon compte'}
)}
TRUSTA · v1.0 · trusta.click
)}
{!isMe && onOpenChat && (
{onLeaveReview && {t('leave_review')} }
onOpenChat(realUser)}>{t('message_btn')}
)}
{/* Modals signaler / bloquer */}
{showReport && window.ReportModal && (
setShowReport(false)}
/>
)}
{showBlock && window.BlockUserModal && (
setShowBlock(false)}
onBlocked={() => {
// Une fois bloqué, retour à l'accueil (on ne devrait plus voir ce profil)
if (onBack) onBack();
}}
/>
)}
);
}
function menuRowStyle() {
return {
display: 'flex', alignItems: 'center', gap: 12, padding: '14px 14px',
background: COLORS.card, borderRadius: 12, border: `1px solid ${COLORS.borderSoft}`,
color: '#fff', fontFamily: 'Inter', fontSize: 14, fontWeight: 500,
cursor: 'pointer', textAlign: 'left',
};
}
function menuIconStyle() {
return {
width: 28, height: 28, borderRadius: 8, background: COLORS.goldSoft,
display: 'flex', alignItems: 'center', justifyContent: 'center',
};
}
function ReviewModal({ user, lang, t, onClose }) {
const [stars, setStars] = React.useState(5);
const [text, setText] = React.useState('');
const [sent, setSent] = React.useState(false);
return (
{sent ? (
{lang === 'ka' ? 'გმადლობთ!' : 'Merci !'}
{lang === 'ka' ? 'შეფასება გაგზავნილია' : 'Votre avis a été envoyé'}
) : (
{userName(user, lang)}
{t('how_was')}
{[1, 2, 3, 4, 5].map(i => (
setStars(i)} style={{ background: 'transparent', border: 'none', cursor: 'pointer', padding: 4 }}>
))}
{lang === 'ka' ? 'გაუქმება' : 'Annuler'}
{ setSent(true); setTimeout(onClose, 1400); }}>{t('send')}
)}
);
}
// === Mes annonces ===
function MyListingsScreen({ lang, t, onBack, onOpenListing, onCreateListing }) {
const [listings, setListings] = React.useState(null);
const [error, setError] = React.useState(null);
const me = (window.TrustaAuth && window.TrustaAuth.getUser()) || null;
const load = React.useCallback(() => {
if (!window.API_AVAILABLE || !me) { setListings([]); return; }
setListings(null); setError(null);
window.TrustaAPI.listListings({ user_id: me.id })
.then(rows => setListings(rows || []))
.catch(err => { setError(err); setListings([]); });
}, [me ? me.id : 0]);
React.useEffect(() => { load(); }, [load]);
const handleDelete = async (l) => {
const msg = lang === 'ka' ? 'წავშალო ეს განცხადება?' : 'Supprimer cette annonce ?';
if (!window.confirm(msg)) return;
try {
await window.TrustaAPI.deleteListing(l.id);
setListings(prev => prev.filter(x => x.id !== l.id));
} catch (e) {
alert(window.errorMessage ? window.errorMessage(e, lang) : 'Erreur');
}
};
return (
{listings === null ? (
{lang === 'ka' ? 'იტვირთება...' : 'Chargement...'}
) : listings.length === 0 ? (
📋
{lang === 'ka' ? 'ჯერ განცხადება არ გაქვთ' : 'Pas encore d\'annonce'}
{lang === 'ka' ? 'შექმენი შენი პირველი განცხადება' : 'Créez votre première annonce'}
{onCreateListing && (
{lang === 'ka' ? '+ განცხადების დამატება' : '+ Créer une annonce'}
)}
) : (
{listings.map(l => {
const title = (typeof l.title === 'object' ? (l.title[lang] || l.title.fr) : l.title) || '';
const city = l.city || (Array.isArray(l.cities) ? l.cities[0] : '');
const photo = (Array.isArray(l.photos) && l.photos[0]) || null;
const price = l.price && l.price.amount ? `${l.price.amount}€` : (lang === 'ka' ? 'შესათანხმებელი' : 'À discuter');
return (
onOpenListing(l)} style={{
width: 84, height: 84, borderRadius: 10, border: 'none', cursor: 'pointer',
flexShrink: 0,
background: photo ? `url(${photo}) center/cover` : `linear-gradient(135deg, ${COLORS.bg2} 0%, rgba(212,175,55,0.06) 100%)`,
}} />
onOpenListing(l)} style={{
background: 'transparent', border: 'none', cursor: 'pointer', textAlign: 'left', padding: 0,
}}>
{title}
📍 {city || '—'} · {price}
onOpenListing(l)} style={smallBtnStyle(false)}>
{lang === 'ka' ? 'ნახვა' : 'Voir'}
handleDelete(l)} style={smallBtnStyle(true)}>
🗑 {lang === 'ka' ? 'წაშლა' : 'Supprimer'}
);
})}
)}
);
}
function smallBtnStyle(danger) {
return {
flex: 1, padding: '8px 10px', borderRadius: 8,
background: 'transparent',
border: danger ? '1px solid rgba(255,107,107,0.5)' : `1px solid ${COLORS.borderSoft}`,
color: danger ? '#ff6b6b' : '#fff',
fontFamily: 'Inter', fontSize: 12, fontWeight: 500, cursor: 'pointer',
};
}
// === Favoris (2 onglets : annonces + profils) ===
function FavoritesScreen({ lang, t, onBack, onOpenListing, onOpenProfile }) {
const [activeTab, setActiveTab] = React.useState('listing');
const [listings, setListings] = React.useState(null);
const [profiles, setProfiles] = React.useState(null);
const [error, setError] = React.useState(null);
const load = React.useCallback(async (tab) => {
if (!window.API_AVAILABLE) { setListings([]); setProfiles([]); return; }
setError(null);
try {
if (tab === 'listing') {
setListings(null);
const rows = await window.TrustaAPI.listFavorites('listing');
setListings(rows || []);
} else {
setProfiles(null);
const rows = await window.TrustaAPI.listFavorites('profile');
setProfiles(rows || []);
}
} catch (e) {
setError(e);
if (tab === 'listing') setListings([]); else setProfiles([]);
}
}, []);
React.useEffect(() => { load(activeTab); }, [activeTab, load]);
return (
{/* Tabs switcher */}
{[
{ key: 'listing', fr: 'Annonces', ka: 'განცხადებები' },
{ key: 'profile', fr: 'Profils', ka: 'პროფილები' },
].map(tab => (
setActiveTab(tab.key)} style={{
flex: 1, padding: '10px 12px', borderRadius: 8,
background: activeTab === tab.key ? COLORS.gold : 'transparent',
color: activeTab === tab.key ? '#0A0A0A' : COLORS.text2,
border: 'none', cursor: 'pointer',
fontFamily: 'Inter', fontSize: 13, fontWeight: 600,
}}>
{tab[lang]}
))}
{activeTab === 'listing' ? (
listings === null ? (
) : listings.length === 0 ? (
) : (
{listings.map(l => )}
)
) : (
profiles === null ? (
) : profiles.length === 0 ? (
) : (
{profiles.map(p => )}
)
)}
);
}
function Loading({ lang }) {
return (
{lang === 'ka' ? 'იტვირთება...' : 'Chargement...'}
);
}
function EmptyFav({ lang, kind }) {
const txt = kind === 'listing'
? { fr: 'Aucune annonce en favori', ka: 'რჩეულ განცხადებებში ცარიელია' }
: { fr: 'Aucun profil en favori', ka: 'რჩეულ პროფილებში ცარიელია' };
const hint = kind === 'listing'
? { fr: 'Touche le ♡ sur une annonce pour l\'ajouter ici', ka: 'დააჭირე ♡-ს განცხადებაზე' }
: { fr: 'Touche le ♡ sur un profil pour l\'ajouter ici', ka: 'დააჭირე ♡-ს პროფილზე' };
return (
♡
{txt[lang]}
{hint[lang]}
);
}
function FavListingRow({ listing, lang, t, onOpen }) {
const title = (typeof listing.title === 'object' ? (listing.title[lang] || listing.title.fr) : listing.title) || '';
const city = listing.city || (Array.isArray(listing.cities) ? listing.cities[0] : '');
const photo = (Array.isArray(listing.photos) && listing.photos[0]) || null;
const price = listing.price && listing.price.amount ? `${listing.price.amount}€` : (lang === 'ka' ? 'შესათანხმებელი' : 'À discuter');
return (
onOpen(listing)} style={{
width: '100%', textAlign: 'left', padding: 0, border: 'none', cursor: 'pointer',
background: COLORS.card, borderRadius: 14, overflow: 'hidden',
display: 'flex', gap: 12, alignItems: 'stretch',
}}>
{title}
📍 {city || '—'}
{price}
);
}
function FavProfileRow({ user, lang, onOpen }) {
return (
onOpen(user)} style={{
width: '100%', textAlign: 'left', padding: 12, border: 'none', cursor: 'pointer',
background: COLORS.card, borderRadius: 14,
display: 'flex', gap: 12, alignItems: 'center',
}}>
{user.name || (lang === 'ka' ? 'უსახელო' : 'Sans nom')}
{user.city ? `📍 ${user.city}` : ''}
{user.role ? `${user.city ? ' · ' : ''}${user.role === 'worker' ? (lang === 'ka' ? 'ვეძებ სამუშაოს' : 'Cherche du travail') : (lang === 'ka' ? 'ვთავაზობ სამუშაოს' : 'Propose du travail')}` : ''}
);
}
window.ProfileScreen = ProfileScreen;
window.MyListingsScreen = MyListingsScreen;
window.FavoritesScreen = FavoritesScreen;
window.ReviewModal = ReviewModal;