import React, { useCallback, useEffect, useMemo, useRef, useState, } from "react"; import { Platform, StyleSheet, View } from "react-native"; import { useIsFocused } from "@react-navigation/native"; import useMinuit from "react-native-minuit/src/hooks/useMinuit.js"; import { background, videos } from "../../assets"; import Alert from "../../components/Alert"; import BorderGradientButton from "../../components/BorderGradientButton"; import FeatureCarousel from "../../components/FeatureCarousel/FeatureCarousel"; import FullscreenIntroVideo from "../../components/FullscreenIntroVideo"; import GradientButton from "../../components/GradientButton"; import MoreMenu from "../../components/MoreMenu"; import ProjectDropDown from "../../components/ProjectDropDown/ProjectDropDown"; import ShareBtn from "../../components/ShareBtn/ShareBtn"; import { projectsRef, usersRef, videosRef } from "../../config/firebase"; import useDataFromRef from "../../hooks/useDataFromRef"; import { isWeb } from "../../hooks/useLayoutType.js"; import Page from "../../layouts/Page"; import LandingPage from "../LandingPage"; import { navigate } from "../../navigation/NavigationService"; import { Routes } from "../../navigation/Routes"; import { useUser } from "../../providers/UserDataProvider"; import { Palette } from "../../styles"; import { findFirstUnlockedStageIndex, getCreationStageStates, getStageAction, } from "../../utils/projectStages"; const isProjectEmpty = (project) => { if (!project) { return false; } return !( typeof project?.title === "string" && project.title.trim().length > 0 ); }; const Home = ({ navigation, route }) => { const isFocused = useIsFocused(); const { userProjects = [], selectProject, selectedProject, selectedProjectId, currentUserData, currentUID, createNewProject, } = useUser(); const { setTooltip } = useMinuit(); if (!currentUID) { return ; } const projects = useMemo( () => (Array.isArray(userProjects) ? userProjects : []), [userProjects] ); const currentProject = useMemo(() => { if (!projects.length) return null; if (selectedProjectId === null) { return null; } const activeId = selectedProject?.id || selectedProjectId; if (!activeId) { return projects[0]; } return projects.find((project) => project?.id === activeId) || projects[0]; }, [projects, selectedProject?.id, selectedProjectId]); const formatDate = useCallback((timestamp) => { try { const value = timestamp?.toDate ? timestamp.toDate() : timestamp; const date = value ? new Date(value) : null; if (!date || Number.isNaN(date.getTime())) { return ""; } const day = date.getDate().toString().padStart(2, "0"); const month = (date.getMonth() + 1).toString().padStart(2, "0"); return `${day}/${month}`; } catch (error) { console.warn("Home.web: formatDate error", error); return ""; } }, []); const stageStates = useMemo( () => getCreationStageStates(currentProject), [currentProject] ); const preferredStageIndex = useMemo(() => { if (!stageStates.length) { return 0; } const actionableIndex = stageStates.findIndex( (stage) => !stage.isCompleted && !stage.isLocked ); if (actionableIndex !== -1) { return actionableIndex; } const firstIncomplete = stageStates.findIndex( (stage) => !stage.isCompleted ); if (firstIncomplete !== -1) { return firstIncomplete; } const firstUnlocked = stageStates.findIndex((stage) => !stage.isLocked); if (firstUnlocked !== -1) { return firstUnlocked; } return stageStates.length - 1; }, [stageStates]); const [activeStageIndex, setActiveStageIndex] = useState(preferredStageIndex); const [menuVisible, setMenuVisible] = useState(false); const [menuAnchor, setMenuAnchor] = useState(null); const [menuProject, setMenuProject] = useState(null); const menuAnchorRef = useRef(null); const testLoaderTimeoutRef = useRef(null); const [isIntroVideoVisible, setIsIntroVideoVisible] = useState(false); const [hasLocalAdventureFlag, setHasLocalAdventureFlag] = useState(false); useEffect(() => { setActiveStageIndex((prev) => { const fallbackIndex = preferredStageIndex >= 0 && preferredStageIndex < stageStates.length ? preferredStageIndex : 0; if (prev == null || prev >= stageStates.length) { return fallbackIndex; } const current = stageStates[prev]; if (!current) { return fallbackIndex; } if ( (current.isLocked || current.isCompleted) && fallbackIndex !== prev && stageStates[fallbackIndex] ) { return fallbackIndex; } return prev; }); }, [stageStates, preferredStageIndex]); const handleSelectProject = useCallback( (project) => { if (!project?.id) return; selectProject(project.id); setActiveStageIndex(findFirstUnlockedStageIndex(project)); }, [selectProject] ); const handleCloseMenu = useCallback(() => { setMenuVisible(false); setMenuAnchor(null); setMenuProject(null); menuAnchorRef.current = null; }, []); const handleDeleteProject = useCallback(async () => { if (!menuProject?.id) return; try { await projectsRef.doc(menuProject.id).delete(); setTooltip?.({ type: "success", text: "Projet supprimé" }); } catch (error) { setTooltip?.({ type: "error", text: error?.message || "Suppression impossible", }); } finally { handleCloseMenu(); } }, [handleCloseMenu, menuProject?.id, setTooltip]); const handleModifyProject = useCallback((project, anchor) => { if (!project?.id) return; const prevAnchor = menuAnchorRef.current; const normalizedAnchor = typeof anchor === "number" ? { top: anchor } : anchor && typeof anchor === "object" ? anchor : {}; setMenuProject(project); setMenuAnchor(normalizedAnchor); setMenuVisible((prev) => { if (!prev) return true; const prevTop = typeof prevAnchor?.top === "number" ? prevAnchor.top : null; const nextTop = typeof normalizedAnchor?.top === "number" ? normalizedAnchor.top : null; return prevTop !== nextTop; }); menuAnchorRef.current = normalizedAnchor; }, []); const moreMenuItems = useMemo(() => { if (!menuProject?.id) return []; return [ { label: "Supprimer", onPress: () => Alert( "Confirmer la suppression", "Cette action supprimera définitivement ce projet.", [ { text: "Annuler", style: "cancel" }, { text: "Supprimer", style: "destructive", onPress: () => { handleDeleteProject(); }, }, ], { cancelable: true } ), }, ]; }, [handleDeleteProject, menuProject?.id]); useEffect(() => { if (!route?.params?.showLyricsCongrats) { return; } const beatmakerStage = currentProject ? getStageAction("beatmaker", currentProject) : null; Alert( "Céline", "Bravo ! Vous venez de terminer de créer les paroles de votre musique ! Maintenant vous pouvez passer à la prochaine étape : le Studio avec Malik. Dans cette étape vous allez pouvoir donner vie à votre chanson !", [ { text: "Retour à l'accueil", style: "cancel", }, { text: "Continuer avec Malik", onPress: () => { if (!currentProject?.id) { return; } if (selectedProject?.id !== currentProject.id) { selectProject(currentProject.id); } const targetRoute = beatmakerStage?.route || Routes.Compose; navigate(targetRoute, beatmakerStage?.params); }, }, ] ); navigation?.setParams?.({ showLyricsCongrats: false }); }, [ currentProject, navigation, selectProject, selectedProject?.id, route?.params?.showLyricsCongrats, ]); const activeStage = stageStates[activeStageIndex] || stageStates[preferredStageIndex]; const stageAction = useMemo(() => { if (!activeStage) { return null; } return getStageAction(activeStage.key, currentProject); }, [activeStage, currentProject]); const stageRoute = stageAction?.route || null; const stageParams = stageAction?.params; const stageLocked = activeStage?.isLocked ?? true; const hasActiveProject = !!currentProject; const ensureProjectSelected = useCallback(() => { if (!currentProject?.id) { return; } if (selectedProject?.id !== currentProject.id) { selectProject(currentProject.id); } }, [currentProject?.id, selectProject, selectedProject?.id]); const handleStageAction = useCallback(() => { if (!hasActiveProject || stageLocked || !stageRoute) { return; } ensureProjectSelected(); navigate(stageRoute, stageParams); }, [ ensureProjectSelected, hasActiveProject, stageLocked, stageParams, stageRoute, ]); useEffect(() => { return () => { if (testLoaderTimeoutRef.current) { clearTimeout(testLoaderTimeoutRef.current); testLoaderTimeoutRef.current = null; } }; }, []); const songwriterAction = useMemo( () => getStageAction("songwriter", null), [] ); const handleStartNew = useCallback(async () => { const targetRoute = songwriterAction?.route || Routes.WritingLyrics; const targetParams = songwriterAction?.params; try { const emptyProject = projects.find((project) => isProjectEmpty(project)) || null; if (emptyProject?.id) { selectProject(emptyProject.id); setActiveStageIndex(findFirstUnlockedStageIndex(emptyProject)); navigate(targetRoute, targetParams); return; } const newProjectId = await createNewProject({ hasLyrics: false }); if (!newProjectId) { return; } setActiveStageIndex(0); navigate(targetRoute, targetParams); } catch (error) { console.warn("Home: unable to start new project", error); setTooltip?.({ type: "error", text: "Impossible de démarrer un nouveau projet", }); } }, [ createNewProject, projects, selectProject, setActiveStageIndex, setTooltip, songwriterAction, ]); const continueDisabled = !hasActiveProject || stageLocked || !stageRoute; const startDisabled = !songwriterAction?.route; useEffect(() => { if (currentUserData?.adventureStarted) { setHasLocalAdventureFlag(true); } }, [currentUserData?.adventureStarted]); const persistAdventureStarted = useCallback(async () => { if (!currentUID) { return; } await usersRef .doc(currentUID) .set({ adventureStarted: true }, { merge: true }); }, [currentUID]); const markAdventureStarted = useCallback(() => { if (hasLocalAdventureFlag) { return; } setHasLocalAdventureFlag(true); persistAdventureStarted().catch((error) => { console.warn("Home: adventureStarted update failed", error); setHasLocalAdventureFlag(false); setTooltip?.({ type: "error", text: "Impossible de mettre à jour votre profil", }); }); }, [hasLocalAdventureFlag, persistAdventureStarted, setTooltip]); const handleStartVisit = useCallback(() => { setIsIntroVideoVisible(true); }, []); const handleIntroVideoClose = useCallback(() => { setIsIntroVideoVisible(false); markAdventureStarted(); }, [markAdventureStarted]); const adventureStarted = hasLocalAdventureFlag || !!currentUserData?.adventureStarted; const { data: video } = useDataFromRef({ ref: videosRef.doc("fr"), simpleRef: true, }); const videoUrl = isWeb ? video?.landingWeb : video?.landing; const homeBackgroundImage = isWeb ? background.homeBGWeb : background.homeBG; const landingBackgroundImage = homeBackgroundImage; return adventureStarted ? ( {!isWeb && } {/* Alert("Test", "Ceci est un test d'alert", [ { text: "Annuler", style: "cancel" }, { text: "OK", onPress: () => console.log("OK pressé") }, ]) } /> */} {/* navigate(Routes.SongReady)} disabled={continueDisabled} /> */} ) : ( <> ); }; export default Home; const styles = StyleSheet.create({ root: { flex: 1, }, rootWeb: { backgroundColor: "transparent", }, rootNative: { backgroundColor: "rgba(66, 91, 135, 0.3)", }, heroImage: { position: "absolute", top: -60, alignSelf: "center", width: "80%", maxWidth: 920, height: 420, opacity: 0.9, }, shareIcon: { width: 18, height: 18, tintColor: Palette.white, }, carouselSection: { width: "100%", flex: 1, }, carouselSectionElevated: { marginTop: -100, paddingTop: 80, }, });