feat: fixes
This commit is contained in:
@@ -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) => (
|
||||
<View style={styles.column}>
|
||||
<ScrollView showsVerticalScrollIndicator={false} contentContainerStyle={styles.scrollContent}>
|
||||
{data.map((item, index) => {
|
||||
const value = getValue(item, index);
|
||||
const isSelected = selectedValue === value;
|
||||
return (
|
||||
<Pressable
|
||||
key={index}
|
||||
onPress={() => onSelect(value)}
|
||||
style={[styles.item, isSelected && styles.selectedItem]}
|
||||
>
|
||||
<Text style={[styles.itemText, isSelected && styles.selectedItemText]}>
|
||||
{getLabel(item)}
|
||||
</Text>
|
||||
</Pressable>
|
||||
);
|
||||
})}
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
|
||||
const handleValidate = () => {
|
||||
const newDate = new Date(year, month, day);
|
||||
onValidate(newDate);
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
transparent={true}
|
||||
animationType="fade"
|
||||
onRequestClose={onClose}
|
||||
>
|
||||
<View style={styles.overlay}>
|
||||
<View style={styles.modalContainer}>
|
||||
<Text style={styles.title}>Date de naissance</Text>
|
||||
<View style={styles.pickerContainer}>
|
||||
{renderPickerColumn(days, day, setDay)}
|
||||
{renderPickerColumn(months, month, setMonth, (m) => m, (_, i) => i)}
|
||||
{renderPickerColumn(years, year, setYear)}
|
||||
</View>
|
||||
<View style={styles.buttonContainer}>
|
||||
<BorderGradientButton
|
||||
title="Annuler"
|
||||
onPress={onClose}
|
||||
containerStyle={{ flex: 1 }}
|
||||
/>
|
||||
<View style={{ width: 10 }} />
|
||||
<GradientButton
|
||||
title="Valider"
|
||||
onPress={handleValidate}
|
||||
containerStyle={{ flex: 1 }}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
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;
|
||||
@@ -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),
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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 (
|
||||
<Page
|
||||
@@ -103,6 +112,25 @@ const StreamSong = () => {
|
||||
</View>
|
||||
</CreateLyricsHeader>
|
||||
<View style={{ marginTop: 24, gap: 12 }}>
|
||||
<View style={{ gap: 6 }}>
|
||||
<AppCheckbox
|
||||
selected={hasAcceptedPublication}
|
||||
onPress={() =>
|
||||
setHasAcceptedPublication((prevState) => !prevState)
|
||||
}
|
||||
label="J'accepte la diffusion de mon contenu sur Musicland et YouTube."
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 13,
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
color: Palette.white,
|
||||
opacity: 0.8,
|
||||
}}
|
||||
>
|
||||
Cette confirmation est requise avant toute mise en ligne.
|
||||
</Text>
|
||||
</View>
|
||||
<GradientButton
|
||||
title={ctaLabel}
|
||||
onPress={handlePrimaryAction}
|
||||
|
||||
+90
-9
@@ -1,6 +1,8 @@
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
import * as AppleAuthentication from "expo-apple-authentication";
|
||||
import DateTimePicker from "@react-native-community/datetimepicker";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useGlobal } from "reactn";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
KeyboardAvoidingView,
|
||||
@@ -16,6 +18,7 @@ import { background } from "../assets";
|
||||
import BorderGradientButton from "../components/BorderGradientButton";
|
||||
import { Input } from "../components/Input";
|
||||
import ItemContainer from "../components/ItemContainer/ItemContainer";
|
||||
import WebDateModal from "../components/modal/WebDateModal";
|
||||
import firebase from "../config/firebase";
|
||||
import { isWeb } from "../hooks/useLayoutType";
|
||||
import useSocialAuth from "../hooks/useSocialAuth";
|
||||
@@ -32,6 +35,9 @@ const Register = () => {
|
||||
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 = () => (
|
||||
<View style={styles.formContent}>
|
||||
@@ -101,6 +143,52 @@ const Register = () => {
|
||||
value={email}
|
||||
setValue={setEmail}
|
||||
/>
|
||||
{isWeb ? (
|
||||
<>
|
||||
<Pressable onPress={() => setShowDatePicker(true)}>
|
||||
<View pointerEvents="none">
|
||||
<Input
|
||||
placeholder="Date de naissance"
|
||||
label="Date de naissance"
|
||||
isBlur
|
||||
value={birthDate ? birthDate.toLocaleDateString() : ""}
|
||||
editable={false}
|
||||
/>
|
||||
</View>
|
||||
</Pressable>
|
||||
<WebDateModal
|
||||
visible={showDatePicker}
|
||||
onClose={() => setShowDatePicker(false)}
|
||||
onValidate={(date) => setBirthDate(date)}
|
||||
initialDate={birthDate}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Pressable onPress={() => setShowDatePicker(true)}>
|
||||
<View pointerEvents="none">
|
||||
<Input
|
||||
placeholder="Date de naissance"
|
||||
label="Date de naissance"
|
||||
isBlur
|
||||
value={birthDate ? birthDate.toLocaleDateString() : ""}
|
||||
editable={false}
|
||||
/>
|
||||
</View>
|
||||
</Pressable>
|
||||
{showDatePicker && (
|
||||
<DateTimePicker
|
||||
testID="dateTimePicker"
|
||||
value={birthDate || new Date()}
|
||||
mode="date"
|
||||
is24Hour={true}
|
||||
display="default"
|
||||
onChange={onDateChange}
|
||||
maximumDate={new Date()}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
<BorderGradientButton
|
||||
title="Créer mon compte"
|
||||
@@ -108,14 +196,7 @@ const Register = () => {
|
||||
width: "90%",
|
||||
alignSelf: "center",
|
||||
}}
|
||||
onPress={() =>
|
||||
navigate(Routes.CreatePassword, {
|
||||
email: email.trim(),
|
||||
firstName: firstName.trim(),
|
||||
lastName: lastName.trim(),
|
||||
preferredLanguage,
|
||||
})
|
||||
}
|
||||
onPress={handleNext}
|
||||
disabled={!isFormValid || isBusy}
|
||||
/>
|
||||
<View style={styles.socialWrapper}>
|
||||
|
||||
@@ -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 }) => {
|
||||
<CreateLyricsHeader
|
||||
title="Choisis des instruments"
|
||||
subTitle="Tu peux choisir jusqu’à 5 instruments différents"
|
||||
/>
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
gap: 6,
|
||||
backgroundColor: Palette.transparentOrange,
|
||||
padding: 8,
|
||||
borderRadius: 8,
|
||||
borderWidth: 1,
|
||||
borderColor: Palette.orange,
|
||||
marginTop: 4,
|
||||
}}
|
||||
>
|
||||
<MaterialIcons name="info-outline" size={20} color={Palette.orange} />
|
||||
<Text
|
||||
style={{
|
||||
flex: 1,
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
fontSize: 12,
|
||||
color: Palette.white,
|
||||
}}
|
||||
>
|
||||
Attention : Suno peut ajuster ou supprimer certains paramètres
|
||||
choisis pour maintenir la cohérence de la création musicale.
|
||||
</Text>
|
||||
</View>
|
||||
</CreateLyricsHeader>
|
||||
<View
|
||||
style={{ flex: 1 }}
|
||||
onLayout={(event) => setContainerLayout(event.nativeEvent.layout)}
|
||||
|
||||
@@ -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 }) => {
|
||||
|
||||
<ClubAdvantagesCard style={styles.clubCardSpacing} />
|
||||
|
||||
<View style={styles.consentContainer}>
|
||||
<AppCheckbox
|
||||
selected={hasAcceptedPublication}
|
||||
onPress={() => setHasAcceptedPublication((prev) => !prev)}
|
||||
label="J'accepte la diffusion de mon contenu sur Musicland et YouTube."
|
||||
/>
|
||||
<Text style={styles.consentDescription}>
|
||||
Cette confirmation est requise pour continuer.
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<BorderGradientButton
|
||||
title={
|
||||
hasActiveSubscription
|
||||
@@ -438,6 +468,16 @@ const styles = StyleSheet.create({
|
||||
scrollContent: {
|
||||
paddingBottom: gutters * 2.6,
|
||||
},
|
||||
consentContainer: {
|
||||
gap: 6,
|
||||
marginTop: 10,
|
||||
},
|
||||
consentDescription: {
|
||||
fontSize: 13,
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
color: Palette.white,
|
||||
opacity: 0.8,
|
||||
},
|
||||
coverRow: {
|
||||
flexDirection: isWeb ? "row" : "column",
|
||||
alignItems: "center",
|
||||
|
||||
Reference in New Issue
Block a user