pull/14/head
Anthony RICHARD 5 months ago committed by Anthony RICHARD
parent 96176e7c39
commit 9b7f77dd74

@ -0,0 +1,9 @@
import { Stack } from "expo-router";
export default function QuizLayout() {
return (
<Stack initialRouteName="quiz">
<Stack.Screen name="quiz" options={{ headerShown: false }} />
</Stack>
);
}

@ -0,0 +1,144 @@
import BackButton from "@/components/BackButton";
import { toBgColor, toTextColor } from "@/components/Constants";
import ActivityQuestion, {
ActivityQuestionRef,
} from "@/components/quiz/ActivityQuestion";
import AgeQuestion, { AgeQuestionRef } from "@/components/quiz/AgeQuestion";
import BeginnerQuestion, {
BeginnerQuestionRef,
} from "@/components/quiz/BeginnerQuestion";
import FrequencyQuestion, {
FrequencyQuestionRef,
} from "@/components/quiz/FrequencyQuestion";
import GenderQuestion, {
GenderQuestionRef,
} from "@/components/quiz/GenderQuestion";
import GoalQuestion, { GoalQuestionRef } from "@/components/quiz/GoalQuestion";
import HeightQuestion, {
HeightQuestionRef,
} from "@/components/quiz/HeightQuestion";
import SleepQuestion, {
SleepQuestionRef,
} from "@/components/quiz/SleepQuestion";
import SportQuestion, {
SportQuestionRef,
} from "@/components/quiz/SportQuestion";
import WeightQuestion, {
WeightQuestionRef,
} from "@/components/quiz/WeightQuestion";
import Button from "@/components/ui/Button";
import Screen from "@/components/ui/Screen";
import Text from "@/components/ui/Text";
import { useSession } from "@/ctx";
import { useRouter } from "expo-router";
import React, { useRef, useState } from "react";
import { View } from "react-native";
export default function Quiz() {
const { signIn, session } = useSession();
const router = useRouter();
const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
const goalRef = useRef<GoalQuestionRef>(null);
const genderRef = useRef<GenderQuestionRef>(null);
const weightRef = useRef<WeightQuestionRef>(null);
const heightRef = useRef<HeightQuestionRef>(null);
const ageRef = useRef<AgeQuestionRef>(null);
const beginnerRef = useRef<BeginnerQuestionRef>(null);
const activityRef = useRef<ActivityQuestionRef>(null);
const frequencyRef = useRef<FrequencyQuestionRef>(null);
const sportQuestionRef = useRef<SportQuestionRef>(null);
const sleepQuestionRef = useRef<SleepQuestionRef>(null);
interface Question<T = any> {
component: React.ForwardRefExoticComponent<T & React.RefAttributes<any>>;
props: T;
}
const questions: Question[] = [
{ component: GoalQuestion, props: { ref: goalRef } },
{ component: GenderQuestion, props: { ref: genderRef } },
{
component: WeightQuestion,
props: { ref: weightRef },
},
{ component: HeightQuestion, props: { ref: heightRef } },
{ component: AgeQuestion, props: { ref: ageRef } },
{ component: BeginnerQuestion, props: { ref: beginnerRef } },
{ component: ActivityQuestion, props: { ref: activityRef } },
//{ component: IllnessQuestion, props: {} },
{
component: FrequencyQuestion,
props: { ref: frequencyRef, isMale: genderRef.current?.getAnswer() },
},
{ component: SportQuestion, props: { ref: sportQuestionRef } },
{ component: SleepQuestion, props: { ref: sleepQuestionRef } },
];
const goNext = () => {
if (currentQuestionIndex < questions.length - 1) {
setCurrentQuestionIndex(currentQuestionIndex + 1);
} else {
Collect();
}
};
function Collect() {
if (session) {
session.healthProblems = [];
session.goals = goalRef.current?.getAnswer();
session.sexe = genderRef.current?.getAnswer();
session.weight = weightRef.current?.getAnswer();
session.height = heightRef.current?.getAnswer();
session.age = ageRef.current?.getAnswer();
session.sportLevel = activityRef.current?.getAnswer();
session.nbSessionPerWeek = frequencyRef.current?.getAnswer();
session.sports = sportQuestionRef.current?.getAnswer();
session.sleepLevel = sleepQuestionRef.current?.getAnswer();
signIn(session);
}
router.replace("/(tabs)/(home)/HomeScreen");
}
return (
<Screen>
<View className="gap-4 justify-between h-full">
<View className="flex-row justify-between items-center gap-4">
<BackButton
className="mt-2"
onPress={() => {
if (currentQuestionIndex === 0) {
router.replace("/(auth)/log-in");
} else {
setCurrentQuestionIndex((i) => Math.max(i - 1, 0));
}
}}
icon={currentQuestionIndex === 0 ? "close" : "arrowleft"}
/>
<Text size="2xl" weight="bold">
Questionnaire
</Text>
<View className={"px-4 py-2 rounded-2xl" + " " + toBgColor("blue")}>
<Text className={toTextColor("blue")} weight="bold">
{currentQuestionIndex + 1} sur {questions.length}
</Text>
</View>
</View>
{questions.map((question, index) =>
React.createElement(question.component, {
isVisible: index === currentQuestionIndex,
key: index,
...question.props,
})
)}
<Button afterIcon="arrowright" onPress={goNext}>
{currentQuestionIndex == questions.length - 1
? "Terminer"
: "Suivant"}
</Button>
</View>
</Screen>
);
}

@ -1,26 +1,26 @@
import {Redirect, Tabs, useRouter} from "expo-router";
import { useSession } from "@/ctx";
import React from "react";
import { AntDesign, Ionicons, MaterialIcons } from "@expo/vector-icons";
import { Redirect, Tabs, useRouter } from "expo-router";
import React from "react";
import Loading from "../loading";
export default function TabBarLayout() {
const { session, isLoading } = useSession();
const router = useRouter()
const router = useRouter();
const sizeIcon = 24;
// You can keep the splash screen open, or render a loading screen like we do here.
if (isLoading) {
return <Loading />;
}
// Only require authentication within the (app) group's layout as users
// need to be able to access the (auth) group and sign in again.
if (!session) {
// On web, static rendering will stop here as the user is not authenticated
// in the headless Node process that the pages are rendered in.
return <Redirect href="/log-in" />;
return <Redirect href={"/log-in"} />;
}
console.log(session);
if (!session.isQuizDone()) {
return <Redirect href={"/quiz"} />;
}
return (
<Tabs
screenOptions={{

@ -1,32 +1,56 @@
import {Link, router, Stack, usePathname, useRouter} from 'expo-router';
import {Button, StyleSheet, Text, View} from 'react-native';
import React from "react";
import {
Image,
SafeAreaView,
StatusBar,
Text,
TouchableOpacity,
View,
} from "react-native";
export default function NotFoundScreen() {
const pathname = usePathname();
const router = useRouter();
return (
<>
<Stack.Screen options={{ title: 'Oops!' }} />
<View style={styles.container}>
<Text>This screen {pathname} doesn't exist: {pathname}</Text>
<Button title="Retour Home" onPress={() => router.replace("/")}/>
<Text>Go to home screen!</Text>
<SafeAreaView className="flex-1 bg-white">
<StatusBar barStyle="dark-content" />
{/* Back Button Area */}
<View className="p-4 absolute top-11 left-0 z-10">
<Text className="text-2xl text-gray-300"></Text>
</View>
{/* Main Content */}
<View className="flex-1 items-center justify-center px-5">
{/* Magnifying Glass Image */}
<Image
source={{
uri: "https://hebbkx1anhila5yf.public.blob.vercel-storage.com/Capture%20d%E2%80%99e%CC%81cran%202025-01-21%20a%CC%80%2008.21.49-5FFPPKCnwJH2QV30OeJup36tsu0CGA.png",
}}
className="w-[200px] h-[200px] mb-10"
resizeMode="contain"
/>
{/* Error Text */}
<Text className="text-4xl font-bold text-gray-800 mb-4">Not Found</Text>
{/* Error Message */}
<Text className="text-lg text-gray-600 text-center mb-6">
Whoops! Coach S can't find this page :(
</Text>
{/* Status Code */}
<View className="bg-red-100/20 py-2 px-4 rounded-lg flex-row items-center mb-10">
<Text className="text-red-400 text-base">
:warning: Status Code: 404
</Text>
</View>
{/* Home Button */}
<TouchableOpacity className="bg-[#F4804F] py-4 px-8 rounded-full flex-row items-center absolute bottom-10 left-5 right-5">
<Text className="text-white text-lg font-semibold text-center flex-1">
Take Me Home
</Text>
</TouchableOpacity>
</View>
</>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
padding: 20,
},
link: {
marginTop: 15,
paddingVertical: 15,
},
});

@ -1,15 +1,16 @@
import { SessionProvider } from "@/ctx";
import "@/global.css";
import FontAwesome from "@expo/vector-icons/FontAwesome";
import { useFonts } from "expo-font";
import { Slot } from "expo-router";
import * as SplashScreen from "expo-splash-screen";
import { useEffect } from "react";
import { SessionProvider } from "@/ctx";
import {Slot, Stack} from "expo-router";
import "@/global.css";
// Prevent the splash screen from auto-hiding before asset loading is complete.
SplashScreen.preventAutoHideAsync();
export default function RootLayout() {
const [loaded, error] = useFonts({
const [loaded] = useFonts({
SpaceMono: require("../assets/fonts/SpaceMono-Regular.ttf"),
...FontAwesome.font,
});
@ -26,7 +27,7 @@ export default function RootLayout() {
return (
<SessionProvider>
<Slot/>
<Slot />
</SessionProvider>
);
}

@ -0,0 +1,15 @@
import { useSession } from "@/ctx";
import { Redirect } from "expo-router";
import Loading from "./loading";
export default function Index() {
const { session, isLoading } = useSession();
if (isLoading) return <Loading />;
if (session) {
return <Redirect href="/(tabs)/(home)/HomeScreen" />;
}
return <Redirect href="/log-in" />;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

@ -5,6 +5,6 @@ module.exports = function (api) {
["babel-preset-expo", { jsxImportSource: "nativewind" }],
"nativewind/babel",
],
plugins: ["react-native-reanimated/plugin"],
};
};

@ -1,26 +1,25 @@
import { router } from "expo-router";
import Button from "./ui/Button";
import { TouchableOpacityProps } from "react-native";
import { AntDesign } from "@expo/vector-icons";
import { router } from "expo-router";
import React from "react";
import { AntDesignIconNames } from "./Icons";
import Button, { ButtonProps } from "./ui/Button";
interface Props extends TouchableOpacityProps {
interface Props extends ButtonProps {
icon?: AntDesignIconNames;
}
export default React.forwardRef<any, Props>(
(props, ref): React.ReactElement => {
const { icon, onPress } = props;
({ icon, onPress, className, ...props }, ref): React.ReactElement => {
const defaultOnPress = () => {
router.back();
};
return (
<Button
className="h-16 w-16 mb-4"
className={"h-16 w-16 mb-4" + " " + className}
onPress={onPress ?? defaultOnPress}
{...ref}
{...props}
>
<AntDesign name={icon ?? "arrowleft"} size={24} />
</Button>

@ -0,0 +1,149 @@
import React, { forwardRef } from "react";
import { View, TouchableOpacity, ViewProps } from "react-native";
import Text from "./ui/Text";
import {
AntDesign,
Entypo,
FontAwesome6,
Ionicons,
MaterialCommunityIcons,
} from "@expo/vector-icons";
import {
AntDesignIconNames,
CommunityIconNames,
EntypoIconNames,
FontAwesome6IconNames,
FontAwesomeIconNames,
IonIconNames,
} from "./Icons";
export type CheckBoxDirection = "row" | "col";
interface CheckBoxProps extends ViewProps {
label?: string;
onChange: () => void;
antIcon?: AntDesignIconNames;
entypoIcon?: EntypoIconNames;
fontAwesomeIcon?: FontAwesomeIconNames;
fontAwesome6Icon?: FontAwesome6IconNames;
communityIcon?: CommunityIconNames;
IonIcon?: IonIconNames;
value: boolean;
isCheckIconVisible?: boolean;
endText?: string;
direction?: CheckBoxDirection;
}
export default forwardRef<any, CheckBoxProps>(
(
{
label,
onChange,
antIcon,
entypoIcon,
fontAwesomeIcon,
fontAwesome6Icon,
communityIcon,
IonIcon,
value,
isCheckIconVisible,
endText,
direction,
className,
...props
},
ref
) => {
return (
<TouchableOpacity
onPress={onChange}
className={
"items-center p-4 rounded-3xl" +
" " +
((direction ?? "row") == "row"
? "flex-row justify-between gap-4"
: "justify-center gap-1") +
" " +
(value
? "bg-orange-600 border-4 border-orange-300"
: "bg-gray-300 border-4 border-gray-300") +
" " +
(className ?? "")
}
{...props}
{...ref}
>
<View>
{antIcon ? (
<AntDesign
name={antIcon}
size={30}
color={value ? "white" : "black"}
/>
) : null}
{entypoIcon ? (
<Entypo
name={entypoIcon}
size={30}
color={value ? "white" : "black"}
/>
) : null}
{communityIcon ? (
<MaterialCommunityIcons
name={communityIcon}
size={30}
color={value ? "white" : "black"}
/>
) : null}
{fontAwesomeIcon ? (
<FontAwesome6
name={fontAwesomeIcon}
size={30}
color={value ? "white" : "black"}
/>
) : null}
{fontAwesome6Icon ? (
<FontAwesome6
name={fontAwesome6Icon}
size={30}
color={value ? "white" : "black"}
/>
) : null}
{IonIcon ? (
<Ionicons
name={IonIcon}
size={30}
color={value ? "white" : "black"}
/>
) : null}
</View>
{label != null ? (
<Text
weight="bold"
color={value ? "white" : "black"}
className={(direction ?? "row") == "row" ? "flex-1" : ""}
>
{label}
</Text>
) : null}
{isCheckIconVisible ? (
<View
className={
"h-5 w-5 rounded border justify-center items-center" +
" " +
(value ? "border-white" : "border-black")
}
>
{value && <View className="h-2 w-2 bg-white" />}
</View>
) : null}
{endText != null ? (
<Text color={value ? "white" : "black"}>{endText}</Text>
) : null}
</TouchableOpacity>
);
}
);

@ -1,5 +1,17 @@
export type Size = "xs" | "md" | "lg" | "xl" | "2xl" | "3xl";
export type Color = "black" | "white" | "orange" | "red";
export type Size =
| "xs"
| "md"
| "lg"
| "xl"
| "2xl"
| "3xl"
| "4xl"
| "5xl"
| "6xl"
| "7xl"
| "8xl"
| "9xl";
export type Color = "black" | "white" | "orange" | "red" | "blue";
export type Weight = "thin" | "normal" | "bold" | "extrabold";
export type Position = "left" | "center" | "right";
@ -17,6 +29,33 @@ export function toTextSize(size: Size): string {
return "text-2xl";
case "3xl":
return "text-3xl";
case "4xl":
return "text-4xl";
case "5xl":
return "text-5xl";
case "6xl":
return "text-6xl";
case "7xl":
return "text-7xl";
case "8xl":
return "text-8xl";
case "9xl":
return "text-9xl";
}
}
export function toBgColor(color: Color): string {
switch (color) {
case "black":
return "bg-black";
case "white":
return "bg-white";
case "orange":
return "bg-orange-500";
case "red":
return "bg-red-500";
case "blue":
return "bg-blue-200";
}
}
@ -30,6 +69,8 @@ export function toTextColor(color: Color): string {
return "text-orange-500";
case "red":
return "text-red-500";
case "blue":
return "text-blue-500";
}
}

@ -1,3 +1,26 @@
import { AntDesign } from "@expo/vector-icons";
import {
AntDesign,
Entypo,
FontAwesome,
Ionicons,
MaterialCommunityIcons,
} from "@expo/vector-icons";
export type AntDesignIconNames = keyof typeof AntDesign.glyphMap;
export type EntypoIconNames = keyof typeof Entypo.glyphMap;
export type FontAwesomeIconNames = keyof typeof FontAwesome.glyphMap;
export type FontAwesome6IconNames =
| "person-running"
| "person-walking"
| "person-hiking"
| "skateboarding"
| "bike"
| "basketball"
| "heart"
| "yoga"
| "setting"
| "beer"
| "shield-heart"
| "weight-scale";
export type CommunityIconNames = keyof typeof MaterialCommunityIcons.glyphMap;
export type IonIconNames = keyof typeof Ionicons.glyphMap;

@ -1,40 +1,40 @@
import {Image, ImageBackground, Text, TouchableOpacity, View} from "react-native";
import {Ionicons} from "@expo/vector-icons";
import { useSession } from "@/ctx";
import React from "react";
import {useSession} from "@/ctx";
import { Image, ImageBackground, Text, View } from "react-native";
export default function WelcomeComponent() {
const date = new Date();
const formattedDate = date.toLocaleDateString('fr-FR', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
const {session} = useSession();
const date = new Date();
const formattedDate = date.toLocaleDateString("fr-FR", {
year: "numeric",
month: "long",
day: "numeric",
});
const { session } = useSession();
return (
<View className="rounded-2xl overflow-hidden shadow-lg h-full bg-black justify-center">
<ImageBackground
source={require("assets/images/black-back.png")}
className="w-full h-full justify-center"
>
<View className="flex-row items-center justify-between">
<View className="flex-row items-center w-full">
<Image
className="h-16 w-16 rounded-2xl"
source={require("assets/images/sigma-profile.jpeg")}
/>
return (
<View className="rounded-2xl overflow-hidden shadow-lg h-full bg-black justify-center">
<ImageBackground
source={require("assets/images/black-back.png")}
className="w-full h-full justify-center"
>
<View className="flex-row items-center justify-between">
<View className="flex-row items-center w-full">
<Image className="h-16 w-16 rounded-2xl"
source={require("assets/images/sigma-profile.jpeg")}
/>
<View>
<Text className="text-gray-500 font-semibold ml-4">{formattedDate}</Text>
<Text className="text-white text-4xl ml-4 mt-0.5">Hello {session} !</Text>
</View>
</View>
</View>
</ImageBackground>
</View>
<View>
<Text className="text-gray-500 font-semibold ml-4">
{formattedDate}
</Text>
<Text className="text-white text-4xl ml-4 mt-0.5">
Hello {session?.name} !
</Text>
</View>
</View>
</View>
</ImageBackground>
</View>
);
}
}

@ -2,6 +2,7 @@ import { Workout } from "@/model/Workout";
import { AntDesign, MaterialCommunityIcons } from "@expo/vector-icons";
import { useRouter } from "expo-router";
import React from "react";
import { ImageBackground, Text, TouchableOpacity, View } from "react-native";
interface WorkoutCardComponentProps {
exercise?: Workout;

@ -1,16 +1,14 @@
import { MaterialIcons } from "@expo/vector-icons";
import React from "react";
import { View } from "react-native";
import Text, { ExtendedTextProps } from "../ui/Text";
import { MaterialIcons } from "@expo/vector-icons";
interface Props extends ExtendedTextProps {
isVisible: boolean;
}
export default React.forwardRef<any, Props>(
(props, ref): React.ReactElement => {
const { isVisible, ...rest } = props;
({ isVisible, ...props }, ref): React.ReactElement => {
const buildClassName = (): string => {
return (
"flex-row items-center gap-2 bg-red-300 p-4 border-2 border-red-500 rounded-3xl" +
@ -22,7 +20,7 @@ export default React.forwardRef<any, Props>(
return (
<View className={buildClassName()} {...ref}>
<MaterialIcons name="error" size={24} color={"red"} />
<Text position="center" weight="bold" {...rest} />
<Text position="center" weight="bold" {...props} />
</View>
);
}

@ -1,7 +1,7 @@
import { AntDesign } from "@expo/vector-icons";
import React from "react";
import { TextInput, TextInputProps, View } from "react-native";
import { AntDesignIconNames } from "../Icons";
import { AntDesign } from "@expo/vector-icons";
import Text from "../ui/Text";
export interface FormInputProps extends TextInputProps {
afterIcon?: AntDesignIconNames;
@ -12,8 +12,8 @@ interface Props extends FormInputProps {
}
export default React.forwardRef<any, Props>(
(props, ref): React.ReactElement => {
const {
(
{
onBlur,
onChangeText,
value,
@ -22,9 +22,10 @@ export default React.forwardRef<any, Props>(
label,
placeholder,
onPress,
...rest
} = props;
...props
},
ref
): React.ReactElement => {
return (
<View className="gap-2">
<Text weight="bold">{label}</Text>
@ -37,7 +38,7 @@ export default React.forwardRef<any, Props>(
onChangeText={onChangeText}
value={value}
{...ref}
{...rest}
{...props}
/>
{afterIcon != null ? (
<AntDesign name={afterIcon} size={24} onPress={onPress} />

@ -1,17 +1,19 @@
import { View, ViewProps } from "react-native";
import TextInput from "./FormInput";
import Button from "../ui/Button";
import PasswordTextInput from "./SecretTextInput";
import { useSession } from "@/ctx";
import { UserServiceStub } from "@/service/user/user.service.stub";
import { router } from "expo-router";
import React from "react";
import { View, ViewProps } from "react-native";
import { isEmail } from "validator";
import { EMPTY_FIELD, INVALID_EMAIL } from "../Errors";
import Button from "../ui/Button";
import FormError from "./FormError";
import { isEmail } from "validator";
import { useSession } from "@/ctx";
import { router } from "expo-router";
import TextInput from "./FormInput";
import PasswordTextInput from "./SecretTextInput";
const userService = new UserServiceStub();
export default React.forwardRef<any, ViewProps>(
(props, ref): React.ReactElement => {
const { ...rest } = props;
({ ...props }, ref): React.ReactElement => {
const [email, setEmail] = React.useState("");
const [password, setPassword] = React.useState("");
const [error, setError] = React.useState("");
@ -33,8 +35,13 @@ export default React.forwardRef<any, ViewProps>(
if (isEmail(email)) {
if (password != "") {
validateForm();
signIn();
router.replace("/HomeScreen");
const user = userService.login(email, password);
if (user) {
signIn(user);
router.replace("/HomeScreen");
} else {
invalidateForm("Email ou mot de passe incorrect");
}
} else {
invalidateForm(EMPTY_FIELD);
}
@ -47,7 +54,7 @@ export default React.forwardRef<any, ViewProps>(
};
return (
<View className="gap-4" {...ref} {...rest}>
<View className="gap-4" {...ref} {...props}>
<View className="gap-4">
<TextInput
beforeIcon="mail"

@ -2,8 +2,10 @@ import React, { useState } from "react";
import FormInput, { FormInputProps } from "./FormInput";
export default React.forwardRef<any, FormInputProps>(
(props, ref): React.ReactElement => {
const { onBlur, onChangeText, value, label, ...rest } = props;
(
{ onBlur, onChangeText, value, label, ...props },
ref
): React.ReactElement => {
const [showPassword, setShowPassword] = useState(false);
const toggleShowPassword = () => {
@ -22,7 +24,7 @@ export default React.forwardRef<any, FormInputProps>(
onPress={toggleShowPassword}
secureTextEntry={!showPassword}
{...ref}
{...rest}
{...props}
/>
);
}

@ -1,15 +1,14 @@
import { View, ViewProps } from "react-native";
import TextInput from "./FormInput";
import Button from "../ui/Button";
import React from "react";
import PasswordTextInput from "./SecretTextInput";
import { View, ViewProps } from "react-native";
import { isEmail } from "validator";
import { EMPTY_FIELD, INVALID_EMAIL, NOT_MATCHING_PASSWORD } from "../Errors";
import Button from "../ui/Button";
import FormError from "./FormError";
import TextInput from "./FormInput";
import PasswordTextInput from "./SecretTextInput";
export default React.forwardRef<any, ViewProps>(
(props, ref): React.ReactElement => {
const { ...rest } = props;
({ ...props }, ref): React.ReactElement => {
const [email, setEmail] = React.useState("");
const [password, setPassword] = React.useState("");
const [confirmPassword, setConfirmPassword] = React.useState("");
@ -52,7 +51,7 @@ export default React.forwardRef<any, ViewProps>(
};
return (
<View className="gap-4" {...ref} {...rest}>
<View className="gap-4" {...ref} {...props}>
<View className="gap-4">
<TextInput
beforeIcon="mail"

@ -1,9 +1,9 @@
import BackButton from "@/components/BackButton";
import Button from "@/components/ui/Button";
import { AntDesign } from "@expo/vector-icons";
import { GestureResponderEvent, Modal, ModalProps, View } from "react-native";
import Text from "@/components/ui/Text";
import { AntDesign } from "@expo/vector-icons";
import React from "react";
import { GestureResponderEvent, Modal, ModalProps, View } from "react-native";
import Screen from "../ui/Screen";
interface Props extends ModalProps {
@ -12,11 +12,9 @@ interface Props extends ModalProps {
}
export default React.forwardRef<any, Props>(
(props, ref): React.ReactElement => {
const { email, onPress, ...rest } = props;
({ email, onPress, ...props }, ref): React.ReactElement => {
return (
<Modal {...ref} animationType="fade" {...rest}>
<Modal {...ref} animationType="fade" {...props}>
<Screen>
<BackButton onPress={onPress} />
<View className="gap-4">

@ -0,0 +1,78 @@
import { ESportLevel, SportLevels } from "@/model/enums/Enums";
import React, { useImperativeHandle, useState } from "react";
import { View } from "react-native";
import Checkbox from "../CheckBox";
import {
AntDesignIconNames,
CommunityIconNames,
EntypoIconNames,
FontAwesome6IconNames,
} from "../Icons";
import Question, { QuestionChildProps } from "./Question";
export interface ActivityQuestionRef {
getAnswer: () => ESportLevel;
}
export default React.forwardRef<ActivityQuestionRef, QuestionChildProps>(
({ ...props }, ref): React.ReactElement => {
const [checkedItems, setCheckedItems] = useState([
true,
...Array(4).fill(false),
]);
const handleChange = (index: number) => {
const newCheckedState = checkedItems.map((_, i) => i === index);
setCheckedItems(newCheckedState);
};
useImperativeHandle(ref, () => ({
getAnswer: () => {
let selected = 0;
checkedItems.forEach((item, index) => {
if (item) {
selected = index;
}
});
return SportLevels[SportLevels.length - 1 - selected];
},
}));
interface IData {
label: string;
commIcon?: CommunityIconNames;
antIcon?: AntDesignIconNames;
entypoIcon?: EntypoIconNames;
fontAwesomeIcon?: FontAwesome6IconNames;
}
const data: IData[] = [
{ label: "Athlète", antIcon: "smile-circle" },
{ label: "Très sportif", antIcon: "smileo" },
{ label: "Un peu sportif", antIcon: "meh" },
{ label: "Peu sportif", antIcon: "frowno" },
{ label: "Pas du tout sportif", antIcon: "frown" },
];
return (
<Question
question="Comment estimez-vous votre niveau d'activité ?"
{...ref}
{...props}
>
<View className="gap-2">
{data.map((item, index) => (
<Checkbox
key={index}
label={item.label}
value={checkedItems[index]}
onChange={() => handleChange(index)}
antIcon={item.antIcon}
endText={(data.length - index).toString()}
/>
))}
</View>
</Question>
);
}
);

@ -0,0 +1,55 @@
import React, {
forwardRef,
ReactElement,
useImperativeHandle,
useState,
} from "react";
import { View } from "react-native";
import Slider from "../ui/Slider";
import Text from "../ui/Text";
import Question, { QuestionChildProps } from "./Question";
const MIN_AGE = 18;
const MAX_AGE = 100;
export interface AgeQuestionRef {
getAnswer: () => number;
}
export default forwardRef<AgeQuestionRef, QuestionChildProps>(
(props, ref): ReactElement => {
const [answer, setAnswer] = useState<number>(MIN_AGE);
useImperativeHandle(ref, () => ({
getAnswer: () => answer,
}));
return (
<Question question="Quel âge avez-vous ?" {...ref} {...props}>
<View className="flex-row justify-center">
{answer <= MIN_AGE ? (
<Text className="mt-8" size="4xl">
- de
</Text>
) : null}
{answer >= MAX_AGE ? (
<Text className="mt-8" size="4xl">
+ de
</Text>
) : null}
<Text size="8xl" weight="bold">
{answer}
</Text>
<Text className="mt-8 ml-1" size="4xl">
ans
</Text>
</View>
<Slider
minimumValue={MIN_AGE}
maximumValue={MAX_AGE}
onValueChange={setAnswer}
/>
</Question>
);
}
);

@ -0,0 +1,59 @@
import React, {
forwardRef,
ReactElement,
useImperativeHandle,
useState,
} from "react";
import { Image, View } from "react-native";
import Question, { QuestionChildProps } from "./Question";
//@ts-ignore
import BenchImage from "@/assets/images/bench.png";
import CheckBox from "../CheckBox";
export interface BeginnerQuestionRef {
getAnswer: () => boolean;
}
export default forwardRef<BeginnerQuestionRef, QuestionChildProps>(
(props, ref): ReactElement => {
const [answer, setAnswer] = useState<boolean>(false);
useImperativeHandle(ref, () => ({
getAnswer: () => answer,
}));
function handleChangeOne() {
setAnswer(true);
}
function handleChangeTwo() {
setAnswer(false);
}
return (
<Question
question="Êtes-vous un novice de la musculation ?"
{...ref}
{...props}
>
<Image className="self-center" source={BenchImage} alt="" />
<View>
<CheckBox
className=""
label="Oui"
value={answer}
onChange={handleChangeOne}
fontAwesomeIcon={"check"}
/>
<CheckBox
label="Non"
value={!answer}
onChange={handleChangeTwo}
entypoIcon={"cross"}
/>
</View>
</Question>
);
}
);

@ -0,0 +1,48 @@
import React, { useImperativeHandle } from "react";
import { View } from "react-native";
import SegmentedControl from "../ui/SegmentedControl";
import Text from "../ui/Text";
import Question, { QuestionChildProps } from "./Question";
const ANSWERS = ["1", "2", "3", "4", "5"];
export interface FrequencyQuestionRef {
getAnswer: () => number;
}
export interface FrequencyQuestionProps extends QuestionChildProps {
isMale: boolean;
}
export default React.forwardRef<FrequencyQuestionRef, FrequencyQuestionProps>(
({ isMale, ...props }, ref): React.ReactElement => {
const [answer, setAnswer] = React.useState("1");
useImperativeHandle(ref, () => ({
getAnswer: () => parseInt(answer),
}));
return (
<Question
question="Nombre de séance(s) par semaine ?"
{...ref}
{...props}
>
<View className="items-center">
<Text size="2xl">
Je suis {isMale ? "prêt" : "prête"} à m'entraîner
</Text>
<Text size="9xl" weight="bold">
{answer}
</Text>
<Text size="2xl">fois par semaine !</Text>
</View>
<SegmentedControl
values={ANSWERS}
selectedIndex={ANSWERS.indexOf(answer)}
onValueChange={setAnswer}
/>
</Question>
);
}
);

@ -0,0 +1,55 @@
import React, {
forwardRef,
ReactElement,
useImperativeHandle,
useState,
} from "react";
import { View } from "react-native";
import Checkbox from "../CheckBox";
import Question, { QuestionChildProps } from "./Question";
export interface GenderQuestionRef {
getAnswer: () => boolean;
}
export default forwardRef<GenderQuestionRef, QuestionChildProps>(
({ ...props }, ref): ReactElement => {
const [answer, setAnswer] = useState(true);
useImperativeHandle(ref, () => ({
getAnswer: () => answer,
}));
const handleChangeOne = () => {
setAnswer(true);
};
const handleChangeTwo = () => {
setAnswer(false);
};
return (
<Question
question="Quel est votre genre physiologique ?"
{...ref}
{...props}
>
<View className="gap-2">
<Checkbox
label="Homme"
value={answer}
onChange={handleChangeOne}
antIcon={"man"}
isCheckIconVisible={true}
/>
<Checkbox
label="Femme"
value={!answer}
onChange={handleChangeTwo}
antIcon={"woman"}
isCheckIconVisible={true}
/>
</View>
</Question>
);
}
);

@ -0,0 +1,88 @@
import { EGoal, Goals } from "@/model/enums/Enums";
import React, {
forwardRef,
ReactElement,
useImperativeHandle,
useState,
} from "react";
import { View } from "react-native";
import CheckBox from "../CheckBox";
import {
AntDesignIconNames,
CommunityIconNames,
EntypoIconNames,
FontAwesome6IconNames,
IonIconNames,
} from "../Icons";
import Question, { QuestionChildProps } from "./Question";
export interface GoalQuestionRef {
getAnswer: () => EGoal;
}
export default forwardRef<GoalQuestionRef, QuestionChildProps>(
({ ...props }, ref): ReactElement => {
const [checkedItems, setCheckedItems] = useState([
true,
...Array(4).fill(false),
]);
interface IData {
label: string;
commIcon?: CommunityIconNames;
antIcon?: AntDesignIconNames;
entypoIcon?: EntypoIconNames;
fontAwesomeIcon?: FontAwesome6IconNames;
ionIcon?: IonIconNames;
}
const data: IData[] = [
{ label: "Perte de poids", commIcon: "weight" },
{ label: "Renforcement musculaire", commIcon: "arm-flex-outline" },
{ label: "Prise de masse", ionIcon: "beer-outline" },
{ label: "Amélioration endurance", fontAwesomeIcon: "shield-heart" },
{ label: "Maintenir en forme", antIcon: "linechart" },
];
const handleChange = (index: number) => {
const newCheckedState = checkedItems.map((_, i) => i === index);
setCheckedItems(newCheckedState);
};
useImperativeHandle(ref, () => ({
getAnswer: () => {
let selected = 0;
checkedItems.forEach((item, index) => {
if (item) {
selected = index;
}
});
return Goals[selected];
},
}));
return (
<Question
question="Quel est votre objectif dans l'application ?"
{...ref}
{...props}
>
<View className="gap-2">
{data.map((item, index) => (
<CheckBox
key={index}
label={item.label}
value={checkedItems[index]}
onChange={() => handleChange(index)}
fontAwesome6Icon={item.fontAwesomeIcon}
communityIcon={item.commIcon}
antIcon={item.antIcon}
entypoIcon={item.entypoIcon}
IonIcon={item.ionIcon}
/>
))}
</View>
</Question>
);
}
);

@ -0,0 +1,63 @@
import React, {
forwardRef,
ReactElement,
useImperativeHandle,
useState,
} from "react";
import { View } from "react-native";
import SegmentedControl from "../ui/SegmentedControl";
import Slider from "../ui/Slider";
import Text from "../ui/Text";
import Question, { QuestionChildProps } from "./Question";
const MIN_HEIGHT = 120;
const MAX_HEIGHT = 250;
export interface HeightQuestionRef {
getAnswer: () => number;
}
export default forwardRef<HeightQuestionRef, QuestionChildProps>(
({ ...props }, ref): ReactElement => {
const [answer, setAnswer] = useState<number>(MIN_HEIGHT);
const UNITS = ["cm", "inch"];
const [unit, setUnit] = useState<string>("cm");
useImperativeHandle(ref, () => ({
getAnswer: () => answer,
}));
return (
<Question question="Quel est votre taille ?" {...ref} {...props}>
<SegmentedControl
values={UNITS}
selectedIndex={UNITS.indexOf(unit)}
onValueChange={setUnit}
/>
<View className="flex-row justify-center">
{answer <= MIN_HEIGHT ? (
<Text className="mt-8" size="4xl">
- de
</Text>
) : null}
{answer >= MAX_HEIGHT ? (
<Text className="mt-8" size="4xl">
+ de
</Text>
) : null}
<Text size="8xl" weight="bold">
{answer}
</Text>
<Text className="mt-8" size="4xl">
{unit}
</Text>
</View>
<Slider
minimumValue={MIN_HEIGHT}
maximumValue={MAX_HEIGHT}
onValueChange={setAnswer}
/>
</Question>
);
}
);

@ -0,0 +1,54 @@
import React, { useState } from "react";
import { Image, Text, View } from "react-native";
import { MultiSelect } from "react-native-element-dropdown";
import Question, { QuestionChildProps } from "./Question";
//@ts-ignore
import WheelChair from "@/assets/images/wheelchair.png";
import { EHealthProblem } from "@/model/enums/Enums";
export default React.forwardRef<any, QuestionChildProps>(
({ ...props }, ref) => {
const [selected, setSelected] = useState<string[]>([]);
type DataItem = {
label: string;
value: EHealthProblem;
};
const data: DataItem[] = [
{ label: "Arthrose", value: "ARTHROSE" },
{ label: "Migraine", value: "MIGRAINE" },
];
const renderItem = (item: { label: string }) => {
return (
<View className="p-4">
<Text>{item.label}</Text>
</View>
);
};
return (
<Question
question="Avez-vous des problèmes physiques ?"
{...props}
{...ref}
>
<Image className="self-center" source={WheelChair} alt="" />
<View className="border-2 border-orange-500 rounded-3xl p-4">
<MultiSelect
data={data}
labelField="label"
valueField="value"
placeholder="Selectionnez un problème physique "
searchPlaceholder="Search..."
value={selected}
onChange={setSelected}
renderItem={renderItem}
/>
</View>
</Question>
);
}
);

@ -0,0 +1,28 @@
import Text from "@/components/ui/Text";
import React from "react";
import { View, ViewProps } from "react-native";
export interface QuestionChildProps extends ViewProps {
isVisible?: boolean;
}
interface QuestionProps extends QuestionChildProps {
question: string;
}
export default React.forwardRef<any, QuestionProps>(
({ question, isVisible, children, ...props }, ref): React.ReactElement => {
const getClassName = () => {
return "gap-6" + " " + (isVisible ? "block" : "hidden");
};
return (
<View className={getClassName()} {...ref} {...props}>
<Text size="4xl" position="center" weight="bold">
{question}
</Text>
{children}
</View>
);
}
);

@ -0,0 +1,91 @@
import { ESleepLevel, SleepLevels } from "@/model/enums/Enums";
import React, { useImperativeHandle, useState } from "react";
import { View } from "react-native";
import Checkbox from "../CheckBox";
import { AntDesignIconNames } from "../Icons";
import Question, { QuestionChildProps } from "./Question";
export interface SleepQuestionRef {
getAnswer: () => ESleepLevel;
}
export default React.forwardRef<SleepQuestionRef, QuestionChildProps>(
({ ...props }, ref): React.ReactElement => {
const [checkedItems, setCheckedItems] = useState([
true,
...Array(4).fill(false),
]);
const handleChange = (index: number) => {
const newCheckedState = checkedItems.map((_, i) => i === index);
setCheckedItems(newCheckedState);
};
useImperativeHandle(ref, () => ({
getAnswer: () => {
let selected = 0;
checkedItems.forEach((item, index) => {
if (item) {
selected = index;
}
});
return SleepLevels[SleepLevels.length - 1 - selected];
},
}));
interface IData {
label: string;
icon: AntDesignIconNames;
endText: string;
}
const data: IData[] = [
{
label: "Excellent",
icon: "smile-circle",
endText: ">8 heures",
},
{
label: "Bien",
icon: "smileo",
endText: "7-8 heures",
},
{
label: "Mauvaise",
icon: "meh",
endText: "6-7 heures",
},
{
label: "Très mauvaise",
icon: "frowno",
endText: "3-4 heures",
},
{
label: "Insomniaque",
icon: "frown",
endText: "<2 heures",
},
];
return (
<Question
question="Quelle est la qualité de votre sommeil ?"
{...ref}
{...props}
>
<View className="gap-2">
{data.map((item, index) => (
<Checkbox
key={index}
label={item.label}
value={checkedItems[index]}
onChange={() => handleChange(index)}
antIcon={item.icon}
endText={item.endText}
/>
))}
</View>
</Question>
);
}
);

@ -0,0 +1,86 @@
import { ESport, Sports } from "@/model/enums/Enums";
import React, {
forwardRef,
ReactElement,
useImperativeHandle,
useState,
} from "react";
import { View } from "react-native";
import CheckBox from "../CheckBox";
import {
AntDesignIconNames,
CommunityIconNames,
EntypoIconNames,
FontAwesome6IconNames,
} from "../Icons";
import Question, { QuestionChildProps } from "./Question";
export interface SportQuestionRef {
getAnswer: () => ESport;
}
export default forwardRef<SportQuestionRef, QuestionChildProps>(
(props, ref): ReactElement => {
const [checkedItems, setCheckedItems] = useState([
true,
...Array(8).fill(false),
]);
const handleChange = (index: number) => {
const newCheckedState = checkedItems.map((_, i) => i === index);
setCheckedItems(newCheckedState);
};
useImperativeHandle(ref, () => ({
getAnswer: () => {
let selected = 0;
checkedItems.forEach((item, index) => {
if (item) {
selected = index;
}
});
return Sports[selected];
},
}));
interface IData {
label: string;
commIcon?: CommunityIconNames;
antIcon?: AntDesignIconNames;
entypoIcon?: EntypoIconNames;
fontAwesomeIcon?: FontAwesome6IconNames;
}
const data: IData[] = [
{ label: "Course", fontAwesomeIcon: "person-running" },
{ label: "Marche", fontAwesomeIcon: "person-walking" },
{ label: "Rando", fontAwesomeIcon: "person-hiking" },
{ label: "Skate", commIcon: "skateboarding" },
{ label: "Cyclisme", commIcon: "bike" },
{ label: "Basket", fontAwesomeIcon: "basketball" },
{ label: "Cardio", antIcon: "heart" },
{ label: "Yoga", commIcon: "yoga" },
{ label: "Autre", entypoIcon: "dots-three-horizontal" },
];
return (
<Question question="Quel sport pratiquez-vous ?" {...ref} {...props}>
<View className="flex-row justify-center flex-wrap gap-2">
{data.map((item, index) => (
<CheckBox
key={index}
label={item.label}
value={checkedItems[index]}
onChange={() => handleChange(index)}
fontAwesome6Icon={item.fontAwesomeIcon}
communityIcon={item.commIcon}
antIcon={item.antIcon}
entypoIcon={item.entypoIcon}
direction="col"
/>
))}
</View>
</Question>
);
}
);

@ -0,0 +1,65 @@
import React, {
forwardRef,
ReactElement,
useImperativeHandle,
useState,
} from "react";
import { View } from "react-native";
import SegmentedControl from "../ui/SegmentedControl";
import Slider from "../ui/Slider";
import Text from "../ui/Text";
import Question, { QuestionChildProps } from "./Question";
const MIN_WEIGHT = 40;
const MAX_WEIGHT = 200;
export interface WeightQuestionRef {
getAnswer: () => number;
}
const WeightQuestion = forwardRef<WeightQuestionRef, QuestionChildProps>(
(props, ref): ReactElement => {
const UNITS = ["kg", "lb"];
const [answer, setAnswer] = useState<number>(MIN_WEIGHT);
const [unit, setUnit] = useState<string>("kg");
useImperativeHandle(ref, () => ({
getAnswer: () => answer,
}));
return (
<Question question="Quel est votre poids ?" {...props}>
<SegmentedControl
values={UNITS}
selectedIndex={UNITS.indexOf(unit)}
onValueChange={setUnit}
/>
<View className="flex-row justify-center gap-2">
{answer <= MIN_WEIGHT && (
<Text className="mt-8" size="4xl">
- de
</Text>
)}
{answer >= MAX_WEIGHT && (
<Text className="mt-8" size="4xl">
+ de
</Text>
)}
<Text size="8xl" weight="bold">
{answer}
</Text>
<Text className="mt-8" size="4xl">
{unit}
</Text>
</View>
<Slider
minimumValue={MIN_WEIGHT}
maximumValue={MAX_WEIGHT}
onValueChange={setAnswer}
/>
</Question>
);
}
);
export default WeightQuestion;

@ -8,7 +8,7 @@ import { Size } from "../Constants";
export type ButtonStyle = "default" | "outline" | "secondary";
//@ts-ignore
interface Props extends TouchableOpacityProps {
export interface ButtonProps extends TouchableOpacityProps {
size?: Size;
style?: ButtonStyle;
insideClassName?: string;
@ -16,9 +16,9 @@ interface Props extends TouchableOpacityProps {
afterIcon?: AntDesignIconNames;
}
export default React.forwardRef<any, Props>(
(props, ref): React.ReactElement => {
const {
export default React.forwardRef<any, ButtonProps>(
(
{
size,
style,
beforeIcon,
@ -27,9 +27,10 @@ export default React.forwardRef<any, Props>(
className,
insideClassName,
children,
...rest
} = props;
...props
},
ref
): React.ReactElement => {
const getButtonStyle = (): string => {
switch (style ?? "default") {
case "default":
@ -67,11 +68,23 @@ export default React.forwardRef<any, Props>(
return 30;
case "3xl":
return 36;
case "4xl":
return 36;
case "5xl":
return 42;
case "6xl":
return 48;
case "7xl":
return 54;
case "8xl":
return 60;
case "9xl":
return 66;
}
};
return (
<View className={getButtonStyle()} {...ref} {...rest}>
<View className={getButtonStyle()} {...ref} {...props}>
<TouchableOpacity
className={"flex-row justify-center items-center gap-2 p-4 w-full"}
onPress={onPress}

@ -3,10 +3,9 @@ import { SafeAreaView } from "react-native-safe-area-context";
import React from "react";
export default React.forwardRef<any, ViewProps>(
(props, ref): React.ReactElement => {
const { children, ...rest } = props;
({ children, ...props }, ref): React.ReactElement => {
return (
<SafeAreaView className={"h-full p-4"} {...ref} {...rest}>
<SafeAreaView className={"h-full p-4"} {...ref} {...props}>
<View>{children}</View>
</SafeAreaView>
);

@ -0,0 +1,22 @@
import React from "react";
import SegmentedControl, {
SegmentedControlProps,
} from "@react-native-segmented-control/segmented-control";
export default React.forwardRef<any, SegmentedControlProps>(
(props, ref): React.ReactElement => {
return (
<SegmentedControl
fontStyle={{
fontSize: 16,
fontWeight: "bold",
color: "black",
}}
tintColor="#60A5FA"
backgroundColor="#D1D5DB"
{...ref}
{...props}
/>
);
}
);

@ -0,0 +1,17 @@
import React from "react";
import Slider, { SliderProps } from "@react-native-community/slider";
export default React.forwardRef<any, SliderProps>(
({ ...props }, ref): React.ReactElement => {
return (
<Slider
step={1}
thumbTintColor="#F97316"
minimumTrackTintColor="#F97316"
maximumTrackTintColor="#F97316"
{...ref}
{...props}
/>
);
}
);

@ -20,18 +20,10 @@ export interface ExtendedTextProps extends TextProps {
}
export default React.forwardRef<any, ExtendedTextProps>(
(props, ref): React.ReactElement => {
const {
position,
color,
size,
weight,
isLink,
className,
children,
...rest
} = props;
(
{ position, color, size, weight, isLink, className, children, ...props },
ref
): React.ReactElement => {
const buildClassName = () => {
const textSize = toTextSize(size ?? "lg");
const textColor = toTextColor(color ?? "black");
@ -54,7 +46,7 @@ export default React.forwardRef<any, ExtendedTextProps>(
};
return (
<Text className={buildClassName()} {...ref} {...rest}>
<Text className={buildClassName()} {...ref} {...props}>
{children}
</Text>
);

@ -1,10 +1,11 @@
import React from "react";
import { User } from "./model/User";
import { useStorageState } from "./useStorageState";
const AuthContext = React.createContext<{
signIn: () => void;
signIn: (user: User) => void;
signOut: () => void;
session?: string | null;
session?: User | null;
isLoading: boolean;
}>({
signIn: () => null,
@ -13,31 +14,23 @@ const AuthContext = React.createContext<{
isLoading: false,
});
// This hook can be used to access the user info.
export function useSession() {
const value = React.useContext(AuthContext);
if (process.env.NODE_ENV !== "production") {
if (!value) {
throw new Error("useSession must be wrapped in a <SessionProvider />");
}
}
return value;
return React.useContext(AuthContext);
}
export function SessionProvider(props: React.PropsWithChildren) {
const [[isLoading, session], setSession] = useStorageState("session");
export function SessionProvider(props: Readonly<React.PropsWithChildren>) {
const [[isLoading, sessionStr], setSessionStr] = useStorageState("session");
const session = sessionStr ? User.fromJSON(JSON.parse(sessionStr)) : null;
return (
<AuthContext.Provider
value={{
signIn: () => {
// Add your login logic here
// For example purposes, we'll just set a fake session in storage
//This likely would be a JWT token or other session data
setSession("John Doe");
signIn: (user: User) => {
setSessionStr(JSON.stringify(user));
},
signOut: () => {
setSession(null);
setSessionStr(null);
},
session,
isLoading,

@ -0,0 +1,204 @@
import {
EHealthProblem,
ESleepLevel,
ESport,
ESportLevel,
} from "./enums/Enums";
export class User {
private _name: string;
private _age: number | undefined;
private _height: number | undefined;
private _weight: number | undefined;
private _sexe: boolean | undefined; // true = Male, false = Female
private _logo: string | undefined;
private _nbSessionPerWeek: number | undefined;
private _goal: string | undefined;
private _healthProblems: EHealthProblem[] | undefined;
private _sport: ESport | undefined;
private _sleepLevel: ESleepLevel | undefined;
private _sportLevel: ESportLevel | undefined;
private _email: string;
private _password: string;
constructor(
name: string,
age: number | undefined,
height: number | undefined,
weight: number | undefined,
sexe: boolean | undefined,
logo: string | undefined,
nbSessionPerWeek: number | undefined,
goal: string | undefined,
healthProblems: EHealthProblem[] | undefined,
sport: ESport | undefined,
sleepLevel: ESleepLevel | undefined,
sportLevel: ESportLevel | undefined,
email: string,
password: string
) {
this._name = name;
this._age = age;
this._height = height;
this._weight = weight;
this._sexe = sexe;
this._logo = logo;
this._nbSessionPerWeek = nbSessionPerWeek;
this._goal = goal;
this._healthProblems = healthProblems;
this._sport = sport;
this._sleepLevel = sleepLevel;
this._sportLevel = sportLevel;
this._email = email;
this._password = password;
}
// Getters
get name(): string {
return this._name;
}
get age(): number | undefined {
return this._age;
}
get height(): number | undefined {
return this._height;
}
get weight(): number | undefined {
return this._weight;
}
get sexe(): boolean | undefined {
return this._sexe;
}
get logo(): string | undefined {
return this._logo;
}
get nbSessionPerWeek(): number | undefined {
return this._nbSessionPerWeek;
}
get goals(): string | undefined {
return this._goal;
}
get healthProblems(): EHealthProblem[] | undefined {
return this._healthProblems;
}
get sports(): ESport | undefined {
return this._sport;
}
get sleepLevel(): ESleepLevel | undefined {
return this._sleepLevel;
}
get sportLevel(): ESportLevel | undefined {
return this._sportLevel;
}
get email(): string {
return this._email;
}
get password(): string {
return this._password;
}
// Setters
set name(value: string) {
this._name = value;
}
set age(value: number | undefined) {
this._age = value;
}
set height(value: number | undefined) {
this._height = value;
}
set weight(value: number | undefined) {
this._weight = value;
}
set sexe(value: boolean | undefined) {
this._sexe = value;
}
set logo(value: string | undefined) {
this._logo = value;
}
set nbSessionPerWeek(value: number | undefined) {
this._nbSessionPerWeek = value;
}
set goals(value: string | undefined) {
this._goal = value;
}
set healthProblems(value: EHealthProblem[] | undefined) {
this._healthProblems = value;
}
set sports(value: ESport | undefined) {
this._sport = value;
}
set sleepLevel(value: ESleepLevel | undefined) {
this._sleepLevel = value;
}
set sportLevel(value: ESportLevel | undefined) {
this._sportLevel = value;
}
set email(value: string) {
this._email = value;
}
set password(value: string) {
this._password = value;
}
public isQuizDone(): boolean {
return (
this._name !== undefined &&
this._age !== undefined &&
this._height !== undefined &&
this._weight !== undefined &&
this._sexe !== undefined &&
this._nbSessionPerWeek !== undefined &&
this._goal !== undefined &&
this._healthProblems !== undefined &&
this._sport !== undefined &&
this._sleepLevel !== undefined &&
this._sportLevel !== undefined
);
}
static fromJSON(json: any): User {
return new User(
json._name,
json._age,
json._height,
json._weight,
json._sexe,
json._logo,
json._nbSessionPerWeek,
json._goal,
json._healthProblems,
json._sport,
json._sleepLevel,
json._sportLevel,
json._email,
json._password
);
}
}

@ -0,0 +1,42 @@
export const Goals = [
"WEIGHT_LOSE",
"IMPROVE_MUSCLE",
"WEIGHT_GAIN",
"IMPROVE_STAMINA",
"KEEP_FIT",
] as const;
export const SportLevels = [
"NOT_SPORTY",
"BEGINNER",
"SPORTY",
"VERY_SPORTY",
"PERFORMER",
] as const;
export const Sports = [
"RUNNING",
"WALKING",
"RANDO",
"SKATEBOARD",
"BIKING",
"BASKETBALL",
"CARDIO",
"YOGA",
"ELSE",
] as const;
export const SleepLevels = [
"TERRIBLE",
"VERY_BAD",
"BAD",
"GOOD",
"EXCELLENT",
] as const;
export type EGoal = (typeof Goals)[number];
export type ESportLevel = (typeof SportLevels)[number];
export type ESport = (typeof Sports)[number];
export type ESleepLevel = (typeof SleepLevels)[number];
export type EHealthProblem = "ARTHROSE" | "MIGRAINE";

5919
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -17,53 +17,55 @@
"dependencies": {
"@expo/html-elements": "^0.4.2",
"@expo/vector-icons": "^14.0.2",
"@react-native-community/slider": "4.5.6",
"@react-native-segmented-control/segmented-control": "2.5.7",
"@react-navigation/bottom-tabs": "^7.2.0",
"@react-navigation/native": "^7.0.14",
"@react-navigation/native-stack": "^7.2.0",
"@react-navigation/stack": "^7.1.1",
"axios": "^1.8.3",
"dayjs": "^1.11.13",
"expo": "^52.0.24",
"expo-asset": "^11.0.2",
"expo-blur": "^14.0.1",
"expo-constants": "^17.0.3",
"expo-font": "^13.0.2",
"expo-haptics": "^14.0.0",
"expo-linear-gradient": "~14.0.2",
"expo-linking": "^7.0.3",
"expo-router": "~4.0.16",
"expo": "^53.0.0",
"expo-asset": "^11.0.5",
"expo-blur": "^14.0.3",
"expo-constants": "^17.0.8",
"expo-font": "^13.0.4",
"expo-haptics": "^14.0.1",
"expo-linear-gradient": "^14.0.2",
"expo-linking": "^7.0.5",
"expo-router": "~5.0.6",
"expo-secure-store": "^14.0.1",
"expo-splash-screen": "^0.29.19",
"expo-status-bar": "^2.0.0",
"expo-symbols": "^0.2.0",
"expo-system-ui": "^4.0.6",
"expo-web-browser": "^14.0.1",
"expo-splash-screen": "~0.30.8",
"expo-status-bar": "^2.0.1",
"expo-symbols": "~0.4.4",
"expo-system-ui": "~5.0.7",
"expo-web-browser": "^14.0.2",
"nativewind": "^4.1.23",
"react": "18.3.1",
"react-dom": "18.3.1",
"react": "19.0.0",
"react-dom": "19.0.0",
"react-hook-form": "^7.54.2",
"react-native": "0.76.6",
"react-native-gesture-handler": "^2.20.2",
"react-native": "0.79.2",
"react-native-element-dropdown": "^2.12.4",
"react-native-gesture-handler": "~2.24.0",
"react-native-gifted-charts": "^1.4.54",
"react-native-linear-timer": "^1.4.0",
"react-native-reanimated": "~3.16.1",
"react-native-safe-area-context": "4.12.0",
"react-native-reanimated": "^3.16.1",
"react-native-safe-area-context": "5.4.0",
"react-native-screens": "^4.4.0",
"react-native-svg": "15.8.0",
"react-native-svg": "15.11.2",
"react-native-vector-icons": "^10.2.0",
"react-native-web": "^0.19.13",
"react-native-webview": "13.12.5",
"react-native-web": "^0.20.0",
"react-native-webview": "13.13.5",
"tailwindcss": "^3.4.17",
"validator": "^13.12.0"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@types/jest": "^29.5.12",
"@types/react": "^18.3.12",
"@types/react": "~19.0.10",
"@types/react-test-renderer": "^19.0.0",
"@types/validator": "^13.12.2",
"jest": "^29.2.1",
"jest-expo": "^52.0.2",
"jest-expo": "~53.0.5",
"react-native-svg-transformer": "^1.5.0",
"react-test-renderer": "19.0.0",
"typescript": "^5.3.3"

@ -0,0 +1,5 @@
import { User } from "@/model/User";
export interface IUserService {
login(email: string, password: string): User | undefined;
}

@ -0,0 +1,171 @@
import { User } from "@/model/User";
import { IUserService } from "./user.service.interface";
export class UserServiceStub implements IUserService {
private readonly users: User[] = [
new User(
"Alice",
28,
165,
58,
false,
"alice.png",
3,
"Perdre du poids",
[],
"YOGA",
"GOOD",
"BEGINNER",
"test@1.com",
"password1"
),
new User(
"Bob",
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
"test@2.com",
"password2"
),
new User(
"Charlie",
22,
172,
70,
true,
"charlie.png",
2,
"Se remettre en forme",
[],
"BIKING",
"GOOD",
"BEGINNER",
"test@3.com",
"password3"
),
new User(
"Diana",
31,
160,
55,
false,
"diana.png",
5,
"Préparer un marathon",
[],
"RUNNING",
"GOOD",
"VERY_SPORTY",
"test@4.com",
"password4"
),
new User(
"Ethan",
40,
180,
88,
true,
"ethan.png",
1,
"Maintenir sa forme",
["MIGRAINE"],
"WALKING",
"BAD",
"SPORTY",
"test@5.com",
"password5"
),
new User(
"Fiona",
26,
167,
62,
false,
"fiona.png",
3,
"Renforcer le dos",
["MIGRAINE"],
"CARDIO",
"BAD",
"BEGINNER",
"test@6.com",
"password6"
),
new User(
"George",
30,
185,
90,
true,
"george.png",
4,
"Perdre du gras",
[],
"BIKING",
"TERRIBLE",
"SPORTY",
"test@7.com",
"password7"
),
new User(
"Hanna",
24,
158,
54,
false,
"hanna.png",
2,
"Se tonifier",
[],
"RANDO",
"GOOD",
"BEGINNER",
"test@8.com",
"password8"
),
new User(
"Ivan",
50,
175,
95,
true,
"ivan.png",
1,
"Rééducation",
["ARTHROSE"],
"WALKING",
"BAD",
"BEGINNER",
"test@9.com",
"password9"
),
new User(
"Julia",
29,
170,
60,
false,
"julia.png",
3,
"Rester active",
[],
"ELSE",
"GOOD",
"SPORTY",
"test@10.com",
"password10"
),
];
login(email: string, password: string): User | undefined {
return this.users.find((x) => x.email === email && x.password === password);
}
}

@ -1,186 +0,0 @@
"use client";
import React from "react";
import { createAvatar } from "@gluestack-ui/avatar";
import { View, Text, Image, Platform } from "react-native";
import { tva } from "@gluestack-ui/nativewind-utils/tva";
import {
withStyleContext,
useStyleContext,
} from "@gluestack-ui/nativewind-utils/withStyleContext";
import type { VariantProps } from "@gluestack-ui/nativewind-utils";
const SCOPE = "AVATAR";
const UIAvatar = createAvatar({
Root: withStyleContext(View, SCOPE),
Badge: View,
Group: View,
Image: Image,
FallbackText: Text,
});
const avatarStyle = tva({
base: "rounded-full justify-center items-center relative bg-primary-600 group-[.avatar-group]/avatar-group:-ml-2.5",
variants: {
size: {
xs: "w-6 h-6",
sm: "w-8 h-8",
md: "w-12 h-12",
lg: "w-16 h-16",
xl: "w-24 h-24",
"2xl": "w-32 h-32",
},
},
});
const avatarFallbackTextStyle = tva({
base: "text-typography-0 font-semibold overflow-hidden text-transform:uppercase web:cursor-default",
parentVariants: {
size: {
xs: "text-2xs",
sm: "text-xs",
md: "text-base",
lg: "text-xl",
xl: "text-3xl",
"2xl": "text-5xl",
},
},
});
const avatarGroupStyle = tva({
base: "group/avatar-group flex-row-reverse relative avatar-group",
});
const avatarBadgeStyle = tva({
base: "w-5 h-5 bg-success-500 rounded-full absolute right-0 bottom-0 border-background-0 border-2",
parentVariants: {
size: {
xs: "w-2 h-2",
sm: "w-2 h-2",
md: "w-3 h-3",
lg: "w-4 h-4",
xl: "w-6 h-6",
"2xl": "w-8 h-8",
},
},
});
const avatarImageStyle = tva({
base: "h-full w-full rounded-full absolute",
});
type IAvatarProps = Omit<
React.ComponentPropsWithoutRef<typeof UIAvatar>,
"context"
> &
VariantProps<typeof avatarStyle>;
const Avatar = React.forwardRef<
React.ElementRef<typeof UIAvatar>,
IAvatarProps
>(({ className, size = "md", ...props }, ref) => {
return (
<UIAvatar
ref={ref}
{...props}
className={avatarStyle({ size, class: className })}
context={{ size }}
/>
);
});
type IAvatarBadgeProps = React.ComponentPropsWithoutRef<typeof UIAvatar.Badge> &
VariantProps<typeof avatarBadgeStyle>;
const AvatarBadge = React.forwardRef<
React.ElementRef<typeof UIAvatar.Badge>,
IAvatarBadgeProps
>(({ className, size, ...props }, ref) => {
const { size: parentSize } = useStyleContext(SCOPE);
return (
<UIAvatar.Badge
ref={ref}
{...props}
className={avatarBadgeStyle({
parentVariants: {
size: parentSize,
},
size,
class: className,
})}
/>
);
});
type IAvatarFallbackTextProps = React.ComponentPropsWithoutRef<
typeof UIAvatar.FallbackText
> &
VariantProps<typeof avatarFallbackTextStyle>;
const AvatarFallbackText = React.forwardRef<
React.ElementRef<typeof UIAvatar.FallbackText>,
IAvatarFallbackTextProps
>(({ className, size, ...props }, ref) => {
const { size: parentSize } = useStyleContext(SCOPE);
return (
<UIAvatar.FallbackText
ref={ref}
{...props}
className={avatarFallbackTextStyle({
parentVariants: {
size: parentSize,
},
size,
class: className,
})}
/>
);
});
type IAvatarImageProps = React.ComponentPropsWithoutRef<typeof UIAvatar.Image> &
VariantProps<typeof avatarImageStyle>;
const AvatarImage = React.forwardRef<
React.ElementRef<typeof UIAvatar.Image>,
IAvatarImageProps
>(({ className, ...props }, ref) => {
return (
<UIAvatar.Image
ref={ref}
{...props}
className={avatarImageStyle({
class: className,
})}
// @ts-expect-error
style={
Platform.OS === "web"
? // eslint-disable-next-line react-native/no-inline-styles
{ height: "revert-layer", width: "revert-layer" }
: undefined
}
/>
);
});
type IAvatarGroupProps = React.ComponentPropsWithoutRef<typeof UIAvatar.Group> &
VariantProps<typeof avatarGroupStyle>;
const AvatarGroup = React.forwardRef<
React.ElementRef<typeof UIAvatar.Group>,
IAvatarGroupProps
>(({ className, ...props }, ref) => {
return (
<UIAvatar.Group
ref={ref}
{...props}
className={avatarGroupStyle({
class: className,
})}
/>
);
});
export { Avatar, AvatarBadge, AvatarFallbackText, AvatarImage, AvatarGroup };

@ -1,237 +0,0 @@
'use client';
import React from 'react';
import { createCheckbox } from '@gluestack-ui/checkbox';
import { View, Pressable, Text, Platform } from 'react-native';
import type { TextProps, ViewProps } from 'react-native';
import { tva } from '@gluestack-ui/nativewind-utils/tva';
import { PrimitiveIcon, IPrimitiveIcon, UIIcon } from '@gluestack-ui/icon';
import {
withStyleContext,
useStyleContext,
} from '@gluestack-ui/nativewind-utils/withStyleContext';
import { cssInterop } from 'nativewind';
import type { VariantProps } from '@gluestack-ui/nativewind-utils';
const IndicatorWrapper = React.forwardRef<
React.ElementRef<typeof View>,
ViewProps
>(({ ...props }, ref) => {
return <View {...props} ref={ref} />;
});
const LabelWrapper = React.forwardRef<React.ElementRef<typeof Text>, TextProps>(
({ ...props }, ref) => {
return <Text {...props} ref={ref} />;
}
);
const IconWrapper = React.forwardRef<
React.ElementRef<typeof PrimitiveIcon>,
IPrimitiveIcon
>(({ ...props }, ref) => {
return <UIIcon {...props} ref={ref} />;
});
const SCOPE = 'CHECKBOX';
const UICheckbox = createCheckbox({
// @ts-expect-error
Root:
Platform.OS === 'web'
? withStyleContext(View, SCOPE)
: withStyleContext(Pressable, SCOPE),
Group: View,
Icon: IconWrapper,
Label: LabelWrapper,
Indicator: IndicatorWrapper,
});
cssInterop(PrimitiveIcon, {
className: {
target: 'style',
nativeStyleToProp: {
height: true,
width: true,
fill: true,
color: 'classNameColor',
stroke: true,
},
},
});
const checkboxStyle = tva({
base: 'group/checkbox flex-row items-center justify-start web:cursor-pointer data-[disabled=true]:cursor-not-allowed',
variants: {
size: {
lg: 'gap-2',
md: 'gap-2',
sm: 'gap-1.5',
},
},
});
const checkboxIndicatorStyle = tva({
base: 'justify-center items-center border-outline-400 bg-transparent rounded web:data-[focus-visible=true]:outline-none web:data-[focus-visible=true]:ring-2 web:data-[focus-visible=true]:ring-indicator-primary data-[checked=true]:bg-primary-600 data-[checked=true]:border-primary-600 data-[hover=true]:data-[checked=false]:border-outline-500 data-[hover=true]:bg-transparent data-[hover=true]:data-[invalid=true]:border-error-700 data-[hover=true]:data-[checked=true]:bg-primary-700 data-[hover=true]:data-[checked=true]:border-primary-700 data-[hover=true]:data-[checked=true]:data-[disabled=true]:border-primary-600 data-[hover=true]:data-[checked=true]:data-[disabled=true]:bg-primary-600 data-[hover=true]:data-[checked=true]:data-[disabled=true]:opacity-40 data-[hover=true]:data-[checked=true]:data-[disabled=true]:data-[invalid=true]:border-error-700 data-[hover=true]:data-[disabled=true]:border-outline-400 data-[hover=true]:data-[disabled=true]:data-[invalid=true]:border-error-700 data-[active=true]:data-[checked=true]:bg-primary-800 data-[active=true]:data-[checked=true]:border-primary-800 data-[invalid=true]:border-error-700 data-[disabled=true]:opacity-40',
parentVariants: {
size: {
lg: 'w-6 h-6 border-[3px]',
md: 'w-5 h-5 border-2',
sm: 'w-4 h-4 border-2',
},
},
});
const checkboxLabelStyle = tva({
base: 'text-typography-600 data-[checked=true]:text-typography-900 data-[hover=true]:text-typography-900 data-[hover=true]:data-[checked=true]:text-typography-900 data-[hover=true]:data-[checked=true]:data-[disabled=true]:text-typography-900 data-[hover=true]:data-[disabled=true]:text-typography-400 data-[active=true]:text-typography-900 data-[active=true]:data-[checked=true]:text-typography-900 data-[disabled=true]:opacity-40 web:select-none',
parentVariants: {
size: {
lg: 'text-lg',
md: 'text-base',
sm: 'text-sm',
},
},
});
const checkboxIconStyle = tva({
base: 'text-typography-50 fill-none',
parentVariants: {
size: {
sm: 'h-3 w-3',
md: 'h-4 w-4',
lg: 'h-5 w-5',
},
},
});
const CheckboxGroup = UICheckbox.Group;
type ICheckboxProps = React.ComponentPropsWithoutRef<typeof UICheckbox> &
VariantProps<typeof checkboxStyle>;
const Checkbox = React.forwardRef<
React.ElementRef<typeof UICheckbox>,
ICheckboxProps
>(({ className, size = 'md', ...props }, ref) => {
return (
<UICheckbox
className={checkboxStyle({
class: className,
size,
})}
{...props}
context={{
size,
}}
ref={ref}
/>
);
});
type ICheckboxIndicatorProps = React.ComponentPropsWithoutRef<
typeof UICheckbox.Indicator
> &
VariantProps<typeof checkboxIndicatorStyle>;
const CheckboxIndicator = React.forwardRef<
React.ElementRef<typeof UICheckbox.Indicator>,
ICheckboxIndicatorProps
>(({ className, ...props }, ref) => {
const { size: parentSize } = useStyleContext(SCOPE);
return (
<UICheckbox.Indicator
className={checkboxIndicatorStyle({
parentVariants: {
size: parentSize,
},
class: className,
})}
{...props}
ref={ref}
/>
);
});
type ICheckboxLabelProps = React.ComponentPropsWithoutRef<
typeof UICheckbox.Label
> &
VariantProps<typeof checkboxLabelStyle>;
const CheckboxLabel = React.forwardRef<
React.ElementRef<typeof UICheckbox.Label>,
ICheckboxLabelProps
>(({ className, ...props }, ref) => {
const { size: parentSize } = useStyleContext(SCOPE);
return (
<UICheckbox.Label
className={checkboxLabelStyle({
parentVariants: {
size: parentSize,
},
class: className,
})}
{...props}
ref={ref}
/>
);
});
type ICheckboxIconProps = React.ComponentPropsWithoutRef<
typeof UICheckbox.Icon
> &
VariantProps<typeof checkboxIconStyle>;
const CheckboxIcon = React.forwardRef<
React.ElementRef<typeof UICheckbox.Icon>,
ICheckboxIconProps
>(({ className, size, ...props }, ref) => {
const { size: parentSize } = useStyleContext(SCOPE);
if (typeof size === 'number') {
return (
<UICheckbox.Icon
ref={ref}
{...props}
className={checkboxIconStyle({ class: className })}
size={size}
/>
);
} else if (
(props.height !== undefined || props.width !== undefined) &&
size === undefined
) {
return (
<UICheckbox.Icon
ref={ref}
{...props}
className={checkboxIconStyle({ class: className })}
/>
);
}
return (
<UICheckbox.Icon
className={checkboxIconStyle({
parentVariants: {
size: parentSize,
},
class: className,
size,
})}
{...props}
ref={ref}
/>
);
});
Checkbox.displayName = 'Checkbox';
CheckboxIndicator.displayName = 'CheckboxIndicator';
CheckboxLabel.displayName = 'CheckboxLabel';
CheckboxIcon.displayName = 'CheckboxIcon';
export {
Checkbox,
CheckboxIndicator,
CheckboxLabel,
CheckboxIcon,
CheckboxGroup,
};
Loading…
Cancel
Save