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.
+
+
+