fix empty project creation and use credit to generate
This commit is contained in:
@@ -5,23 +5,30 @@ import React, {
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { Dimensions, FlatList, View } from "react-native";
|
||||
import { Dimensions, FlatList, Modal, Text, View } from "react-native";
|
||||
import { background } from "../../assets";
|
||||
import BorderGradientButton from "../../components/BorderGradientButton";
|
||||
import GradientButton from "../../components/GradientButton";
|
||||
import MusicLandHeader from "../../components/MusicLandHeader";
|
||||
import firebase from "../../config/firebase";
|
||||
import AppAlert from "../../components/Alert";
|
||||
import firebase, { usersRef } from "../../config/firebase";
|
||||
import Page from "../../layouts/Page";
|
||||
import { Routes } from "../../navigation";
|
||||
import { goBack, navigate } from "../../navigation/NavigationService";
|
||||
import { useUser } from "../../providers/UserDataProvider";
|
||||
import { gutters } from "../../styles";
|
||||
import { gutters, Palette } from "../../styles";
|
||||
import { FONT_FAMILY } from "../../styles/Fonts";
|
||||
import { normalizeStructureType } from "../../utils/songStructure";
|
||||
import { openCoinPackModal } from "../../utils/coinPackModal";
|
||||
import ChooseGenre from "./ChooseGenre";
|
||||
import ChooseInstruments from "./ChooseInstruments";
|
||||
import ChooseRhythm from "./ChooseRhythm";
|
||||
import CustomizeVoice from "./CustomizeVoice";
|
||||
import CreateLyricsHeader from "../Writing/components/CreateLyricsHeader";
|
||||
|
||||
const { width: windowWidth } = Dimensions.get("window");
|
||||
const MUSIC_GENERATION_COIN_COST = 8;
|
||||
const CONFIRM_MODAL_MAX_WIDTH = 540;
|
||||
|
||||
const ComposeSong = () => {
|
||||
const scrollRef = useRef(null);
|
||||
@@ -29,12 +36,56 @@ const ComposeSong = () => {
|
||||
const [progress, setProgress] = useState(18);
|
||||
const [parentLayout, setParentLayout] = useState(null);
|
||||
const containerWidth = parentLayout?.width || windowWidth || 1;
|
||||
const { selectedProjectId, selectedProject, updateProjectData } = useUser();
|
||||
const {
|
||||
selectedProjectId,
|
||||
selectedProject,
|
||||
updateProjectData,
|
||||
currentUserData,
|
||||
currentUID,
|
||||
} = useUser();
|
||||
|
||||
const [genres, setGenres] = useState([]);
|
||||
const [voice, setVoice] = useState({});
|
||||
const [instruments, setInstruments] = useState([]);
|
||||
const [rhythm, setRhythm] = useState(null);
|
||||
const [isConfirmVisible, setIsConfirmVisible] = useState(false);
|
||||
const [isProcessingConfirmation, setIsProcessingConfirmation] =
|
||||
useState(false);
|
||||
|
||||
const coinBalance = useMemo(() => {
|
||||
const data = currentUserData || {};
|
||||
const candidates = [
|
||||
data?.coins,
|
||||
data?.coinBalance,
|
||||
data?.coin,
|
||||
data?.wallet?.coins,
|
||||
data?.wallet?.coinBalance,
|
||||
];
|
||||
|
||||
for (const value of candidates) {
|
||||
if (typeof value === "number" && Number.isFinite(value)) {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
const parsed = Number(value);
|
||||
if (Number.isFinite(parsed)) {
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}, [currentUserData]);
|
||||
|
||||
const formattedCoinBalance = useMemo(() => {
|
||||
try {
|
||||
return new Intl.NumberFormat("fr-FR", {
|
||||
maximumFractionDigits: 0,
|
||||
}).format(coinBalance);
|
||||
} catch (_error) {
|
||||
return `${coinBalance}`;
|
||||
}
|
||||
}, [coinBalance]);
|
||||
|
||||
const isStepValid = useMemo(() => {
|
||||
switch (selectedIndex) {
|
||||
@@ -132,32 +183,91 @@ const ComposeSong = () => {
|
||||
[containerWidth]
|
||||
);
|
||||
|
||||
const onPressNext = async () => {
|
||||
if (selectedIndex === steps.length - 1) {
|
||||
try {
|
||||
if (selectedProjectId) {
|
||||
await updateProjectData({
|
||||
musicConfig: {
|
||||
title: musicConfig?.title || "",
|
||||
lyrics: Array.isArray(musicConfig?.lyrics)
|
||||
? musicConfig.lyrics
|
||||
: [],
|
||||
genres: Array.isArray(musicConfig?.genres)
|
||||
? musicConfig.genres
|
||||
: [],
|
||||
voice: Array.isArray(musicConfig?.voice) ? musicConfig.voice : [],
|
||||
instruments: Array.isArray(musicConfig?.instruments)
|
||||
? musicConfig.instruments
|
||||
: [],
|
||||
tempo: musicConfig?.tempo || "",
|
||||
},
|
||||
musicStatus: null,
|
||||
sunoTaskId: firebase.firestore.FieldValue.delete(),
|
||||
musicUrls: firebase.firestore.FieldValue.delete(),
|
||||
});
|
||||
}
|
||||
} catch (e) {}
|
||||
const persistMusicConfig = useCallback(async () => {
|
||||
try {
|
||||
if (!selectedProjectId) return;
|
||||
await updateProjectData({
|
||||
musicConfig: {
|
||||
title: musicConfig?.title || "",
|
||||
lyrics: Array.isArray(musicConfig?.lyrics) ? musicConfig.lyrics : [],
|
||||
genres: Array.isArray(musicConfig?.genres) ? musicConfig.genres : [],
|
||||
voice: Array.isArray(musicConfig?.voice) ? musicConfig.voice : [],
|
||||
instruments: Array.isArray(musicConfig?.instruments)
|
||||
? musicConfig.instruments
|
||||
: [],
|
||||
tempo: musicConfig?.tempo || "",
|
||||
},
|
||||
musicStatus: null,
|
||||
sunoTaskId: firebase.firestore.FieldValue.delete(),
|
||||
musicUrls: firebase.firestore.FieldValue.delete(),
|
||||
});
|
||||
} catch (_error) {}
|
||||
}, [musicConfig, selectedProjectId, updateProjectData]);
|
||||
|
||||
const spendCoinsForGeneration = useCallback(async () => {
|
||||
if (!currentUID) {
|
||||
throw new Error("Utilisateur introuvable. Merci de réessayer.");
|
||||
}
|
||||
|
||||
const decrement = firebase.firestore.FieldValue.increment(
|
||||
-MUSIC_GENERATION_COIN_COST,
|
||||
);
|
||||
const timestamp = firebase.firestore.FieldValue.serverTimestamp();
|
||||
|
||||
await usersRef.doc(currentUID).set(
|
||||
{
|
||||
coins: decrement,
|
||||
coinBalance: decrement,
|
||||
wallet: {
|
||||
coins: decrement,
|
||||
coinBalance: decrement,
|
||||
},
|
||||
lastCoinSpendAt: timestamp,
|
||||
lastCoinSpendAmount: MUSIC_GENERATION_COIN_COST,
|
||||
},
|
||||
{ merge: true },
|
||||
);
|
||||
}, [currentUID]);
|
||||
|
||||
const handleConfirmGeneration = useCallback(async () => {
|
||||
if (isProcessingConfirmation) return;
|
||||
setIsProcessingConfirmation(true);
|
||||
try {
|
||||
const availableCoins = Number.isFinite(coinBalance) ? coinBalance : 0;
|
||||
if (availableCoins < MUSIC_GENERATION_COIN_COST) {
|
||||
setIsConfirmVisible(false);
|
||||
AppAlert(
|
||||
"Crédits insuffisants",
|
||||
"Tu n'as pas assez de pièces pour générer une musique. Recharge ton compte pour continuer.",
|
||||
);
|
||||
openCoinPackModal();
|
||||
return;
|
||||
}
|
||||
|
||||
await spendCoinsForGeneration();
|
||||
await persistMusicConfig();
|
||||
setIsConfirmVisible(false);
|
||||
navigate(Routes.GeneratingSong, { config: musicConfig });
|
||||
} catch (error) {
|
||||
setIsConfirmVisible(false);
|
||||
const message =
|
||||
error?.message ||
|
||||
"Une erreur est survenue lors du lancement de la génération.";
|
||||
AppAlert("Impossible de lancer la génération", message);
|
||||
} finally {
|
||||
setIsProcessingConfirmation(false);
|
||||
}
|
||||
}, [
|
||||
coinBalance,
|
||||
isProcessingConfirmation,
|
||||
musicConfig,
|
||||
persistMusicConfig,
|
||||
spendCoinsForGeneration,
|
||||
]);
|
||||
|
||||
const onPressNext = () => {
|
||||
if (selectedIndex === steps.length - 1) {
|
||||
setIsConfirmVisible(true);
|
||||
return;
|
||||
}
|
||||
setSelectedIndex((prev) => Math.min(prev + 1, steps.length - 1));
|
||||
@@ -215,6 +325,94 @@ const ComposeSong = () => {
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
<Modal
|
||||
animationType="slide"
|
||||
transparent
|
||||
visible={isConfirmVisible}
|
||||
onRequestClose={() => {
|
||||
if (isProcessingConfirmation) return;
|
||||
setIsConfirmVisible(false);
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: gutters,
|
||||
paddingVertical: gutters * 1.5,
|
||||
backgroundColor: "rgba(0, 0, 0, 0.6)",
|
||||
}}
|
||||
>
|
||||
<CreateLyricsHeader
|
||||
containerStyle={{
|
||||
width: "100%",
|
||||
maxWidth: CONFIRM_MODAL_MAX_WIDTH,
|
||||
alignSelf: "center",
|
||||
paddingVertical: 24,
|
||||
paddingHorizontal: 24,
|
||||
gap: 24,
|
||||
}}
|
||||
>
|
||||
<View style={{ gap: 30, alignItems: "center" }}>
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: 15,
|
||||
gap: 12,
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 22,
|
||||
color: Palette.white,
|
||||
fontFamily: FONT_FAMILY.InterSemiBold,
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
Générer la musique ?
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 16,
|
||||
color: Palette.white,
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
Cette action coûte {MUSIC_GENERATION_COIN_COST} pièces.
|
||||
{"\n"}Souhaites-tu les utiliser pour lancer la génération ?
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 14,
|
||||
color: Palette.white,
|
||||
fontFamily: FONT_FAMILY.InterMedium,
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
Solde disponible : {formattedCoinBalance} pièces
|
||||
</Text>
|
||||
</View>
|
||||
<View style={{ width: "85%", alignSelf: "center", gap: 15 }}>
|
||||
<GradientButton
|
||||
title="Confirmer"
|
||||
onPress={handleConfirmGeneration}
|
||||
disabled={isProcessingConfirmation}
|
||||
/>
|
||||
<BorderGradientButton
|
||||
title="Annuler"
|
||||
onPress={() => {
|
||||
if (isProcessingConfirmation) return;
|
||||
setIsConfirmVisible(false);
|
||||
}}
|
||||
disabled={isProcessingConfirmation}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</CreateLyricsHeader>
|
||||
</View>
|
||||
</Modal>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user