Commit e40c06c7 by G

cleanup: removed more unused components from the codebase. Remove unused context from App.tsx

parent 9585491f
import ProvideQueryClient from "@components/providers_wrappers/ProvideQueryClient";
import { LOG } from "@logger"; import { LOG } from "@logger";
import { DefaultTheme, NavigationContainer } from "@react-navigation/native"; import { DefaultTheme, NavigationContainer } from "@react-navigation/native";
import { ThemeProvider } from "@shopify/restyle"; import { ThemeProvider } from "@shopify/restyle";
import { injectStoreIntoAxiosInstance } from "@/axios"; import { injectStoreIntoAxiosInstance } from "@/axios";
import { ModalsManagerProvider } from "@/contexts/ModalsManagerContext"; import ProvideQueryClient from "@/contexts/ProvideQueryClient";
import { UserAuthenticationContextProvider } from "@/contexts/UserAuthenticationContext";
import { AppMainStackNavigatorAuthWrapper } from "@/navigations/AppMainStackNavigator"; import { AppMainStackNavigatorAuthWrapper } from "@/navigations/AppMainStackNavigator";
import theme from "@/themes/Theme"; import theme from "@/themes/Theme";
import "react-native-gesture-handler"; import "react-native-gesture-handler";
...@@ -24,30 +22,22 @@ export default function App() { ...@@ -24,30 +22,22 @@ export default function App() {
<Provider store={store}> <Provider store={store}>
<GestureHandlerRootView style={{ flex: 1 }}> <GestureHandlerRootView style={{ flex: 1 }}>
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<ModalsManagerProvider> <SafeAreaProvider>
<SafeAreaProvider> <ProvideQueryClient>
<ProvideQueryClient> <NavigationContainer
<UserAuthenticationContextProvider> theme={{
<NavigationContainer ...DefaultTheme,
theme={{ colors: {
...DefaultTheme, ...DefaultTheme.colors,
colors: { background: "white",
...DefaultTheme.colors, },
background: "white", }}
}, >
}} <AppMainStackNavigatorAuthWrapper />
> <StatusBar translucent backgroundColor="transparent" style="dark" />
<AppMainStackNavigatorAuthWrapper /> </NavigationContainer>
<StatusBar </ProvideQueryClient>
translucent </SafeAreaProvider>
backgroundColor="transparent"
style="dark"
/>
</NavigationContainer>
</UserAuthenticationContextProvider>
</ProvideQueryClient>
</SafeAreaProvider>
</ModalsManagerProvider>
</ThemeProvider> </ThemeProvider>
</GestureHandlerRootView> </GestureHandlerRootView>
</Provider> </Provider>
......
import Box from "@components/bases/Box";
import Text from "@components/bases/Text";
import Card from "@re-card";
import { images } from "@styles/Commons";
import * as LocalAuthentication from "expo-local-authentication";
import { useState } from "react";
import { Image, TouchableOpacity } from "react-native";
type Props = { balance: number; label: string };
const BalanceContainer = ({ label, balance }: Props) => {
const [showBalance, setShowBalance] = useState(false);
const handleLocalAuthentication = async () => {
console.log("handleLocalAuthentication :: start");
if (showBalance) {
return setShowBalance(false);
}
const result = await LocalAuthentication.authenticateAsync();
if (result.success) {
setShowBalance(true);
}
console.log("handleLocalAuthentication :: end", result);
};
return (
<Card position={"relative"} variant={"balanceContainer"}>
<Card variant={"balanceContainer"} position={"absolute"}>
<Image
source={require("../../assets/balance_container.png")}
style={images.cover}
/>
</Card>
<Box alignItems={"center"} gap={"s"}>
<TouchableOpacity onPress={handleLocalAuthentication}>
<Box height={50} alignItems={"center"} justifyContent={"center"}>
{showBalance ? (
<Text fontSize={30} variant={"black"}>
{balance}
</Text>
) : (
<Box width={40} height={40}>
<Image
source={require("../../assets/eye_hidden.png")}
style={images.contain}
/>
</Box>
)}
</Box>
</TouchableOpacity>
<Text color={"gray"}>{label}</Text>
</Box>
</Card>
);
};
export default BalanceContainer;
import {} from "react-native";
import BeasyLogoIcon from "./BeasyLogoIcon";
import NotificationIconButton from "./NotificationIconButton";
import Box from "./bases/Box";
const BarWithBeasyAndNotificationsIcon = () => {
return (
<Box px={"l"} flexDirection={"row"} justifyContent={"space-between"} alignItems={"center"}>
<BeasyLogoIcon />
<NotificationIconButton />
</Box>
);
};
export default BarWithBeasyAndNotificationsIcon;
import { asp as g } from "@asp/asp";
import Box from "@components/bases/Box"; import Box from "@components/bases/Box";
import { images } from "@styles/Commons";
import { Image } from "react-native"; import { Image } from "react-native";
const BeasyLogoIcon = () => { const BeasyLogoIcon = () => {
return ( return (
<Box width={100} height={30}> <Box width={100} height={30}>
<Image source={require("../../assets/logo_beasy.png")} style={images.contain} /> <Image
source={require("../../assets/logo_beasy.png")}
style={[g.flex_1]}
resizeMode="contain"
/>
</Box> </Box>
); );
}; };
......
import type { BoxProps, VariantProps } from "@shopify/restyle";
import type { Theme } from "@themes/Theme";
import { ActivityIndicator, TouchableOpacity } from "react-native";
import ButtonBase from "./bases/ButtonBase";
import Text from "./bases/Text";
type Props = BoxProps<Theme> &
VariantProps<Theme, "buttonVariants"> &
VariantProps<Theme, "textVariants", "textVariants"> & {
label: string;
onPress: () => void;
isLoading?: boolean;
};
const Button = ({ onPress, label, isLoading, textVariants, variant, ...rest }: Props) => {
return (
<TouchableOpacity onPress={onPress}>
<ButtonBase
variant={variant}
justifyContent="center"
alignItems="center"
flexDirection={"row"}
{...rest}
gap={"m"}
>
{isLoading ? (
<ActivityIndicator color="white" />
) : (
<Text variant={textVariants}>{label}</Text>
)}
</ButtonBase>
</TouchableOpacity>
);
};
export default Button;
import { containers } from "@styles/Commons";
import { useEffect } from "react";
import { Animated, Dimensions } from "react-native";
import Box from "./bases/Box";
type Props = { children: React.ReactNode };
const ContainerBorderTopCurved = ({ children }: Props) => {
const animated = new Animated.Value(0);
// biome-ignore lint/correctness/useExhaustiveDependencies: <we do not want animation to replay everytime this component rerender>
useEffect(() => {
Animated.spring(animated, {
toValue: 1,
useNativeDriver: true,
}).start();
}, []);
return (
<Animated.View
style={[
containers.containerFull,
{
transform: [
{
translateY: animated.interpolate({
inputRange: [0, 1],
outputRange: [Dimensions.get("window").height, 0],
}),
},
],
},
]}
>
<Box
backgroundColor={"white"}
borderTopLeftRadius={30}
borderTopRightRadius={30}
style={containers.containerFull}
>
{children}
</Box>
</Animated.View>
);
};
export default ContainerBorderTopCurved;
import Box from "@components/bases/Box";
import { Ionicons } from "@expo/vector-icons";
import { TouchableOpacity } from "react-native";
type Props = {
onPress: () => void;
};
const GoBackIconButton = ({ onPress }: Props) => {
return (
<TouchableOpacity onPress={onPress}>
<Box width={40} height={40} alignItems={"center"} justifyContent={"center"}>
<Ionicons name="arrow-back" size={24} color="black" />
</Box>
</TouchableOpacity>
);
};
export default GoBackIconButton;
import type { VariantProps } from "@shopify/restyle";
import type { Theme } from "@themes/Theme";
import type { TextInputProps } from "react-native";
import { TextInput } from "react-native";
import Box from "./bases/Box";
type Props = TextInputProps & VariantProps<Theme, "textVariants", "textVariants">;
const Input = ({ textVariants, ...rest }: Props) => {
return (
<Box>
<Box backgroundColor={"lightGray"} height={50} borderRadius={10} my={"m"} p={"s"}>
<TextInput style={{ height: "100%", width: "100%" }} {...rest} />
</Box>
</Box>
);
};
export default Input;
import type { VariantProps } from "@shopify/restyle";
import type { Theme } from "@themes/Theme";
import type { TextInputProps } from "react-native";
import { TextInput } from "react-native";
import Box from "./bases/Box";
import Text from "./bases/Text";
type Props = TextInputProps &
VariantProps<Theme, "textVariants", "textVariants"> & {
label: string;
};
const InputWithTopLabel = ({ label, textVariants, ...rest }: Props) => {
return (
<Box>
<Text variant={textVariants}>{label}</Text>
<Box backgroundColor={"lightGray"} height={50} borderRadius={10} my={"m"} p={"s"}>
<TextInput style={{ height: "100%", width: "100%" }} {...rest} />
</Box>
</Box>
);
};
export default InputWithTopLabel;
import Box from "@components/bases/Box";
import { Ionicons } from "@expo/vector-icons";
const NotificationIconButton = () => {
return (
<Box width={40} height={40} position={"relative"}>
<Box
backgroundColor={"white"}
opacity={0.5}
p={"s"}
borderRadius={50}
style={{ height: "100%", width: "100%", position: "absolute" }}
/>
<Box
style={{
height: "100%",
width: "100%",
borderRadius: 50,
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<Ionicons
name="notifications-outline"
size={24}
color="white"
style={{ opacity: 1 }}
/>
</Box>
</Box>
);
};
export default NotificationIconButton;
import type { PaymentCode } from "@/utils/requests/types";
import { images } from "@styles/Commons";
import { Image, TouchableOpacity } from "react-native";
import Box from "./bases/Box";
type PaymentOptions = "OM" | "MTN" | "FLOOZ" | "WAVE" | "CB";
type Props = {
onPress: () => void;
paymentMethod: PaymentCode;
};
const PaymentOptionContainer = ({ children }: { children: React.ReactNode }) => {
return (
<Box
height={50}
// p={"s"}
style={{ width: "100%", height: "100%" }}
overflow={"hidden"}
backgroundColor={"yellow"}
borderRadius={30}
>
{children}
</Box>
);
};
const Orange = () => {
return (
<Image source={require("../../assets/operators/orange_money.png")} style={images.cover} />
);
};
const Mtn = () => {
return <Image source={require("../../assets/operators/mtn_money.png")} style={images.cover} />;
};
const Flooz = () => {
return <Image source={require("../../assets/operators/moov_money.png")} style={images.cover} />;
};
const Wave = () => {
return <Image source={require("../../assets/operators/wave_money.png")} style={images.cover} />;
};
const Cb = () => {
return <Image source={require("../../assets/operators/visa_card.png")} style={images.cover} />;
};
const PaymentOption = ({ onPress, paymentMethod }: Props) => {
return (
<TouchableOpacity style={{ width: "100%", height: "100%" }} onPress={onPress}>
{paymentMethod === "OM" && <Orange />}
{paymentMethod === "MTN" && <Mtn />}
{paymentMethod === "FLOOZ" && <Flooz />}
{paymentMethod === "WAVE" && <Wave />}
{paymentMethod === "CB" && <Cb />}
</TouchableOpacity>
);
};
export default PaymentOption;
import type React from "react";
import { Dimensions } from "react-native";
import Box from "./bases/Box";
// const PaymentsOptionsRendererTwoColumn = () => {
// return (
// <View>
// <Box flexDirection={"row"} justifyContent={"space-between"} mb={"s"}>
// <PaymentOptionContainer>
// <PaymentOption
// onPress={() => navigation.navigate("paymentAmountInputScreen")}
// paymentMethod={"OrangeMoney"}
// />
// </PaymentOptionContainer>
// <PaymentOptionContainer>
// <PaymentOption
// onPress={() => navigation.navigate("paymentAmountInputScreen")}
// paymentMethod={"MtnMoney"}
// />
// </PaymentOptionContainer>
// </Box>
// <Box flexDirection={"row"} justifyContent={"space-between"} mb={"s"}>
// <PaymentOptionContainer>
// <PaymentOption
// onPress={() => navigation.navigate("paymentAmountInputScreen")}
// paymentMethod={"MoovMoney"}
// />
// </PaymentOptionContainer>
// <PaymentOptionContainer>
// <PaymentOption
// onPress={() => navigation.navigate("paymentAmountInputScreen")}
// paymentMethod={"WaveMoney"}
// />
// </PaymentOptionContainer>
// </Box>
// <PaymentOptionContainer>
// <PaymentOption
// onPress={() => navigation.navigate("paymentAmountInputScreen")}
// paymentMethod={"VisaCard"}
// />
// </PaymentOptionContainer>
// </View>
// );
// };
const screenWidth = Dimensions.get("window").width;
const paymentOptionCardWidth = screenWidth / 2 - 30;
const paymentOptionCardHeight = paymentOptionCardWidth * 0.65;
const PaymentOptionContainer = ({ children }: { children: React.ReactNode }) => {
return (
<Box width={paymentOptionCardWidth} height={paymentOptionCardHeight}>
{children}
</Box>
);
};
// export default PaymentsOptionsRendererTwoColumn;
import type { PaymentCode } from "@/utils/requests/types";
import moment from "moment";
import "moment/locale/fr";
import PaymentOption from "./PaymentOption";
import Box from "./bases/Box";
import Text from "./bases/Text";
interface Props {
paymentType: PaymentCode;
reference: string;
date: string;
amount: number;
status: "SUCCESS" | "INITIATED" | "FAILED";
}
moment.locale("fr");
const TransactionInformationsItem = ({ paymentType, reference, date, amount, status }: Props) => {
const dateObject = Date.parse(date);
return (
<Box
width={"100%"}
py={"s"}
px={"s"}
flexDirection={"row"}
gap={"s"}
borderRadius={10}
borderColor={"lightGray"}
shadowColor={"black"}
shadowOffset={{ width: 0, height: 0 }}
shadowOpacity={0.5}
backgroundColor={"white"}
justifyContent={"space-between"}
>
<Box flexDirection={"row"} flex={1} gap={"s"}>
<Box width={50} height={50} borderRadius={10} overflow={"hidden"}>
<PaymentOption paymentMethod={paymentType} onPress={() => {}} />
</Box>
<Box height={50} flex={1}>
<Text variant={"black"}>{reference}</Text>
<Text>{moment(dateObject).fromNow()}</Text>
</Box>
</Box>
<Box height={50}>
<AmountColorRenderer status={status} amount={amount} />
</Box>
</Box>
);
};
const AmountColorRenderer = ({ status, amount }: { status: string; amount: number }) => {
if (status === "SUCCESS") {
return <AmountWrapper color="secondary">{amount}</AmountWrapper>;
}
if (status === "INITIATED") {
return <AmountWrapper color="softYellow">{amount}</AmountWrapper>;
}
return <AmountWrapper color="softRed">{amount}</AmountWrapper>;
};
const AmountWrapper = ({
color,
children,
}: { color: "secondary" | "softYellow" | "softRed"; children: React.ReactNode }) => {
return (
<Box backgroundColor={color} px={"m"} py={"xs"} borderRadius={7}>
<Text variant={"white"} fontWeight={"bold"}>
{children} F
</Text>
</Box>
);
};
export default TransactionInformationsItem;
import type React from "react";
import Box from "./bases/Box";
const WrapperTopEdgeCurved = ({ children }: { children?: React.ReactElement }) => {
return (
<Box
flex={1}
backgroundColor={"white"}
borderTopLeftRadius={20}
borderTopRightRadius={20}
// p={"l"}
flexDirection={"column"}
>
{children}
</Box>
);
};
export default WrapperTopEdgeCurved;
import { containers, images } from "@styles/Commons";
import { ImageBackground, View } from "react-native";
type Props = { children: React.ReactNode };
const BackgroundGreenWhiteContentArea = ({ children }: Props) => {
return (
<View style={containers.containerFull}>
<ImageBackground
source={require("../../../assets/background_content_white2.png")}
resizeMode="cover"
style={images.cover}
>
{children}
</ImageBackground>
</View>
);
};
export default BackgroundGreenWhiteContentArea;
import BeasyLogoIcon from "@components/BeasyLogoIcon";
import GoBackIconButton from "@components/GoBackIconButton";
import WrapperTopEdgeCurved from "@components/WrapperTopEdgeCurved";
import Box from "@components/bases/Box";
import { useNavigation } from "@react-navigation/native";
import type { ReactElement } from "react";
import BackgroundDefault from "./BeasyDefaultBackground";
const BackgroundWithBeasyIconAndWhiteContentArea = ({
children,
goBack = false,
}: { children?: ReactElement; goBack?: boolean }) => {
const navigation = useNavigation();
return (
<BackgroundDefault>
<Box
px={"l"}
flexDirection={"row"}
justifyContent={"space-between"}
alignItems={"center"}
mb={"m"}
>
<BeasyLogoIcon />
{goBack && <GoBackIconButton onPress={() => navigation.goBack()} />}
</Box>
<WrapperTopEdgeCurved>{children}</WrapperTopEdgeCurved>
</BackgroundDefault>
);
};
export default BackgroundWithBeasyIconAndWhiteContentArea;
import { images } from "@styles/Commons";
import { ImageBackground } from "react-native";
type Props = { children: React.ReactNode };
const BeasyDefaultBackground = ({ children }: Props) => {
return (
<ImageBackground source={require("../../../assets/background.png")} style={images.cover}>
{children}
</ImageBackground>
);
};
export default BeasyDefaultBackground;
import { type BoxProps, type VariantProps, createRestyleComponent } from "@shopify/restyle";
import type { Theme } from "@themes/Theme";
import { buttonVariants } from "@themes/Variants";
import Box from "./Box";
const ButtonBase = createRestyleComponent<
VariantProps<Theme, "buttonVariants"> &
BoxProps<Theme> & {
children: React.ReactNode;
},
Theme
>([buttonVariants], Box);
export default ButtonBase;
import type { Theme } from "@/themes/Theme";
import Box from "@re-box";
import { type VariantProps, createRestyleComponent, createVariant } from "@shopify/restyle";
const Card = createRestyleComponent<
VariantProps<Theme, "cardVariants"> & React.ComponentProps<typeof Box>,
Theme
>(
[
createVariant({
themeKey: "cardVariants",
defaults: {
margin: {
phone: "s",
tablet: "m",
},
backgroundColor: "red",
},
}),
],
Box,
);
export default Card;
import type { Theme } from "@/themes/Theme";
import { createText } from "@shopify/restyle";
const Text = createText<Theme>();
export default Text;
import Box from "@components/bases/Box";
import type React from "react";
import {} from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
const SafeAreaViewTopLeftRightFull = ({ children }: { children: React.ReactNode }) => {
return (
<SafeAreaView edges={["top", "left", "right"]}>
<Box
style={{
height: "100%",
width: "100%",
}}
>
{children}
</Box>
</SafeAreaView>
);
};
export default SafeAreaViewTopLeftRightFull;
import { asp as g } from "@asp/asp";
import Box from "@components/bases/Box"; import Box from "@components/bases/Box";
import { images } from "@styles/Commons";
import { Image } from "react-native"; import { Image } from "react-native";
const CheckIcon = () => { const CheckIcon = () => {
...@@ -7,7 +7,8 @@ const CheckIcon = () => { ...@@ -7,7 +7,8 @@ const CheckIcon = () => {
<Box width={50} height={50}> <Box width={50} height={50}>
<Image <Image
source={require("../../../assets/icon_check_success.png")} source={require("../../../assets/icon_check_success.png")}
style={images.contain} style={[g.flex_1]}
resizeMode="contain"
/> />
</Box> </Box>
); );
......
import { asp as g } from "@asp/asp";
import Box from "@components/bases/Box"; import Box from "@components/bases/Box";
import { images } from "@styles/Commons";
import { Image } from "react-native"; import { Image } from "react-native";
const ErrorIcon = () => { const ErrorIcon = () => {
...@@ -7,7 +7,8 @@ const ErrorIcon = () => { ...@@ -7,7 +7,8 @@ const ErrorIcon = () => {
<Box width={50} height={50}> <Box width={50} height={50}>
<Image <Image
source={require("../../../assets/icon_close_failure.png")} source={require("../../../assets/icon_close_failure.png")}
style={images.contain} style={[g.flex_1]}
resizeMode="contain"
/> />
</Box> </Box>
); );
......
import { asp as g } from "@asp/asp";
import Box from "@components/bases/Box"; import Box from "@components/bases/Box";
import { images } from "@styles/Commons";
import { Image } from "react-native"; import { Image } from "react-native";
const InformationIcon = () => { const InformationIcon = () => {
...@@ -7,7 +7,8 @@ const InformationIcon = () => { ...@@ -7,7 +7,8 @@ const InformationIcon = () => {
<Box width={50} height={50}> <Box width={50} height={50}>
<Image <Image
source={require("../../../assets/icon_alert_information.png")} source={require("../../../assets/icon_alert_information.png")}
style={images.contain} style={[g.flex_1]}
resizeMode="contain"
/> />
</Box> </Box>
); );
......
import { useModalsManagerContext } from "@/contexts/ModalsManagerContext";
import Button from "@components/Button";
import ErrorIcon from "@components/icons/ErrorIcon";
import Box from "@re-box";
import Card from "@re-card";
import Text from "@re-text";
interface Props {
message?: string;
// onPress?: () => void;
}
const ErrorModal = ({ message = "Une erreur s'est produite" }: Props) => {
const { closeModal } = useModalsManagerContext();
return (
<Card variant={"modal"}>
<ErrorIcon />
<Text variant={"gray"} fontWeight={"bold"}>
{message}
</Text>
<Box style={{ width: "80%" }}>
<Button
variant={"fullError"}
textVariants={"white"}
label="Fermer"
onPress={closeModal}
/>
</Box>
</Card>
);
};
export default ErrorModal;
import { useModalsManagerContext } from "@/contexts/ModalsManagerContext";
import Button from "@components/Button";
import Box from "@components/bases/Box";
import InformationIcon from "@components/icons/InformationIcon";
import { Text } from "react-native";
interface Props {
message?: string;
onPress?: () => void;
actionLabel?: string;
}
const InformationModal = ({
message = "Une erreur s'est produite",
onPress = undefined,
actionLabel = "Ok",
}: Props) => {
const { closeModal } = useModalsManagerContext();
return (
<Box
width={300}
// height={200}
backgroundColor={"white"}
alignItems={"center"}
justifyContent={"center"}
alignSelf={"center"}
marginTop={"x240"}
position={"absolute"}
zIndex={10}
borderRadius={20}
gap={"m"}
shadowColor={"black"}
shadowOffset={{ width: 0, height: 0 }}
shadowOpacity={0.5}
p={"m"}
>
<InformationIcon />
<Text>{message}</Text>
<Box style={{ width: "80%" }}>
{onPress && (
<Button
variant={"fullInformation"}
textVariants={"white"}
label={actionLabel}
onPress={onPress}
/>
)}
<Button
variant={"noMargin"}
textVariants={"error"}
label="Fermer"
onPress={closeModal}
/>
</Box>
</Box>
);
};
export default InformationModal;
import Box from "@components/bases/Box";
import { ActivityIndicator, Text } from "react-native";
interface Props {
message?: string;
}
const LoadingModal = ({ message = "Veuillez patienter..." }: Props) => {
return (
<Box
width={300}
height={200}
backgroundColor={"white"}
alignItems={"center"}
justifyContent={"center"}
alignSelf={"center"}
marginTop={"x240"}
position={"absolute"}
zIndex={10}
borderRadius={20}
gap={"m"}
shadowColor={"black"}
shadowOffset={{ width: 0, height: 0 }}
shadowOpacity={0.5}
>
<ActivityIndicator size={"large"} />
<Text>{message}</Text>
</Box>
);
};
export default LoadingModal;
import Button from "@components/Button";
import Box from "@components/bases/Box";
import InformationIcon from "@components/icons/InformationIcon";
import { create } from "react-modal-promise";
import { Text } from "react-native";
interface Props {
isOpen: boolean;
onResolve: () => void;
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
onReject: any;
}
const MyModal = ({ isOpen, onResolve, onReject }: Props) => {
return (
<Box
width={300}
// height={200}
backgroundColor={"white"}
alignItems={"center"}
justifyContent={"center"}
alignSelf={"center"}
marginTop={"x240"}
position={"absolute"}
zIndex={10}
borderRadius={20}
gap={"m"}
shadowColor={"black"}
shadowOffset={{ width: 0, height: 0 }}
shadowOpacity={0.5}
p={"m"}
visible={isOpen}
>
<InformationIcon />
<Text>Modal</Text>
<Box style={{ width: "80%" }}>
<Button
variant={"fullInformation"}
textVariants={"white"}
label={"Ok"}
onPress={onResolve}
/>
<Button
variant={"noMargin"}
textVariants={"error"}
label="Fermer"
onPress={() => onReject("Fermer")}
/>
</Box>
</Box>
);
};
const myPromiseModal = create(MyModal);
export { myPromiseModal };
export default MyModal;
import BeasyDefaultBackground from "@components/backgrounds/BeasyDefaultBackground";
import type React from "react";
import { SafeAreaView } from "react-native-safe-area-context";
type Props = { children: React.ReactNode };
const WrapperWithDefaultBeasyBackgroundAndSafeAreaFull = ({ children }: Props) => {
return (
<BeasyDefaultBackground>
<SafeAreaView edges={["top", "bottom", "left", "right"]}>{children}</SafeAreaView>
</BeasyDefaultBackground>
);
};
export default WrapperWithDefaultBeasyBackgroundAndSafeAreaFull;
import BeasyDefaultBackground from "@components/backgrounds/BeasyDefaultBackground";
import type React from "react";
import { SafeAreaView } from "react-native-safe-area-context";
type Props = { children: React.ReactNode };
const WrapperWithDefaultBeasyBackgroundAndSafeAreaTopLeftRight = ({ children }: Props) => {
return (
<BeasyDefaultBackground>
<SafeAreaView edges={["top", "left", "right"]}>{children}</SafeAreaView>
</BeasyDefaultBackground>
);
};
export default WrapperWithDefaultBeasyBackgroundAndSafeAreaTopLeftRight;
import { createContext, useContext, useEffect, useState } from "react";
import { View } from "react-native";
export interface ImodalsManagerContext {
showModal(element: React.ReactElement): void;
closeModal(): void;
}
export const ModalsManagerContext = createContext<ImodalsManagerContext>({
showModal: () => {},
closeModal: () => {},
});
export const ModalsManagerProvider = ({ children }: { children: React.ReactNode }) => {
const [showBackdrop, setShowBackdrop] = useState(false);
const [modalElement, setModalElement] = useState<React.ReactElement | null>(null);
const showModal = (element: React.ReactElement) => {
setModalElement(element);
setShowBackdrop(true);
};
const closeModal = () => {
setModalElement(null);
setShowBackdrop(false);
};
useEffect(() => {
return () => {
setShowBackdrop(false);
setModalElement(null);
};
}, []);
return (
<ModalsManagerContext.Provider
value={{
showModal,
closeModal,
}}
>
{children}
{modalElement && (
<>
<OverlayBackdrop />
{modalElement}
</>
)}
</ModalsManagerContext.Provider>
);
};
export const useModalsManagerContext = () => {
return useContext(ModalsManagerContext);
};
export const OVERLAY_BACKDROP_Z_INDEX = 10;
const OverlayBackdrop = () => {
return (
<View
style={{
flex: 1,
width: "100%",
height: "100%",
// justifyContent: "center",
// alignItems: "center",
zIndex: OVERLAY_BACKDROP_Z_INDEX,
backgroundColor: "black",
opacity: 0.5,
position: "absolute",
}}
/>
);
};
export interface IauthenticationData {
access: string;
refresh: string;
}
import ErrorModal from "@components/modals/ErrorModal";
import { LOG } from "@logger";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { useMutation } from "@tanstack/react-query";
import * as SplashScreen from "expo-splash-screen";
import { createContext, useCallback, useContext, useState } from "react";
// import { type NavigationProp, useNavigation } from "@react-navigation/native";
import authenticateUser, { parseAuthicationErrors } from "@/utils/requests/authenticateUser";
import type { IuserInformations } from "@/utils/requests/types";
import getUserInformations, {
parseUserInformationsErrors,
} from "@/utils/requests/userInformations";
import { useModalsManagerContext } from "./ModalsManagerContext";
import type { IauthenticationData } from "./Types";
const log = LOG.extend("UserAuthenticationContext");
// SplashScreen.preventAutoHideAsync();
SplashScreen.hideAsync();
export interface UserAuthenticationContextProps {
isAuthenticated: boolean;
setIsAuthenticated: React.Dispatch<React.SetStateAction<boolean>>;
setAuthenticationData: React.Dispatch<React.SetStateAction<IauthenticationData>>;
userInformations: IuserInformations;
setUserInformations: React.Dispatch<React.SetStateAction<IuserInformations>>;
login: (email: string, password: string) => void;
isAuthenticating: boolean;
logout: () => void;
}
export const UserAuthenticationContext = createContext<UserAuthenticationContextProps>({
isAuthenticated: false,
setIsAuthenticated: () => {},
setAuthenticationData: () => {},
userInformations: {
username: "",
email: "",
// biome-ignore lint/style/useNamingConvention: <Api response>
first_name: "",
// biome-ignore lint/style/useNamingConvention: <Api response>
last_name: "",
marchand: {
// biome-ignore lint/style/useNamingConvention: <Api response>
marchand_id: "",
nom: "",
code: "",
adresse: "",
// biome-ignore lint/style/useNamingConvention: <Api response>
url_succes: "",
// biome-ignore lint/style/useNamingConvention: <Api response>
url_echec: "",
entreprise: 0,
user: 0,
},
},
setUserInformations: () => {},
login: () => {},
isAuthenticating: false,
logout: () => {},
});
export const UserAuthenticationContextProvider = ({ children }: { children: React.ReactNode }) => {
// States
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [isAuthenticating, setIsAuthenticating] = useState(false);
const [authenticationData, setAuthenticationData] = useState<IauthenticationData>({
access: "",
refresh: "",
});
const [_error, setError] = useState("");
const [userInformations, setUserInformations] = useState<IuserInformations>({
username: "JohnDoe",
email: "JohnDoe@example.com",
// biome-ignore lint/style/useNamingConvention: <Api response>
first_name: "John",
// biome-ignore lint/style/useNamingConvention: <Api response>
last_name: "Doe",
marchand: {
// biome-ignore lint/style/useNamingConvention: <Api response>
marchand_id: "id123",
nom: "Beasy",
code: "BEASY-EXAMPLE-1",
adresse: "Plateau 2, 1023, Immeuble Chardy",
// biome-ignore lint/style/useNamingConvention: <Api response>
url_succes: "https://example.com/success",
// biome-ignore lint/style/useNamingConvention: <Api response>
url_echec: "https://example.com/echec",
entreprise: 0,
user: 0,
},
});
// Hoooks
const { showModal } = useModalsManagerContext();
// const navigation = useNavigation<NavigationProp<ImainStackNavigator>>();
// Mutations
const authenticationMutation = useMutation({
mutationFn: authenticateUser,
onMutate: () => {
setIsAuthenticating(true);
setError("");
},
onSuccess: (data) => {
setAuthenticationData(data);
log.info("Receive data from authenticateUser, running getUserInformations...");
userInformationsMutation.mutate(data.access);
},
onError: (error: unknown) => {
const errorString = parseAuthicationErrors(error);
showModal(<ErrorModal message={errorString} />);
},
onSettled: () => {
setIsAuthenticating(false);
},
});
const clearStorages = useCallback(async () => {
try {
await AsyncStorage.clear();
} catch (error) {
log.error("clearStorages |", JSON.stringify(error, null, 2));
// saving error
}
}, []);
const userInformationsMutation = useMutation({
mutationFn: (userAccessToken: string) => getUserInformations(userAccessToken),
onMutate: () => {
setIsAuthenticating(true);
setError("");
},
onSettled: () => {
setIsAuthenticating(false);
},
onSuccess: (userInformations) => {
log.info("getUserInformations request was a success, navigating to homepage");
setUserInformations(userInformations);
setIsAuthenticated(true);
storeAuthenticationData(authenticationData);
storeUserInformations(userInformations);
// navigation.navigate("appBottomTabsNavigator");
},
onError: (error) => {
log.error("userInformationsMutation", error);
const errorString = parseUserInformationsErrors(error);
showModal(<ErrorModal message={errorString} />);
clearStorages();
},
});
// Methods
const login = useCallback(
(email: string, password: string) => {
authenticationMutation.mutate({
username: email,
password: password,
});
},
[authenticationMutation],
);
const logout = useCallback(() => {
(async () => {
setIsAuthenticated(false);
setAuthenticationData({
access: "",
refresh: "",
});
setUserInformations({
username: "",
email: "",
// biome-ignore lint/style/useNamingConvention: <explanation>
first_name: "",
// biome-ignore lint/style/useNamingConvention: <explanation>
last_name: "",
marchand: {
// biome-ignore lint/style/useNamingConvention: <explanation>
marchand_id: "",
nom: "",
code: "",
adresse: "",
// biome-ignore lint/style/useNamingConvention: <explanation>
url_succes: "",
// biome-ignore lint/style/useNamingConvention: <explanation>
url_echec: "",
entreprise: 0,
user: 0,
},
});
await clearStorages();
})();
}, [clearStorages]);
// Storages
const storeAuthenticationData = async (authenticationData: IauthenticationData) => {
try {
await AsyncStorage.setItem("authenticationData", JSON.stringify(authenticationData));
} catch (error) {
log.error("storeAuthenticationData |", JSON.stringify(error, null, 2));
// saving error
}
};
const storeUserInformations = async (userInformations: IuserInformations) => {
try {
await AsyncStorage.setItem("userInformations", JSON.stringify(userInformations));
} catch (error) {
log.error("storeUserInformations |", JSON.stringify(error, null, 2));
// saving error
}
};
const _loadAuthenticationData = async () => {
log.debug("loadAuthenticationData | Loading authentication data");
const jsonRepresentation = await AsyncStorage.getItem("authenticationData");
return jsonRepresentation ? JSON.parse(jsonRepresentation) : null;
};
const _loadUserInformations = async () => {
log.debug("loadUserInformations | Loading user informations");
const jsonRepresentation = await AsyncStorage.getItem("userInformations");
return jsonRepresentation ? JSON.parse(jsonRepresentation) : null;
};
// biome-ignore lint/correctness/useExhaustiveDependencies: <This should only be executed once. At startup.>
// useEffect(() => {
// log.debug("UserAuthenticationContext | App Startup | loading saved user data.");
// (async () => {
// try {
// // await loadAssetsAsync();
// await cacheAssetsAsync({
// images: [
// "../assets/background_default.png",
// "../assets/beasy_default.png",
// "../assets/beasy_background.png",
// "../assets/background_content_white2.png",
// "../../assets/background.png",
// ],
// });
// const authenticationData = await loadAuthenticationData();
// const userInformations = await loadUserInformations();
// if (authenticationData && userInformations) {
// setAuthenticationData(authenticationData);
// setUserInformations(userInformations);
// setIsAuthenticated(true);
// }
// } catch (error) {
// log.error(
// "UserAuthenticationContext | App Startup | error during retrieval of stored data |",
// JSON.stringify(error, null, 2),
// );
// } finally {
// setTimeout(async () => await SplashScreen.hideAsync(), 500);
// }
// })();
// }, []);
return (
<UserAuthenticationContext.Provider
value={{
isAuthenticated: isAuthenticated,
isAuthenticating: isAuthenticating,
setIsAuthenticated,
setAuthenticationData,
userInformations,
setUserInformations,
login,
logout,
}}
>
{children}
</UserAuthenticationContext.Provider>
);
};
export const useUserAuthenticationContext = () => {
return useContext(UserAuthenticationContext);
};
export interface Merchant {
marchand_id: string;
nom: string;
code: string;
adresse: string;
url_succes: string;
url_echec: string;
entreprise: number;
user: number;
}
export type UserData = { export type UserData = {
id: number; id: number;
username: string; username: string;
...@@ -5,7 +15,7 @@ export type UserData = { ...@@ -5,7 +15,7 @@ export type UserData = {
first_name: string; first_name: string;
last_name: string; last_name: string;
role: string; role: string;
marchand: number; marchand: Merchant;
}; };
export type Token = { export type Token = {
......
import ErrorModal from "@components/modals/ErrorModal";
import LoadingModal from "@components/modals/LoadingModal";
import { LOG } from "@logger";
import type { NativeStackNavigationProp } from "@react-navigation/native-stack";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { AxiosError } from "axios";
import * as WebBrowser from "expo-web-browser";
import { useRef, useState } from "react";
import { AppState, Platform } from "react-native";
import { useModalsManagerContext } from "@/contexts/ModalsManagerContext";
import type { IpaymentStackNavigator } from "@/navigations/types";
import {
getTransactionStatus,
getTransactionsData,
type IorangePaymentStarter,
} from "@/utils/requests/orangePayment";
const log = LOG.extend("useOrangeMoney");
const paymentObjectDefault: IorangePaymentStarter = {
// biome-ignore lint/style/useNamingConvention: <api expect type_paiement>
type_paiement: 1,
marchand: "1",
service: "2",
montant: 0,
numero: "0707070707",
commentaire: "Un commentaire",
};
const useOrangeMoney = (
navigation?: NativeStackNavigationProp<
IpaymentStackNavigator,
"paymentAmountInputScreen",
"IpaymentStackNavigator"
>,
) => {
const queryClient = useQueryClient();
const [isBrowserOpen, setIsBrowserOpen] = useState(false);
const { showModal, closeModal } = useModalsManagerContext();
const appState = useRef(AppState.currentState);
const [_appStateVisible, _setAppStateVisiblee] = useState(appState.current);
const handlePaymentUsingBrowser = async (url: string) => {
setIsBrowserOpen(true);
const result = await WebBrowser.openBrowserAsync(url);
// setResult(result);
log.debug("handlePaymentUsingBrowser | Result ::", result);
setIsBrowserOpen(false);
};
const orangeTransactionInitializerMutation = useMutation({
mutationFn: (amount: number) =>
getTransactionsData({
...paymentObjectDefault,
montant: amount,
}),
onSuccess: (_data) => {
// return data.payment_url
log.debug("orangeTransactionInitializerMutation request success, opening browser...");
queryClient.invalidateQueries({ queryKey: ["transactionsHistory"] });
// await handlePaymentUsingBrowser(data.payment_url);
// await transactionsStatusMutation.mutate(data.order_id);
// setResult(result);
},
onError: (err) => {
log.error("orangeTransactionInitializerMutation |", err);
},
});
const _maxRetry = 3;
const _retryDelay = 5000;
const transactionsStatusMutation = useMutation({
mutationFn: (orderId: string) => getTransactionStatus(orderId),
onSuccess: (data) => {
log.debug("transactionsStatusMutation request success");
queryClient.invalidateQueries({ queryKey: ["transactionsHistory"] });
return data.status;
},
onError: (err) => {
log.error("transactionsStatusMutation |", err);
queryClient.invalidateQueries({ queryKey: ["transactionsHistory"] });
},
// retry: (failureCount, error) => {
// log.warn("transactionsStatusMutation | retrying", failureCount, error);
// return failureCount < maxRetry;
// },
// retryDelay(_failureCount, _error) {
// return retryDelay;
// },
});
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: <Necessarry evil>
const checkStatus = async (orderId: string, retry = 0): Promise<string | undefined> => {
// Check the transactions 'retry' time.
let numberOfTries = 0;
while (numberOfTries <= retry) {
log.verbose("useOrangePayment | checkStatus | Try No.", numberOfTries + 1);
try {
await transactionsStatusMutation.mutateAsync(orderId);
return;
} catch (error) {
// errors are throwned from getTransactions status when the staus is either initiated or failed.
if (error instanceof AxiosError && error.response?.status === 404) {
log.error("useOrangeMoney | checkStatus | Transaction not found");
return error.response.data.error || `Transaction ${orderId} not found !!`;
}
if (error instanceof AxiosError && error.name === "ORANGE_PAYMENT_FAILED") {
return "La transaction à échoué.";
}
if (error instanceof AxiosError && error.name === "ORANGE_UNKNOWN_PAYMENT_ERROR") {
return "Une erreur est survenue.";
}
}
numberOfTries += 1;
}
return "La transaction est toujours en cours.";
};
const openBrowserThenCheckStatus = async (paymentUrl: string, orderId: string) => {
try {
await handlePaymentUsingBrowser(paymentUrl);
if (Platform.OS === "android") {
log.debug(
"useOrangeMoney | openBrowserThenCheckStatus | Android device. Setup listener for browser close event.",
);
const sub = AppState.addEventListener("change", async (nextAppState) => {
log.debug(
"useOrangeMoney | openBrowserThenCheckStatus | Android device. Browser state :",
nextAppState,
);
if (nextAppState === "active") {
log.debug(
"useOrangeMoney | openBrowserThenCheckStatus | Android device. Browser is closed. Removing listener. Checking for transaction State.",
);
sub.remove();
log.info("openBrowserThenCheckStatus | Verifying transaction status...");
showModal(
<LoadingModal message="Vérification du statut de la transaction..." />,
);
// await transactionsStatusMutation.mutateAsync(orderId);
const message = await checkStatus(orderId);
if (message) {
showModal(<ErrorModal message={message} />);
} else {
navigation?.getParent()?.navigate("paymentResultScreen");
}
}
});
} else {
log.info("openBrowserThenCheckStatus | Verifying transaction status...");
showModal(<LoadingModal message="Vérification du statut de la transaction..." />);
// await transactionsStatusMutation.mutateAsync(orderId);
const message = await checkStatus(orderId);
if (message) {
showModal(<ErrorModal message={message} />);
} else {
navigation?.getParent()?.navigate("paymentResultScreen");
}
}
// closeModal();
} catch (error: unknown) {
log.verbose("Error catching");
if (error instanceof AxiosError) {
log.error("openBrowserThenCheckStatus Catch Block|", error);
showModal(
<ErrorModal
message={error.response?.data.error || "Une erreur Axios est survenue."}
/>,
);
} else {
showModal(<ErrorModal message="Une erreur est survenue." />);
}
// log.error("openBrowserThenCheckStatus Catch Block|", error);
// if (error instanceof Error) {
// log.debug("1");
// if (error.name === "ORANGE_PAYMENT_IN_PROGRESS") {
// log.debug("2");
// log.warn("openBrowserThenCheckStatus | ORANGE_PAYMENT_IN_PROGRESS");
// await showModal(
// <InformationModal
// message="Le payment est toujours en cours."
// actionLabel="Rééssayer"
// onPress={() => openBrowserThenCheckStatus(paymentUrl, orderId)}
// />,
// );
// log.debug("3");
// } else if (error.name === "ORANGE_PAYMENT_FAILED") {
// showModal(<ErrorModal message="Le paiment à échoué." />);
// log.error("openBrowserThenCheckStatus | ORANGE_PAYMENT_FAILED");
// }
// log.debug("4 --", error.name);
// } else {
// log.error("openBrowserThenCheckStatus Else Block|", error);
// closeModal();
// throw error;
// }
}
};
const orangePaymentTransactionHandler = async (amount: number) => {
try {
showModal(<LoadingModal message="Initialization de la transaction." />);
const { payment_url, order_id } =
await orangeTransactionInitializerMutation.mutateAsync(amount);
log.info("orangePaymentTransactionHandler |", payment_url, order_id);
log.info("Opening browser for payment...");
await openBrowserThenCheckStatus(payment_url, order_id);
// biome-ignore lint/suspicious/noExplicitAny: <TODO: Change this later>
} catch (error: any) {
log.error("makePayment |", error);
showModal(
<ErrorModal message={error.response?.data?.error || "Une erreur est survenue."} />,
);
throw error;
} finally {
//closeModal(); // just to be ultra sure that the modal is closed
}
};
return {
orangeTransactionInitializerMutation: orangeTransactionInitializerMutation,
handlePaymentUsingBrowser,
isBrowserOpen,
isWaitingForOmPaymentUrl: orangeTransactionInitializerMutation.isPending,
isCheckingForTransactionStatus: transactionsStatusMutation.isPending,
transactionsStatusMutation,
orangePaymentTransactionHandler,
};
};
export default useOrangeMoney;
import { type Transaction, getTransactionsHistory } from "@/utils/requests/transactions";
import type { PaymentCode } from "@/utils/requests/types";
import { LOG } from "@logger";
import { useQuery } from "@tanstack/react-query";
import { useCallback, useMemo, useState } from "react";
const log = LOG.extend("useTransactionsHistory");
// biome-ignore lint/style/useNamingConvention: <explanation>
export type OperatorsFilter = { [key in PaymentCode]: boolean };
const useTransactionsHistory = () => {
log.verbose("useTransactionsHistory");
const [referenceFilter, setReferenceFilter] = useState<string>("");
// biome-ignore lint/style/useNamingConvention: <Types values are in uppercase>
const [operatorsFilter, setOperatorsFilter] = useState<OperatorsFilter>({
// biome-ignore lint/style/useNamingConvention: <Types values are in uppercase>
OM: true,
// biome-ignore lint/style/useNamingConvention: <Types values are in uppercase>
MTN: true,
// biome-ignore lint/style/useNamingConvention: <Types values are in uppercase>
FLOOZ: true,
// biome-ignore lint/style/useNamingConvention: <Types values are in uppercase>
WAVE: true,
// biome-ignore lint/style/useNamingConvention: <Types values are in uppercase>
CB: true,
});
const { data, isLoading, error, refetch } = useQuery({
queryKey: ["transactionsHistory"],
queryFn: getTransactionsHistory,
});
const filterByReference = useCallback(
(reference: string) => {
if (!data?.length) return [];
return data.filter(
(transaction) => transaction.reference.includes(reference) && transaction.reference,
);
},
[data],
);
const filterDataByReference = (data: Transaction[], reference: string) => {
if (!data?.length) return [];
return data.filter(
(transaction) => transaction.reference.includes(reference) && transaction.reference,
);
};
const filterByOperators = (data: Transaction[]) => {
if (!data?.length) return [];
// create a set
const set = new Set<PaymentCode>();
for (const key of Object.keys(operatorsFilter)) {
if (operatorsFilter[key as keyof OperatorsFilter]) {
set.add(key as PaymentCode);
}
}
return data.filter((transaction) => {
// return true if the set is empty, as there is no need to check
if (set.size === 0) return true;
// return true if the set contains the value
return set.has(transaction.type_paiement_label);
});
};
const transactionsHistory: Transaction[] = useMemo(() => {
if (!data) return [];
const filteredByOperators = filterByOperators(data);
const filteredByReference = filterDataByReference(filteredByOperators, referenceFilter);
return filteredByReference;
// return filterByReference(referenceFilter);
}, [data, referenceFilter, filterByOperators, filterDataByReference]);
return {
transactionsHistory,
isLoading,
error,
refetch,
setReferenceFilter,
operatorsFilter,
setOperatorsFilter,
};
};
export default useTransactionsHistory;
import ErrorModal from "@components/modals/ErrorModal";
import LoadingModal from "@components/modals/LoadingModal";
import { LOG } from "@logger";
import type { NativeStackNavigationProp } from "@react-navigation/native-stack";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import * as WebBrowser from "expo-web-browser";
import { useState } from "react";
import { useModalsManagerContext } from "@/contexts/ModalsManagerContext";
import type { IpaymentStackNavigator } from "@/navigations/types";
import {
getTransactionStatus,
type IwavePaymentStarter,
initTransaction,
} from "@/utils/requests/wavePayment";
const log = LOG.extend("useWave");
const paymentObjectDefault: IwavePaymentStarter = {
// biome-ignore lint/style/useNamingConvention: <api>
type_paiement: 2,
marchand: "1",
service: "2",
montant: 0,
};
const useWave = (
navigation?: NativeStackNavigationProp<
IpaymentStackNavigator,
"paymentAmountInputScreen",
"IpaymentStackNavigator"
>,
) => {
const { showModal, closeModal } = useModalsManagerContext();
const [_isBrowserOpen, setIsBrowserOpen] = useState(false);
const _queryClient = useQueryClient();
// Mutations
const waveTransactionInitializerMutation = useMutation({
mutationFn: (amount: number) =>
initTransaction({
...paymentObjectDefault,
montant: amount,
}),
onSuccess: (_data) => {},
onError: (err) => {
log.error("waveTransactionInitializerMutation |", err);
},
});
const waveTransactionStatusMutation = useMutation({
mutationFn: (orderId: string) => getTransactionStatus(orderId),
onSuccess: (_data) => {
log.debug("waveTransactionStatusMutation request success");
},
onError: (err) => {
log.error("waveTransactionStatusMutation |", err);
},
});
// Browser stuff
const handlePaymentUsingBrowser = async (url: string) => {
log.debug("handlePaymentUsingBrowser | Opening the browser at url :: ", url);
setIsBrowserOpen(true);
const result = await WebBrowser.openBrowserAsync(url);
// setResult(result);
log.debug("handlePaymentUsingBrowser | Result ::", result);
setIsBrowserOpen(false);
};
const _openBrowserThenCheckStatus = async (paymentUrl: string, orderId: string) => {
try {
await handlePaymentUsingBrowser(paymentUrl);
log.info("openBrowserThenCheckStatus | Verifying transaction status...");
showModal(<LoadingModal message="Vérification du statut de la transaction..." />);
await waveTransactionStatusMutation.mutateAsync(orderId);
closeModal();
// navigation?.getParent()?.navigate("paymentResultScreen");
} catch (error) {
log.error("openBrowserThenCheckStatus |", error);
// if (error instanceof Error) {
// if (error.name === "ORANGE_PAYMENT_IN_PROGRESS") {
// log.warn("openBrowserThenCheckStatus | ORANGE_PAYMENT_IN_PROGRESS");
// await showModal(
// <InformationModal
// message="Le payment est toujours en cours."
// actionLabel="Rééssayer"
// onPress={() => openBrowserThenCheckStatus(paymentUrl, orderId)}
// />,
// );
// } else if (error.name === "ORANGE_PAYMENT_FAILED") {
// showModal(<ErrorModal message="Le paiment à échoué." />);
// log.error("openBrowserThenCheckStatus | ORANGE_PAYMENT_FAILED");
// }
// } else {
// log.error("openBrowserThenCheckStatus |", error);
// closeModal();
// throw error;
// }
}
};
// Handlers
const waveTransactionHandler = async (amount: number) => {
try {
showModal(<LoadingModal message="Initialization de la transaction." />);
const response = await waveTransactionInitializerMutation.mutateAsync(amount);
log.info("waveTransactionHandler payment url received :: ", response.wave_launch_url);
// log.info("Opening browser for payment...");
log.info("Navigating to the qr code screen...");
closeModal();
navigation?.getParent()?.navigate("waveQrCodePaymentScreen", { data: response });
// await openBrowserThenCheckStatus(response.wave_launch_url, response.id);
} catch (error) {
log.error("waveTransactionHandler |", error);
showModal(<ErrorModal message="Une erreur s'est produite." />);
throw error;
}
};
const handlePaymentVerification = async (id: string) => {
log.info("handlePaymentVerification |", id);
try {
showModal(<LoadingModal message="Vérification du statut de la transaction..." />);
const _response = await waveTransactionStatusMutation.mutateAsync(id);
closeModal();
} catch (error) {
log.error("handlePaymentVerification |", error);
// closeModal();
// showModal(<ErrorModal message="Le paiment a été echoué." />);
} finally {
// TODO : remove this finally block once a proper implementation workflow is set. currently we close the modal after logging whatever response we get from the request
closeModal();
}
};
return {
waveTransactionInitializerMutation,
waveTransactionHandler,
handlePaymentVerification,
};
};
export default useWave;
...@@ -3,10 +3,9 @@ import { createBottomTabNavigator } from "@react-navigation/bottom-tabs"; ...@@ -3,10 +3,9 @@ import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
import TransactionHistoryScreen from "@screens/TransactionHistoryScreen"; import TransactionHistoryScreen from "@screens/TransactionHistoryScreen";
import UserProfileScreen from "@screens/UserProfileScreen"; import UserProfileScreen from "@screens/UserProfileScreen";
import { useTheme } from "@shopify/restyle"; import { useTheme } from "@shopify/restyle";
// import palette
import type { Theme } from "@themes/Theme"; import type { Theme } from "@themes/Theme";
import { View } from "react-native"; import { Text, View } from "react-native";
import Text from "../components/bases/Text";
import PaymentStackNavigator from "./PaymentStackNavigation"; import PaymentStackNavigator from "./PaymentStackNavigation";
const Tab = createBottomTabNavigator(); const Tab = createBottomTabNavigator();
...@@ -63,22 +62,6 @@ export const AppBottomTabsNavigator = () => { ...@@ -63,22 +62,6 @@ export const AppBottomTabsNavigator = () => {
); );
}; };
const HomeScreen = () => {
return (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<Text>Home!</Text>
</View>
);
};
const Transactions = () => {
return (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<Text>Transactions!</Text>
</View>
);
};
const SettingsScreen = () => { const SettingsScreen = () => {
return ( return (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}> <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
...@@ -86,11 +69,3 @@ const SettingsScreen = () => { ...@@ -86,11 +69,3 @@ const SettingsScreen = () => {
</View> </View>
); );
}; };
const ProfileScreen = () => {
return (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<Text>Profile!</Text>
</View>
);
};
/** biome-ignore-all lint/style/useNamingConvention: <explanation> */ /** biome-ignore-all lint/style/useNamingConvention: <For filters to match the codes> */
import { asp as g } from "@asp/asp"; import { asp as g } from "@asp/asp";
import { BarnoinPayBackground } from "@components/BarnoinPayBackground"; import { BarnoinPayBackground } from "@components/BarnoinPayBackground";
......
...@@ -5,17 +5,16 @@ import * as Button from "@components/ButtonNew"; ...@@ -5,17 +5,16 @@ import * as Button from "@components/ButtonNew";
import Ionicons from "@expo/vector-icons/Ionicons"; import Ionicons from "@expo/vector-icons/Ionicons";
import { LOG } from "@logger"; import { LOG } from "@logger";
import { Text, View } from "react-native"; import { Text, View } from "react-native";
import { useDispatch } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { useUserAuthenticationContext } from "@/contexts/UserAuthenticationContext";
import { logout } from "@/features/auth/slice"; import { logout } from "@/features/auth/slice";
import type { RootState } from "@/redux";
const log = LOG.extend("UserProfileScreen"); const log = LOG.extend("UserProfileScreen");
const UserProfileScreen = () => { const UserProfileScreen = () => {
log.verbose("UserProfileScreen"); log.verbose("UserProfileScreen");
const dispatch = useDispatch(); const dispatch = useDispatch();
const userInformations = useSelector((state: RootState) => state.auth.user);
const { userInformations } = useUserAuthenticationContext();
return ( return (
<BarnoinPayBackground style={[g.flex_col, g.justify_between]}> <BarnoinPayBackground style={[g.flex_col, g.justify_between]}>
......
import { StyleSheet } from "react-native";
export const containers = StyleSheet.create({
fullScreenContentCentered: {
width: "100%",
height: "100%",
flex: 1,
alignItems: "center",
justifyContent: "center",
},
containerFull: {
width: "100%",
height: "100%",
flex: 1,
},
containerFlexUno: {
flex: 1,
},
});
export const images = StyleSheet.create({
cover: {
flex: 1,
width: "100%",
height: "100%",
resizeMode: "cover",
},
background: {
flex: 1,
resizeMode: "cover",
justifyContent: "center",
},
contain: {
flex: 1,
width: "100%",
height: "100%",
resizeMode: "contain",
},
});
import { OVERLAY_BACKDROP_Z_INDEX } from "@/contexts/ModalsManagerContext"; const OVERLAY_BACKDROP_Z_INDEX = 999;
import { Dimensions } from "react-native"; import { Dimensions } from "react-native";
export const cardVariants = { export const cardVariants = {
......
// To load assets asynchronously
import { Asset } from "expo-asset";
import { LOG } from "@logger";
const log = LOG.extend("assetsCache");
export default function cacheAssetsAsync({
images = [],
fonts = [],
videos = [],
}: { images?: string[]; fonts?: string[]; videos?: string[] }) {
return Promise.all([...cacheImages(images)]);
}
function cacheImages(images: string[]) {
return images.map((image) => Asset.fromModule(image).downloadAsync());
}
// function cacheVideos(videos) {
// return videos.map((video) => Asset.fromModule(video).downloadAsync());
// }
// function cacheFonts(fonts) {
// return fonts.map((font) => Font.loadAsync(font));
// }
import { LOG } from "@logger";
import axios, { type AxiosError, type AxiosResponse } from "axios";
const log = LOG.extend("AxiosRequest");
const baseUrl = process.env.EXPO_PUBLIC_API_URL;
// biome-ignore lint/style/useNamingConvention: <baseURL is not for me to change.>
const client = axios.create({ baseURL: baseUrl });
const axiosRequest = async <T>({ ...options }): Promise<T> => {
// console.log("base Url", baseUrl);
// client.defaults.headers.common.Authorization = `Bearer ${""}`;
// client.defaults.headers.common['Content-Type'] = 'application/json';
// console.log("client default", client.defaults);
// console.log("client datas", client.defaults.data);
log.debug("Base url :: ", client.getUri());
log.debug("RequestOptions :: ", options);
const onSuccess = (response: T) => {
return response;
};
const onError = (error: AxiosError) => {
log.error(error);
throw error;
};
try {
const response: AxiosResponse<T> = await client({ ...options });
return onSuccess(response.data);
} catch (error: unknown) {
if (axios.isAxiosError(error)) {
log.error("axiosRequest | Response :: ", JSON.stringify(error.response, null, 2));
// log.error("Axios RequestError Reponse message:: ", error.message);
// log.error("Axios RequestError Reponse name:: ", error.response?.data);
} else {
log.error("axiosRequest | General RequestError :: ", JSON.stringify(error, null, 2));
}
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
return onError(error as any);
}
};
export default axiosRequest;
import type { IauthenticationData } from "@/contexts/Types";
import { LOG } from "@logger";
import { AxiosError } from "axios";
import axiosRequest from "../axiosRequest";
const log = LOG.extend("authenticateUser");
const authenticateUser = async ({ username, password }: { username: string; password: string }) => {
log.http({ username, password });
const response = await axiosRequest<IauthenticationData>({
url: "/login/token/",
method: "POST",
data: {
username: username,
password: password,
},
});
log.http(JSON.stringify(response, null, 2));
return response;
};
export default authenticateUser;
export const parseAuthicationErrors = (error: unknown): string => {
if (error instanceof AxiosError && error.response) {
switch (error.response.status) {
case 401:
return "Wrong username or password";
default:
return "Unknown Error. Please try again.";
}
}
if (error instanceof AxiosError && error.request) {
return "Network error";
}
return "Unknown error";
};
import { LOG } from "@logger";
import base64 from "react-native-base64";
import axiosRequest from "../axiosRequest";
import type { IpaginatedResponse, IpaymentType } from "./types";
const basictoken = base64.encode("admin:admin");
const log = LOG.extend("getPaymentTypes");
const getPaymentTypes = async () => {
log.http("getPaymentTypes");
const response = await axiosRequest<IpaginatedResponse<IpaymentType[]>>({
url: "/operateur/",
method: "GET",
headers: {
// biome-ignore lint/style/useNamingConvention: <explanation>
Authorization: `Basic ${basictoken}`,
},
});
log.http(JSON.stringify(response, null, 2));
return response;
};
export default getPaymentTypes;
import { LOG } from "@logger";
import base64 from "react-native-base64";
import axiosRequest from "../axiosRequest";
export interface IorangePaymentStarter {
// biome-ignore lint/style/useNamingConvention: <api expect type_paiement>
type_paiement: number;
marchand: "1";
service: string;
montant: number;
commentaire: string;
numero: string;
}
export interface IorangeResponse {
status: number;
message: string;
// biome-ignore lint/style/useNamingConvention: <api expect pay_token>
pay_token: string;
// biome-ignore lint/style/useNamingConvention: <api expect payment_url>
payment_url: string;
// biome-ignore lint/style/useNamingConvention: <api expect notif_token>
notif_token: string;
// biome-ignore lint/style/useNamingConvention: <api expect order_id>
order_id: string;
}
const basictoken = base64.encode("admin:admin");
export type OrangeStatus = "INITIATED" | "SUCCESS" | "FAILED";
export interface IorangePaymentStatus {
status: OrangeStatus;
code: number;
message: {
status: OrangeStatus;
// biome-ignore lint/style/useNamingConvention: <api expect order_id>
order_id: string;
txnid?: string;
};
}
const log = LOG.extend("orangePayment");
export const getTransactionsData = async (payload: IorangePaymentStarter) => {
log.http("getTransactionsData", payload);
// const basictoken = base64.encode("admin:admin");
const response = await axiosRequest<IorangeResponse>({
url: "/transactions/",
method: "POST",
headers: {
// biome-ignore lint/style/useNamingConvention: <explanation>
Authorization: `Basic ${basictoken}`,
},
data: payload,
});
log.http("getTransactionsData |", JSON.stringify(response, null, 2));
return response;
};
export const getTransactionStatus = async (orderId: string) => {
log.http("getTransactionStatus |", { orderId });
try {
const response = await axiosRequest<IorangePaymentStatus>({
url: `/api/TransactionCheckStatus/${orderId}/`,
method: "GET",
headers: {
// biome-ignore lint/style/useNamingConvention: <explanation>
Authorization: `Basic ${basictoken}`,
},
});
log.http("getTransactionStatus |", JSON.stringify(response, null, 2));
switch (response.status) {
case "SUCCESS": {
log.http("getTransactionStatus |", JSON.stringify(response, null, 2));
return response;
}
case "INITIATED": {
log.warn("Payment is still in progress, throwing error for mutation to catch");
const error = new Error("Payment is still in progress");
error.name = "ORANGE_PAYMENT_IN_PROGRESS";
throw error;
}
case "FAILED": {
log.warn("Payment failed, throwing error for mutation to catch");
const error = new Error("Payment failed");
error.name = "ORANGE_PAYMENT_FAILED";
throw error;
}
default: {
log.warn("An unknown error occured, throwing error for mutation to catch");
const error = new Error("Payment failed");
error.name = "ORANGE_UNKNOWN_PAYMENT_ERROR";
throw error;
}
}
} catch (error) {
log.error(
"getTransactionStatus | An unexpected error occured |",
JSON.stringify(error, null, 2),
);
throw error;
}
};
import { LOG } from "@logger";
import base64 from "react-native-base64";
import axiosRequest from "../axiosRequest";
import type { PaymentCode } from "./types";
const log = LOG.extend("transactions");
export interface Transaction {
// biome-ignore lint/style/useNamingConvention: <api response>
type_paiement: number;
// biome-ignore lint/style/useNamingConvention: <api response>
type_paiement_label: PaymentCode;
marchand: string;
// biome-ignore lint/style/useNamingConvention: <api response>
marchand_name: string;
service: string;
montant: number;
date: string;
commentaire: string;
etat: boolean;
status: "SUCCESS" | "INITIATED" | "FAILED";
reference: string;
// biome-ignore lint/style/useNamingConvention: <api response>
transaction_id: number;
// biome-ignore lint/style/useNamingConvention: <api response>
marchand_code: string;
}
export interface TransactionHistoryResponse {
count: number;
next: string | null;
previous: string | null;
results: Transaction[];
}
export const getTransactionsHistory = async (): Promise<Transaction[]> => {
const basictoken = base64.encode("admin:admin");
log.http("getTransactionsHistory");
try {
const response = await axiosRequest<TransactionHistoryResponse>({
url: "/transactions/",
headers: {
// biome-ignore lint/style/useNamingConvention: <explanation>
Authorization: `Basic ${basictoken}`,
},
});
log.http("getTransactionsHistory |", JSON.stringify(response, null, 2));
// TODO: Update this when the api is fixed, response should not be reversed
return response.results.reverse();
} catch (error) {
log.error("getTransactionsHistory |", error);
throw error;
}
};
export type PaymentCode = "OM" | "FLOOZ" | "MTN" | "WAVE" | "CB";
export interface IpaymentType {
id: number;
code: PaymentCode;
etat: boolean;
}
export interface IpaginatedResponse<T> {
count: number;
next: string | null;
previous: string | null;
results: T;
}
export interface ImerchandInformations {
// biome-ignore lint/style/useNamingConvention: <Api response>
marchand_id: string;
nom: string;
code: string;
adresse: string;
// biome-ignore lint/style/useNamingConvention: <Api response>
url_succes: string;
// biome-ignore lint/style/useNamingConvention: <Api response>
url_echec: string;
entreprise: number;
user: number;
}
export interface IuserInformations {
username: string;
email: string;
// biome-ignore lint/style/useNamingConvention: <Api response>
first_name: string;
// biome-ignore lint/style/useNamingConvention: <Api response>
last_name: string;
marchand: ImerchandInformations;
}
import { LOG } from "@logger";
import base64 from "react-native-base64";
import axiosRequest from "../axiosRequest";
import type { IuserInformations } from "./types";
const log = LOG.extend("getUserInformations");
const getUserInformations = async (userAccessToken: string) => {
log.http("getUserInformations", userAccessToken);
const basictoken = base64.encode("admin:admin");
log.http("basictoken", basictoken);
const response = await axiosRequest<IuserInformations>({
url: "/user-info/",
method: "GET",
headers: {
// biome-ignore lint/style/useNamingConvention: <Header>
Authorization: `Basic ${basictoken}`,
},
});
log.http(JSON.stringify(response, null, 2));
return response;
};
export default getUserInformations;
export const parseUserInformationsErrors = (error: unknown): string => {
// if (error instanceof AxiosError && error.response) {
// switch (error.response.status) {
// case 401:
// return "Wrong username or password";
// default:
// return "Unknown Error. Please try again.";
// }
// }
// if (error instanceof AxiosError && error.request) {
// return "Network error";
// }
return "Failure to fetch user informations. Please try again.";
};
import { LOG } from "@logger";
import base64 from "react-native-base64";
import axiosRequest from "../axiosRequest";
const log = LOG.extend("wavePayment");
export interface IwavePaymentStarter {
// biome-ignore lint/style/useNamingConvention: <API requirement>
type_paiement: 2; // id 2 is for wave.
marchand: string;
service: string;
montant: number;
}
export interface IwaveStarterRespone {
id: string;
amount: string;
// biome-ignore lint/style/useNamingConvention: <api response>
checkout_status: string;
// biome-ignore lint/style/useNamingConvention: <api response>
client_reference: unknown;
currenfy: string;
// biome-ignore lint/style/useNamingConvention: <api response>
error_url: string;
// biome-ignore lint/style/useNamingConvention: <api response>
last_payment_errror: unknown;
// biome-ignore lint/style/useNamingConvention: <api response>
business_name: string;
// biome-ignore lint/style/useNamingConvention: <api response>
payment_status: string;
// biome-ignore lint/style/useNamingConvention: <api response>
succes_url: string;
// biome-ignore lint/style/useNamingConvention: <api response>
wave_launch_url: string;
// biome-ignore lint/style/useNamingConvention: <api response>
when_completed: unknown;
// biome-ignore lint/style/useNamingConvention: <api response>
when_created: string;
// biome-ignore lint/style/useNamingConvention: <api response>
when_expires: string;
}
export interface IwaveStatusResponse {
status: string;
code: number;
message: IwaveStarterRespone;
}
const basictoken = base64.encode("admin:admin");
export const initTransaction = async (payload: IwavePaymentStarter) => {
log.http("initTransaction", payload);
// const basictoken = base64.encode("admin:admin");
try {
const response = await axiosRequest<IwaveStarterRespone>({
url: "/transactions/",
method: "POST",
headers: {
// biome-ignore lint/style/useNamingConvention: <explanation>
Authorization: `Basic ${basictoken}`,
},
data: payload,
});
log.http("initTransaction |", JSON.stringify(response, null, 2));
return response;
} catch (error) {
log.error("initTransaction |", error);
throw error;
}
};
export const getTransactionStatus = async (id: string) => {
log.http("getTransactionStatus", id);
try {
const response = await axiosRequest<IwaveStarterRespone>({
url: `/wave-session/${id}/`,
headers: {
// biome-ignore lint/style/useNamingConvention: <explanation>
Authorization: `Basic ${basictoken}`,
},
});
log.http("getTransactionStatus |", JSON.stringify(response, null, 2));
return response;
} catch (error) {
log.error("getTransactionStatus |", error);
throw error;
}
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment