fix tickets add reort mail and design fixes
This commit is contained in:
@@ -8,8 +8,7 @@ import MusicLandHeader from "../../components/MusicLandHeader";
|
||||
import { OTHER_OBJECTIVE_OPTION, OTHER_STYLE_OPTION } from "../../data/data";
|
||||
import useLayoutType from "../../hooks/useLayoutType";
|
||||
import Page from "../../layouts/Page";
|
||||
import { Routes } from "../../navigation";
|
||||
import { goBack, navigate } from "../../navigation/NavigationService";
|
||||
import { goBack } from "../../navigation/NavigationService";
|
||||
import { useUser } from "../../providers/UserDataProvider";
|
||||
import { gutters } from "../../styles";
|
||||
import { sanitizeStructureList } from "../../utils/songStructure";
|
||||
@@ -23,13 +22,13 @@ import SongStyle from "./SongStyle";
|
||||
import SongTo from "./SongTo";
|
||||
import SpecificityContext from "./SpecificityContext";
|
||||
const { width: windowWidth } = Dimensions.get("window");
|
||||
const MAX_STEP_INDEX = 8;
|
||||
|
||||
const CreateLyricsWithAi = () => {
|
||||
const { isWeb } = useLayoutType();
|
||||
const { selectedProject, updateProjectData } = useUser();
|
||||
const hasLyrics = selectedProject?.hasLyrics === true;
|
||||
const { selectedProject } = useUser();
|
||||
const scrollRef = useRef(null);
|
||||
const [selectedIndex, setSelectedIndex] = useState(hasLyrics ? 5 : 0);
|
||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||
const [progress, setProgress] = useState(16);
|
||||
const [parentLayout, setparentLayout] = useState(null);
|
||||
const containerWidth = windowWidth;
|
||||
@@ -188,13 +187,6 @@ const CreateLyricsWithAi = () => {
|
||||
}
|
||||
}, [structure]);
|
||||
|
||||
// Si déjà des paroles, forcer l'accès à partir de l'étape 5 et ignorer 0-4
|
||||
React.useEffect(() => {
|
||||
if (hasLyrics && selectedIndex < 5) {
|
||||
setSelectedIndex(5);
|
||||
}
|
||||
}, [hasLyrics]);
|
||||
|
||||
const lyricsConfig = useMemo(() => {
|
||||
const resolvedObjective = shouldUseOtherObjective
|
||||
? persistedOtherObjective || undefined
|
||||
@@ -297,37 +289,7 @@ const CreateLyricsWithAi = () => {
|
||||
]);
|
||||
|
||||
const onPressNext = async () => {
|
||||
// Si l'utilisateur a déjà des paroles et vient de finir CustomizeSongStructure (index 6),
|
||||
// sauter Rhymes et CreatingLyrics et aller directement sur Lyrics
|
||||
if (hasLyrics && selectedIndex === 6) {
|
||||
const chosenStructure = sanitizeStructureList(
|
||||
Array.isArray(customStructure) && customStructure.length > 0
|
||||
? customStructure
|
||||
: Array.isArray(parsedStructure)
|
||||
? parsedStructure
|
||||
: []
|
||||
);
|
||||
await updateProjectData({
|
||||
config: { structure: chosenStructure },
|
||||
selections: {
|
||||
objective,
|
||||
otherObjective: persistedOtherObjective,
|
||||
context,
|
||||
emotion,
|
||||
style,
|
||||
otherStyle: persistedOtherStyle,
|
||||
audience,
|
||||
structure,
|
||||
parsedStructure,
|
||||
customStructure: sanitizeStructureList(customStructure),
|
||||
rhymes,
|
||||
},
|
||||
hasLyrics: true,
|
||||
});
|
||||
navigate(Routes.Lyrics);
|
||||
return;
|
||||
}
|
||||
setSelectedIndex((idx) => idx + 1);
|
||||
setSelectedIndex((idx) => Math.min(idx + 1, MAX_STEP_INDEX));
|
||||
};
|
||||
|
||||
const onPressBack = () => {
|
||||
@@ -344,14 +306,36 @@ const CreateLyricsWithAi = () => {
|
||||
// Sync scroll position and progress with selectedIndex
|
||||
React.useEffect(() => {
|
||||
if (!parentLayout?.height) return;
|
||||
try {
|
||||
const nextProgress = 16 + selectedIndex * 9;
|
||||
setProgress(nextProgress);
|
||||
scrollRef.current?.scrollToIndex?.({
|
||||
index: selectedIndex,
|
||||
animated: true,
|
||||
});
|
||||
} catch (_) {}
|
||||
const nextProgress = Math.max(0, Math.min(100, 16 + selectedIndex * 9));
|
||||
setProgress(nextProgress);
|
||||
|
||||
const ref = scrollRef.current;
|
||||
if (!ref || typeof ref.scrollToIndex !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
let cancelled = false;
|
||||
const attemptScroll = (retries = 0) => {
|
||||
if (cancelled) return;
|
||||
try {
|
||||
ref.scrollToIndex({ index: selectedIndex, animated: true });
|
||||
} catch (error) {
|
||||
if (retries < 3) {
|
||||
setTimeout(() => attemptScroll(retries + 1), 40);
|
||||
} else {
|
||||
console.warn("[CreateLyricsWithAi] scrollToIndex failed", {
|
||||
index: selectedIndex,
|
||||
error,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
attemptScroll();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [selectedIndex, parentLayout?.height]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -6,8 +6,7 @@ import GradientButton from "../../components/GradientButton";
|
||||
import MusicLandHeader from "../../components/MusicLandHeader";
|
||||
import { OTHER_OBJECTIVE_OPTION, OTHER_STYLE_OPTION } from "../../data/data";
|
||||
import Page from "../../layouts/Page";
|
||||
import { Routes } from "../../navigation";
|
||||
import { goBack, navigate } from "../../navigation/NavigationService";
|
||||
import { goBack } from "../../navigation/NavigationService";
|
||||
import { useUser } from "../../providers/UserDataProvider";
|
||||
import { gutters } from "../../styles";
|
||||
import { sanitizeStructureList } from "../../utils/songStructure";
|
||||
@@ -21,10 +20,11 @@ import SongStyle from "./SongStyle";
|
||||
import SongTo from "./SongTo";
|
||||
import SpecificityContext from "./SpecificityContext";
|
||||
|
||||
const MAX_STEP_INDEX = 8;
|
||||
|
||||
const CreateLyricsWithAi = () => {
|
||||
const { selectedProject, updateProjectData } = useUser();
|
||||
const hasLyrics = selectedProject?.hasLyrics === true;
|
||||
const [selectedIndex, setSelectedIndex] = useState(hasLyrics ? 5 : 0);
|
||||
const { selectedProject } = useUser();
|
||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||
|
||||
// Collected state across steps
|
||||
const [objective, setObjective] = useState(null); // from Goals list
|
||||
@@ -187,13 +187,6 @@ const CreateLyricsWithAi = () => {
|
||||
}
|
||||
}, [structure]);
|
||||
|
||||
// Si déjà des paroles, forcer l'accès à partir de l'étape 5 et ignorer 0-4
|
||||
React.useEffect(() => {
|
||||
if (hasLyrics && selectedIndex < 5) {
|
||||
setSelectedIndex(5);
|
||||
}
|
||||
}, [hasLyrics]);
|
||||
|
||||
const progress = useMemo(
|
||||
() => 16 + selectedIndex * 9,
|
||||
[selectedIndex]
|
||||
@@ -301,37 +294,7 @@ const CreateLyricsWithAi = () => {
|
||||
]);
|
||||
|
||||
const onPressNext = async () => {
|
||||
// Si l'utilisateur a déjà des paroles et vient de finir CustomizeSongStructure (index 6),
|
||||
// sauter Rhymes et CreatingLyrics et aller directement sur Lyrics
|
||||
if (hasLyrics && selectedIndex === 6) {
|
||||
const chosenStructure = sanitizeStructureList(
|
||||
Array.isArray(customStructure) && customStructure.length > 0
|
||||
? customStructure
|
||||
: Array.isArray(parsedStructure)
|
||||
? parsedStructure
|
||||
: []
|
||||
);
|
||||
await updateProjectData({
|
||||
config: { structure: chosenStructure },
|
||||
selections: {
|
||||
objective,
|
||||
otherObjective: persistedOtherObjective,
|
||||
context,
|
||||
emotion,
|
||||
style,
|
||||
otherStyle: persistedOtherStyle,
|
||||
audience,
|
||||
structure,
|
||||
parsedStructure,
|
||||
customStructure: sanitizeStructureList(customStructure),
|
||||
rhymes,
|
||||
},
|
||||
hasLyrics: true,
|
||||
});
|
||||
navigate(Routes.Lyrics);
|
||||
return;
|
||||
}
|
||||
setSelectedIndex((idx) => idx + 1);
|
||||
setSelectedIndex((idx) => Math.min(idx + 1, MAX_STEP_INDEX));
|
||||
};
|
||||
|
||||
const onPressBack = () => {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { View } from "react-native";
|
||||
import ItemContainer from "../../components/ItemContainer/ItemContainer";
|
||||
import ListSelection from "../../components/ListSelection/ListSelection";
|
||||
import { EMOTION_CONVEY } from "../../data/data";
|
||||
import { strings } from "../../constants/strings";
|
||||
import CreateLyricsHeader from "./components/CreateLyricsHeader";
|
||||
|
||||
const EmotionConvey = ({
|
||||
@@ -19,8 +20,8 @@ const EmotionConvey = ({
|
||||
return (
|
||||
<View style={{ flex: 1, gap: 10, marginTop: 16 }}>
|
||||
<CreateLyricsHeader
|
||||
title={`Quelle émotion veux-tu\ntransmettre ?`}
|
||||
subTitle="Sélectionne une seule intention émotionnelle."
|
||||
title={strings.writing.steps.emotionTitle}
|
||||
subTitle={strings.writing.steps.emotionSubtitle}
|
||||
/>
|
||||
|
||||
<View
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import React, { useMemo, useState } from "react";
|
||||
import { Platform, ScrollView, StyleSheet, View } from "react-native";
|
||||
import { Platform, ScrollView, StyleSheet, Text, View } from "react-native";
|
||||
import ItemContainer from "../../components/ItemContainer/ItemContainer";
|
||||
import ListSelection from "../../components/ListSelection/ListSelection";
|
||||
import { GOALS, OTHER_OBJECTIVE_OPTION } from "../../data/data";
|
||||
import { strings } from "../../constants/strings";
|
||||
import { Palette } from "../../styles";
|
||||
import { FONT_FAMILY } from "../../styles/Fonts";
|
||||
import CreateLyricsHeader from "./components/CreateLyricsHeader";
|
||||
@@ -39,9 +40,14 @@ const Goals = ({
|
||||
<ScrollView contentContainerStyle={{ flex: 1, gap: 16, marginTop: 16 }}>
|
||||
<View style={{ gap: 10 }}>
|
||||
<CreateLyricsHeader
|
||||
title="Quel est le contexte ?"
|
||||
subTitle="Besoin d'idées ? Pense à un anniversaire, une équipe de sport ou ton entreprise."
|
||||
title={strings.writing.steps.contextTitle}
|
||||
subTitle={strings.writing.steps.contextSubtitle}
|
||||
/>
|
||||
<View style={styles.examplesContainer}>
|
||||
<Text style={styles.examplesText}>
|
||||
{strings.writing.steps.contextExamples}
|
||||
</Text>
|
||||
</View>
|
||||
<ItemContainer>
|
||||
<ListSelection
|
||||
options={goalsOptions}
|
||||
@@ -55,8 +61,8 @@ const Goals = ({
|
||||
</ItemContainer>
|
||||
</View>
|
||||
<CustomInput
|
||||
label="As-tu un autre objectif ?"
|
||||
placeholder="Décrire l’objectif"
|
||||
label={strings.writing.labels.otherObjective}
|
||||
placeholder={strings.writing.labels.otherObjectivePlaceholder}
|
||||
value={otherObjective}
|
||||
setValue={handleOtherObjectiveChange}
|
||||
height={Platform.OS === "web" ? 160 : undefined}
|
||||
@@ -95,4 +101,15 @@ const styles = StyleSheet.create({
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
textAlign: "center",
|
||||
},
|
||||
examplesContainer: {
|
||||
backgroundColor: Palette.ultraLightWhite,
|
||||
borderRadius: 12,
|
||||
padding: 12,
|
||||
},
|
||||
examplesText: {
|
||||
fontSize: 14,
|
||||
color: Palette.white,
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
lineHeight: 20,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ import GradientButton from "../../components/GradientButton";
|
||||
import ItemContainer from "../../components/ItemContainer/ItemContainer";
|
||||
import MusicLandHeader from "../../components/MusicLandHeader";
|
||||
import firebase, { projectsRef } from "../../config/firebase";
|
||||
import { strings } from "../../constants/strings";
|
||||
import { isWeb } from "../../hooks/useLayoutType";
|
||||
import Page from "../../layouts/Page";
|
||||
import { Routes } from "../../navigation";
|
||||
@@ -363,24 +364,17 @@ const Lyrics = ({ navigation }) => {
|
||||
}}
|
||||
>
|
||||
<View style={styles.headerContainer}>
|
||||
<Text style={styles.headerTitle}>Proposition de paroles</Text>
|
||||
<Text style={styles.instructions}>
|
||||
Relis attentivement la proposition générée avant de valider.
|
||||
<Text style={styles.headerTitle}>
|
||||
{strings.writing.lyrics.title}
|
||||
</Text>
|
||||
<Text style={styles.instructions}>
|
||||
<Text style={styles.instructionsHighlight}>
|
||||
Personnalisation :
|
||||
</Text>{" "}
|
||||
modifie le titre et chaque section pour que les paroles te
|
||||
ressemblent.
|
||||
</Text>
|
||||
<Text style={styles.instructions}>
|
||||
<Text style={styles.instructionsHighlight}>
|
||||
Nouvelle génération :
|
||||
</Text>{" "}
|
||||
appuie sur "Générer d'autres paroles" si tu souhaites une autre
|
||||
suggestion.
|
||||
{strings.writing.lyrics.instructions}
|
||||
</Text>
|
||||
<View style={styles.personalizationBanner}>
|
||||
<Text style={styles.personalizationText}>
|
||||
{strings.writing.lyrics.personalizationBanner}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<CustomInput
|
||||
label="Titre"
|
||||
@@ -489,8 +483,18 @@ const styles = StyleSheet.create({
|
||||
fontSize: 14,
|
||||
lineHeight: 20,
|
||||
},
|
||||
instructionsHighlight: {
|
||||
fontFamily: FONT_FAMILY.InterSemiBold,
|
||||
personalizationBanner: {
|
||||
backgroundColor: Palette.ultraLightWhite,
|
||||
borderRadius: 12,
|
||||
paddingVertical: 10,
|
||||
paddingHorizontal: 12,
|
||||
marginTop: 4,
|
||||
},
|
||||
personalizationText: {
|
||||
color: Palette.white,
|
||||
fontFamily: FONT_FAMILY.InterMedium,
|
||||
fontSize: 14,
|
||||
lineHeight: 20,
|
||||
},
|
||||
instrumentalBlock: {
|
||||
padding: 16,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { Image, StyleSheet, View } from "react-native";
|
||||
import { Image, StyleSheet, Text, View } from "react-native";
|
||||
import useMinuit from "react-native-minuit/src/hooks/useMinuit.js";
|
||||
import { ai, background, icons } from "../../assets";
|
||||
import BorderGradientButton from "../../components/BorderGradientButton";
|
||||
@@ -10,22 +10,24 @@ import Page from "../../layouts/Page";
|
||||
import { Routes } from "../../navigation";
|
||||
import { goBack, navigate } from "../../navigation/NavigationService";
|
||||
import { useUserData } from "../../providers/UserDataProvider";
|
||||
import { gutters } from "../../styles";
|
||||
import { strings } from "../../constants/strings";
|
||||
import { Palette, gutters } from "../../styles";
|
||||
import { FONT_FAMILY } from "../../styles/Fonts";
|
||||
|
||||
const WritingLyrics = () => {
|
||||
const { createNewProject, selectedProject, updateProjectData } =
|
||||
useUserData();
|
||||
const { setIsLoading } = useMinuit();
|
||||
|
||||
async function createAndNavigate({ hasLyrics = false }) {
|
||||
const startWriting = React.useCallback(async () => {
|
||||
try {
|
||||
await setIsLoading(true);
|
||||
if (selectedProject) {
|
||||
updateProjectData({ hasLyrics });
|
||||
updateProjectData({ hasLyrics: false });
|
||||
setTimeout(() => navigate(Routes.CreateLyricsWithAi), 500);
|
||||
return;
|
||||
}
|
||||
const newProjectId = await createNewProject({ hasLyrics });
|
||||
const newProjectId = await createNewProject({ hasLyrics: false });
|
||||
if (!newProjectId) {
|
||||
return;
|
||||
}
|
||||
@@ -35,7 +37,7 @@ const WritingLyrics = () => {
|
||||
} finally {
|
||||
await setIsLoading(false);
|
||||
}
|
||||
}
|
||||
}, [createNewProject, navigate, selectedProject, setIsLoading, updateProjectData]);
|
||||
|
||||
return (
|
||||
<Page
|
||||
@@ -48,27 +50,34 @@ const WritingLyrics = () => {
|
||||
progress={9}
|
||||
logo={icons.musicLandWriting}
|
||||
/>
|
||||
<View
|
||||
style={{ flex: 1, position: "relative", justifyContent: "flex-end" }}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
paddingBottom: gutters * 2,
|
||||
paddingHorizontal: gutters,
|
||||
gap: 12,
|
||||
}}
|
||||
>
|
||||
<View style={styles.contentWrapper}>
|
||||
<View style={styles.heroContainer}>
|
||||
<Text style={styles.heroTitle}>
|
||||
{strings.writing.onboarding.heroTitle}
|
||||
</Text>
|
||||
<Text style={styles.heroSubtitle}>
|
||||
{strings.writing.onboarding.heroSubtitle}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.ctaGroup}>
|
||||
<GradientButton
|
||||
maxWidth={500}
|
||||
size="large"
|
||||
maxWidth={520}
|
||||
containerStyle={{ alignSelf: "center", width: "100%" }}
|
||||
title="Écrire des paroles avec une IA"
|
||||
onPress={() => createAndNavigate({ hasLyrics: false })}
|
||||
title={strings.writing.onboarding.primaryCta}
|
||||
onPress={startWriting}
|
||||
textStyle={{ fontFamily: FONT_FAMILY.InterSemiBold }}
|
||||
/>
|
||||
<BorderGradientButton
|
||||
maxWidth={500}
|
||||
onPress={() => createAndNavigate({ hasLyrics: true })}
|
||||
size="small"
|
||||
title={strings.writing.onboarding.secondaryCta}
|
||||
disabled
|
||||
containerStyle={{ alignSelf: "center", width: "100%" }}
|
||||
maxWidth={360}
|
||||
/>
|
||||
<Text style={styles.secondaryNote}>
|
||||
{strings.writing.onboarding.secondaryNote}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</Page>
|
||||
@@ -85,4 +94,39 @@ const styles = StyleSheet.create({
|
||||
bottom: -40,
|
||||
right: -30,
|
||||
},
|
||||
contentWrapper: {
|
||||
flex: 1,
|
||||
justifyContent: "space-between",
|
||||
paddingTop: gutters * 6,
|
||||
paddingBottom: gutters * 2,
|
||||
paddingHorizontal: gutters,
|
||||
gap: 32,
|
||||
},
|
||||
heroContainer: {
|
||||
gap: 12,
|
||||
maxWidth: 560,
|
||||
},
|
||||
heroTitle: {
|
||||
fontSize: 28,
|
||||
color: Palette.white,
|
||||
fontFamily: FONT_FAMILY.InterSemiBold,
|
||||
},
|
||||
heroSubtitle: {
|
||||
fontSize: 16,
|
||||
lineHeight: 24,
|
||||
color: Palette.white,
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
opacity: 0.88,
|
||||
},
|
||||
ctaGroup: {
|
||||
gap: 12,
|
||||
},
|
||||
secondaryNote: {
|
||||
textAlign: "center",
|
||||
color: Palette.white,
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
fontSize: 14,
|
||||
opacity: 0.85,
|
||||
fontStyle: "italic",
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user