diff --git a/src/components/modal/WebDateModal.js b/src/components/modal/WebDateModal.js new file mode 100644 index 0000000..fb95dee --- /dev/null +++ b/src/components/modal/WebDateModal.js @@ -0,0 +1,155 @@ +import React, { useState, useEffect } from "react"; +import { Modal, View, Text, StyleSheet, ScrollView, Pressable } from "react-native"; +import { Palette, gutters } from "../../styles"; +import { FONT_FAMILY } from "../../styles/Fonts"; +import GradientButton from "../GradientButton"; +import BorderGradientButton from "../BorderGradientButton"; + +const WebDateModal = ({ visible, onClose, onValidate, initialDate }) => { + const currentYear = new Date().getFullYear(); + const [day, setDay] = useState(initialDate ? initialDate.getDate() : 1); + const [month, setMonth] = useState(initialDate ? initialDate.getMonth() : 0); + const [year, setYear] = useState(initialDate ? initialDate.getFullYear() : currentYear - 18); + + useEffect(() => { + if (visible && initialDate) { + setDay(initialDate.getDate()); + setMonth(initialDate.getMonth()); + setYear(initialDate.getFullYear()); + } else if (visible && !initialDate) { + setYear(currentYear - 18); // Default to 18 years ago + } + }, [visible, initialDate]); + + const days = Array.from({ length: 31 }, (_, i) => i + 1); + const months = [ + "Janvier", "Février", "Mars", "Avril", "Mai", "Juin", + "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre" + ]; + const years = Array.from({ length: 100 }, (_, i) => currentYear - i); + + const renderPickerColumn = (data, selectedValue, onSelect, getLabel = (item) => item, getValue = (item, index) => item) => ( + + + {data.map((item, index) => { + const value = getValue(item, index); + const isSelected = selectedValue === value; + return ( + onSelect(value)} + style={[styles.item, isSelected && styles.selectedItem]} + > + + {getLabel(item)} + + + ); + })} + + + ); + + const handleValidate = () => { + const newDate = new Date(year, month, day); + onValidate(newDate); + onClose(); + }; + + return ( + + + + Date de naissance + + {renderPickerColumn(days, day, setDay)} + {renderPickerColumn(months, month, setMonth, (m) => m, (_, i) => i)} + {renderPickerColumn(years, year, setYear)} + + + + + + + + + + ); +}; + +const styles = StyleSheet.create({ + overlay: { + flex: 1, + backgroundColor: "rgba(0,0,0,0.7)", + justifyContent: "center", + alignItems: "center", + }, + modalContainer: { + width: 400, + backgroundColor: Palette.black, + borderRadius: 20, + padding: gutters, + borderWidth: 1, + borderColor: Palette.gray, + maxHeight: "80%", + }, + title: { + color: Palette.white, + fontFamily: FONT_FAMILY.InterSemiBold, + fontSize: 18, + textAlign: "center", + marginBottom: 20, + }, + pickerContainer: { + flexDirection: "row", + justifyContent: "space-between", + height: 200, + marginBottom: 20, + }, + column: { + flex: 1, + borderWidth: 1, + borderColor: Palette.transparentPrimary, + borderRadius: 8, + marginHorizontal: 4, + backgroundColor: Palette.glass, + }, + scrollContent: { + paddingVertical: 10, + }, + item: { + paddingVertical: 8, + alignItems: "center", + }, + selectedItem: { + backgroundColor: Palette.primary, + }, + itemText: { + color: Palette.gray, + fontFamily: FONT_FAMILY.InterRegular, + fontSize: 16, + }, + selectedItemText: { + color: Palette.white, + fontFamily: FONT_FAMILY.InterSemiBold, + }, + buttonContainer: { + flexDirection: "row", + justifyContent: "space-between", + }, +}); + +export default WebDateModal; diff --git a/src/navigation/BottomTab.js b/src/navigation/BottomTab.js index 035b9d5..c92cfd8 100644 --- a/src/navigation/BottomTab.js +++ b/src/navigation/BottomTab.js @@ -52,7 +52,7 @@ export const BottomTabScreen = () => { name={Routes.HitParade} component={HitParade} options={{ - tabBarLabel: "Hit Parade", + tabBarLabel: "Streaming", headerShown: false, tabBarShowLabel: true, tabBarIcon: ({ focused }) => renderIcon(tabs.ribbon, focused), diff --git a/src/screens/CreatePassword.js b/src/screens/CreatePassword.js index 442a3cd..16c963d 100644 --- a/src/screens/CreatePassword.js +++ b/src/screens/CreatePassword.js @@ -26,6 +26,7 @@ const CreatePassword = () => { const city = route?.params?.city || ""; const country = route?.params?.country || null; const preferredLanguage = route?.params?.preferredLanguage || null; + const birthDate = route?.params?.birthDate || null; const [password, setPassword] = useState(""); const [passwordError, setPasswordError] = useState(""); const [loading, setLoading] = useState(false); @@ -80,6 +81,7 @@ const CreatePassword = () => { ...(trimmedLastName ? { lastName: trimmedLastName } : {}), ...(trimmedCity ? { city: trimmedCity } : {}), ...(preferredLanguage ? { preferredLanguage } : {}), + ...(birthDate ? { birthDate } : {}), ...(country?.code ? { countryCode: country.code } : {}), ...(country?.name ? { countryName: country.name } : {}), createdAt: firebase.firestore.FieldValue.serverTimestamp(), diff --git a/src/screens/Production/StreamSong.js b/src/screens/Production/StreamSong.js index 64a19f7..5aa3354 100644 --- a/src/screens/Production/StreamSong.js +++ b/src/screens/Production/StreamSong.js @@ -1,5 +1,6 @@ import { View, Text, Image } from "react-native"; -import React, { useCallback, useEffect } from "react"; +import React, { useCallback, useEffect, useState } from "react"; +import AppCheckbox from "../../components/AppCheckbox"; import Page from "../../layouts/Page"; import MusicLandHeader from "../../components/MusicLandHeader"; import GradientButton from "../../components/GradientButton"; @@ -22,6 +23,7 @@ const StreamSong = () => { const { hasActiveSubscription } = useUser() || {}; const userHasActiveSubscription = hasActiveSubscription; const { setTooltip } = useMinuit(); + const [hasAcceptedPublication, setHasAcceptedPublication] = useState(true); useEffect(() => { if (action && action !== "playback") { @@ -37,6 +39,13 @@ const StreamSong = () => { ? "Publier mon playback" : "Rejoindre le Club MusicLand"; const handlePrimaryAction = useCallback(() => { + if (!hasAcceptedPublication) { + setTooltip({ + type: "error", + text: "Confirme la diffusion sur Musicland et YouTube avant de publier", + }); + return; + } if (userHasActiveSubscription) { if (action === "playback") { setTooltip({ @@ -50,7 +59,7 @@ const StreamSong = () => { return; } navigate(Routes.Payments); - }, [action, setTooltip, userHasActiveSubscription]); + }, [action, hasAcceptedPublication, setTooltip, userHasActiveSubscription]); return ( { + + + setHasAcceptedPublication((prevState) => !prevState) + } + label="J'accepte la diffusion de mon contenu sur Musicland et YouTube." + /> + + Cette confirmation est requise avant toute mise en ligne. + + { const [firstName, setFirstName] = useState(""); const [lastName, setLastName] = useState(""); const [preferredLanguage, setPreferredLanguage] = useState(null); + const [birthDate, setBirthDate] = useState(null); + const [showDatePicker, setShowDatePicker] = useState(false); + const [, setTooltip] = useGlobal("_tooltip"); const afterSocialAuth = useCallback(async () => { const uid = firebase.auth().currentUser?.uid; @@ -65,7 +71,43 @@ const Register = () => { const isFormValid = email.trim().length > 0 && firstName.trim().length > 0 && - lastName.trim().length > 0; + lastName.trim().length > 0 && + birthDate; + + function isAdult(date) { + if (!date) return false; + const today = new Date(); + let age = today.getFullYear() - date.getFullYear(); + const m = today.getMonth() - date.getMonth(); + if (m < 0 || (m === 0 && today.getDate() < date.getDate())) { + age--; + } + return age >= 18; + } + + const handleNext = () => { + if (!isAdult(birthDate)) { + setTooltip({ + text: "Vous devez avoir au moins 18 ans pour créer un compte", + type: "error", + }); + return; + } + navigate(Routes.CreatePassword, { + email: email.trim(), + firstName: firstName.trim(), + lastName: lastName.trim(), + birthDate: birthDate ? birthDate.toISOString() : null, + preferredLanguage, + }); + }; + + const onDateChange = (event, selectedDate) => { + setShowDatePicker(Platform.OS === "ios"); + if (selectedDate) { + setBirthDate(selectedDate); + } + }; const renderFormContent = () => ( @@ -101,6 +143,52 @@ const Register = () => { value={email} setValue={setEmail} /> + {isWeb ? ( + <> + setShowDatePicker(true)}> + + + + + setShowDatePicker(false)} + onValidate={(date) => setBirthDate(date)} + initialDate={birthDate} + /> + + ) : ( + <> + setShowDatePicker(true)}> + + + + + {showDatePicker && ( + + )} + + )} { width: "90%", alignSelf: "center", }} - onPress={() => - navigate(Routes.CreatePassword, { - email: email.trim(), - firstName: firstName.trim(), - lastName: lastName.trim(), - preferredLanguage, - }) - } + onPress={handleNext} disabled={!isFormValid || isBusy} /> diff --git a/src/screens/Studio/ChooseInstruments.js b/src/screens/Studio/ChooseInstruments.js index 463744d..b0c30c5 100644 --- a/src/screens/Studio/ChooseInstruments.js +++ b/src/screens/Studio/ChooseInstruments.js @@ -1,4 +1,5 @@ -import { View, StyleSheet } from "react-native"; +import { View, StyleSheet, Text } from "react-native"; +import { MaterialIcons } from "@expo/vector-icons"; import React, { useState } from "react"; import CreateLyricsHeader from "../Writing/components/CreateLyricsHeader"; import ItemContainer from "../../components/ItemContainer/ItemContainer"; @@ -17,7 +18,33 @@ const ChooseInstruments = ({ selected = [], setSelected }) => { + > + + + + Attention : Suno peut ajuster ou supprimer certains paramètres + choisis pour maintenir la cohérence de la création musicale. + + + setContainerLayout(event.nativeEvent.layout)} diff --git a/src/screens/cover/SongDownload.js b/src/screens/cover/SongDownload.js index 8fbd274..516408b 100644 --- a/src/screens/cover/SongDownload.js +++ b/src/screens/cover/SongDownload.js @@ -8,9 +8,12 @@ import { StyleSheet, Text, View, + Alert, } from "react-native"; +import useMinuit from "react-native-minuit/src/hooks/useMinuit.js"; import * as FileSystem from "expo-file-system"; import * as Sharing from "expo-sharing"; +import AppCheckbox from "../../components/AppCheckbox"; import BorderGradientButton from "../../components/BorderGradientButton"; import MusicLandHeader from "../../components/MusicLandHeader"; import Page from "../../layouts/Page"; @@ -38,9 +41,11 @@ const SongDownload = ({ route }) => { } = route?.params || {}; const { selectedProject, updateProjectData, hasActiveSubscription } = useUser(); + const { setTooltip } = useMinuit(); const { setLoading } = useGlobalLoading(); const [isDownloading, setIsDownloading] = useState(false); const [showConfirmModal, setShowConfirmModal] = useState(false); + const [hasAcceptedPublication, setHasAcceptedPublication] = useState(true); const projectForStage = useMemo( () => routeProject || selectedProject || null, @@ -130,12 +135,26 @@ const SongDownload = ({ route }) => { ]); const handleContinue = useCallback(() => { + if (!hasAcceptedPublication) { + if (setTooltip) { + setTooltip({ + type: "error", + text: "Confirme la diffusion sur Musicland et YouTube avant de continuer", + }); + } else { + Alert.alert( + "Confirmation requise", + "Confirme la diffusion sur Musicland et YouTube avant de continuer" + ); + } + return; + } if (hasActiveSubscription) { continueFlow(); return; } setShowConfirmModal(true); - }, [continueFlow, hasActiveSubscription]); + }, [continueFlow, hasAcceptedPublication, hasActiveSubscription, setTooltip]); const handleDownload = useCallback(async () => { const downloadUrl = @@ -401,6 +420,17 @@ const SongDownload = ({ route }) => { + + setHasAcceptedPublication((prev) => !prev)} + label="J'accepte la diffusion de mon contenu sur Musicland et YouTube." + /> + + Cette confirmation est requise pour continuer. + + +