/* eslint-disable react/display-name */ import { useDataFromRef } from "react-native-minuit/src/hooks"; import useMinuit from "react-native-minuit/src/hooks/useMinuit"; import { createContext, useContext, useGlobal } from "reactn"; import { useCallback, useEffect, useMemo, useState } from "react"; import { checkIfEmailIsValid } from "../actions/signupActions"; import { showPremiumRequiredAlert } from "../components/Alert"; import firebase, { arrayRemove, arrayUnion, playlistsRef, projectsRef, usersRef, videosRef, } from "../config/firebase"; import { getUserPreferredArtistName } from "../utils/artistName"; import { getLikeFieldPath, LIKE_TARGET } from "../utils/likes"; import { ensureAuthenticated } from "../utils/authRedirect"; import { isWeb } from "../hooks/useLayoutType"; import useUserLikedProjects from "../hooks/useUserLikedProjects"; const PLAYBACK_LIKES_FIELD = getLikeFieldPath(LIKE_TARGET.PLAYBACK); const ACTIVE_SUBSCRIPTION_STATUSES = new Set([ "trialing", "active", "past_due", "unpaid", ]); export const UserDataContext = createContext(); export default ({ children }) => { const { setIsLoading, setTooltip } = useMinuit(); const [pendingUserData, setPendingUserData] = useState({ createdAt: new Date(), }); const [currentUID] = useGlobal("currentUID"); const [currentUserData, setCurrentUserData] = useGlobal("currentUserData"); const [currentUserRoles] = useGlobal("currentUserRoles"); // Current user document (live) const { data: currentUserDoc = null } = useDataFromRef({ ref: currentUID ? usersRef.doc(currentUID) : null, updateGlobalState: "currentUserData", simpleRef: true, listener: true, condition: !!currentUID, refreshArray: [currentUID], }); // Subscribe to user's projects (musics) const { data: userProjects = [], loading: userProjectsLoading = true } = useDataFromRef({ ref: currentUID ? projectsRef .where("userId", "==", currentUID) .orderBy("updatedAt", "desc") : null, simpleRef: false, listener: true, condition: !!currentUID, refreshArray: [currentUID], }); const userPlaybacks = Array.isArray(userProjects) ? userProjects.filter((project) => project.playbackUrl) : []; // Subscribe to user's liked projects (both new and legacy storage) const { projects: userLikedProjects = [], loading: userLikedProjectsLoading = true, } = useUserLikedProjects(); const toTimestamp = useCallback((value) => { if (!value) return 0; if (typeof value.toDate === "function") { return value.toDate().getTime(); } if (typeof value.seconds === "number") { return value.seconds * 1000; } if (typeof value === "number") { return value; } return 0; }, []); const sortByPlaybackLikeDate = useCallback( (list) => { if (!Array.isArray(list)) return []; const getLikeTimestamp = (project) => { const likedAt = project?.likes?.playbackLikedAt?.[currentUID]; const likedAtTs = likedAt ? toTimestamp(likedAt) : 0; if (likedAtTs > 0) return likedAtTs; return toTimestamp(project?.updatedAt); }; return [...list].sort( (a, b) => getLikeTimestamp(b) - getLikeTimestamp(a), ); }, [currentUID, toTimestamp], ); const { data: userLikedPlaybacks = [], loading: userLikedPlaybacksLoading = true, } = useDataFromRef({ ref: currentUID ? projectsRef.where(PLAYBACK_LIKES_FIELD, "array-contains", currentUID) : null, simpleRef: false, listener: true, condition: !!currentUID, refreshArray: [currentUID], format: sortByPlaybackLikeDate, }); // Subscribe to user's playlists const { data: userPlaylists = [] } = useDataFromRef({ ref: currentUID ? playlistsRef.where("createdBy", "==", currentUID) : null, simpleRef: false, listener: true, condition: !!currentUID, refreshArray: [currentUID], }); // Live list of users that the current user is following ("abonnements") const { data: userFollowing = [] } = useDataFromRef({ ref: currentUID ? usersRef.where("followedBy", "array-contains", currentUID) : null, simpleRef: false, listener: true, condition: !!currentUID, refreshArray: [currentUID], }); const updatePendingUserData = (newData) => { setPendingUserData((prevData) => ({ ...prevData, ...newData, })); }; // Selected project state and helpers const [selectedProjectId, setSelectedProjectId] = useState(null); const { data: selectedProject = null, setData: setSelectedProject } = useDataFromRef({ ref: selectedProjectId ? projectsRef.doc(selectedProjectId) : null, simpleRef: true, listener: true, condition: !!selectedProjectId, refreshArray: [selectedProjectId], }); const persistSelectedProjectId = useCallback( async (projectId) => { if (!currentUID) return; try { await usersRef.doc(currentUID).set( { selectedProjectId: projectId || null, }, { merge: true }, ); console.log("update selected project"); } catch (error) { console.log( "UserDataProvider: unable to persist selectedProjectId", error?.message || error, ); } }, [currentUID], ); const selectProject = useCallback( (projectId) => { const safeId = projectId || null; setSelectedProjectId(safeId); if (!safeId) { setSelectedProject(null); } if (currentUID) { persistSelectedProjectId(safeId).catch((error) => { console.log( "UserDataProvider: persist selectedProjectId failed", error?.message || error, ); }); } }, [currentUID, persistSelectedProjectId, setSelectedProject], ); const resetSelectedProject = useCallback(() => { selectProject(null); }, [selectProject]); // Ancienne variante de création de projet supprimée pour éviter les doublons. const updateProjectData = async (partial = {}, options = { merge: true }) => { if (!selectedProjectId) return; try { await projectsRef.doc(selectedProjectId).set( { ...partial, updatedAt: firebase.firestore.FieldValue.serverTimestamp(), }, options, ); } catch (e) { console.log("updateProjectData error", e?.message); throw e; } }; const createNewProject = async ({ hasLyrics = false } = {}) => { if (!ensureAuthenticated(currentUID)) { return null; } const existingProjectsCount = Array.isArray(userProjects) ? userProjects.length : 0; const hasUnlimitedCreation = true; // currentUserDoc?.isPremium; mettre en place en prod if (!hasUnlimitedCreation && existingProjectsCount >= 1) { showPremiumRequiredAlert(); return null; } try { await setIsLoading(true); const artistDisplayName = getUserPreferredArtistName( currentUserDoc || currentUserData || {}, ); const payload = { userId: currentUID || null, userName: artistDisplayName, createdAt: firebase.firestore.FieldValue.serverTimestamp(), updatedAt: firebase.firestore.FieldValue.serverTimestamp(), }; const { id } = await projectsRef.add(payload); selectProject(id); return id; } catch (e) { console.log("createNewProject error", e?.message); throw e; } finally { await setIsLoading(false); } }; const followUser = async (userId) => { try { if (!ensureAuthenticated(currentUID)) { return; } if (userId) { await usersRef.doc(userId).set( { followedBy: arrayUnion(currentUID), lastFollowersUpdateAt: new Date(), }, { merge: true }, ); setTooltip({ type: "success", text: "Abonnement mis à jour" }); } } catch (e) { console.log(e); setTooltip({ type: "error", text: e?.message || "Action impossible" }); } }; const unfollowUser = async (userId) => { try { if (!ensureAuthenticated(currentUID)) { return; } if (userId) { await usersRef.doc(userId).set( { followedBy: arrayRemove(currentUID), lastFollowersUpdateAt: new Date(), }, { merge: true }, ); setTooltip({ type: "success", text: "Vous ne suivez plus cet utilisateur", }); } } catch (e) { console.log(e); setTooltip({ type: "error", text: e?.message || "Action impossible" }); } }; const getUserByUid = async (uid) => { try { const userDoc = await usersRef.doc(uid).get(); return userDoc.exists ? { id: userDoc.id, ...userDoc.data(), } : null; } catch (e) { console.log(e); return null; } }; const updateUserData = async ({ data, shouldSetTooltip = true }) => { try { await setIsLoading(true); let dynamicUID = currentUID || null; let cleanData = { ...currentUserData, ...data, lastUpdateTimestamp: new Date(), }; // if (cleanData?.firstName?.length > 2 && cleanData?.lastName?.length > 2) { // cleanData = { // ...cleanData, // firstName: capitalize(cleanData?.firstName?.toLowerCase()), // lastName: capitalize(cleanData?.lastName?.toLowerCase()), // }; // } else { // throw new Error("Veuillez renseigner un prénom et un nom"); // } if (!checkIfEmailIsValid({ email: cleanData?.email })) { throw new Error("Veuillez renseigner un email valide"); } // let cleanPhoneNumber = formatPhoneNumber({ // phoneNumber: cleanData.phoneNumber, // }); // if (cleanPhoneNumber) { // cleanData.phoneNumber = cleanPhoneNumber; // } else { // throw new Error("Veuillez renseigner un numéro de téléphone valide"); // } if (!dynamicUID) { const { email, password } = cleanData; const { user = null } = await firebase .auth() .createUserWithEmailAndPassword(email, password); dynamicUID = user.uid; } delete cleanData.password; cleanData = { ...cleanData, userID: dynamicUID, }; if (!cleanData?.createdAt) { cleanData.createdAt = firebase.firestore.FieldValue.serverTimestamp(); } await usersRef.doc(dynamicUID).set(cleanData, { merge: true }); if (shouldSetTooltip) { setTooltip({ type: "success", text: "Données enregistrées avec succès", }); } } catch (e) { console.log(e); setTooltip({ type: "error", text: "Erreur lors de la mise à jour des données utilisateur", }); throw e; } finally { await setIsLoading(false); } }; const onSignOut = async () => { try { setIsLoading(true); await firebase.auth().signOut(); } catch (e) { console.log(e); } finally { setIsLoading(false); } }; const isSuperAdmin = currentUserRoles.some((role) => role === "SUPERADMIN"); useEffect(() => { const remoteSelectedId = currentUserDoc?.selectedProjectId; if (!remoteSelectedId) { if (remoteSelectedId === null && selectedProjectId !== null) { setSelectedProjectId(null); setSelectedProject(null); } return; } if (remoteSelectedId !== selectedProjectId) { setSelectedProjectId(remoteSelectedId); } }, [ currentUserDoc?.selectedProjectId, selectedProjectId, setSelectedProject, ]); useEffect(() => { if (!Array.isArray(userProjects) || userProjects.length === 0) return; if (selectedProjectId) return; const remoteSelectedId = currentUserDoc?.selectedProjectId; if (remoteSelectedId !== undefined) return; const fallbackProjectId = userProjects[0]?.id; if (!fallbackProjectId) return; selectProject(fallbackProjectId); }, [ currentUserDoc?.selectedProjectId, selectProject, selectedProjectId, userProjects, ]); const { data: videos } = useDataFromRef({ ref: videosRef.doc("fr"), simpleRef: true, }); const hasActiveSubscription = useMemo(() => { const pickStatus = (value) => { if (typeof value !== "string") return null; const trimmed = value.trim(); return trimmed ? trimmed.toLowerCase() : null; }; const statusCandidates = [ pickStatus(currentUserDoc?.stripeSubscriptionStatus), pickStatus(currentUserDoc?.stripeSubscription?.status), pickStatus(currentUserDoc?.stripeSubscription?.stripeSubscriptionStatus), pickStatus(currentUserDoc?.stripeSubscription?.metadata?.status), ].filter(Boolean); if ( statusCandidates.some((status) => ACTIVE_SUBSCRIPTION_STATUSES.has(status), ) ) { return true; } const premiumUntil = currentUserDoc?.premiumUntil; if (premiumUntil) { const asDate = premiumUntil?.toDate?.() || (premiumUntil?.seconds ? new Date(premiumUntil.seconds * 1000) : null); if (asDate && asDate > new Date()) { return true; } } if (currentUserDoc?.premium?.active) { return true; } const hasPremiumLevel = typeof currentUserDoc?.premiumLevel === "string" && currentUserDoc.premiumLevel.trim().length > 0; return hasPremiumLevel; }, [currentUserDoc]); return ( {children} ); }; export const useUserData = () => { return useContext(UserDataContext); }; // Convenience alias returning the same context, as requested export const useUser = () => { return useContext(UserDataContext); };