firtname and lastname before username

This commit is contained in:
2025-10-22 12:01:32 +02:00
parent 91163b8a75
commit a33e2ed555
16 changed files with 495 additions and 75 deletions
+351 -11
View File
@@ -1,26 +1,128 @@
import * as ImagePicker from "expo-image-picker";
import React, { useState } from "react";
import { Image, View } from "react-native";
import { BlurView } from "expo-blur";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import {
Image,
Modal,
Pressable,
StyleSheet,
Text,
View,
} from "react-native";
import useMinuit from "react-native-minuit/src/hooks/useMinuit";
import { ai, background } from "../../assets";
import BorderGradientButton from "../../components/BorderGradientButton";
import FullscreenIntroVideo from "../../components/FullscreenIntroVideo";
import GradientButton from "../../components/GradientButton";
import MusicLandHeader from "../../components/MusicLandHeader";
import { Input } from "../../components/Input";
import firebase, { projectsRef, usersRef } from "../../config/firebase";
import { uploadFileToFirebase } from "../../helpers/uploadToFirebase";
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 { buildFullName } from "../../utils/artistName";
import { gutters, Palette } from "../../styles";
import { FONT_FAMILY } from "../../styles/Fonts";
export const ChooseCoverType = () => {
const [showIntro, setShowIntro] = useState(true);
const { selectedProject, selectedProjectId, updateProjectData } =
useUserData();
const { setIsLoading } = useMinuit();
const [choiceVisible, setChoiceVisible] = useState(false);
const [pseudoVisible, setPseudoVisible] = useState(false);
const [pseudo, setPseudo] = useState("");
const [pendingAction, setPendingAction] = useState(null);
const [savingChoice, setSavingChoice] = useState(false);
const [savingPseudo, setSavingPseudo] = useState(false);
const onPickUserImage = async () => {
const {
selectedProject,
selectedProjectId,
updateProjectData,
currentUserData,
currentUID,
} = useUserData();
const { setIsLoading, setTooltip } = useMinuit();
const projectId = selectedProject?.id || selectedProjectId || null;
const hasArtistPreference = useMemo(() => {
if (currentUserData?.userName) return true;
const pref = currentUserData?.artistNamePreference;
return pref === "REAL_NAME" || pref === "CUSTOM";
}, [currentUserData?.artistNamePreference, currentUserData?.userName]);
const realName = useMemo(() => buildFullName(currentUserData), [
currentUserData?.firstName,
currentUserData?.lastName,
currentUserData?.displayName,
]);
useEffect(() => {
if (pseudoVisible) {
setPseudo(currentUserData?.userName || "");
}
}, [currentUserData?.userName, pseudoVisible]);
const ensureArtistPreference = useCallback(
(nextAction) => {
if (hasArtistPreference) {
if (typeof nextAction === "function") {
nextAction();
}
return;
}
setPendingAction(() => nextAction);
setChoiceVisible(true);
},
[hasArtistPreference]
);
const runPendingAction = useCallback(async () => {
if (typeof pendingAction === "function") {
const action = pendingAction;
setPendingAction(null);
await action();
} else {
setPendingAction(null);
}
}, [pendingAction]);
const applyDisplayNameToProjects = useCallback(
async (displayName) => {
if (!currentUID) return;
const safeName =
typeof displayName === "string" && displayName.trim().length > 0
? displayName.trim()
: null;
try {
const snapshot = await projectsRef
.where("userId", "==", currentUID)
.get();
if (!snapshot.empty) {
const batch = firebase.firestore().batch();
snapshot.docs.forEach((doc) => {
batch.set(doc.ref, { userName: safeName }, { merge: true });
});
await batch.commit();
}
} catch (error) {
console.log("applyDisplayNameToProjects error", error?.message);
}
if (!projectId) return;
try {
await projectsRef
.doc(projectId)
.set({ userName: safeName }, { merge: true });
} catch (error) {
console.log("update current project userName error", error?.message);
}
},
[currentUID, projectId]
);
const pickUserImage = useCallback(async () => {
try {
await setIsLoading(true);
const result = await ImagePicker.launchImageLibraryAsync({
@@ -33,7 +135,7 @@ export const ChooseCoverType = () => {
const uri = result?.assets?.[0]?.uri || null;
if (!uri) return;
const projectId = selectedProject?.id || selectedProjectId;
if (!projectId) return;
const path = `musics/${projectId}/userSelectedCover.png`;
const { resultURI } = await uploadFileToFirebase({
@@ -57,7 +159,120 @@ export const ChooseCoverType = () => {
} finally {
await setIsLoading(false);
}
};
}, [projectId, setIsLoading, updateProjectData]);
const handlePickUserImage = useCallback(() => {
ensureArtistPreference(pickUserImage);
}, [ensureArtistPreference, pickUserImage]);
const handleGenerateCover = useCallback(() => {
ensureArtistPreference(() => navigate(Routes.PouchReady));
}, [ensureArtistPreference]);
const closeChoiceModal = useCallback(() => {
setChoiceVisible(false);
setPendingAction(null);
}, []);
const openPseudoModal = useCallback(() => {
setChoiceVisible(false);
setPseudoVisible(true);
}, []);
const handleUseRealName = useCallback(async () => {
if (!currentUID) return;
if (!realName) {
setTooltip({
type: "error",
text:
"Renseigne ton prénom et nom dans ton profil ou crée un nom d'artiste.",
});
setChoiceVisible(false);
setPseudoVisible(true);
return;
}
try {
setSavingChoice(true);
const updates = {
artistNamePreference: "REAL_NAME",
userName: realName,
userNameLower: realName.toLowerCase(),
updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
};
await usersRef.doc(currentUID).set(updates, { merge: true });
await applyDisplayNameToProjects(realName);
setTooltip({ type: "success", text: "Nom d'artiste mis à jour" });
setChoiceVisible(false);
await runPendingAction();
} catch (e) {
console.log("handleUseRealName error", e?.message);
setTooltip({
type: "error",
text: e?.message || "Impossible de mettre à jour le nom",
});
} finally {
setSavingChoice(false);
}
}, [
applyDisplayNameToProjects,
currentUID,
currentUserData?.userName,
currentUserData?.userNameLower,
realName,
runPendingAction,
setTooltip,
]);
const handleCancelPseudo = useCallback(() => {
setPseudoVisible(false);
setPendingAction(null);
}, []);
const handleSavePseudo = useCallback(async () => {
const value = (pseudo || "").trim();
if (!value || !currentUID) return;
try {
setSavingPseudo(true);
const lower = value.toLowerCase();
const existing = await usersRef
.where("userNameLower", "==", lower)
.limit(1)
.get();
if (!existing.empty && existing.docs[0].id !== currentUID) {
setTooltip({ text: "Ce pseudo est déjà pris", type: "error" });
return;
}
await usersRef.doc(currentUID).set(
{
userName: value,
userNameLower: lower,
artistNamePreference: "CUSTOM",
updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
},
{ merge: true }
);
await applyDisplayNameToProjects(value);
setTooltip({ type: "success", text: "Pseudo enregistré" });
setPseudoVisible(false);
await runPendingAction();
} catch (e) {
console.log("handleSavePseudo error", e?.message);
setTooltip({
type: "error",
text: e?.message || "Enregistrement impossible",
});
} finally {
setSavingPseudo(false);
}
}, [
applyDisplayNameToProjects,
currentUID,
pseudo,
runPendingAction,
setTooltip,
]);
return (
<Page backgroundImg={background.studioBG2} headerType="NONE">
@@ -85,11 +300,11 @@ export const ChooseCoverType = () => {
>
<BorderGradientButton
title="Choisir une image"
onPress={onPickUserImage}
onPress={handlePickUserImage}
/>
<GradientButton
title="Générer une pochette"
onPress={() => navigate(Routes.PouchReady)}
onPress={handleGenerateCover}
/>
</View>
</View>
@@ -97,6 +312,131 @@ export const ChooseCoverType = () => {
visible={showIntro}
onClose={() => setShowIntro(false)}
/>
<Modal
visible={choiceVisible}
transparent
animationType="fade"
onRequestClose={closeChoiceModal}
>
<View style={styles.modalBackdrop}>
<Pressable
style={styles.modalOverlay}
onPress={closeChoiceModal}
accessibilityRole="button"
/>
<BlurView intensity={20} style={styles.modalCard}>
<View style={styles.modalContent}>
<Text style={styles.modalTitle}>Nom d'artiste</Text>
<Text style={styles.modalDescription}>
Veux-tu utiliser ton prénom et nom pour tes musiques ou créer un
nom d'artiste ?
</Text>
<View style={styles.modalButtons}>
<BorderGradientButton
title={
savingChoice ? "Chargement..." : "Utiliser prénom/nom"
}
onPress={handleUseRealName}
disabled={savingChoice}
/>
<GradientButton
title="Créer un nom d'artiste"
onPress={openPseudoModal}
disabled={savingChoice}
/>
</View>
</View>
</BlurView>
</View>
</Modal>
<Modal
visible={pseudoVisible}
transparent
animationType="fade"
onRequestClose={handleCancelPseudo}
>
<View style={styles.modalBackdrop}>
<Pressable
style={styles.modalOverlay}
onPress={handleCancelPseudo}
accessibilityRole="button"
/>
<BlurView intensity={20} style={styles.modalCard}>
<View style={styles.modalContent}>
<Text style={styles.modalTitle}>Créer un nom d'artiste</Text>
<Text style={styles.modalDescription}>
Choisis le nom qui sera visible sur tes musiques.
</Text>
<View style={styles.inputWrapper}>
<Input
placeholder="Nom d'artiste"
label="Nom d'artiste"
value={pseudo}
setValue={setPseudo}
isBlur
/>
</View>
<View style={styles.modalButtons}>
<BorderGradientButton
title="Annuler"
onPress={handleCancelPseudo}
disabled={savingPseudo}
/>
<GradientButton
title={savingPseudo ? "Enregistrement..." : "Valider"}
onPress={handleSavePseudo}
disabled={savingPseudo || !(pseudo || "").trim()}
/>
</View>
</View>
</BlurView>
</View>
</Modal>
</Page>
);
};
const styles = StyleSheet.create({
modalBackdrop: {
flex: 1,
backgroundColor: "rgba(0,0,0,0.65)",
justifyContent: "center",
alignItems: "center",
padding: gutters,
},
modalOverlay: {
...StyleSheet.absoluteFillObject,
},
modalCard: {
width: "100%",
maxWidth: 420,
borderRadius: 24,
overflow: "hidden",
backgroundColor: Palette.glass,
},
modalContent: {
paddingHorizontal: 24,
paddingVertical: 28,
gap: 18,
},
modalTitle: {
fontSize: 22,
color: Palette.white,
fontFamily: FONT_FAMILY.InterSemiBold,
textAlign: "center",
},
modalDescription: {
fontSize: 15,
color: Palette.gray,
fontFamily: FONT_FAMILY.InterRegular,
textAlign: "center",
},
modalButtons: {
width: "80%",
alignSelf: "center",
gap: 12,
},
inputWrapper: {
gap: 12,
},
});