home, subscription and other fixes
This commit is contained in:
+226
-226
@@ -5,13 +5,11 @@ import React, {
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { Platform, StyleSheet, View } from "react-native";
|
||||
import { useIsFocused } from "@react-navigation/native";
|
||||
import { StyleSheet, Text, View } from "react-native";
|
||||
import { Image as ExpoImage } from "expo-image";
|
||||
import useMinuit from "react-native-minuit/src/hooks/useMinuit.js";
|
||||
import { background, videos } from "../../assets";
|
||||
import { background, cardsImg, icons, 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";
|
||||
@@ -26,11 +24,13 @@ import { navigate } from "../../navigation/NavigationService";
|
||||
import { Routes } from "../../navigation/Routes";
|
||||
import { useUser } from "../../providers/UserDataProvider";
|
||||
import { Palette } from "../../styles";
|
||||
import { FONT_FAMILY } from "../../styles/Fonts";
|
||||
import {
|
||||
findFirstUnlockedStageIndex,
|
||||
getCreationStageStates,
|
||||
getStageAction,
|
||||
} from "../../utils/projectStages";
|
||||
import StageCard from "./components/StageCard";
|
||||
import ClubCard from "./components/ClubCard";
|
||||
|
||||
const isProjectEmpty = (project) => {
|
||||
if (!project) {
|
||||
@@ -42,8 +42,55 @@ const isProjectEmpty = (project) => {
|
||||
);
|
||||
};
|
||||
|
||||
const HOME_BACKGROUND_WIDTH = isWeb ? 1280 : 520;
|
||||
const HOME_BACKGROUND_HEIGHT = isWeb ? 760 : 360;
|
||||
|
||||
const STAGE_CARD_CONTENT = [
|
||||
{
|
||||
key: "songwriter",
|
||||
step: "ÉTAPE 1",
|
||||
description:
|
||||
"Ensemble, nous allons écrire ta chanson.\nMéthodiquement je vais te guider pour construire une oeuvre unique et authentique.",
|
||||
image: cardsImg.writing,
|
||||
imagePosition: "left",
|
||||
textAlign: "left",
|
||||
lockSide: "right",
|
||||
},
|
||||
{
|
||||
key: "beatmaker",
|
||||
step: "ÉTAPE 2",
|
||||
description:
|
||||
"Je suis Malik, responsable du studio de musicland, je vais mettre en musique tes paroles en fonction de tes goûts musicaux, ça va être top!",
|
||||
image: cardsImg.studio,
|
||||
imagePosition: "right",
|
||||
textAlign: "right",
|
||||
lockSide: "left",
|
||||
},
|
||||
{
|
||||
key: "director",
|
||||
step: "ÉTAPE 3",
|
||||
description:
|
||||
"Tu vas faire une expérience extraordinaire, tu vas te filmer en train d’interpreter ta chanson en Play Back et je vais te guider pour te faciliter la tâche!",
|
||||
image: cardsImg.video,
|
||||
imagePosition: "left",
|
||||
textAlign: "left",
|
||||
lockSide: "right",
|
||||
},
|
||||
{
|
||||
key: "publisher",
|
||||
step: "ÉTAPE 4",
|
||||
description:
|
||||
"Je suis Mr Benhaï, Producteur de MusicLand et je vais te faire une proposition qui pourrait t’intéresser, on se retrouve à la sotie du studio!",
|
||||
image: cardsImg.production,
|
||||
imagePosition: "right",
|
||||
textAlign: "right",
|
||||
lockSide: "left",
|
||||
},
|
||||
];
|
||||
|
||||
const CLUB_CARD_IMAGE = icons.clubIcon;
|
||||
|
||||
const Home = ({ navigation, route }) => {
|
||||
const isFocused = useIsFocused();
|
||||
const {
|
||||
userProjects = [],
|
||||
selectProject,
|
||||
@@ -61,7 +108,7 @@ const Home = ({ navigation, route }) => {
|
||||
|
||||
const projects = useMemo(
|
||||
() => (Array.isArray(userProjects) ? userProjects : []),
|
||||
[userProjects]
|
||||
[userProjects],
|
||||
);
|
||||
|
||||
const currentProject = useMemo(() => {
|
||||
@@ -94,73 +141,34 @@ const Home = ({ navigation, route }) => {
|
||||
|
||||
const stageStates = useMemo(
|
||||
() => getCreationStageStates(currentProject),
|
||||
[currentProject]
|
||||
[currentProject],
|
||||
);
|
||||
|
||||
const preferredStageIndex = useMemo(() => {
|
||||
if (!stageStates.length) {
|
||||
return 0;
|
||||
const stageStatesByKey = useMemo(() => {
|
||||
if (!Array.isArray(stageStates)) {
|
||||
return {};
|
||||
}
|
||||
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;
|
||||
return stageStates.reduce((acc, stage) => {
|
||||
if (stage?.key) {
|
||||
acc[stage.key] = stage;
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
}, [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]
|
||||
[selectProject],
|
||||
);
|
||||
|
||||
const handleCloseMenu = useCallback(() => {
|
||||
@@ -228,7 +236,7 @@ const Home = ({ navigation, route }) => {
|
||||
},
|
||||
},
|
||||
],
|
||||
{ cancelable: true }
|
||||
{ cancelable: true },
|
||||
),
|
||||
},
|
||||
];
|
||||
@@ -262,7 +270,7 @@ const Home = ({ navigation, route }) => {
|
||||
navigate(targetRoute, beatmakerStage?.params);
|
||||
},
|
||||
},
|
||||
]
|
||||
],
|
||||
);
|
||||
navigation?.setParams?.({ showLyricsCongrats: false });
|
||||
}, [
|
||||
@@ -273,19 +281,6 @@ const Home = ({ navigation, route }) => {
|
||||
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(() => {
|
||||
@@ -297,32 +292,9 @@ const Home = ({ navigation, route }) => {
|
||||
}
|
||||
}, [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 () => {
|
||||
@@ -335,7 +307,6 @@ const Home = ({ navigation, route }) => {
|
||||
|
||||
if (emptyProject?.id) {
|
||||
selectProject(emptyProject.id);
|
||||
setActiveStageIndex(findFirstUnlockedStageIndex(emptyProject));
|
||||
navigate(targetRoute, targetParams);
|
||||
return;
|
||||
}
|
||||
@@ -344,7 +315,6 @@ const Home = ({ navigation, route }) => {
|
||||
if (!newProjectId) {
|
||||
return;
|
||||
}
|
||||
setActiveStageIndex(0);
|
||||
navigate(targetRoute, targetParams);
|
||||
} catch (error) {
|
||||
console.warn("Home: unable to start new project", error);
|
||||
@@ -353,17 +323,7 @@ const Home = ({ navigation, route }) => {
|
||||
text: "Impossible de démarrer un nouveau projet",
|
||||
});
|
||||
}
|
||||
}, [
|
||||
createNewProject,
|
||||
projects,
|
||||
selectProject,
|
||||
setActiveStageIndex,
|
||||
setTooltip,
|
||||
songwriterAction,
|
||||
]);
|
||||
|
||||
const continueDisabled = !hasActiveProject || stageLocked || !stageRoute;
|
||||
const startDisabled = !songwriterAction?.route;
|
||||
}, [createNewProject, projects, selectProject, setTooltip, songwriterAction]);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentUserData?.adventureStarted) {
|
||||
@@ -412,104 +372,102 @@ const Home = ({ navigation, route }) => {
|
||||
simpleRef: true,
|
||||
});
|
||||
const videoUrl = isWeb ? video?.landingWeb : video?.landing;
|
||||
const homeBackgroundImage = isWeb
|
||||
? background.homeBGWeb
|
||||
: background.homeBG;
|
||||
const homeBackgroundImage = background.bgTrans;
|
||||
const landingBackgroundImage = homeBackgroundImage;
|
||||
|
||||
const stageCards = useMemo(
|
||||
() =>
|
||||
STAGE_CARD_CONTENT.map((card) => ({
|
||||
...card,
|
||||
isLocked: stageStatesByKey[card.key]?.isLocked ?? true,
|
||||
})),
|
||||
[stageStatesByKey],
|
||||
);
|
||||
|
||||
const handleStagePress = useCallback(
|
||||
(stageKey, isLocked) => {
|
||||
if (!hasActiveProject || isLocked) {
|
||||
return;
|
||||
}
|
||||
ensureProjectSelected();
|
||||
const action = getStageAction(stageKey, currentProject);
|
||||
if (!action?.route) {
|
||||
return;
|
||||
}
|
||||
navigate(action.route, action.params);
|
||||
},
|
||||
[currentProject, ensureProjectSelected, hasActiveProject],
|
||||
);
|
||||
|
||||
const handleClubPress = useCallback(() => {
|
||||
navigate(Routes.Payments);
|
||||
}, []);
|
||||
|
||||
return adventureStarted ? (
|
||||
<Page
|
||||
shareBtn
|
||||
headerType="NONE"
|
||||
containerStyle={{ width: "100%", maxWidth: "100%", paddingHorizontal: 0 }}
|
||||
backgroundImg={homeBackgroundImage}
|
||||
>
|
||||
<View style={[styles.root, isWeb ? styles.rootWeb : styles.rootNative]}>
|
||||
<View
|
||||
style={{
|
||||
zIndex: 10,
|
||||
position: "absolute",
|
||||
top: 10,
|
||||
width: 400,
|
||||
alignSelf: "center",
|
||||
flexDirection: isWeb ? "column" : "row",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: isWeb ? 0 : 30,
|
||||
gap: 10,
|
||||
}}
|
||||
>
|
||||
<ProjectDropDown
|
||||
style={[isWeb ? { width: "100%" } : { flex: 1 }]}
|
||||
projects={projects}
|
||||
selectedProject={currentProject}
|
||||
allowEmptySelection
|
||||
onSelectProject={handleSelectProject}
|
||||
onModifyProject={handleModifyProject}
|
||||
onCreateProject={handleStartNew}
|
||||
formatDate={formatDate}
|
||||
/>
|
||||
{!isWeb && <ShareBtn />}
|
||||
</View>
|
||||
<MoreMenu
|
||||
visible={menuVisible}
|
||||
top={menuAnchor?.top ?? 0}
|
||||
position={menuAnchor}
|
||||
onClose={handleCloseMenu}
|
||||
inPlaylist={false}
|
||||
projectId={menuProject?.id || null}
|
||||
extraItems={moreMenuItems}
|
||||
/>
|
||||
<View style={[styles.carouselSection, styles.carouselSectionElevated]}>
|
||||
<FeatureCarousel
|
||||
selectedProject={currentProject}
|
||||
stageStates={stageStates}
|
||||
activeIndex={activeStageIndex}
|
||||
onActiveIndexChange={setActiveStageIndex}
|
||||
backgroundImage={homeBackgroundImage}
|
||||
isFocused={isFocused}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: isWeb ? 180 : 140,
|
||||
flexDirection: isWeb ? "row" : "column",
|
||||
gap: 5,
|
||||
alignSelf: "center",
|
||||
}}
|
||||
>
|
||||
<GradientButton
|
||||
containerStyle={{ width: Platform.OS === "web" ? 250 : "100%" }}
|
||||
title={"Commencer à créer"}
|
||||
onPress={handleStartNew}
|
||||
disabled={startDisabled}
|
||||
/>
|
||||
<BorderGradientButton
|
||||
containerStyle={{ width: Platform.OS === "web" ? 250 : "100%" }}
|
||||
title={"Continuer la création"}
|
||||
onPress={handleStageAction}
|
||||
disabled={continueDisabled}
|
||||
<View style={styles.root}>
|
||||
<Page
|
||||
shareBtn
|
||||
headerType="NONE"
|
||||
scrollEnabled={false}
|
||||
containerStyle={styles.page}
|
||||
contentContainerStyle={styles.pageContent}
|
||||
width="100%"
|
||||
maxWidth={null}
|
||||
backgroundColor="#303438"
|
||||
>
|
||||
<View style={styles.inner}>
|
||||
<ExpoImage
|
||||
source={homeBackgroundImage}
|
||||
contentFit="cover"
|
||||
style={styles.centerImage}
|
||||
/>
|
||||
|
||||
{/* <BorderGradientButton
|
||||
containerStyle={{ width: Platform.OS === "web" ? 250 : "100%" }}
|
||||
title={"Test Alert"}
|
||||
onPress={() =>
|
||||
Alert("Test", "Ceci est un test d'alert", [
|
||||
{ text: "Annuler", style: "cancel" },
|
||||
{ text: "OK", onPress: () => console.log("OK pressé") },
|
||||
])
|
||||
}
|
||||
/> */}
|
||||
{/* <BorderGradientButton
|
||||
containerStyle={{ width: Platform.OS === "web" ? 250 : "100%" }}
|
||||
title={"Continuer la création"}
|
||||
onPress={() => navigate(Routes.SongReady)}
|
||||
disabled={continueDisabled}
|
||||
/> */}
|
||||
<View style={styles.topBar}>
|
||||
<ProjectDropDown
|
||||
style={styles.projectDropDown}
|
||||
projects={projects}
|
||||
selectedProject={currentProject}
|
||||
allowEmptySelection
|
||||
onSelectProject={handleSelectProject}
|
||||
onModifyProject={handleModifyProject}
|
||||
onCreateProject={handleStartNew}
|
||||
formatDate={formatDate}
|
||||
/>
|
||||
{!isWeb && <ShareBtn />}
|
||||
</View>
|
||||
|
||||
<MoreMenu
|
||||
visible={menuVisible}
|
||||
top={menuAnchor?.top ?? 0}
|
||||
position={menuAnchor}
|
||||
onClose={handleCloseMenu}
|
||||
inPlaylist={false}
|
||||
projectId={menuProject?.id || null}
|
||||
extraItems={moreMenuItems}
|
||||
/>
|
||||
|
||||
<Text style={styles.subtitle}>5 espaces à découvrir</Text>
|
||||
|
||||
<View style={styles.cardsGrid}>
|
||||
{stageCards.map((card) => (
|
||||
<StageCard
|
||||
key={card.key}
|
||||
step={card.step}
|
||||
description={card.description}
|
||||
image={card.image}
|
||||
imagePosition={card.imagePosition}
|
||||
textAlign={card.textAlign}
|
||||
isLocked={card.isLocked}
|
||||
lockSide={card.lockSide}
|
||||
onPress={() => handleStagePress(card.key, card.isLocked)}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
|
||||
<ClubCard image={CLUB_CARD_IMAGE} onPress={handleClubPress} />
|
||||
</View>
|
||||
</View>
|
||||
</Page>
|
||||
</Page>
|
||||
</View>
|
||||
) : (
|
||||
<>
|
||||
<Page
|
||||
@@ -545,33 +503,75 @@ export default Home;
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
flex: 1,
|
||||
backgroundColor: "#303438",
|
||||
},
|
||||
rootWeb: {
|
||||
page: {
|
||||
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: {
|
||||
padding: 0,
|
||||
paddingLeft: isWeb ? 32 : 0,
|
||||
paddingRight: isWeb ? 32 : 0,
|
||||
paddingTop: 0,
|
||||
paddingBottom: 0,
|
||||
width: "100%",
|
||||
flex: 1,
|
||||
alignSelf: "stretch",
|
||||
},
|
||||
carouselSectionElevated: {
|
||||
marginTop: -100,
|
||||
paddingTop: 80,
|
||||
pageContent: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
inner: {
|
||||
flex: 1,
|
||||
width: "100%",
|
||||
alignSelf: "stretch",
|
||||
alignItems: "stretch",
|
||||
justifyContent: "flex-start",
|
||||
position: "relative",
|
||||
},
|
||||
centerImage: {
|
||||
width: HOME_BACKGROUND_WIDTH + 120,
|
||||
height: HOME_BACKGROUND_HEIGHT + 80,
|
||||
borderRadius: 22,
|
||||
overflow: "hidden",
|
||||
position: "absolute",
|
||||
top: "45%",
|
||||
left: "50%",
|
||||
transform: [
|
||||
{ translateX: -HOME_BACKGROUND_WIDTH / 2 },
|
||||
{ translateY: -HOME_BACKGROUND_HEIGHT / 2 },
|
||||
],
|
||||
pointerEvents: "none",
|
||||
},
|
||||
topBar: {
|
||||
width: "100%",
|
||||
flexDirection: "row",
|
||||
flexWrap: "wrap",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
gap: 12,
|
||||
marginTop: 24,
|
||||
marginBottom: 8,
|
||||
zIndex: 10,
|
||||
},
|
||||
projectDropDown: {
|
||||
width: isWeb ? 420 : "100%",
|
||||
maxWidth: 420,
|
||||
flexGrow: 1,
|
||||
},
|
||||
subtitle: {
|
||||
width: "100%",
|
||||
fontFamily: FONT_FAMILY.InterSemiBold,
|
||||
fontSize: 18,
|
||||
color: Palette.white,
|
||||
textAlign: "center",
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 1,
|
||||
marginBottom: 16,
|
||||
},
|
||||
cardsGrid: {
|
||||
width: "100%",
|
||||
flexDirection: "row",
|
||||
flexWrap: "wrap",
|
||||
justifyContent: "space-between",
|
||||
rowGap: 20,
|
||||
columnGap: 20,
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user