clear more tickets
This commit is contained in:
@@ -94,7 +94,6 @@ const STAGE_CARD_CONTENT = [
|
||||
},
|
||||
];
|
||||
|
||||
const CLUB_CARD_IMAGE = icons.clubIcon;
|
||||
const ACTIVE_SUBSCRIPTION_STATUSES = new Set([
|
||||
"trialing",
|
||||
"active",
|
||||
@@ -114,6 +113,7 @@ const Home = ({ navigation, route }) => {
|
||||
videos,
|
||||
} = useUser();
|
||||
const { setTooltip } = useMinuit();
|
||||
const [videoUrl, setVideoUrl] = useState(null);
|
||||
|
||||
const hasActiveSubscription = useMemo(() => {
|
||||
if (!currentUserData) {
|
||||
@@ -208,7 +208,6 @@ const Home = ({ navigation, route }) => {
|
||||
const [menuAnchor, setMenuAnchor] = useState(null);
|
||||
const [menuProject, setMenuProject] = useState(null);
|
||||
const menuAnchorRef = useRef(null);
|
||||
const [isIntroVideoVisible, setIsIntroVideoVisible] = useState(false);
|
||||
const [hasLocalAdventureFlag, setHasLocalAdventureFlag] = useState(false);
|
||||
|
||||
const handleSelectProject = useCallback(
|
||||
@@ -404,18 +403,17 @@ const Home = ({ navigation, route }) => {
|
||||
}, [hasLocalAdventureFlag, persistAdventureStarted, setTooltip]);
|
||||
|
||||
const handleStartVisit = useCallback(() => {
|
||||
setIsIntroVideoVisible(true);
|
||||
setVideoUrl(isWeb ? videos?.landingWeb : videos?.landing);
|
||||
}, []);
|
||||
|
||||
const handleIntroVideoClose = useCallback(() => {
|
||||
setIsIntroVideoVisible(false);
|
||||
setVideoUrl(null);
|
||||
markAdventureStarted();
|
||||
}, [markAdventureStarted]);
|
||||
|
||||
const adventureStarted =
|
||||
hasLocalAdventureFlag || !!currentUserData?.adventureStarted;
|
||||
|
||||
const videoUrl = isWeb ? videos?.landingWeb : videos?.landing;
|
||||
const homeBackgroundImage = background.bgTrans;
|
||||
const landingBackgroundImage = homeBackgroundImage;
|
||||
|
||||
@@ -523,10 +521,7 @@ const Home = ({ navigation, route }) => {
|
||||
iconPosition="left"
|
||||
/>
|
||||
</Pressable>
|
||||
<ShareBtn
|
||||
style={styles.mobileShareButton}
|
||||
iconOnly
|
||||
/>
|
||||
<ShareBtn style={styles.mobileShareButton} label="Partager" />
|
||||
</View>
|
||||
) : null;
|
||||
|
||||
@@ -590,7 +585,6 @@ const Home = ({ navigation, route }) => {
|
||||
{stageCardsList}
|
||||
|
||||
<ClubCard
|
||||
image={CLUB_CARD_IMAGE}
|
||||
onPress={handleClubPress}
|
||||
hasActiveSubscription={hasActiveSubscription}
|
||||
/>
|
||||
@@ -654,7 +648,6 @@ const Home = ({ navigation, route }) => {
|
||||
}}
|
||||
>
|
||||
<GradientButton
|
||||
url={videoUrl}
|
||||
title="Commencer l'aventure MusicLand"
|
||||
onPress={handleStartVisit}
|
||||
/>
|
||||
@@ -662,7 +655,7 @@ const Home = ({ navigation, route }) => {
|
||||
</Page>
|
||||
<FullscreenIntroVideo
|
||||
url={videoUrl}
|
||||
visible={isIntroVideoVisible}
|
||||
visible={!!videoUrl}
|
||||
onClose={handleIntroVideoClose}
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -19,7 +19,6 @@ const StageCard = ({
|
||||
}) => {
|
||||
const isMobileVariant = variant === "mobile";
|
||||
const isImageOnLeft = !isMobileVariant && imagePosition !== "right";
|
||||
const isTextRight = !isMobileVariant && textAlign === "right";
|
||||
|
||||
const basePressableStyle =
|
||||
variant === "web"
|
||||
@@ -72,7 +71,7 @@ const StageCard = ({
|
||||
{
|
||||
width: "100%",
|
||||
height: 190,
|
||||
minHeight: 190,
|
||||
minHeight: 210,
|
||||
borderRadius: 0,
|
||||
},
|
||||
isMobileVariant
|
||||
@@ -96,9 +95,7 @@ const StageCard = ({
|
||||
},
|
||||
isMobileVariant
|
||||
? { justifyContent: "center" }
|
||||
: isTextRight
|
||||
? { justifyContent: "flex-end" }
|
||||
: { justifyContent: "flex-start" },
|
||||
: { justifyContent: "flex-start" },
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
@@ -109,11 +106,7 @@ const StageCard = ({
|
||||
color: Palette.white,
|
||||
flexShrink: 1,
|
||||
},
|
||||
isMobileVariant
|
||||
? { textAlign: "center" }
|
||||
: isTextRight
|
||||
? { textAlign: "right" }
|
||||
: { textAlign: "left" },
|
||||
isMobileVariant ? { textAlign: "center" } : { textAlign: "left" },
|
||||
]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
@@ -153,9 +146,6 @@ const StageCard = ({
|
||||
borderTopLeftRadius: 20,
|
||||
borderBottomLeftRadius: 20,
|
||||
}),
|
||||
...(isTextRight
|
||||
? { alignItems: "flex-end" }
|
||||
: { alignItems: "flex-start" }),
|
||||
};
|
||||
|
||||
const textBlock = (
|
||||
@@ -177,12 +167,8 @@ const StageCard = ({
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
zIndex: 2,
|
||||
right: 10,
|
||||
},
|
||||
isMobileVariant
|
||||
? { right: 10 }
|
||||
: isTextRight
|
||||
? { left: 10 }
|
||||
: { right: 10 },
|
||||
]}
|
||||
>
|
||||
<FontAwesome name="lock" size={18} color={Palette.primary} />
|
||||
@@ -193,9 +179,7 @@ const StageCard = ({
|
||||
style={[
|
||||
isMobileVariant
|
||||
? { textAlign: "center" }
|
||||
: isTextRight
|
||||
? { textAlign: "right" }
|
||||
: { textAlign: "left" },
|
||||
: { textAlign: "left", marginRight: 10 },
|
||||
{
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
fontSize: 13,
|
||||
|
||||
@@ -18,6 +18,7 @@ import { Routes } from "../navigation/Routes";
|
||||
import { useUser } from "../providers/UserDataProvider";
|
||||
import { Palette } from "../styles";
|
||||
import { FONT_FAMILY } from "../styles/Fonts";
|
||||
import { isMobile } from "../hooks/useLayoutType";
|
||||
|
||||
const LANGUAGE_STORAGE_KEY = "preferredLanguage";
|
||||
|
||||
@@ -103,13 +104,19 @@ export default function LandingPage() {
|
||||
title="Présentation de Musicland"
|
||||
onPress={() =>
|
||||
setVideoUrl(
|
||||
Platform.OS === "web" ? video?.landingWeb : video?.landing
|
||||
Platform.OS === "web"
|
||||
? video?.landingWeb
|
||||
: video?.landing,
|
||||
)
|
||||
}
|
||||
containerStyle={styles.actionButton}
|
||||
/>
|
||||
<GradientButton
|
||||
title="Je me lance dans l'aventure"
|
||||
title={
|
||||
Platform.OS === "web"
|
||||
? "Je me lance dans l'aventure"
|
||||
: "Commencer"
|
||||
}
|
||||
onPress={handleLaunchAdventure}
|
||||
containerStyle={styles.actionButton}
|
||||
/>
|
||||
|
||||
+52
-2
@@ -14,11 +14,12 @@ import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import GradientButton from "../components/GradientButton";
|
||||
import CreditAmount from "../components/CreditAmount";
|
||||
import Page from "../layouts/Page";
|
||||
import { background, subBadges } from "../assets";
|
||||
import { background, icons, subBadges } from "../assets";
|
||||
import { Palette, gutters } from "../styles";
|
||||
import { FONT_FAMILY } from "../styles/Fonts";
|
||||
import { isWeb } from "../hooks/useLayoutType";
|
||||
import { useStripe } from "../providers/StripeProvider";
|
||||
import { goBack } from "../navigation/NavigationService";
|
||||
|
||||
const formatCurrency = (amount, currency = "eur") => {
|
||||
if (typeof amount !== "number") {
|
||||
@@ -489,6 +490,9 @@ export default function Payments() {
|
||||
}),
|
||||
[mobileActionSafePadding]
|
||||
);
|
||||
const handleBackPress = React.useCallback(() => {
|
||||
goBack();
|
||||
}, []);
|
||||
|
||||
const renderHeaderSection = React.useCallback(() => {
|
||||
return (
|
||||
@@ -615,6 +619,25 @@ export default function Payments() {
|
||||
isActionDisabled,
|
||||
mobileActionSafePadding,
|
||||
]);
|
||||
const renderWebBackButton = React.useCallback(() => {
|
||||
if (isMobile) return null;
|
||||
return (
|
||||
<View style={styles.webBackContainer}>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
onPress={handleBackPress}
|
||||
style={styles.webBackButton}
|
||||
>
|
||||
<ExpoImage
|
||||
source={icons.chevronDown}
|
||||
contentFit="contain"
|
||||
style={styles.webBackIcon}
|
||||
/>
|
||||
<Text style={styles.webBackText}>Retour</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
);
|
||||
}, [handleBackPress, isMobile]);
|
||||
|
||||
return (
|
||||
<View style={styles.root}>
|
||||
@@ -629,7 +652,8 @@ export default function Payments() {
|
||||
isMobile && styles.pageContentMobile,
|
||||
]}
|
||||
backgroundColor={PAGE_BACKGROUND_COLOR}
|
||||
topStickyContent={isMobile ? renderMobileTopSticky : undefined}
|
||||
hideBackButton={!isMobile}
|
||||
topStickyContent={isMobile ? renderMobileTopSticky : renderWebBackButton}
|
||||
>
|
||||
<View style={[styles.inner, isMobile && styles.mobileInner]}>
|
||||
<ExpoImage
|
||||
@@ -771,6 +795,32 @@ const styles = StyleSheet.create({
|
||||
gap: 12,
|
||||
alignItems: "center",
|
||||
},
|
||||
webBackContainer: {
|
||||
alignSelf: "flex-start",
|
||||
marginBottom: 12,
|
||||
},
|
||||
webBackButton: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 8,
|
||||
alignSelf: "flex-start",
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 7,
|
||||
borderRadius: 999,
|
||||
borderWidth: 1,
|
||||
borderColor: Palette.ultraLightWhite,
|
||||
backgroundColor: Palette.ultraLightWhite,
|
||||
},
|
||||
webBackIcon: {
|
||||
width: 16,
|
||||
height: 16,
|
||||
transform: [{ rotate: "90deg" }],
|
||||
},
|
||||
webBackText: {
|
||||
fontSize: 14,
|
||||
color: Palette.white,
|
||||
fontFamily: FONT_FAMILY.InterSemiBold,
|
||||
},
|
||||
title: {
|
||||
fontFamily: FONT_FAMILY.InterBold,
|
||||
fontSize: isWeb ? 32 : 24,
|
||||
|
||||
@@ -154,6 +154,7 @@ const ManageSubscription = ({ navigation }) => {
|
||||
currentUserData?.stripeSubscription?.id ||
|
||||
currentUserData?.stripeSubscription?.subscriptionId ||
|
||||
null;
|
||||
const localSubscription = currentUserData?.stripeSubscription || null;
|
||||
|
||||
const triggerRefresh = useCallback(() => {
|
||||
setRefreshToken((value) => value + 1);
|
||||
@@ -219,8 +220,9 @@ const ManageSubscription = ({ navigation }) => {
|
||||
}, [stripeCustomerId, localSubscriptionId, refreshToken]);
|
||||
|
||||
const subscriptionInfo = useMemo(() => {
|
||||
const rawSubscription =
|
||||
remoteSubscription || currentUserData?.stripeSubscription || null;
|
||||
const subscriptionSources = [remoteSubscription, localSubscription].filter(
|
||||
Boolean,
|
||||
);
|
||||
|
||||
const pickString = (value) => {
|
||||
if (typeof value !== "string") {
|
||||
@@ -230,40 +232,71 @@ const ManageSubscription = ({ navigation }) => {
|
||||
return trimmed ? trimmed : null;
|
||||
};
|
||||
|
||||
const pickFromSources = (resolver) => {
|
||||
for (const source of subscriptionSources) {
|
||||
if (!source) {
|
||||
continue;
|
||||
}
|
||||
const value = resolver(source);
|
||||
if (value !== undefined && value !== null) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const pickDateFromSources = (...resolvers) => {
|
||||
for (const source of subscriptionSources) {
|
||||
if (!source) {
|
||||
continue;
|
||||
}
|
||||
for (const resolveValue of resolvers) {
|
||||
const date = toDate(resolveValue(source));
|
||||
if (date) {
|
||||
return date;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const statusSource =
|
||||
pickString(currentUserData?.stripeSubscriptionStatus) ||
|
||||
pickString(remoteSubscription?.status) ||
|
||||
pickString(currentUserData?.stripeSubscription?.status) ||
|
||||
pickString(
|
||||
currentUserData?.stripeSubscription?.stripeSubscriptionStatus,
|
||||
) ||
|
||||
pickString(rawSubscription?.status) ||
|
||||
pickString(pickFromSources((source) => source?.status)) ||
|
||||
pickString(pickFromSources((source) => source?.stripeSubscriptionStatus)) ||
|
||||
pickString(pickFromSources((source) => source?.metadata?.status)) ||
|
||||
null;
|
||||
|
||||
const status = statusSource ? statusSource.toLowerCase() : null;
|
||||
|
||||
const cancelAtPeriodEnd =
|
||||
remoteSubscription?.cancelAtPeriodEnd === true ||
|
||||
rawSubscription?.cancelAtPeriodEnd === true ||
|
||||
rawSubscription?.cancel_at_period_end === true ||
|
||||
false;
|
||||
const cancelAtPeriodEnd = subscriptionSources.some(
|
||||
(source) =>
|
||||
source?.cancelAtPeriodEnd === true ||
|
||||
source?.cancel_at_period_end === true,
|
||||
);
|
||||
|
||||
const resolveLevelSource = () => {
|
||||
const candidates = [
|
||||
pickString(remoteSubscription?.level),
|
||||
pickString(pickFromSources((source) => source?.level)),
|
||||
pickString(currentUserData?.premiumLevel),
|
||||
pickString(rawSubscription?.metadata?.level),
|
||||
pickString(rawSubscription?.metadata?.subscriptionLevel),
|
||||
pickString(pickFromSources((source) => source?.metadata?.level)),
|
||||
pickString(
|
||||
pickFromSources((source) => source?.metadata?.subscriptionLevel),
|
||||
),
|
||||
];
|
||||
return candidates.find(Boolean) || null;
|
||||
};
|
||||
|
||||
const resolvePeriodSource = () => {
|
||||
const candidates = [
|
||||
pickString(remoteSubscription?.billingPeriod),
|
||||
pickString(pickFromSources((source) => source?.billingPeriod)),
|
||||
pickString(currentUserData?.premiumBillingPeriod),
|
||||
pickString(rawSubscription?.metadata?.billingPeriod),
|
||||
pickString(rawSubscription?.metadata?.subscriptionBillingPeriod),
|
||||
pickString(pickFromSources((source) => source?.metadata?.billingPeriod)),
|
||||
pickString(
|
||||
pickFromSources(
|
||||
(source) => source?.metadata?.subscriptionBillingPeriod,
|
||||
),
|
||||
),
|
||||
];
|
||||
return candidates.find(Boolean) || null;
|
||||
};
|
||||
@@ -292,14 +325,15 @@ const ManageSubscription = ({ navigation }) => {
|
||||
? planLabelParts.join(" · ")
|
||||
: "Abonnement Musicland";
|
||||
|
||||
const currentPeriodEndDate =
|
||||
toDate(remoteSubscription?.currentPeriodEnd) ||
|
||||
toDate(rawSubscription?.currentPeriodEnd) ||
|
||||
toDate(rawSubscription?.current_period_end);
|
||||
const createdAtDate =
|
||||
toDate(remoteSubscription?.created) ||
|
||||
toDate(rawSubscription?.createdAt) ||
|
||||
toDate(rawSubscription?.created_at);
|
||||
const currentPeriodEndDate = pickDateFromSources(
|
||||
(source) => source?.currentPeriodEnd,
|
||||
(source) => source?.current_period_end,
|
||||
);
|
||||
const createdAtDate = pickDateFromSources(
|
||||
(source) => source?.created,
|
||||
(source) => source?.createdAt,
|
||||
(source) => source?.created_at,
|
||||
);
|
||||
|
||||
const statusLabelBase =
|
||||
STATUS_LABELS[status] ||
|
||||
@@ -307,7 +341,13 @@ const ManageSubscription = ({ navigation }) => {
|
||||
const statusLabel = statusLabelBase ? capitalize(statusLabelBase) : null;
|
||||
const statusColors = getStatusColors(status);
|
||||
|
||||
const hasAnySubscription = Boolean(rawSubscription?.id);
|
||||
const subscriptionId =
|
||||
pickString(pickFromSources((source) => source?.id)) ||
|
||||
pickString(pickFromSources((source) => source?.subscriptionId)) ||
|
||||
pickString(currentUserData?.stripeSubscription?.subscriptionId) ||
|
||||
null;
|
||||
|
||||
const hasAnySubscription = Boolean(subscriptionId);
|
||||
const hasActiveSubscription =
|
||||
hasAnySubscription && ACTIVE_SUBSCRIPTION_STATUSES.has(status);
|
||||
const canCancel = hasActiveSubscription && !cancelAtPeriodEnd;
|
||||
@@ -317,10 +357,7 @@ const ManageSubscription = ({ navigation }) => {
|
||||
: "—";
|
||||
const createdAtLabel = createdAtDate ? formatDate(createdAtDate) : null;
|
||||
|
||||
let coinsPerMonth =
|
||||
typeof remoteSubscription?.coinsPerMonth === "number"
|
||||
? remoteSubscription.coinsPerMonth
|
||||
: null;
|
||||
let coinsPerMonth = pickFromSources((source) => source?.coinsPerMonth);
|
||||
|
||||
if (
|
||||
coinsPerMonth === null &&
|
||||
@@ -395,17 +432,13 @@ const ManageSubscription = ({ navigation }) => {
|
||||
createdAtLabel,
|
||||
helperMessage: helperMessage || null,
|
||||
level: level || null,
|
||||
subscriptionId:
|
||||
remoteSubscription?.id ||
|
||||
rawSubscription?.id ||
|
||||
currentUserData?.stripeSubscription?.subscriptionId ||
|
||||
null,
|
||||
subscriptionId,
|
||||
coinsPerMonth: normalizedCoins,
|
||||
isAnnual,
|
||||
nextGrantDate: resolvedNextGrantDate,
|
||||
nextGrantLabel,
|
||||
};
|
||||
}, [currentUserData, remoteSubscription]);
|
||||
}, [currentUserData, localSubscription, remoteSubscription]);
|
||||
|
||||
const handleOpenPlans = useCallback(() => {
|
||||
const params =
|
||||
|
||||
+20
-232
@@ -1,26 +1,21 @@
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
import * as AppleAuthentication from "expo-apple-authentication";
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
FlatList,
|
||||
KeyboardAvoidingView,
|
||||
Modal,
|
||||
Platform,
|
||||
Pressable,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TextInput,
|
||||
View,
|
||||
} from "react-native";
|
||||
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
|
||||
import { BlurView } from "expo-blur";
|
||||
import Svg, { Path } from "react-native-svg";
|
||||
import { background } from "../assets";
|
||||
import BorderGradientButton from "../components/BorderGradientButton";
|
||||
import { Input } from "../components/Input";
|
||||
import ItemContainer from "../components/ItemContainer/ItemContainer";
|
||||
import COUNTRIES from "../constants/countries";
|
||||
import firebase from "../config/firebase";
|
||||
import { isWeb } from "../hooks/useLayoutType";
|
||||
import useSocialAuth from "../hooks/useSocialAuth";
|
||||
@@ -35,11 +30,6 @@ const LANGUAGE_STORAGE_KEY = "preferredLanguage";
|
||||
const Register = () => {
|
||||
const [email, setEmail] = useState("");
|
||||
const [firstName, setFirstName] = useState("");
|
||||
const [lastName, setLastName] = useState("");
|
||||
const [city, setCity] = useState("");
|
||||
const [selectedCountry, setSelectedCountry] = useState(null);
|
||||
const [countrySearch, setCountrySearch] = useState("");
|
||||
const [isCountryModalVisible, setIsCountryModalVisible] = useState(false);
|
||||
const [preferredLanguage, setPreferredLanguage] = useState(null);
|
||||
|
||||
const afterSocialAuth = useCallback(async () => {
|
||||
@@ -71,33 +61,7 @@ const Register = () => {
|
||||
});
|
||||
}, []);
|
||||
|
||||
const filteredCountries = useMemo(() => {
|
||||
const query = countrySearch.trim().toLowerCase();
|
||||
if (!query) {
|
||||
return COUNTRIES;
|
||||
}
|
||||
return COUNTRIES.filter((country) =>
|
||||
country.name.toLowerCase().includes(query),
|
||||
);
|
||||
}, [countrySearch]);
|
||||
|
||||
const isFormValid =
|
||||
email.trim().length > 0 &&
|
||||
firstName.trim().length > 0 &&
|
||||
lastName.trim().length > 0 &&
|
||||
city.trim().length > 0 &&
|
||||
selectedCountry;
|
||||
|
||||
const handleSelectCountry = (country) => {
|
||||
setSelectedCountry(country);
|
||||
setIsCountryModalVisible(false);
|
||||
setCountrySearch("");
|
||||
};
|
||||
|
||||
const handleCloseModal = () => {
|
||||
setIsCountryModalVisible(false);
|
||||
setCountrySearch("");
|
||||
};
|
||||
const isFormValid = email.trim().length > 0 && firstName.trim().length > 0;
|
||||
|
||||
const renderFormContent = () => (
|
||||
<View style={styles.formContent}>
|
||||
@@ -111,58 +75,13 @@ const Register = () => {
|
||||
</Text>
|
||||
) : null}
|
||||
<View style={{ gap: 16 }}>
|
||||
<View style={{ gap: 12 }}>
|
||||
<Input
|
||||
placeholder="Prénom"
|
||||
label="Prénom"
|
||||
isBlur
|
||||
value={firstName}
|
||||
setValue={setFirstName}
|
||||
/>
|
||||
<Input
|
||||
placeholder="Nom"
|
||||
label="Nom"
|
||||
isBlur
|
||||
value={lastName}
|
||||
setValue={setLastName}
|
||||
/>
|
||||
<View style={styles.countryField}>
|
||||
<Text style={styles.inputLabel}>Pays</Text>
|
||||
<BlurView
|
||||
tint="dark"
|
||||
style={[
|
||||
styles.countryBlurWrapper,
|
||||
selectedCountry ? styles.countryButtonFilled : null,
|
||||
]}
|
||||
>
|
||||
<Pressable
|
||||
style={({ pressed }) => [
|
||||
styles.countryButton,
|
||||
pressed ? styles.countryButtonPressed : null,
|
||||
]}
|
||||
onPress={() => setIsCountryModalVisible(true)}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.countryButtonText,
|
||||
!selectedCountry && styles.countryButtonPlaceholder,
|
||||
]}
|
||||
>
|
||||
{selectedCountry
|
||||
? selectedCountry.name
|
||||
: "Sélectionne ton pays"}
|
||||
</Text>
|
||||
</Pressable>
|
||||
</BlurView>
|
||||
</View>
|
||||
<Input
|
||||
placeholder="Ville"
|
||||
label="Ville"
|
||||
isBlur
|
||||
value={city}
|
||||
setValue={setCity}
|
||||
/>
|
||||
</View>
|
||||
<Input
|
||||
placeholder="Prénom"
|
||||
label="Prénom"
|
||||
isBlur
|
||||
value={firstName}
|
||||
setValue={setFirstName}
|
||||
/>
|
||||
<Input
|
||||
placeholder="Adresse mail"
|
||||
label="Adresse mail"
|
||||
@@ -182,9 +101,6 @@ const Register = () => {
|
||||
navigate(Routes.CreatePassword, {
|
||||
email: email.trim(),
|
||||
firstName: firstName.trim(),
|
||||
lastName: lastName.trim(),
|
||||
city: city.trim(),
|
||||
country: selectedCountry,
|
||||
preferredLanguage,
|
||||
})
|
||||
}
|
||||
@@ -239,63 +155,22 @@ const Register = () => {
|
||||
keyboardShouldPersistTaps="handled"
|
||||
>
|
||||
{renderFormContent()}
|
||||
<View style={styles.bottomLoginPrompt}>
|
||||
<Text style={styles.bottomFooterText}>
|
||||
Tu as déjà un compte ?{" "}
|
||||
<Text
|
||||
style={styles.bottomFooterLink}
|
||||
onPress={() => navigate(Routes.Login)}
|
||||
>
|
||||
Se connecter
|
||||
</Text>
|
||||
</Text>
|
||||
</View>
|
||||
</KeyboardAwareScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
)}
|
||||
</ItemContainer>
|
||||
<View style={styles.bottomLoginPrompt}>
|
||||
<Text style={styles.bottomFooterText}>
|
||||
Tu as déjà un compte ?{" "}
|
||||
<Text
|
||||
style={styles.bottomFooterLink}
|
||||
onPress={() => navigate(Routes.Login)}
|
||||
>
|
||||
Se connecter
|
||||
</Text>
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<Modal
|
||||
transparent
|
||||
visible={isCountryModalVisible}
|
||||
animationType="fade"
|
||||
onRequestClose={handleCloseModal}
|
||||
>
|
||||
<View style={styles.modalOverlay}>
|
||||
<View style={styles.modalContainer}>
|
||||
<View style={styles.modalHeader}>
|
||||
<Text style={styles.modalTitle}>Sélectionne ton pays</Text>
|
||||
<Pressable onPress={handleCloseModal}>
|
||||
<Text style={styles.closeText}>Fermer</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
<TextInput
|
||||
value={countrySearch}
|
||||
onChangeText={setCountrySearch}
|
||||
placeholder="Rechercher un pays"
|
||||
placeholderTextColor={Palette.gray}
|
||||
style={styles.searchInput}
|
||||
/>
|
||||
<FlatList
|
||||
data={filteredCountries}
|
||||
keyExtractor={(item) => item.code}
|
||||
renderItem={({ item }) => (
|
||||
<Pressable
|
||||
style={styles.countryItem}
|
||||
onPress={() => handleSelectCountry(item)}
|
||||
>
|
||||
<Text style={styles.countryItemText}>{item.name}</Text>
|
||||
</Pressable>
|
||||
)}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
showsVerticalScrollIndicator={false}
|
||||
ListEmptyComponent={
|
||||
<Text style={styles.emptyListText}>Aucun pays trouvé</Text>
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
@@ -413,36 +288,6 @@ const styles = StyleSheet.create({
|
||||
color: Palette.white,
|
||||
fontFamily: FONT_FAMILY.InterSemiBold,
|
||||
},
|
||||
countryBlurWrapper: {
|
||||
borderRadius: 12,
|
||||
backgroundColor: Palette.glass,
|
||||
overflow: "hidden",
|
||||
},
|
||||
countryButton: {
|
||||
minHeight: 50,
|
||||
justifyContent: "center",
|
||||
paddingHorizontal: 16,
|
||||
width: "100%",
|
||||
},
|
||||
countryButtonPressed: {
|
||||
opacity: 0.85,
|
||||
},
|
||||
countryButtonText: {
|
||||
fontSize: 14,
|
||||
color: Palette.white,
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
},
|
||||
countryButtonPlaceholder: {
|
||||
color: Palette.gray,
|
||||
},
|
||||
countryField: {
|
||||
gap: 4,
|
||||
},
|
||||
inputLabel: {
|
||||
fontSize: 14,
|
||||
color: Palette.white,
|
||||
fontFamily: FONT_FAMILY.HelveticaNeueRegular,
|
||||
},
|
||||
footer: {
|
||||
alignItems: "center",
|
||||
gap: 4,
|
||||
@@ -542,61 +387,4 @@ const styles = StyleSheet.create({
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
modalOverlay: {
|
||||
flex: 1,
|
||||
backgroundColor: Palette.transparentBlack,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: 16,
|
||||
},
|
||||
modalContainer: {
|
||||
width: "100%",
|
||||
maxWidth: 420,
|
||||
maxHeight: "80%",
|
||||
backgroundColor: Palette.ultraLightBlack,
|
||||
borderRadius: 20,
|
||||
padding: 16,
|
||||
gap: 12,
|
||||
},
|
||||
modalHeader: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
},
|
||||
modalTitle: {
|
||||
fontSize: 18,
|
||||
color: Palette.white,
|
||||
fontFamily: FONT_FAMILY.InterSemiBold,
|
||||
},
|
||||
closeText: {
|
||||
fontSize: 14,
|
||||
color: Palette.white,
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
},
|
||||
searchInput: {
|
||||
borderRadius: 12,
|
||||
borderWidth: 1,
|
||||
borderColor: Palette.ultraLightWhite,
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 10,
|
||||
color: Palette.white,
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
},
|
||||
countryItem: {
|
||||
paddingVertical: 12,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: Palette.ultraLightWhite,
|
||||
},
|
||||
countryItemText: {
|
||||
fontSize: 14,
|
||||
color: Palette.white,
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
},
|
||||
emptyListText: {
|
||||
fontSize: 14,
|
||||
color: Palette.gray,
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
textAlign: "center",
|
||||
paddingVertical: 20,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -9,9 +9,11 @@ import ListSelection from "../../components/ListSelection/ListSelection";
|
||||
const SECTION_INSTRUCTIONS = {
|
||||
BASE: "Choisis ta base",
|
||||
SENSIBILITE: "Choisis ta sensibilité",
|
||||
TECHNIQUE: "Choisis ta technique (Facultatif)",
|
||||
TECHNIQUE: "Choisis ta technique",
|
||||
};
|
||||
|
||||
const OPTIONAL_SECTION_CATEGORIES = new Set(["SENSIBILITE", "TECHNIQUE"]);
|
||||
|
||||
const normalizeCategory = (value) => {
|
||||
if (typeof value !== "string") return "";
|
||||
return value
|
||||
@@ -76,6 +78,10 @@ const CustomizeVoice = ({
|
||||
const subtitle =
|
||||
SECTION_INSTRUCTIONS[normalizedCategory] ||
|
||||
"Choisis la voix pour ta chanson";
|
||||
const isOptionalSection = OPTIONAL_SECTION_CATEGORIES.has(normalizedCategory);
|
||||
const title = `Personnalise la voix que tu veux pour ta chanson${
|
||||
isOptionalSection ? " (Facultatif)" : ""
|
||||
}`;
|
||||
|
||||
const voiceObject = useMemo(
|
||||
() => selectionToObject(selected),
|
||||
@@ -105,7 +111,7 @@ const CustomizeVoice = ({
|
||||
return (
|
||||
<View style={{ flex: 1, gap: 10, paddingTop: 16 }}>
|
||||
<CreateLyricsHeader
|
||||
title="Personnalise la voix que tu veux pour ta chanson"
|
||||
title={title}
|
||||
subTitle={subtitle}
|
||||
/>
|
||||
<View
|
||||
|
||||
@@ -396,7 +396,6 @@ const Lyrics = ({ navigation }) => {
|
||||
<MusicLandHeader
|
||||
onPressBack={() => navigate(Routes.Home)}
|
||||
progress={95}
|
||||
logo={icons.musicLandWriting}
|
||||
/>
|
||||
<View
|
||||
style={{
|
||||
|
||||
@@ -11,6 +11,7 @@ const SongStructure = ({
|
||||
selected: selectedProp,
|
||||
setSelected: setSelectedProp,
|
||||
}) => {
|
||||
const [containerLayout, setContainerLayout] = useState(null);
|
||||
const [internalSelected, setInternalSelected] = useState(null);
|
||||
const selected = selectedProp ?? internalSelected;
|
||||
const setSelected = setSelectedProp ?? setInternalSelected;
|
||||
@@ -23,17 +24,22 @@ const SongStructure = ({
|
||||
title="Comment veux-tu structurer ta chanson ?"
|
||||
subTitle="Sélectionne une structure."
|
||||
/>
|
||||
<ItemContainer>
|
||||
<ListSelection
|
||||
options={SONG_STRUCTURE}
|
||||
variant="simple"
|
||||
selected={selected}
|
||||
setSelected={setSelected}
|
||||
contentContainerStyle={styles.contentContainer}
|
||||
itemContainerStyle={styles.itemContainer}
|
||||
itemTextStyle={styles.itemText}
|
||||
/>
|
||||
</ItemContainer>
|
||||
<View
|
||||
style={{ flex: 1 }}
|
||||
onLayout={(event) => setContainerLayout(event.nativeEvent.layout)}
|
||||
>
|
||||
<ItemContainer height={containerLayout?.height}>
|
||||
<ListSelection
|
||||
options={SONG_STRUCTURE}
|
||||
variant="simple"
|
||||
selected={selected}
|
||||
setSelected={setSelected}
|
||||
contentContainerStyle={styles.contentContainer}
|
||||
itemContainerStyle={styles.itemContainer}
|
||||
itemTextStyle={styles.itemText}
|
||||
/>
|
||||
</ItemContainer>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -25,6 +25,7 @@ const SongStyle = ({
|
||||
const [previousOtherStyle, setPreviousOtherStyle] = useState(
|
||||
normalizedOtherStyle
|
||||
);
|
||||
const [containerLayout, setContainerLayout] = useState(null);
|
||||
|
||||
const selected = selectedProp ?? internalSelected;
|
||||
const setSelectedBase = setSelectedProp ?? setInternalSelected;
|
||||
@@ -97,21 +98,26 @@ const SongStyle = ({
|
||||
return (
|
||||
<>
|
||||
<View style={{ flex: 1, gap: 16, marginTop: 16 }}>
|
||||
<View style={{ gap: 10 }}>
|
||||
<View style={{ flex: 1, gap: 10 }}>
|
||||
<CreateLyricsHeader
|
||||
title={`Quel est le style de ta chanson ?`}
|
||||
subTitle="Choisis l'ambiance émotions que tu veux faire passer."
|
||||
/>
|
||||
<ItemContainer>
|
||||
<ListSelection
|
||||
options={songStyleOptions}
|
||||
variant="titleDescription"
|
||||
selected={selected}
|
||||
setSelected={handleStyleSelect}
|
||||
contentContainerStyle={styles.contentContainer}
|
||||
itemContainerStyle={styles.itemContainer}
|
||||
/>
|
||||
</ItemContainer>
|
||||
<View
|
||||
style={{ flex: 1 }}
|
||||
onLayout={(event) => setContainerLayout(event.nativeEvent.layout)}
|
||||
>
|
||||
<ItemContainer height={containerLayout?.height}>
|
||||
<ListSelection
|
||||
options={songStyleOptions}
|
||||
variant="titleDescription"
|
||||
selected={selected}
|
||||
setSelected={handleStyleSelect}
|
||||
contentContainerStyle={styles.contentContainer}
|
||||
itemContainerStyle={styles.itemContainer}
|
||||
/>
|
||||
</ItemContainer>
|
||||
</View>
|
||||
</View>
|
||||
{selected === OTHER_STYLE_OPTION ? (
|
||||
<View style={styles.otherStyleSummary}>
|
||||
|
||||
@@ -25,7 +25,8 @@ const WritingLyrics = () => {
|
||||
const [videoUrl, setVideoUrl] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
setVideoUrl(isWeb ? videos.celineWeb : videos?.celine);
|
||||
// setVideoUrl(isWeb ? videos.celineWeb : videos?.celine);
|
||||
setVideoUrl(videos?.test);
|
||||
}, []);
|
||||
|
||||
const startWriting = React.useCallback(async () => {
|
||||
|
||||
Reference in New Issue
Block a user