start adding payment

This commit is contained in:
Thomas Demirdjian
2025-11-04 14:46:12 +01:00
parent 02a2333a41
commit 4bb083ea46
24 changed files with 3507 additions and 7604 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

+1 -32
View File
@@ -54,10 +54,6 @@ import cloud from "./icons/cloud.png";
import dashboard from "./icons/dashboard.png";
import file from "./icons/file.png";
import circle from "./art/circle.png";
import gradientTriangle from "./art/gradientTriangle.png";
import square from "./art/square.png";
import triangle from "./art/triangle.png";
import algolia from "./icons/algolia.png";
import calendar from "./icons/calendar.png";
import chatBubble from "./icons/chatBubble.png";
@@ -82,16 +78,6 @@ import play from "./icons/play.png";
import share from "./icons/share.png";
import stars from "./icons/stars.png";
import boost from "./art/boost.png";
import boostBig from "./art/boostBig.png";
import threeCoins from "./art/threeCoins.png";
import tutorialChat from "./tutorial/chat.png";
import coins from "./tutorial/coins.png";
import followProgress from "./tutorial/followProgress.png";
import tutorialTasks from "./tutorial/tasks.png";
import welcome from "./tutorial/welcome.png";
import hitParadeBG from "./UI/hitParadeBG.png";
import homeBG from "./UI/homeBG.png";
import libraryBG from "./UI/libraryBG.png";
@@ -202,24 +188,6 @@ export const icons = {
coin,
};
export const art = {
triangle,
circle,
square,
gradientTriangle,
threeCoins,
boost,
boostBig,
};
export const tutorial = {
welcome,
chat: tutorialChat,
coins,
followProgress,
tasks: tutorialTasks,
};
export const background = {
writingBG,
writingBgWeb: require("./UI/writingBgWeb.png"),
@@ -240,6 +208,7 @@ export const background = {
homeBGWeb: require("./UI/homeBGWeb.png"),
loginBgWeb: require("./UI/loginBgWeb.png"),
profileWebBG: require("./UI/profileWebBG.jpg"),
bgTrans: require("./UI/bgTrans.png"),
};
export const ai = {
@@ -56,6 +56,7 @@ const FeatureCarousel = ({
activeIndex,
onActiveIndexChange,
backgroundImage,
isFocused,
}) => {
const stageStates = useMemo(() => {
if (stageStatesProp) {
@@ -223,6 +224,23 @@ const FeatureCarousel = ({
scrollToIndex(activeIndexRef.current, false);
}, [carouselItems.length, isWeb, itemHeight, scrollToIndex]);
useEffect(() => {
if (!isWeb || !isFocused) {
return;
}
if (!itemHeight) {
return;
}
clearPendingAlignment();
scrollToIndex(activeIndexRef.current, false);
}, [
clearPendingAlignment,
isFocused,
isWeb,
itemHeight,
scrollToIndex,
]);
const alignToOffset = useCallback(
(offset, layoutHeight, options = {}) => {
const { forceSnap = false } = options || {};
+38
View File
@@ -1,4 +1,5 @@
import AsyncStorage from "@react-native-async-storage/async-storage";
import { Platform } from "react-native";
import {
getReactNativePersistence,
initializeAuth,
@@ -9,6 +10,26 @@ import "firebase/compat/firestore";
import "firebase/compat/functions";
import "firebase/compat/storage";
const functionsInstances = {};
const emulatorConfigured = {};
const emulatorHost = Platform.OS === "web" ? "localhost" : "192.168.1.146";
const configureFunctionsEmulator = (instance, regionKey = "us-central1") => {
if (!__DEV__ || !instance?.useEmulator || emulatorConfigured[regionKey]) {
return;
}
try {
instance.useEmulator(emulatorHost, 5001);
emulatorConfigured[regionKey] = true;
} catch (error) {
console.warn(
`[firebase] Unable to set functions emulator for region ${regionKey}`,
error?.message,
);
}
};
export const firebaseConfig = {
apiKey: "AIzaSyCuHJHdwVN_F-VmUG4Hd7bGiMRqj6rPlLo",
authDomain: "musicland-d33f9.firebaseapp.com",
@@ -29,6 +50,9 @@ if (!firebase?.apps?.filter(({ name_ }) => name_ === "[DEFAULT]").length) {
// if (__DEV__) {
// firebase.functions().useEmulator("localhost", 5001);
// }
const defaultFunctions = firebase.functions();
functionsInstances["us-central1"] = defaultFunctions;
configureFunctionsEmulator(defaultFunctions, "us-central1");
console.log("Firebase init");
}
@@ -57,4 +81,18 @@ export const reportsRef = firestore.collection("reports");
export const { arrayUnion, arrayRemove, increment, serverTimestamp } =
firebase.firestore.FieldValue;
export const getFunctionsClient = (region = "us-central1") => {
const regionKey = region || "us-central1";
if (!functionsInstances[regionKey]) {
functionsInstances[regionKey] = firebase.app().functions(regionKey);
}
const instance = functionsInstances[regionKey];
configureFunctionsEmulator(instance, regionKey);
return instance;
};
export default firebase;
+3 -2
View File
@@ -1,5 +1,6 @@
export const STRIPE_PUBLISHABLE_KEY_TEST = "..";
export const STRIPE_PUBLISHABLE_KEY_LIVE = "...";
export const STRIPE_PUBLISHABLE_KEY_TEST =
"pk_test_51SPfcjCzf2o5bDRdxkCvPUnZnc5XS80HEIo1mlOmqsC2i8pkFBwD78dVLM5AOlFDMnddKH6EFA2kiPzIuqrTi7YQ001YJz4T7r";
export const STRIPE_PUBLISHABLE_KEY_LIVE = "";
export const GOOGLE_API_KEY = "...";
// OAuth Client IDs for Google Sign-In
+6
View File
@@ -22,6 +22,7 @@ import RecordedPlayback from "../screens/Playback/RecordedPlayback";
import VideoFinalize from "../screens/Playback/VideoFinalize";
import PublishYoutube from "../screens/Publishing/PublishYoutube";
import PrivacyPolicy from "../screens/PrivacyPolicy";
import Payments from "../screens/Payments";
import DownloadPrices from "../screens/Production/DownloadPrices";
import DownloadSongs from "../screens/Production/DownloadSongs";
import PlaybackExample from "../screens/Production/PlaybackExample";
@@ -109,6 +110,11 @@ const baseScreens = [
component: PrivacyPolicy,
title: "Politique de confidentialité",
},
{
name: Routes.Payments,
component: Payments,
title: "Abonnements",
},
{
name: Routes.Writing,
component: Writing,
+1
View File
@@ -23,6 +23,7 @@ export const Routes = {
TermsOfUse: "TermsOfUse",
PrivacyPolicy: "PrivacyPolicy",
Payments: "Payments",
Writing: "Writing",
WritingLyrics: "WritingLyrics",
+40 -1
View File
@@ -9,7 +9,7 @@ import React, {
useRef,
useState,
} from "react";
import { Platform } from "react-native";
import { AppState, Platform } from "react-native";
import { useGlobal } from "reactn";
import firebase, {
arrayUnion,
@@ -66,6 +66,18 @@ export default function NotificationProvider({ children }) {
}
}, [notifications]);
const clearBadgeCount = useCallback(async () => {
if (Platform.OS === "web") {
return;
}
try {
await Notifications.setBadgeCountAsync(0);
} catch (error) {
console.log("clearBadgeCount error:", error);
}
}, []);
const markNotificationAsRead = useCallback(async (notificationId) => {
try {
if (!notificationId) return;
@@ -117,6 +129,33 @@ export default function NotificationProvider({ children }) {
registerForPushNotificationsAsync();
}, [user, uid, notifInit]);
useEffect(() => {
if (Platform.OS === "web") {
return;
}
clearBadgeCount();
const handleAppStateChange = (state) => {
if (state === "active") {
clearBadgeCount();
}
};
const subscription = AppState.addEventListener(
"change",
handleAppStateChange
);
return () => {
if (subscription?.remove) {
subscription.remove();
} else {
AppState.removeEventListener("change", handleAppStateChange);
}
};
}, [clearBadgeCount]);
useEffect(() => {
if (!allowNotifications) {
return;
+20
View File
@@ -84,6 +84,7 @@ const DEFAULT_CONTEXT = {
isLooping: false,
setLooping: noop,
toggleLooping: noop,
getTrackInfo: () => null,
};
export const PlayerContext = createContext(DEFAULT_CONTEXT);
@@ -230,6 +231,7 @@ const PlayerProvider = ({ children }) => {
const autoPlayRef = useRef(false);
const pendingSeekValueRef = useRef(null);
const didJustFinishRef = useRef(false);
const trackInfoRef = useRef({});
const setQueueIndexValue = useCallback((index = -1) => {
const list = queueRef.current;
@@ -401,6 +403,18 @@ const PlayerProvider = ({ children }) => {
durationMs,
};
});
if (currentTrack?.id) {
trackInfoRef.current[currentTrack.id] = {
durationMs:
durationMs > 0
? durationMs
: trackInfoRef.current[currentTrack.id]?.durationMs || 0,
positionMs,
isLoaded,
updatedAt: Date.now(),
};
}
}, [status, currentTrack]);
useEffect(() => {
@@ -614,6 +628,10 @@ const PlayerProvider = ({ children }) => {
const toggleLooping = useCallback(() => {
setIsLooping((prev) => !prev);
}, []);
const getTrackInfo = useCallback((trackId) => {
if (!trackId) return null;
return trackInfoRef.current[trackId] || null;
}, []);
useEffect(() => {
if (!currentTrack?.id) {
@@ -692,6 +710,7 @@ const PlayerProvider = ({ children }) => {
isLooping,
setLooping,
toggleLooping,
getTrackInfo,
}),
[
currentTrack,
@@ -715,6 +734,7 @@ const PlayerProvider = ({ children }) => {
setLooping,
toggleLooping,
setQueue,
getTrackInfo,
]
);
@@ -1,37 +0,0 @@
import { View, Text, Image } from "react-native";
import React from "react";
import { Palette, Style } from "../../../styles";
import { icons } from "../../../assets";
import { size } from "../../../styles/Style";
import { FONT_FAMILY } from "../../../styles/Fonts";
const CoinBadge = () => {
return (
<View style={{ ...Style.containerRow }}>
<View
style={{
paddingLeft: 5,
paddingRight: 10,
paddingVertical: 3,
borderTopLeftRadius: 8,
borderBottomLeftRadius: 8,
backgroundColor: Palette.glass,
right: -10,
}}
>
<Text
style={{
fontSize: 12,
color: Palette.white,
fontFamily: FONT_FAMILY.InterRegular,
}}
>
100
</Text>
</View>
<Image source={icons.coin} style={size({ size: 26 })} />
</View>
);
};
export default CoinBadge;
@@ -4,7 +4,6 @@ import { BlurView } from "expo-blur";
import { img } from "../../../assets";
import { Palette, Style } from "../../../styles";
import { FONT_FAMILY } from "../../../styles/Fonts";
import CoinBadge from "./CoinBadge";
const PlaybacksCard = ({
rank = 1,
@@ -72,7 +71,6 @@ const PlaybacksCard = ({
</Text>
</View>
</View>
<CoinBadge />
</BlurView>
</Pressable>
);
@@ -12,7 +12,6 @@ import { BlurView } from "expo-blur";
import Style, { size } from "../../../styles/Style";
import { Palette } from "../../../styles";
import { FONT_FAMILY } from "../../../styles/Fonts";
import CoinBadge from "./CoinBadge";
const SongCard = ({
rank = 1,
@@ -91,7 +90,6 @@ const SongCard = ({
</Text>
</View>
</View>
<CoinBadge />
</BlurView>
</Pressable>
);
File diff suppressed because it is too large Load Diff
+14
View File
@@ -17,6 +17,7 @@ import { responsiveHeight } from "react-native-responsive-dimensions";
import { useGlobal } from "reactn";
import { background, icons } from "../../assets";
import BorderGradient from "../../components/BorderGradient/BorderGradient";
import GradientButton from "../../components/GradientButton";
import MoreMenu from "../../components/MoreMenu";
import PressableScale from "../../components/PressableScale";
import ProfilePicture from "../../components/ProfilePicture";
@@ -473,6 +474,19 @@ const Profile = () => {
<Text style={styles.label}>abonnements</Text>
</Pressable>
</View>
{isSelf ? (
<GradientButton
title="Découvrir les abonnements"
size="large"
onPress={() => push(Routes.Payments)}
containerStyle={{
marginTop: 18,
width: "80%",
alignSelf: "center",
}}
gradientStyle={{ width: "100%" }}
/>
) : null}
{!isSelf && (
<Pressable onPress={handleFollowUser}>
<View
+3 -3
View File
@@ -181,7 +181,7 @@ const GeneratingSong = () => {
"Le service Suno est indisponible. Veuillez réessayer plus tard.";
AppAlert(
"Impossible de générer la musique",
sanitized || fallbackMessage
sanitized || fallbackMessage,
);
if (selectedProjectId) {
try {
@@ -196,12 +196,12 @@ const GeneratingSong = () => {
message: sanitized || fallbackMessage,
},
},
{ merge: true }
{ merge: true },
);
} catch (firestoreError) {
console.log(
"GeneratingSong Firestore update error",
firestoreError?.message
firestoreError?.message,
);
}
}