diff --git a/backend/src/visited/templates/visit_template.ts b/backend/src/visited/templates/visit_template.ts
index c16cab2f5c96612626254f39b89eef3239540dd4..6511106f519234fcfda25667e4c9a29f4ad34433 100644
--- a/backend/src/visited/templates/visit_template.ts
+++ b/backend/src/visited/templates/visit_template.ts
@@ -19,6 +19,8 @@ export const visit_template = (places: string[]) => `
position: relative;
display: flex;
flex-direction: row;
+ justify-content: center;
+ align-items: center;
height: 300px;
width: 450px;
gap: 10px;
@@ -29,7 +31,7 @@ export const visit_template = (places: string[]) => `
}
.container .image {
flex-grow: 1;
- max-width: 100%;
+ max-width: 50%;
height: 90%;
border-radius: 10px;
overflow: hidden;
diff --git a/mobile/app/_layout.tsx b/mobile/app/_layout.tsx
index 34580862ce1cf15d73c30d59cc26997b9016a299..0a2b0091972113e1420b55349b5d8da4968b600d 100644
--- a/mobile/app/_layout.tsx
+++ b/mobile/app/_layout.tsx
@@ -15,6 +15,7 @@ import {
useSetUp,
} from "../src/common/contexts/set_up_context";
import { useEffect } from "react";
+import { LANG_CONSTANTS } from "../src/lang/lang";
export default function Root() {
return (
@@ -128,6 +129,21 @@ const MainLayout = () => {
statusBarColor: LIGHT_THEME.color.primary,
}}
/>
+
();
+ if (!townId) {
+ return null;
+ }
+ return ;
+}
diff --git a/mobile/app/state/[stateId]/town/_layout.tsx b/mobile/app/state/[stateId]/town/_layout.tsx
index 891a48c487513dded04edf3ba0875ec419c4dd16..ce24299b26e3c4953c2ef3b65c189b1f9650bf58 100644
--- a/mobile/app/state/[stateId]/town/_layout.tsx
+++ b/mobile/app/state/[stateId]/town/_layout.tsx
@@ -1,6 +1,8 @@
-import { Stack } from "expo-router";
+import { router, Stack } from "expo-router";
import { LIGHT_THEME } from "../../../../src/common/const/theme";
import { useTranslation } from "react-i18next";
+import React from "react";
+import { MaterialCommunityIcons } from "@expo/vector-icons";
export default function Layout() {
const { t } = useTranslation();
diff --git a/mobile/assets/GPLv3_Logo-removebg-preview.png b/mobile/assets/GPLv3_Logo-removebg-preview.png
new file mode 100644
index 0000000000000000000000000000000000000000..fef7cbc761a256c9077a3ef879d05dbc7ea3deb2
Binary files /dev/null and b/mobile/assets/GPLv3_Logo-removebg-preview.png differ
diff --git a/mobile/assets/labsol-removebg-preview.png b/mobile/assets/labsol-removebg-preview.png
new file mode 100644
index 0000000000000000000000000000000000000000..2b0cf4d2c8ca55be4c4699c8535f38e31fbb1f28
Binary files /dev/null and b/mobile/assets/labsol-removebg-preview.png differ
diff --git a/mobile/package-lock.json b/mobile/package-lock.json
index 7382d52821c21fbc45764483e981e012a938645e..0ec993e864fe3ffbce381f0754d92d31a44090da 100644
--- a/mobile/package-lock.json
+++ b/mobile/package-lock.json
@@ -34,6 +34,7 @@
"i18n-js": "^4.4.3",
"i18next": "^23.11.5",
"metro-config": "^0.80.12",
+ "mime": "^4.0.4",
"nativewind": "^2.0.11",
"react": "18.2.0",
"react-hook-form": "^7.51.2",
@@ -5372,6 +5373,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/@react-native-community/cli-tools/node_modules/mime": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
+ "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
+ "license": "MIT",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
"node_modules/@react-native-community/cli-tools/node_modules/mimic-fn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
@@ -12296,14 +12309,18 @@
}
},
"node_modules/mime": {
- "version": "2.6.0",
- "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
- "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.4.tgz",
+ "integrity": "sha512-v8yqInVjhXyqP6+Kw4fV3ZzeMRqEW6FotRsKXjRS5VMTNIuXsdRoAvklpoRgSqXm6o9VNH4/C0mgedko9DdLsQ==",
+ "funding": [
+ "https://github.com/sponsors/broofa"
+ ],
+ "license": "MIT",
"bin": {
- "mime": "cli.js"
+ "mime": "bin/cli.js"
},
"engines": {
- "node": ">=4.0.0"
+ "node": ">=16"
}
},
"node_modules/mime-db": {
diff --git a/mobile/package.json b/mobile/package.json
index b2ed53aafcbccd7aae0d599bd468a3dc50c94183..601123fab2201da9338927b1ae137cb882a5a204 100644
--- a/mobile/package.json
+++ b/mobile/package.json
@@ -21,10 +21,13 @@
"expo-camera": "~14.1.1",
"expo-checkbox": "~2.7.0",
"expo-constants": "~15.4.5",
+ "expo-crypto": "~12.8.1",
+ "expo-file-system": "~16.0.9",
"expo-image-picker": "~14.7.1",
"expo-linear-gradient": "~12.7.2",
"expo-linking": "~6.2.2",
"expo-localization": "~14.8.4",
+ "expo-media-library": "~15.9.2",
"expo-router": "~3.4.8",
"expo-screen-orientation": "~6.4.1",
"expo-secure-store": "~12.8.1",
@@ -32,6 +35,7 @@
"i18n-js": "^4.4.3",
"i18next": "^23.11.5",
"metro-config": "^0.80.12",
+ "mime": "^4.0.4",
"nativewind": "^2.0.11",
"react": "18.2.0",
"react-hook-form": "^7.51.2",
@@ -45,10 +49,7 @@
"react-native-reanimated": "~3.6.2",
"react-native-safe-area-context": "4.8.2",
"react-native-screens": "~3.29.0",
- "react-native-svg": "14.1.0",
- "expo-file-system": "~16.0.9",
- "expo-media-library": "~15.9.2",
- "expo-crypto": "~12.8.1"
+ "react-native-svg": "14.1.0"
},
"devDependencies": {
"@babel/core": "^7.20.0",
diff --git a/mobile/src/activity/components/activity_bottom_sheet.tsx b/mobile/src/activity/components/activity_bottom_sheet.tsx
index 17054a27843cbc5911c0cb279c829cbaf6437810..45c585987b597888313d19e39c0dcdee04c53bdf 100644
--- a/mobile/src/activity/components/activity_bottom_sheet.tsx
+++ b/mobile/src/activity/components/activity_bottom_sheet.tsx
@@ -67,17 +67,9 @@ export const ActivityBottomSheet = ({
keyExtractor={(item) => item}
ItemSeparatorComponent={() => }
renderItem={({ item }) => (
-
- {item}
-
+
+ {item}
+
)}
/>
)}
@@ -146,4 +138,13 @@ const styles = StyleSheet.create({
show_more_text: {
fontWeight: "600",
},
+ tag: {
+ backgroundColor: LIGHT_THEME.color.primary,
+ color: LIGHT_THEME.color.white,
+ height: 30,
+ paddingVertical: 5,
+ paddingHorizontal: 10,
+ borderRadius: 15,
+ marginHorizontal: 5,
+ },
});
diff --git a/mobile/src/activity/domain/datasources/activity_datasource.ts b/mobile/src/activity/domain/datasources/activity_datasource.ts
index ea2fe13974d9bb60d588479f0d3b25247f0282f0..6e7e701f71f872bcf5d5a09f767d03159a32249d 100644
--- a/mobile/src/activity/domain/datasources/activity_datasource.ts
+++ b/mobile/src/activity/domain/datasources/activity_datasource.ts
@@ -1,6 +1,11 @@
import { ActivityPlaceEntity } from "../entities/activity_place_entity";
export interface ActivityDataSource {
- getPlaceActivity(activityId: number, townId: number, stateId: number, placeNumber: number): Promise;
- rankActivity(activityId: number, rank: number): Promise;
-}
\ No newline at end of file
+ getPlaceActivity(
+ activityId: number,
+ townId: number,
+ stateId: number,
+ placeNumber: number
+ ): Promise;
+ rankActivity(activityId: number, rank: number): Promise;
+}
diff --git a/mobile/src/activity/hooks/useGetOpenActivities.ts b/mobile/src/activity/hooks/useGetOpenActivities.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3050394f844da804ada163d1360c5bf86943452b
--- /dev/null
+++ b/mobile/src/activity/hooks/useGetOpenActivities.ts
@@ -0,0 +1,12 @@
+import { useDataContext } from "../../common/contexts/data_context";
+import { useGet } from "../../common/hooks/useGet";
+import { ActivityInfoEntity } from "../domain/entities/activity_info_entity";
+
+export const useGetOpenActivities = (townId: number) => {
+ const { statesRepository } = useDataContext();
+ const callback = async () => {
+ return (await statesRepository!.getOpenActivities(townId)) || [];
+ };
+ const { data, requestStatus } = useGet(callback);
+ return { activities: data, requestStatus };
+};
diff --git a/mobile/src/activity/infrastructure/datasources/prod/activity_datasource.ts b/mobile/src/activity/infrastructure/datasources/prod/activity_datasource.ts
index 2d2fb3d871d553a7045da9609557960195bc2c35..a2478ba93b3a9e258282b8fb1bb9c4556b30cf9d 100644
--- a/mobile/src/activity/infrastructure/datasources/prod/activity_datasource.ts
+++ b/mobile/src/activity/infrastructure/datasources/prod/activity_datasource.ts
@@ -13,7 +13,6 @@ export class ActivityDatasourceProd implements ActivityDataSource {
idPlace: activityId,
rating: rank,
});
- console.info("rankActivity", status);
if (status !== 201) {
throw new Error("Error al calificar la actividad");
}
@@ -30,7 +29,6 @@ export class ActivityDatasourceProd implements ActivityDataSource {
if (status !== 200) {
throw new Error("Error al obtener la información del lugar");
}
- console.log(data);
return activityPlaceModelToEntity(data);
}
}
diff --git a/mobile/src/activity/screens/activity_point.tsx b/mobile/src/activity/screens/activity_point.tsx
index ff99b59a4677b1c49011700ff788771dea9f169d..010b3c79cd8d2e3d8c4e285d9bfb3664600d726d 100644
--- a/mobile/src/activity/screens/activity_point.tsx
+++ b/mobile/src/activity/screens/activity_point.tsx
@@ -14,6 +14,7 @@ import { useRankActivity } from "../hooks/useRankActivity";
import { useGetActivityPoint } from "../hooks/useGetActivityPoint";
import { FloatingBackButton } from "../../common/components/floating_back_button";
import { Ionicons } from "@expo/vector-icons";
+import { useTranslation } from "react-i18next";
const touristGuide = require("../../../assets/guide.gif");
@@ -32,6 +33,7 @@ export const ActivityPointScreen = memo(
stateId,
placeNumber: id,
});
+ const { t } = useTranslation();
const { onUnmount } = useAudio();
const { openRatingModal, closeRatingModal, ratingModal, rankActivity } =
@@ -89,14 +91,14 @@ export const ActivityPointScreen = memo(
- Activity Point
+ {t("activityPointScreen.activityPointLavel")}
- End Activity
+ {t("activityPointScreen.endActivityButton")}
@@ -124,7 +126,9 @@ export const ActivityPointScreen = memo(
{data.directions && (
<>
- Directions
+
+ {t("activityPointScreen.directionsLabel")}
+
{data.directions.content}
@@ -134,7 +138,7 @@ export const ActivityPointScreen = memo(
style={styles.endActivityButtonText}
onPress={doNextActivity}
>
- Next Activity
+ {t("activityPointScreen.nextActivityButton")}
>
@@ -145,14 +149,14 @@ export const ActivityPointScreen = memo(
style={styles.endActivityButtonText}
onPress={openRatingModal}
>
- End Activity
+ {t("activityPointScreen.endActivityButton")}
)}
- {!data.directions && ratingModal && (
+ {ratingModal && (
;
@@ -9,16 +10,17 @@ interface CodeFormProps {
}
export const CodeForm = ({ setValue, getNewResetCode }: CodeFormProps) => {
+ const { t } = useTranslation();
const onTextChange = (value: string) => {
setValue("code", value);
};
return (
- Introduce el código de verificación
+ {t("code_form.insertVerificationCode")}
{getNewResetCode && (
- Obtener nuevo código
+ {t("code_form.resendCode")}
)}
);
diff --git a/mobile/src/auth/components/login_form.tsx b/mobile/src/auth/components/login_form.tsx
index c96d403d12bda2272c300bfda6880726dd4f99f6..dc85758605238d260b46c2f3520bc85d3f8c3bc4 100644
--- a/mobile/src/auth/components/login_form.tsx
+++ b/mobile/src/auth/components/login_form.tsx
@@ -82,7 +82,7 @@ export const LoginForm = ({ control, onSubmit }: LoginFormProps) => {
style={{ width: "100%", textAlign: "right" }}
>
- Recuperar contraseña
+ {LANG.t("loginScreen.forgotPasswordButton")}
diff --git a/mobile/src/auth/components/reset_password_form.tsx b/mobile/src/auth/components/reset_password_form.tsx
index f585d51a82fda631b24dd3279735352b2c6b057d..e26070c904b59293896aaf0e26d335304ecd2d28 100644
--- a/mobile/src/auth/components/reset_password_form.tsx
+++ b/mobile/src/auth/components/reset_password_form.tsx
@@ -1,26 +1,31 @@
import { View, Text, StyleSheet } from "react-native";
import { CustomTextInput } from "../../common/components/form/text_input";
import { Control, Controller } from "react-hook-form";
-import { ResetPasswordFormValues } from "../pages/reset_password_page";
+import { ResetPasswordFormValues } from "../hooks/useResetPassword";
+import { useTranslation } from "react-i18next";
interface ResetPasswordFormProps {
control: Control;
}
export const ResetPasswordForm = ({ control }: ResetPasswordFormProps) => {
+ const { t } = useTranslation();
return (
- Para reestablecer tu contraseña te enviaremos un email
+ {t("code_form.toResetPassword")}
(
+ render={({
+ field: { onChange, onBlur, value },
+ formState: { errors },
+ }) => (
;
@@ -21,9 +22,10 @@ interface SignUpFormProps {
}
export const SignUpForm = ({ control, onSubmit }: SignUpFormProps) => {
+ const { t } = useTranslation();
return (
- Sign Up
+ {t("registerScreen.registerButton")}
{
formState: { errors },
}) => (
{
/>
)}
rules={{
- required: "Name is required",
+ required: t("formsErrors.nameIsRequired"),
}}
/>
{
formState: { errors },
}) => (
{
/>
)}
rules={{
- required: "Last name is required",
+ required: t("formsErrors.lastNameIsRequired"),
}}
/>
{
formState: { errors },
}) => (
{
/>
)}
rules={{
- required: "Email is required",
+ required: t("formsErrors.emailIsRequired"),
pattern: { value: /\S+@\S+\.\S+/, message: "Invalid email" },
}}
-
/>
{
formState: { errors },
}) => (
)}
- rules={{ required: "Password is required" }}
+ rules={{ required: t("formsErrors.passwordIsRequired") }}
/>
{
formState: { errors },
}) => (
{
}}
/>
)}
- rules={{ required: "Confirm password is required" }}
+ rules={{ required: t("formsErrors.requiredField") }}
/>
(
)}
defaultValue=" "
-
/>
- Sign Up
+
+ {t("registerScreen.registerButton")}
+
-
+
-
);
};
diff --git a/mobile/src/auth/contexts/auth_context.tsx b/mobile/src/auth/contexts/auth_context.tsx
index 25d8ddc5c6199c59bf019eab29a04cf4296e57a4..8933c1920663d3edbc8e5f51355c16c3684a2f1c 100644
--- a/mobile/src/auth/contexts/auth_context.tsx
+++ b/mobile/src/auth/contexts/auth_context.tsx
@@ -47,10 +47,8 @@ export const AuthContextProvider = ({ children }: AuthContextProviderProps) => {
const parsedUserPreferences = JSON.parse(
userPreferences
) as UserPreferences;
- console.log("User Preferences from verify: 2", userPreferences);
setIsVerified(parsedUserPreferences.isVerifiedEmail);
} else {
- console.log("User Preferences from verify: 4", userPreferences);
const emptyUserPreferences = {
isFirstTime: true,
isVerifiedEmail: false,
@@ -66,24 +64,31 @@ export const AuthContextProvider = ({ children }: AuthContextProviderProps) => {
setIsLoading(false);
};
- const verify = () => {
+ const verify = async () => {
if (!user) return;
- const userPreferences = SecureStore.getItemAsync(user.email);
- console.log("User Preferences from verify 1: ", userPreferences);
const sskey = user.email.split("@")[0];
- console.log(sskey);
- SecureStore.setItemAsync(
+ const userPreferences = await SecureStore.getItemAsync(sskey);
+ if (!userPreferences) return;
+ const parsedUserPreferences = JSON.parse(
+ userPreferences
+ ) as UserPreferences;
+ await SecureStore.setItemAsync(
sskey,
- JSON.stringify({ ...userPreferences, isVerifiedEmail: true })
+ JSON.stringify({ ...parsedUserPreferences, isVerifiedEmail: true })
);
setIsVerified(true);
};
+ const saveUser = async (userInfo: UserInfoEntity) => {
+ setUser(userInfo);
+ };
+
const login = async (userInfo: UserInfoEntity, token: string) => {
await SecureStore.setItemAsync("user", JSON.stringify(userInfo));
await SecureStore.setItemAsync("token", token);
- setUser(userInfo);
+ await saveUser(userInfo);
axios.defaults.headers.common["Authorization"] = `Bearer ${token}`;
+ await verify();
};
const logout = async () => {
diff --git a/mobile/src/auth/hooks/useResetPasswordControl.tsx b/mobile/src/auth/hooks/useResetPasswordControl.tsx
index 9b83b9425277f3fe2a35d10e4a870d6f21e446c2..8dcb164e0e5ede131a3326e0ea446d98a0c8d409 100644
--- a/mobile/src/auth/hooks/useResetPasswordControl.tsx
+++ b/mobile/src/auth/hooks/useResetPasswordControl.tsx
@@ -27,9 +27,7 @@ export const useResetPasswordControl = () => {
},
{
slide: ,
- callback: async () => {
- console.log("Reset Password 1");
- },
+ callback: async () => {},
},
{
slide: ,
diff --git a/mobile/src/auth/infrastructure/dev/datasources/auth_datasource.ts b/mobile/src/auth/infrastructure/dev/datasources/auth_datasource.ts
index 3a9bbf9269e21639295220c52ca80336a2f4c424..47d420d00714a14f88e79978817d18ef72a9bb61 100644
--- a/mobile/src/auth/infrastructure/dev/datasources/auth_datasource.ts
+++ b/mobile/src/auth/infrastructure/dev/datasources/auth_datasource.ts
@@ -1,42 +1,49 @@
-import { delay } from '../../../../common/utils/delay_time';
-import { AuthDataSource } from '../../../domain/datasources/auth_datasource';
-import { LoginInfoEntity } from '../../../domain/entities/login_info_entity';
-import { RegisterInfoEntity } from '../../../domain/entities/register_info';
-import { ResetPasswordInfoEntity } from '../../../domain/entities/reset_password_entity';
-import { UserInfoEntity } from '../../../domain/entities/user_info_entity';
+import { delay } from "../../../../common/utils/delay_time";
+import { AuthDataSource } from "../../../domain/datasources/auth_datasource";
+import { LoginInfoEntity } from "../../../domain/entities/login_info_entity";
+import { RegisterInfoEntity } from "../../../domain/entities/register_info";
+import { ResetPasswordInfoEntity } from "../../../domain/entities/reset_password_entity";
+import { UserInfoEntity } from "../../../domain/entities/user_info_entity";
export class AuthDataSourceDev implements AuthDataSource {
- getResetCode (email: string): Promise{
- throw new Error("Method not implemented.");
- }
- resetPassword (resetInfo: ResetPasswordInfoEntity): Promise {
- throw new Error("Method not implemented.");
- }
- async login(email: string, password: string): Promise{
- await delay(1000);
- return new Promise((resolve) => {
- resolve({
- user: {
- id: 1,
- email: email,
- name: 'John',
- lastName: 'Doe',
- birthDate: new Date('1990-01-01'),
- },
- token: 'token'
- })});
- }
- async register(user: RegisterInfoEntity): Promise{
- return new Promise((resolve) => {
- resolve({
- user: {
- id: 1,
- email: user.email,
- name: user.name,
- lastName: user.lastName,
- birthDate: new Date('1990-01-01'),
- },
- token: 'token'
- })});
- }
-
+ verifyAccount(code: string): Promise {
+ throw new Error("Method not implemented.");
+ }
+ async resendConfirmationCode() {
+ throw new Error("Method not implemented.");
+ }
+ getResetCode(email: string): Promise {
+ throw new Error("Method not implemented.");
+ }
+ resetPassword(resetInfo: ResetPasswordInfoEntity): Promise {
+ throw new Error("Method not implemented.");
+ }
+ async login(email: string, password: string): Promise {
+ await delay(1000);
+ return new Promise((resolve) => {
+ resolve({
+ user: {
+ id: 1,
+ email: email,
+ name: "John",
+ lastName: "Doe",
+ birthDate: new Date("1990-01-01"),
+ },
+ token: "token",
+ });
+ });
+ }
+ async register(user: RegisterInfoEntity): Promise {
+ return new Promise((resolve) => {
+ resolve({
+ user: {
+ id: 1,
+ email: user.email,
+ name: user.name,
+ lastName: user.lastName,
+ birthDate: new Date("1990-01-01"),
+ },
+ token: "token",
+ });
+ });
+ }
}
diff --git a/mobile/src/common/components/audio_player.tsx b/mobile/src/common/components/audio_player.tsx
index 3f27515e40cecae98f5662c905c2985dd7c53824..aa3c426894a897295baa6eb136ceda02101e2e1f 100644
--- a/mobile/src/common/components/audio_player.tsx
+++ b/mobile/src/common/components/audio_player.tsx
@@ -6,18 +6,26 @@ import { LIGHT_THEME } from "../const/theme";
import { useAudio } from "../contexts/audio_context";
import { useEffect } from "react";
import { API_URL } from "../const/api";
+import { useDataContext } from "../contexts/data_context";
interface AudioPlayerProps {
pointId: number;
}
export const AudioPlayer = ({ pointId }: AudioPlayerProps) => {
- const { loadAudio, position, togglePlay, isPlaying, duration, onValueChange } =
- useAudio();
+ const {
+ loadAudio,
+ position,
+ togglePlay,
+ isPlaying,
+ duration,
+ onValueChange,
+ } = useAudio();
+ const { language } = useDataContext();
- useEffect(() => {
- loadAudio({ uri: `${API_URL}/point/${pointId}/audio?lang=es`});
- }, []);
+ useEffect(() => {
+ loadAudio({ uri: `${API_URL}/point/${pointId}/audio?lang=${language}` });
+ }, []);
return (
diff --git a/mobile/src/common/components/circle_avatar.tsx b/mobile/src/common/components/circle_avatar.tsx
index 118d296bb24d8c2b34c9a5d10dcbda39d684da80..4033778767692ac80cd170f409b3b95d49838638 100644
--- a/mobile/src/common/components/circle_avatar.tsx
+++ b/mobile/src/common/components/circle_avatar.tsx
@@ -1,14 +1,44 @@
import { View, Image, ImageSourcePropType } from "react-native";
+import { set } from "react-hook-form";
+import { useEffect, useState } from "react";
+import { use } from "i18next";
+
+const avatarSource = require("../../../assets/avatar.png");
interface CircleAvatarProps {
- size: number;
- source: ImageSourcePropType;
+ size: number;
+ source: ImageSourcePropType | null;
}
-export const CircleAvatar = ({ size, source }: CircleAvatarProps) => {
- return (
-
-
-
- );
-}
\ No newline at end of file
+export const CircleAvatar = ({
+ size,
+ source: imageSource,
+}: CircleAvatarProps) => {
+ const [source, setSource] = useState(imageSource);
+
+ const handleError = () => {
+ setSource(avatarSource);
+ };
+
+ useEffect(() => {
+ setSource(imageSource);
+ }, [imageSource]);
+
+ return (
+
+
+
+ );
+};
diff --git a/mobile/src/common/components/floating_end_action_button.tsx b/mobile/src/common/components/floating_end_action_button.tsx
index 570457100111353db4e7fde36db511ce8cd79385..c3f4cce60e779c766dce4457b3960b60d81136df 100644
--- a/mobile/src/common/components/floating_end_action_button.tsx
+++ b/mobile/src/common/components/floating_end_action_button.tsx
@@ -2,16 +2,31 @@ import { TouchableOpacity, Text, StyleSheet } from "react-native";
import { LIGHT_THEME } from "../const/theme";
interface FloatingEndActionButtonProps {
- onPress: () => void;
- title: string;
+ onPress: () => void;
+ title: string;
+ isDisabled?: boolean;
}
-export const FloatingEndActionButton = ({ onPress, title }: FloatingEndActionButtonProps) => {
+export const FloatingEndActionButton = ({
+ onPress,
+ title,
+ isDisabled,
+}: FloatingEndActionButtonProps) => {
+ const handleOnPress = () => {
+ if (isDisabled) {
+ return;
+ }
+ onPress();
+ };
+
return (
{title}
@@ -19,20 +34,20 @@ export const FloatingEndActionButton = ({ onPress, title }: FloatingEndActionBut
};
const styles = StyleSheet.create({
- generate_route_button: {
- position: "absolute",
- justifyContent: "center",
- alignItems: "center",
- backgroundColor: LIGHT_THEME.color.primary,
- borderRadius: 25,
- bottom: 10,
- width: 200,
- height: 50,
- elevation: 5,
- alignSelf: "center",
- },
- generate_router_button_text: {
- color: LIGHT_THEME.color.white,
- fontWeight: "bold",
- },
+ generate_route_button: {
+ position: "absolute",
+ justifyContent: "center",
+ alignItems: "center",
+ backgroundColor: LIGHT_THEME.color.primary,
+ borderRadius: 25,
+ bottom: 10,
+ width: 200,
+ height: 50,
+ elevation: 5,
+ alignSelf: "center",
+ },
+ generate_router_button_text: {
+ color: LIGHT_THEME.color.white,
+ fontWeight: "bold",
+ },
});
diff --git a/mobile/src/common/components/floating_splitted_end_button.tsx b/mobile/src/common/components/floating_splitted_end_button.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..6cabfefa7facf327c785e09a9c3d2ac39998d4f1
--- /dev/null
+++ b/mobile/src/common/components/floating_splitted_end_button.tsx
@@ -0,0 +1,99 @@
+import { TouchableOpacity, Text, StyleSheet, View } from "react-native";
+import { LIGHT_THEME } from "../const/theme";
+
+interface FloatingSplittedActionButtonProps {
+ onLeftPress: () => void;
+ onRightPress: () => void;
+ mainTitle: string;
+ leftTitle: string;
+ rightTitle: string;
+ isDisabled?: boolean;
+}
+
+export const FloatingSplittedActionButton = ({
+ onLeftPress,
+ onRightPress,
+ mainTitle,
+ leftTitle,
+ rightTitle,
+ isDisabled,
+}: FloatingSplittedActionButtonProps) => {
+ const handleOnLeftPress = () => {
+ if (isDisabled) {
+ return;
+ }
+ onLeftPress();
+ };
+
+ const handleOnRightPress = () => {
+ if (isDisabled) {
+ return;
+ }
+ onRightPress();
+ };
+
+ return (
+
+ {mainTitle}
+
+
+ {leftTitle}
+
+
+ {rightTitle}
+
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ generate_route_button: {
+ position: "absolute",
+ justifyContent: "center",
+ alignItems: "center",
+ backgroundColor: LIGHT_THEME.color.primary,
+ borderRadius: 25,
+ bottom: 10,
+ width: 200,
+ height: 75,
+ elevation: 5,
+ gap: 5,
+ alignSelf: "center",
+ overflow: "hidden",
+ },
+ generate_router_button_text: {
+ color: LIGHT_THEME.color.white,
+ fontWeight: "bold",
+ },
+ leftButton: {
+ justifyContent: "center",
+ alignItems: "center",
+ backgroundColor: LIGHT_THEME.color.secondary,
+ width: 100,
+ height: 40,
+ borderRightWidth: 1,
+ borderColor: LIGHT_THEME.color.white,
+ },
+ rigthButton: {
+ justifyContent: "center",
+ alignItems: "center",
+ backgroundColor: LIGHT_THEME.color.secondary,
+ width: 100,
+ height: 40,
+ },
+});
diff --git a/mobile/src/common/components/slide_control.tsx b/mobile/src/common/components/slide_control.tsx
index 95501c6edd19eadb72ff613f85215b70cbabbe99..be51bcc17fe51867246a8fdbc8ae6037a62709c1 100644
--- a/mobile/src/common/components/slide_control.tsx
+++ b/mobile/src/common/components/slide_control.tsx
@@ -1,6 +1,7 @@
import { TouchableOpacity, View, Text, StyleSheet } from "react-native";
import { LIGHT_THEME } from "../const/theme";
import { FontAwesome, MaterialIcons } from "@expo/vector-icons";
+import { useTranslation } from "react-i18next";
interface SlideControlProps {
onNext: () => void;
@@ -17,25 +18,28 @@ export const SlideControl = ({
isFirst,
isLast,
}: SlideControlProps) => {
+ const { t } = useTranslation();
return (
- {isFirst || !onPrevious ? : (
+ {isFirst || !onPrevious ? (
+
+ ) : (
- Previous
+ {t("slideControl.previousButton")}
)}
{isLast ? (
- Finish
+ {t("slideControl.finishButton")}
) : (
- Next
+ {t("slideControl.nextButton")}
;
@@ -43,6 +44,7 @@ const DataContext = createContext({
travelRepository: null,
routeRepository: null,
profileRepository: null,
+ language: "",
});
const getProductionContext = (language: string): DataContextType => {
@@ -57,6 +59,7 @@ const getProductionContext = (language: string): DataContextType => {
profileRepository: new ProfileRepositoryImpl(
new ProfileDataSourceProd(language)
),
+ language,
};
};
@@ -68,6 +71,7 @@ const getDevelopmentContext = (): DataContextType => {
travelRepository: new TravelRepositoryImpl(new TravelDatasourceDev()),
routeRepository: new RouteRepositoryImpl(new RouteDataSourceDev()),
profileRepository: new ProfileRepositoryImpl(new ProfileDataSourceDev()),
+ language: "en",
};
};
diff --git a/mobile/src/lang/english_lang.ts b/mobile/src/lang/english_lang.ts
index 3ccc01d479644a587de47820bacc27e19e007e5f..d98ade4581ac3c1a12cc7f20fc2cea5953e62bcc 100644
--- a/mobile/src/lang/english_lang.ts
+++ b/mobile/src/lang/english_lang.ts
@@ -5,6 +5,7 @@ export const ENGLISH_LANG: Lang = {
title: "Login",
loginButton: "Login",
registerButton: "Register",
+ forgotPasswordButton: "Forgot password, click here",
},
registerScreen: {
title: "Register",
@@ -24,12 +25,24 @@ export const ENGLISH_LANG: Lang = {
title: "Account",
logoutButton: "Logout",
changePasswordButton: "Change password",
+ editUserDataButton: "Edit user data",
changeLanguageButton: "Change App Language",
changeMyInterestsButton: "Change my interests",
aboutUsButton: "About the app",
selectLanguage: "Select language",
saveButton: "Save",
},
+ activityPointScreen: {
+ endActivityButton: "End Activity",
+ nextActivityButton: "Next Activity",
+ activityPointLavel: "Activity Point",
+ directionsLabel: "Directions",
+ },
+ code_form: {
+ insertVerificationCode: "Insert verification code",
+ resendCode: "Resend code",
+ toResetPassword: "To reset your password we will send you an email",
+ },
changePasswordScreen: {
title: "Change password",
saveButton: "Save",
@@ -72,17 +85,38 @@ export const ENGLISH_LANG: Lang = {
descriptionText: "Description",
generateRouteButton: "Generate route",
},
+ generateManualRouteScreen: {
+ title: "Generate manual route",
+ startTime: "Start time",
+ endTime: "End time",
+ descriptionText: "Select the activities you want to do",
+ generateRouteButton: "Generate route",
+ },
routePreviewScreen: {
title: "Route preview",
startDayLabel: "Start day",
endDayLabel: "End day",
saveRouteButton: "Save route",
},
+ generateRouteButton: {
+ mainLabel: "Generate route",
+ manual: "Manual",
+ withAI: "With AI",
+ },
formsErrors: {
invalidEmailFormat: "Invalid email format",
requiredField: "Required field",
passwordNotMatch: "Passwords do not match",
emailAlreadyInUse: "Email already in use",
+ nameIsRequired: "Name is required",
+ lastNameIsRequired: "Last name is required",
+ passwordIsRequired: "Password is required",
+ emailIsRequired: "Email is required",
+ },
+ slideControl: {
+ previousButton: "Previous",
+ nextButton: "Next",
+ finishButton: "Finish",
},
common: {
or: "or",
diff --git a/mobile/src/lang/lang.ts b/mobile/src/lang/lang.ts
index ddee8e049bc11fcbb91c5cde73222918f65d8e0b..7d3d4db00630b251a7847b19d28b431675f25448 100644
--- a/mobile/src/lang/lang.ts
+++ b/mobile/src/lang/lang.ts
@@ -3,6 +3,7 @@ export interface Lang {
title: string;
loginButton: string;
registerButton: string;
+ forgotPasswordButton: string;
};
registerScreen: {
title: string;
@@ -12,6 +13,11 @@ export interface Lang {
homeScreen: {
title: string;
};
+ slideControl: {
+ previousButton: string;
+ nextButton: string;
+ finishButton: string;
+ };
travelHistoryScreen: {
title: string;
activeTravelsLabel: string;
@@ -27,6 +33,7 @@ export interface Lang {
aboutUsButton: string;
selectLanguage: string;
saveButton: string;
+ editUserDataButton: string;
};
changePasswordScreen: {
title: string;
@@ -40,6 +47,11 @@ export interface Lang {
title: string;
generateRouteButton: string;
};
+ code_form: {
+ insertVerificationCode: string;
+ resendCode: string;
+ toResetPassword: string;
+ };
activityInfoScreen: {
doActivityButton: string;
descriptionText: string;
@@ -55,12 +67,25 @@ export interface Lang {
descriptionText: string;
generateRouteButton: string;
};
+ generateManualRouteScreen: {
+ title: string;
+ descriptionText: string;
+ startTime: string;
+ endTime: string;
+ generateRouteButton: string;
+ };
routePreviewScreen: {
title: string;
startDayLabel: string;
endDayLabel: string;
saveRouteButton: string;
};
+ activityPointScreen: {
+ activityPointLavel: string;
+ endActivityButton: string;
+ directionsLabel: string;
+ nextActivityButton: string;
+ };
tabBar: {
homeLabel: string;
travelHistoryLabel: string;
@@ -81,6 +106,15 @@ export interface Lang {
requiredField: string;
passwordNotMatch: string;
emailAlreadyInUse: string;
+ nameIsRequired: string;
+ lastNameIsRequired: string;
+ emailIsRequired: string;
+ passwordIsRequired: string;
+ };
+ generateRouteButton: {
+ mainLabel: string;
+ withAI: string;
+ manual: string;
};
common: {
or: string;
@@ -88,3 +122,93 @@ export interface Lang {
showMore: string;
};
}
+
+export const LANG_CONSTANTS = {
+ loginScreenTitle: "loginScreen.title",
+ loginScreenLoginButton: "loginScreen.loginButton",
+ loginScreenRegisterButton: "loginScreen.registerButton",
+ loginScreenForgotPasswordButton: "loginScreen.forgotPasswordButton",
+ registerScreenTitle: "registerScreen.title",
+ registerScreenLoginButton: "registerScreen.loginButton",
+ registerScreenRegisterButton: "registerScreen.registerButton",
+ homeScreenTitle: "homeScreen.title",
+ slideControlPreviousButton: "slideControl.previousButton",
+ slideControlNextButton: "slideControl.nextButton",
+ slideControlFinishButton: "slideControl.finishButton",
+ travelHistoryScreenTitle: "travelHistoryScreen.title",
+ travelHistoryScreenActiveTravelsLabel:
+ "travelHistoryScreen.activeTravelsLabel",
+ travelHistoryScreenPastTravelsLabel: "travelHistoryScreen.pastTravelsLabel",
+ travelHistoryScreenNoTravelsLabel: "travelHistoryScreen.noTravelsLabel",
+ accountScreenTitle: "accountScreen.title",
+ accountScreenLogoutButton: "accountScreen.logoutButton",
+ accountScreenChangeLanguageButton: "accountScreen.changeLanguageButton",
+ accountScreenChangePasswordButton: "accountScreen.changePasswordButton",
+ accountScreenChangeMyInterestsButton: "accountScreen.changeMyInterestsButton",
+ accountScreenAboutUsButton: "accountScreen.aboutUsButton",
+ accountScreenSelectLanguage: "accountScreen.selectLanguage",
+ accountScreenSaveButton: "accountScreen.saveButton",
+ accountScreenEditUserDataButton: "accountScreen.editUserDataButton",
+ changePasswordScreenTitle: "changePasswordScreen.title",
+ changePasswordScreenSaveButton: "changePasswordScreen.saveButton",
+ selectTownScreenTitle: "selectTownScreen.title",
+ selectTownScreenDescriptionText: "selectTownScreen.descriptionText",
+ selectTownActivityScreenTitle: "selectTownActivityScreen.title",
+ selectTownActivityScreenGenerateRouteButton:
+ "selectTownActivityScreen.generateRouteButton",
+ codeFormInsertVerificationCode: "code_form.insertVerificationCode",
+ codeFormResendCode: "code_form.resendCode",
+ codeFormToResetPassword: "code_form.toResetPassword",
+ activityInfoScreenDoActivityButton: "activityInfoScreen.doActivityButton",
+ activityInfoScreenDescriptionText: "activityInfoScreen.descriptionText",
+ activityInfoScreenShowMoreButton: "activityInfoScreen.showMoreButton",
+ activityInfoScreenShowLessButton: "activityInfoScreen.showLessButton",
+ activityInfoScreenNoDesciptionFound: "activityInfoScreen.noDesciptionFound",
+ scanScreenTitle: "scanScreen.title",
+ generateRouteScreenTitle: "generateRouteScreen.title",
+ generateRouteScreenDescriptionText: "generateRouteScreen.descriptionText",
+ generateRouteScreenGenerateRouteButton:
+ "generateRouteScreen.generateRouteButton",
+ generateManualRouteScreenTitle: "generateManualRouteScreen.title",
+ generateManualRouteScreenDescriptionText:
+ "generateManualRouteScreen.descriptionText",
+ generateManualRouteScreenStartTime: "generateManualRouteScreen.startTime",
+ generateManualRouteScreenEndTime: "generateManualRouteScreen.endTime",
+ generateManualRouteScreenGenerateRouteButton:
+ "generateManualRouteScreen.generateRouteButton",
+ routePreviewScreenTitle: "routePreviewScreen.title",
+ routePreviewScreenStartDayLabel: "routePreviewScreen.startDayLabel",
+ routePreviewScreenEndDayLabel: "routePreviewScreen.endDayLabel",
+ routePreviewScreenSaveRouteButton: "routePreviewScreen.saveRouteButton",
+ activityPointScreenActivityPointLavel:
+ "activityPointScreen.activityPointLavel",
+ activityPointScreenEndActivityButton: "activityPointScreen.endActivityButton",
+ activityPointScreenDirectionsLabel: "activityPointScreen.directionsLabel",
+ activityPointScreenNextActivityButton:
+ "activityPointScreen.nextActivityButton",
+ tabBarHomeLabel: "tabBar.homeLabel",
+ tabBarTravelHistoryLabel: "tabBar.travelHistoryLabel",
+ tabBarAccountLabel: "tabBar.accountLabel",
+ formsEmail: "forms.email",
+ formsPassword: "forms.password",
+ formsConfirmPassword: "forms.confirmPassword",
+ formsName: "forms.name",
+ formsLastName: "forms.lastName",
+ formsDateOfBirth: "forms.dateOfBirth",
+ formsStartTime: "forms.startTime",
+ formsEndTime: "forms.endTime",
+ formsErrorsInvalidEmailFormat: "formsErrors.invalidEmailFormat",
+ formsErrorsRequiredField: "formsErrors.requiredField",
+ formsErrorsPasswordNotMatch: "formsErrors.passwordNotMatch",
+ formsErrorsEmailAlreadyInUse: "formsErrors.emailAlreadyInUse",
+ formsErrorsNameIsRequired: "formsErrors.nameIsRequired",
+ formsErrorsLastNameIsRequired: "formsErrors.lastNameIsRequired",
+ formsErrorsEmailIsRequired: "formsErrors.emailIsRequired",
+ formsErrorsPasswordIsRequired: "formsErrors.passwordIsRequired",
+ commonOr: "common.or",
+ commonDescriptionLabel: "common.descriptionLabel",
+ commonShowMore: "common.showMore",
+ generateRouteButtonMainLabel: "generateRouteButton.mainLabel",
+ generateRouteButtonWithAI: "generateRouteButton.withAI",
+ generateRouteButtonManual: "generateRouteButton.manual",
+};
diff --git a/mobile/src/lang/spanish_lang.ts b/mobile/src/lang/spanish_lang.ts
index 9ad2cb5ffeb9497670f7703106e12f7c3d314d30..31e779f0454859bbffd99bc829ded16d4cfeafc3 100644
--- a/mobile/src/lang/spanish_lang.ts
+++ b/mobile/src/lang/spanish_lang.ts
@@ -5,6 +5,7 @@ export const SPANISH_LANG: Lang = {
title: "Iniciar sesión",
loginButton: "Iniciar sesión",
registerButton: "Registrarse",
+ forgotPasswordButton: "Olvidaste tu contraseña, haz click aquí",
},
registerScreen: {
title: "Registrarse",
@@ -23,6 +24,7 @@ export const SPANISH_LANG: Lang = {
accountScreen: {
title: "Cuenta",
logoutButton: "Cerrar sesión",
+ editUserDataButton: "Editar datos de usuario",
changePasswordButton: "Cambiar contraseña",
changeLanguageButton: "Cambiar idioma de la aplicación",
changeMyInterestsButton: "Cambiar mis intereses",
@@ -30,6 +32,17 @@ export const SPANISH_LANG: Lang = {
selectLanguage: "Seleccionar idioma",
saveButton: "Guardar",
},
+ activityPointScreen: {
+ endActivityButton: "Finalizar actividad",
+ nextActivityButton: "Siguiente actividad",
+ activityPointLavel: "Punto de actividad",
+ directionsLabel: "Direcciones",
+ },
+ code_form: {
+ insertVerificationCode: "Insertar código de verificación",
+ resendCode: "Reenviar código",
+ toResetPassword: "Para restablecer tu contraseña te enviaremos un email",
+ },
changePasswordScreen: {
title: "Cambiar contraseña",
saveButton: "Guardar",
@@ -63,6 +76,13 @@ export const SPANISH_LANG: Lang = {
endDayLabel: "Día de finalización",
saveRouteButton: "Guardar ruta",
},
+ generateManualRouteScreen: {
+ title: "Generar ruta manual",
+ descriptionText: "Descripción",
+ startTime: "Hora de inicio",
+ endTime: "Hora de finalización",
+ generateRouteButton: "Generar ruta",
+ },
tabBar: {
homeLabel: "Inicio",
travelHistoryLabel: "Historial de viajes",
@@ -78,11 +98,25 @@ export const SPANISH_LANG: Lang = {
endTime: "Hora de finalización",
startTime: "Hora de inicio",
},
+ slideControl: {
+ previousButton: "Anterior",
+ nextButton: "Siguiente",
+ finishButton: "Finalizar",
+ },
+ generateRouteButton: {
+ mainLabel: "Generar ruta",
+ withAI: "con IA",
+ manual: "manualmente",
+ },
formsErrors: {
invalidEmailFormat: "Formato de correo electrónico inválido",
requiredField: "Campo requerido",
passwordNotMatch: "Las contraseñas no coinciden",
emailAlreadyInUse: "Correo electrónico ya en uso",
+ nameIsRequired: "El nombre es requerido",
+ lastNameIsRequired: "El apellido es requerido",
+ passwordIsRequired: "La contraseña es requerida",
+ emailIsRequired: "El correo electrónico es requerido",
},
common: {
or: "o",
diff --git a/mobile/src/place/domain/datasources/state_datasource.ts b/mobile/src/place/domain/datasources/state_datasource.ts
index 99f1c97341e871e37913c8d561eb305452ed2102..86502a885427eb4948803f0dc27a9fb1eda05ac7 100644
--- a/mobile/src/place/domain/datasources/state_datasource.ts
+++ b/mobile/src/place/domain/datasources/state_datasource.ts
@@ -1,11 +1,11 @@
-
import { ActivityInfoEntity } from "../../../activity/domain/entities/activity_info_entity";
import { StateEntity } from "../entities/state_entity";
import { TownEntity } from "../entities/town_entity";
export interface StateDataSource {
- getStates(): Promise;
- getTowns(stateId: number): Promise;
- getTownActivities(townId: number): Promise;
- getActivityInfo(activityId: number): Promise;
-}
\ No newline at end of file
+ getStates(): Promise;
+ getTowns(stateId: number): Promise;
+ getTownActivities(townId: number): Promise;
+ getOpenActivities(townId: number): Promise;
+ getActivityInfo(activityId: number): Promise;
+}
diff --git a/mobile/src/place/domain/repositories/state_repository.ts b/mobile/src/place/domain/repositories/state_repository.ts
index f6e9b14f41a7d311c188a0b3f8d81766a2cffcff..c795e10033a6d9604afa687557051fda1f6c54ff 100644
--- a/mobile/src/place/domain/repositories/state_repository.ts
+++ b/mobile/src/place/domain/repositories/state_repository.ts
@@ -3,8 +3,9 @@ import { StateEntity } from "../entities/state_entity";
import { TownEntity } from "../entities/town_entity";
export interface StateRepository {
- getStates(): Promise;
- getTowns(stateId: number): Promise;
- getTownActivities(townId: number): Promise;
- getActivityInfo(activityId: number): Promise;
-}
\ No newline at end of file
+ getStates(): Promise;
+ getTowns(stateId: number): Promise;
+ getTownActivities(townId: number): Promise;
+ getOpenActivities(townId: number): Promise;
+ getActivityInfo(activityId: number): Promise;
+}
diff --git a/mobile/src/place/infrastructure/datasources/dev/state_datasource.ts b/mobile/src/place/infrastructure/datasources/dev/state_datasource.ts
index 31c0f59004047589b4828de4c9e57248702320a0..c3651838b928085dc3bab3039b901da77ddec4e8 100644
--- a/mobile/src/place/infrastructure/datasources/dev/state_datasource.ts
+++ b/mobile/src/place/infrastructure/datasources/dev/state_datasource.ts
@@ -1,97 +1,110 @@
-import { ActivityInfoEntity } from '../../../../activity/domain/entities/activity_info_entity';
-import { StateDataSource } from '../../../domain/datasources/state_datasource';
-import { StateEntity } from '../../../domain/entities/state_entity';
-import { TownEntity } from '../../../domain/entities/town_entity';
+import { ActivityInfoEntity } from "../../../../activity/domain/entities/activity_info_entity";
+import { StateDataSource } from "../../../domain/datasources/state_datasource";
+import { StateEntity } from "../../../domain/entities/state_entity";
+import { TownEntity } from "../../../domain/entities/town_entity";
export class StateDataSourceDev implements StateDataSource {
- getTowns(stateId: number): Promise {
- return new Promise((resolve) => {
- resolve(towns.filter(town => town.stateId === stateId));
- });
- }
- getStates(): Promise {
- return new Promise((resolve) => {
- resolve(states);
- });
- }
- getTownActivities(townId: number): Promise {
- return new Promise((resolve) => {
- resolve(activities.filter(activity => activity.townId === townId));
- });
- }
- getActivityInfo(activityId: number): Promise {
- return new Promise((resolve) => {
- resolve(activities.find(activity => activity.id === activityId));
- });
- }
+ getOpenActivities(townId: number): Promise {
+ throw new Error("Method not implemented.");
+ }
+ getTowns(stateId: number): Promise {
+ return new Promise((resolve) => {
+ resolve(towns.filter((town) => town.stateId === stateId));
+ });
+ }
+ getStates(): Promise {
+ return new Promise((resolve) => {
+ resolve(states);
+ });
+ }
+ getTownActivities(townId: number): Promise {
+ return new Promise((resolve) => {
+ resolve(activities.filter((activity) => activity.townId === townId));
+ });
+ }
+ getActivityInfo(activityId: number): Promise {
+ return new Promise((resolve) => {
+ resolve(activities.find((activity) => activity.id === activityId));
+ });
+ }
}
const states: StateEntity[] = [
- {
- id: 1,
- name: 'Zacatecas',
- imageUri: 'https://www.mexicodesconocido.com.mx/wp-content/uploads/2010/07/Catedral-_Plaza-de-armas-scaled.jpg'
- },
- {
- id: 2,
- name: 'Aguascalientes',
- imageUri: 'https://www.gob.mx/cms/uploads/article/main_image/103003/fundacion-ags.jpg'
- },
- {
- id: 3,
- name: 'Durango',
- imageUri: 'https://upload.wikimedia.org/wikipedia/commons/thumb/7/78/Panoramica_plaza_de_armas_Durango.jpg/288px-Panoramica_plaza_de_armas_Durango.jpg'
- }
+ {
+ id: 1,
+ name: "Zacatecas",
+ imageUri:
+ "https://www.mexicodesconocido.com.mx/wp-content/uploads/2010/07/Catedral-_Plaza-de-armas-scaled.jpg",
+ },
+ {
+ id: 2,
+ name: "Aguascalientes",
+ imageUri:
+ "https://www.gob.mx/cms/uploads/article/main_image/103003/fundacion-ags.jpg",
+ },
+ {
+ id: 3,
+ name: "Durango",
+ imageUri:
+ "https://upload.wikimedia.org/wikipedia/commons/thumb/7/78/Panoramica_plaza_de_armas_Durango.jpg/288px-Panoramica_plaza_de_armas_Durango.jpg",
+ },
];
// Towns es una lista de pueblos mágicos de cada estado
const towns: TownEntity[] = [
- {
- id: 1,
- name: 'Jerez',
- imageUri: 'https://www.lugaresturisticosenmexico.com/wp-content/uploads/2022/04/Jerez-Zacatecas-Pueblo-Magico-2.jpg',
- description: 'Jerez es un municipio del estado de Zacatecas, México. Es uno de los 58 municipios del estado y su cabecera es la ciudad de Jerez de García Salinas. Jerez es conocido por su arquitectura colonial y su gastronomía.',
- stateId: 1,
- },
- {
- id: 2,
- name: 'Calvillo',
- imageUri: 'https://www.mexicodesconocido.com.mx/wp-content/uploads/2014/04/aguascalientes_pueblo_magico_calvillo_plaza_principal_ig.jpg',
- stateId: 2,
- },
- {
- id: 3,
- name: 'Mapimí',
- imageUri: 'https://www.debate.com.mx/__export/1689796560699/sites/debate/img/2023/07/19/mapimx.jpg_759710130.jpg',
- stateId: 3,
- },
- {
- id: 4,
- name: 'Jalpa',
- imageUri: 'https://www.liderempresarial.com/wp-content/uploads/2022/12/Jalpa-1140x570.jpg',
- stateId: 1,
- }
+ {
+ id: 1,
+ name: "Jerez",
+ imageUri:
+ "https://www.lugaresturisticosenmexico.com/wp-content/uploads/2022/04/Jerez-Zacatecas-Pueblo-Magico-2.jpg",
+ description:
+ "Jerez es un municipio del estado de Zacatecas, México. Es uno de los 58 municipios del estado y su cabecera es la ciudad de Jerez de García Salinas. Jerez es conocido por su arquitectura colonial y su gastronomía.",
+ stateId: 1,
+ },
+ {
+ id: 2,
+ name: "Calvillo",
+ imageUri:
+ "https://www.mexicodesconocido.com.mx/wp-content/uploads/2014/04/aguascalientes_pueblo_magico_calvillo_plaza_principal_ig.jpg",
+ stateId: 2,
+ },
+ {
+ id: 3,
+ name: "Mapimí",
+ imageUri:
+ "https://www.debate.com.mx/__export/1689796560699/sites/debate/img/2023/07/19/mapimx.jpg_759710130.jpg",
+ stateId: 3,
+ },
+ {
+ id: 4,
+ name: "Jalpa",
+ imageUri:
+ "https://www.liderempresarial.com/wp-content/uploads/2022/12/Jalpa-1140x570.jpg",
+ stateId: 1,
+ },
];
// Activities es una lista de actividades turísticas de cada pueblo mágico
const activities: ActivityInfoEntity[] = [
- {
- id: 1,
- name: 'Santuario de Nuestra Señora de la Soledad',
- description: 'Santuario de Nuestra Señora de la Soledad en Jerez',
- townId: 1,
- available: 'Todo el año',
- location: 'Jerez, Zacatecas',
- imageUri: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSAOt4JS0AzrMsxDp0caz26vuanMq692g17nbI6-_nycw&s'
- },
- {
- id: 2,
- name: 'Feria de la tostada',
- description: 'Feria de la tostada en Jerez',
- townId: 1,
- available: 'Septiembre',
- location: 'Jerez, Zacatecas',
- imageUri: 'https://www.liderempresarial.com/wp-content/uploads/2022/08/WhatsApp-Image-2022-08-03-at-4.14.35-PM-856x570.jpeg'
- }
-]
\ No newline at end of file
+ {
+ id: 1,
+ name: "Santuario de Nuestra Señora de la Soledad",
+ description: "Santuario de Nuestra Señora de la Soledad en Jerez",
+ townId: 1,
+ available: "Todo el año",
+ location: "Jerez, Zacatecas",
+ imageUri:
+ "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSAOt4JS0AzrMsxDp0caz26vuanMq692g17nbI6-_nycw&s",
+ },
+ {
+ id: 2,
+ name: "Feria de la tostada",
+ description: "Feria de la tostada en Jerez",
+ townId: 1,
+ available: "Septiembre",
+ location: "Jerez, Zacatecas",
+ imageUri:
+ "https://www.liderempresarial.com/wp-content/uploads/2022/08/WhatsApp-Image-2022-08-03-at-4.14.35-PM-856x570.jpeg",
+ },
+];
diff --git a/mobile/src/place/infrastructure/datasources/prod/state_datasource.ts b/mobile/src/place/infrastructure/datasources/prod/state_datasource.ts
index 37a4f33b1285a51f1673bf2dc1c12857f0cf239f..3896577732378ce5f87f7d54d33544034b57cfce 100644
--- a/mobile/src/place/infrastructure/datasources/prod/state_datasource.ts
+++ b/mobile/src/place/infrastructure/datasources/prod/state_datasource.ts
@@ -10,12 +10,34 @@ import { placeModelToEntity } from "../../utils/place_utils";
import { ActivityInfoEntity } from "../../../../activity/domain/entities/activity_info_entity";
import { ActivityModel } from "../../../../activity/infrastructure/models/activity_model";
import { API_URL } from "../../../../common/const/api";
+import { ApiResponseError } from "../../../../auth/errors/api_response_error";
+import { ApiRequestStatus } from "../../../../common/const/api_request_states";
export class StateDataSourceProd implements StateDataSource {
private lang: string;
constructor(lang: string = "es") {
this.lang = lang;
}
+ async getOpenActivities(townId: number): Promise {
+ try {
+ const { status, data } = await axios.get(
+ `${API_URL}/place/open/${townId}/${this.lang}`
+ );
+ if (status !== 200) {
+ throw new Error("Error fetching activities");
+ }
+ return data.map(placeModelToEntity);
+ } catch (error) {
+ if (axios.isAxiosError(error)) {
+ throw new ApiResponseError(
+ error.response!.status,
+ "Error fetching activities"
+ );
+ } else {
+ throw new ApiResponseError(500, "Error fetching activities");
+ }
+ }
+ }
async getStates(): Promise {
const { status, data } = await axios.get(API_URL + "/state");
if (status !== 200) {
@@ -39,7 +61,6 @@ export class StateDataSourceProd implements StateDataSource {
if (status !== 200) {
throw new Error("Error fetching activities");
}
- console.log("data", data);
return data.map(placeModelToEntity);
}
async getActivityInfo(
diff --git a/mobile/src/place/infrastructure/repositories/state_repository.ts b/mobile/src/place/infrastructure/repositories/state_repository.ts
index a58adcbfd7d72a8f9e42d695a9d7d8f1122653c9..6cd9b204fff8172954761812a94cbd4f433f0a59 100644
--- a/mobile/src/place/infrastructure/repositories/state_repository.ts
+++ b/mobile/src/place/infrastructure/repositories/state_repository.ts
@@ -5,19 +5,20 @@ import { TownEntity } from "../../domain/entities/town_entity";
import { StateRepository } from "../../domain/repositories/state_repository";
export class StateRepositoryImpl implements StateRepository {
- constructor(
- private datasource: StateDataSource
- ){}
- getTowns(stateId: number): Promise {
- return this.datasource.getTowns(stateId);
- }
- getStates(): Promise {
- return this.datasource.getStates();
- }
- getTownActivities(townId: number): Promise {
- return this.datasource.getTownActivities(townId);
- }
- getActivityInfo(activityId: number): Promise {
- return this.datasource.getActivityInfo(activityId);
- }
-}
\ No newline at end of file
+ constructor(private datasource: StateDataSource) {}
+ getOpenActivities(townId: number): Promise {
+ return this.datasource.getOpenActivities(townId);
+ }
+ getTowns(stateId: number): Promise {
+ return this.datasource.getTowns(stateId);
+ }
+ getStates(): Promise {
+ return this.datasource.getStates();
+ }
+ getTownActivities(townId: number): Promise {
+ return this.datasource.getTownActivities(townId);
+ }
+ getActivityInfo(activityId: number): Promise {
+ return this.datasource.getActivityInfo(activityId);
+ }
+}
diff --git a/mobile/src/place/screens/town_activities_page.tsx b/mobile/src/place/screens/town_activities_page.tsx
index 7a5c88ae254a5f8dc7918123b6a82ff1155c8067..05618a710e1d11ddf87d0527c9055f0c5dd51ffa 100644
--- a/mobile/src/place/screens/town_activities_page.tsx
+++ b/mobile/src/place/screens/town_activities_page.tsx
@@ -16,6 +16,8 @@ import { FloatingEndActionButton } from "../../common/components/floating_end_ac
import { useState } from "react";
import { useGetActivities } from "../../activity/hooks/useGetActivities";
import { useTranslation } from "react-i18next";
+import { FloatingSplittedActionButton } from "../../common/components/floating_splitted_end_button";
+import { LANG_CONSTANTS } from "../../lang/lang";
interface TownActivitiesPageProps {
townId: number;
@@ -45,10 +47,14 @@ export const TownActivitiesPage = ({
router.push(`/state/${stateId}/town/${townId}/activity/${activityId}/`);
};
- const handleGenerateRoute = () => {
+ const handleOnLeftPress = () => {
router.push(`/routes/generate_route_form?townId=${townId}`);
};
+ const handleOnRightPress = () => {
+ router.push(`generate_route_manual?townId=${townId}`);
+ };
+
return (
}
keyExtractor={(item) => item.id.toString()}
/>
-
);
diff --git a/mobile/src/profile/domain/datasources/profile_datasource.ts b/mobile/src/profile/domain/datasources/profile_datasource.ts
index 6a9e0a3af089a747874ee23433698e8610ded31e..21cdd40c7d5027fb37df368685e899eec6f21f3d 100644
--- a/mobile/src/profile/domain/datasources/profile_datasource.ts
+++ b/mobile/src/profile/domain/datasources/profile_datasource.ts
@@ -1,9 +1,16 @@
import { IOption } from "../../../common/domain/entities/option";
+import { UserUpdateEntity } from "../entities/user_update_entity";
export interface ProfileDataSource {
- getInterests: () => Promise;
- getUserInterests: () => Promise;
- saveInterests: (options: IOption[]) => Promise;
- setUpProfile: (birthdate: string, interests: IOption[]) => Promise;
- changePassword: (oldPassword: string, newPassword: string) => Promise;
-};
\ No newline at end of file
+ getInterests: () => Promise;
+ getUserInterests: () => Promise;
+ saveInterests: (options: IOption[]) => Promise;
+ setUpProfile: (birthdate: string, interests: IOption[]) => Promise;
+ changePassword: (oldPassword: string, newPassword: string) => Promise;
+ getProfile: () => Promise;
+ editProfile: (
+ name: string,
+ lastName: string,
+ profileImage?: File | null
+ ) => Promise;
+}
diff --git a/mobile/src/profile/domain/entities/user_update_entity.ts b/mobile/src/profile/domain/entities/user_update_entity.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f0446d298d1e527f93eb8855772d3fff42768fde
--- /dev/null
+++ b/mobile/src/profile/domain/entities/user_update_entity.ts
@@ -0,0 +1,7 @@
+export interface UserUpdateEntity {
+ imageUrl: string | null;
+ lastName: string;
+ email: string;
+ name: string;
+ emailConfirmed: boolean;
+}
diff --git a/mobile/src/profile/domain/repositories/profile_repository.ts b/mobile/src/profile/domain/repositories/profile_repository.ts
index 728307e27caa3e3783e4915cb399dc9db8aa0992..4cf922dc640e87a8a13530844e1f9992daa97100 100644
--- a/mobile/src/profile/domain/repositories/profile_repository.ts
+++ b/mobile/src/profile/domain/repositories/profile_repository.ts
@@ -1,9 +1,16 @@
import { IOption } from "../../../common/domain/entities/option";
+import { UserUpdateEntity } from "../entities/user_update_entity";
export interface ProfileRepository {
- getInterests: () => Promise;
- getUserInterests: () => Promise;
- saveInterests: (options: IOption[]) => Promise;
- setUpProfile: (birthdate: string, interests: IOption[]) => Promise;
- changePassword: (oldPassword: string, newPassword: string) => Promise;
-};
\ No newline at end of file
+ getInterests: () => Promise;
+ getUserInterests: () => Promise;
+ saveInterests: (options: IOption[]) => Promise;
+ setUpProfile: (birthdate: string, interests: IOption[]) => Promise;
+ changePassword: (oldPassword: string, newPassword: string) => Promise;
+ getProfile: () => Promise;
+ editProfile: (
+ name: string,
+ lastName: string,
+ profileImage?: File | null
+ ) => Promise;
+}
diff --git a/mobile/src/profile/hooks/useEditProfile.ts b/mobile/src/profile/hooks/useEditProfile.ts
index b67a2bc5c47d49470f2e349a6390bf67bf64687a..100ccd288de3cd4d4ed789333556bae4f4b38718 100644
--- a/mobile/src/profile/hooks/useEditProfile.ts
+++ b/mobile/src/profile/hooks/useEditProfile.ts
@@ -1,6 +1,13 @@
-import { useState } from "react";
+import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import * as ImagePicker from "expo-image-picker";
+import { useDataContext } from "../../common/contexts/data_context";
+import { useGetUserInfo } from "./useGetUserInfo";
+import { ApiRequestStatus } from "../../common/const/api_request_states";
+import * as FileSystem from "expo-file-system";
+import { API_URL } from "../../common/const/api";
+import { useAuth } from "../../auth/contexts/auth_context";
+import axios from "axios";
export type EditProfileFormValues = {
name: string;
@@ -11,6 +18,29 @@ export type EditProfileFormValues = {
export const useEditProfile = () => {
const { control, handleSubmit, setValue } = useForm();
const [profileImage, setProfileImage] = useState(null);
+ const { userInfo, getProfileStatus } = useGetUserInfo();
+ const [requestStatus, setRequestStatus] = useState(
+ ApiRequestStatus.IDLE
+ );
+
+ const { profileRepository } = useDataContext();
+
+ useEffect(() => {
+ const requests = [getProfileStatus];
+ if (
+ requests.every(
+ (request) =>
+ request === ApiRequestStatus.SUCCESS ||
+ request === ApiRequestStatus.IDLE
+ )
+ ) {
+ setRequestStatus(ApiRequestStatus.IDLE);
+ } else if (requests.some((request) => request === ApiRequestStatus.ERROR)) {
+ setRequestStatus(ApiRequestStatus.ERROR);
+ } else {
+ setRequestStatus(ApiRequestStatus.LOADING);
+ }
+ }, [getProfileStatus]);
const pickProfileImage = async () => {
const permissions = await ImagePicker.requestMediaLibraryPermissionsAsync();
@@ -25,18 +55,41 @@ export const useEditProfile = () => {
});
if (!result.canceled) {
- console.log(result.assets[0].uri);
setProfileImage(result.assets[0].uri);
}
};
+ const setLoading = () => setRequestStatus(ApiRequestStatus.LOADING);
+
const onValidSubmit = async (data: EditProfileFormValues) => {
- if (profileImage) {
- data.profileImage = new File([profileImage], "profileImage");
- setValue("profileImage", data.profileImage);
+ setLoading();
+ try {
+ if (profileImage) {
+ const result = await FileSystem.uploadAsync(
+ `${API_URL}/user/photo`,
+ `${profileImage}`,
+ {
+ uploadType: FileSystem.FileSystemUploadType.MULTIPART,
+ fieldName: "image",
+ httpMethod: "PATCH",
+ headers: {
+ "Content-Type": "multipart/form-data",
+ Authorization:
+ axios.defaults.headers.common["Authorization"]?.toString() ||
+ "",
+ },
+ }
+ );
+ }
+ await profileRepository!.editProfile(
+ data.name,
+ data.lastName,
+ data.profileImage
+ );
+ setRequestStatus(ApiRequestStatus.SUCCESS);
+ } catch (error) {
+ setRequestStatus(ApiRequestStatus.ERROR);
}
- //TODO: Send data to the server
- console.log(data);
};
const onInvalidSubmit = (errors: any) => {
@@ -46,5 +99,12 @@ export const useEditProfile = () => {
const onSubmit = async () => {
await handleSubmit(onValidSubmit, onInvalidSubmit)();
};
- return { onSubmit, control, pickProfileImage, profileImage };
+ return {
+ onSubmit,
+ control,
+ pickProfileImage,
+ profileImage,
+ requestStatus,
+ userInfo,
+ };
};
diff --git a/mobile/src/profile/hooks/useGetUserInfo.ts b/mobile/src/profile/hooks/useGetUserInfo.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2398df647d91f19c38f0b6a7a569eac99f5b9af9
--- /dev/null
+++ b/mobile/src/profile/hooks/useGetUserInfo.ts
@@ -0,0 +1,18 @@
+import { useDataContext } from "../../common/contexts/data_context";
+import { useGet } from "../../common/hooks/useGet";
+
+export const useGetUserInfo = () => {
+ const { profileRepository } = useDataContext();
+
+ const callback = async () => {
+ return await profileRepository!.getProfile();
+ };
+
+ const { data, requestStatus, refresh } = useGet(callback);
+
+ return {
+ userInfo: data,
+ getProfileStatus: requestStatus,
+ refreshProfile: refresh,
+ };
+};
diff --git a/mobile/src/profile/infrastructure/datasources/dev/profile_datasource.ts b/mobile/src/profile/infrastructure/datasources/dev/profile_datasource.ts
index 863a94fef02a15d1a708398ac6cb5fbb81bbe66d..aa6a59c462a558fdaf4cdc75b87cf26d671cc4ec 100644
--- a/mobile/src/profile/infrastructure/datasources/dev/profile_datasource.ts
+++ b/mobile/src/profile/infrastructure/datasources/dev/profile_datasource.ts
@@ -1,37 +1,78 @@
-import { IOption } from '../../../../common/domain/entities/option';
-import { ProfileDataSource } from '../../../domain/datasources/profile_datasource';
+import axios from "axios";
+import { IOption } from "../../../../common/domain/entities/option";
+import { ProfileDataSource } from "../../../domain/datasources/profile_datasource";
+import { UserUpdateEntity } from "../../../domain/entities/user_update_entity";
+import { API_URL } from "../../../../common/const/api";
export class ProfileDataSourceDev implements ProfileDataSource {
- async getUserInterests(): Promise {
- const interests = await this.getInterests();
- interests[0].isSelected = true;
- interests[1].isSelected = true;
- interests[2].isSelected = true;
- return interests;
- }
- async getInterests(): Promise {
- return [
- { id: 1, name: 'Tecnología', isSelected: false },
- { id: 2, name: 'Deportes', isSelected: false },
- { id: 3, name: 'Cine', isSelected: false},
- { id: 4, name: 'Música', isSelected: false },
- { id: 5, name: 'Ciencia', isSelected: false },
- { id: 6, name: 'Historia', isSelected: false },
- { id: 7, name: 'Arte', isSelected: false },
- { id: 8, name: 'Cocina', isSelected: false },
- { id: 9, name: 'Viajes', isSelected: false },
- { id: 10, name: 'Moda', isSelected: false },
- ];
+ async getProfile(): Promise {
+ const { data } = await axios.get<{
+ imageUrl: string | null;
+ lastName: string;
+ email: string;
+ name: string;
+ emailConfirmed: boolean;
+ }>(`${API_URL}/user/info`);
+ return {
+ imageUrl: data.imageUrl,
+ lastName: data.lastName,
+ email: data.email,
+ name: data.name,
+ emailConfirmed: data.emailConfirmed,
+ };
+ }
+ async editProfile(
+ name: string,
+ lastName: string,
+ profileImage?: File | null
+ ) {
+ try {
+ const { status } = await axios.patch(`${API_URL}/user/info`, {
+ name,
+ lastName,
+ });
+ if (profileImage) {
+ const formData = new FormData();
+ formData.append("image", profileImage);
+ await axios.patch(`${API_URL}/user/photo`, formData);
+ }
+ } catch (error) {
+ console.error(error);
}
+ }
+ async getUserInterests(): Promise {
+ const interests = await this.getInterests();
+ interests[0].isSelected = true;
+ interests[1].isSelected = true;
+ interests[2].isSelected = true;
+ return interests;
+ }
+ async getInterests(): Promise {
+ return [
+ { id: 1, name: "Tecnología", isSelected: false },
+ { id: 2, name: "Deportes", isSelected: false },
+ { id: 3, name: "Cine", isSelected: false },
+ { id: 4, name: "Música", isSelected: false },
+ { id: 5, name: "Ciencia", isSelected: false },
+ { id: 6, name: "Historia", isSelected: false },
+ { id: 7, name: "Arte", isSelected: false },
+ { id: 8, name: "Cocina", isSelected: false },
+ { id: 9, name: "Viajes", isSelected: false },
+ { id: 10, name: "Moda", isSelected: false },
+ ];
+ }
- async changePassword(oldPassword: string, newPassword: string): Promise {
- console.log('Changing password', oldPassword, newPassword);
- }
+ async changePassword(
+ oldPassword: string,
+ newPassword: string
+ ): Promise {
+ console.log("Changing password", oldPassword, newPassword);
+ }
- async saveInterests(options: IOption[]): Promise {
- console.log('Saving interests', options);
- }
+ async saveInterests(options: IOption[]): Promise {
+ console.log("Saving interests", options);
+ }
- async setUpProfile(birthdate: string, interests: IOption[]): Promise {
- console.log('Setting up profile', birthdate, interests);
- }
-}
\ No newline at end of file
+ async setUpProfile(birthdate: string, interests: IOption[]): Promise {
+ console.log("Setting up profile", birthdate, interests);
+ }
+}
diff --git a/mobile/src/profile/infrastructure/datasources/prod/profile_datasource.ts b/mobile/src/profile/infrastructure/datasources/prod/profile_datasource.ts
index 62e93095f44ae5b3518143978c878a0c974959ac..3cb3a18df80ef4418d11afbf517503a558cb3bec 100644
--- a/mobile/src/profile/infrastructure/datasources/prod/profile_datasource.ts
+++ b/mobile/src/profile/infrastructure/datasources/prod/profile_datasource.ts
@@ -1,17 +1,92 @@
-import axios from "axios";
+import axios, { AxiosError } from "axios";
import { IOption } from "../../../../common/domain/entities/option";
import { ProfileDataSource } from "../../../domain/datasources/profile_datasource";
import { API_URL } from "../../../../common/const/api";
import { CategoryModel } from "../../model/categorie_model";
import { categoryModelToOption } from "../../utils/category";
+import { UserUpdateEntity } from "../../../domain/entities/user_update_entity";
+import { UserInfoModel } from "../../model/user_info_model";
+import { ApiResponseError } from "../../../../auth/errors/api_response_error";
+import * as mime from "mime";
export class ProfileDataSourceProd implements ProfileDataSource {
private readonly lang: string;
constructor(lang: string) {
this.lang = lang;
}
+ async getProfile(): Promise {
+ try {
+ const { data, status } = await axios.get(
+ `${API_URL}/user/info`
+ );
+ if (status !== 200) {
+ throw new Error("Error getting user info");
+ }
+ return {
+ imageUrl: data.imageUrl,
+ lastName: data.lastName,
+ email: data.email,
+ name: data.name,
+ emailConfirmed: data.emailConfirmed,
+ };
+ } catch (error) {
+ console.log(error);
+ if (axios.isAxiosError(error)) {
+ const axiosError = error as AxiosError;
+ if (axiosError.response?.status === 401) {
+ throw new ApiResponseError(401, "Unauthorized");
+ }
+ }
+ throw new ApiResponseError(500, "Internal server error");
+ }
+ }
+ async editProfile(
+ name: string,
+ lastName: string,
+ profileImage?: File | null
+ ): Promise {
+ try {
+ const { status: statusInfo } = await axios.patch(`${API_URL}/user/info`, {
+ name,
+ lastName,
+ });
+
+ if (statusInfo !== 200) {
+ throw new ApiResponseError(500, "Error updating user info");
+ }
+
+ if (profileImage) {
+ const formData = new FormData();
+
+ formData.append("image", profileImage);
+ const { status: statusImage } = await axios.patch(
+ `${API_URL}/user/photo`,
+ formData,
+ {
+ headers: {
+ "Content-Type": "multipart/form-data",
+ },
+ }
+ );
+ if (statusImage !== 200) {
+ throw new ApiResponseError(500, "Error updating user photo");
+ }
+ }
+ } catch (error) {
+ console.log("Error: ", error);
+ if (axios.isAxiosError(error)) {
+ const axiosError = error as AxiosError;
+ if (axiosError.response?.status === 401) {
+ throw new ApiResponseError(401, "Unauthorized");
+ }
+ }
+ throw new ApiResponseError(500, "Internal server error");
+ }
+ }
async getUserInterests(): Promise {
- const {data, status} = await axios.get(`${API_URL}/user/prefered-categories?lang=${this.lang}`);
+ const { data, status } = await axios.get(
+ `${API_URL}/user/prefered-categories?lang=${this.lang}`
+ );
if (status !== 200) {
throw new Error("Error getting user interests");
}
@@ -61,6 +136,5 @@ export class ProfileDataSourceProd implements ProfileDataSource {
newPassword: newPassword,
}
);
- console.log(status, data);
}
}
diff --git a/mobile/src/profile/infrastructure/model/user_info_model.ts b/mobile/src/profile/infrastructure/model/user_info_model.ts
new file mode 100644
index 0000000000000000000000000000000000000000..21daf9a1fe518067082c8cd3098e9d2281009109
--- /dev/null
+++ b/mobile/src/profile/infrastructure/model/user_info_model.ts
@@ -0,0 +1,7 @@
+export interface UserInfoModel {
+ imageUrl: string | null;
+ lastName: string;
+ email: string;
+ name: string;
+ emailConfirmed: boolean;
+}
diff --git a/mobile/src/profile/infrastructure/repositories/profile_repository.ts b/mobile/src/profile/infrastructure/repositories/profile_repository.ts
index 8ca0c03334a4cab61fb81687396593a2d3ba08a0..80b783d484673bb559e64021017be8b8281f677c 100644
--- a/mobile/src/profile/infrastructure/repositories/profile_repository.ts
+++ b/mobile/src/profile/infrastructure/repositories/profile_repository.ts
@@ -3,22 +3,35 @@ import { ProfileDataSource } from "../../domain/datasources/profile_datasource";
import { ProfileRepository } from "../../domain/repositories/profile_repository";
export class ProfileRepositoryImpl implements ProfileRepository {
- constructor(
- private dataSource: ProfileDataSource
- ){}
- getUserInterests(): Promise{
- return this.dataSource.getUserInterests();
- }
- async changePassword (oldPassword: string, newPassword: string): Promise {
- return this.dataSource.changePassword(oldPassword, newPassword);
- };
- async getInterests() {
- return this.dataSource.getInterests();
- }
- async saveInterests(options: IOption[]) {
- return this.dataSource.saveInterests(options);
- }
- async setUpProfile(birthdate: string, interests: IOption[]) {
- return this.dataSource.setUpProfile(birthdate, interests);
- }
-}
\ No newline at end of file
+ constructor(private dataSource: ProfileDataSource) {}
+ getUserInterests(): Promise {
+ return this.dataSource.getUserInterests();
+ }
+ async changePassword(
+ oldPassword: string,
+ newPassword: string
+ ): Promise {
+ return this.dataSource.changePassword(oldPassword, newPassword);
+ }
+ async getInterests() {
+ return this.dataSource.getInterests();
+ }
+ async saveInterests(options: IOption[]) {
+ return this.dataSource.saveInterests(options);
+ }
+ async setUpProfile(birthdate: string, interests: IOption[]) {
+ return this.dataSource.setUpProfile(birthdate, interests);
+ }
+
+ async getProfile() {
+ return this.dataSource.getProfile();
+ }
+
+ async editProfile(
+ name: string,
+ lastName: string,
+ profileImage?: File | null
+ ) {
+ return this.dataSource.editProfile(name, lastName, profileImage);
+ }
+}
diff --git a/mobile/src/profile/screens/about_us_page.tsx b/mobile/src/profile/screens/about_us_page.tsx
index c13e342f9bffcb48b67364870112bc2cae38b427..c3d3877a539f7eab8c0dc13d90cbb3e503de167c 100644
--- a/mobile/src/profile/screens/about_us_page.tsx
+++ b/mobile/src/profile/screens/about_us_page.tsx
@@ -1,57 +1,73 @@
-import { View, Text, StyleSheet } from "react-native";
+import { View, Text, StyleSheet, Image } from "react-native";
import { LIGHT_THEME } from "../../common/const/theme";
import { FontAwesome } from "@expo/vector-icons";
+const labsolLogoImage = require("../../../assets/labsol-removebg-preview.png");
+
+const gplLogoImage = require("../../../assets/GPLv3_Logo-removebg-preview.png");
+
export const AboutUsPage = () => {
- return (
-
- Equipo de desarrollo
-
-
-
-
-
-
- );
+ return (
+
+
+ Equipo de desarrollo
+
+
+
+
+
+
+
+
+
+ );
};
interface ItemTeam {
- name: string;
- role: string;
+ name: string;
+ role: string;
}
-const ItemTeamList = ({name, role}: ItemTeam) => {
- return (
-
-
- {name}
-
- {role}
-
- );
+const ItemTeamList = ({ name, role }: ItemTeam) => {
+ return (
+
+ {name}
+ {role}
+
+ );
};
const styles = StyleSheet.create({
- container: {
- flex: 1,
- width: "100%",
- padding: 20,
- backgroundColor: LIGHT_THEME.color.background,
- },
- itemTeam: {
- width: "100%",
- flexDirection: "row",
- backgroundColor: "red",
- gap: 10,
- alignItems: "center",
- height: 30,
- },
- nameTeam: {
- fontSize: 18,
- },
- roleTeamText: {
- fontSize: 18,
- fontWeight: "bold",
-
- }
-});
\ No newline at end of file
+ container: {
+ flex: 1,
+ width: "100%",
+ padding: 20,
+ alignItems: "center",
+ },
+ itemTeam: {
+ width: "100%",
+ gap: 5,
+ alignItems: "center",
+ },
+ nameTeam: {
+ fontSize: 18,
+ },
+ roleTeamText: {
+ fontSize: 18,
+ fontWeight: "bold",
+ },
+});
diff --git a/mobile/src/profile/screens/account_page.tsx b/mobile/src/profile/screens/account_page.tsx
index e42127d457a9ff4e71bc34dfc3f9bb6da7e6f055..669b542d7115cbc3cb4a1b7ee70fe7ba5d877365 100644
--- a/mobile/src/profile/screens/account_page.tsx
+++ b/mobile/src/profile/screens/account_page.tsx
@@ -7,31 +7,57 @@ import {
FontAwesome,
} from "@expo/vector-icons";
import { CustomTileButton } from "../../common/components/custom_tile_button";
-import { router } from "expo-router";
+import { router, useFocusEffect } from "expo-router";
import { LIGHT_THEME } from "../../common/const/theme";
import { useRef, useState } from "react";
import Modal from "react-native-modal";
-import { set } from "react-hook-form";
import { AntDesign } from "@expo/vector-icons";
import RNPickerSelect from "react-native-picker-select";
import { AvailableLanguages, Languages } from "../../lang/translations";
-import i18n from "i18next";
import { useTranslation } from "react-i18next";
import { useLang } from "../../lang/hooks/useLang";
+import { useGetUserInfo } from "../hooks/useGetUserInfo";
+import { ApiRequestStatus } from "../../common/const/api_request_states";
+import { FullPageLoader } from "../../common/components/full_page_loader";
//TODO: Add source to CircleAvatar
const source = require("../../../assets/avatar.png");
export const AccountPage = () => {
- const { logout, user } = useAuth();
const LANG = useTranslation();
+ const { userInfo, refreshProfile, getProfileStatus } = useGetUserInfo();
const [isModalVisible, setModalVisible] = useState(false);
+ const { logout, user } = useAuth();
+ const isRefreshNeeded = useRef(false);
+
+ useFocusEffect(() => {
+ if (isRefreshNeeded.current) {
+ refreshProfile();
+ isRefreshNeeded.current = false;
+ }
+ });
+
+ if (getProfileStatus === ApiRequestStatus.LOADING) {
+ return ;
+ }
+
return (
-
+
- {user?.name + " " + user?.lastName}
+ {userInfo
+ ? `${userInfo.name} ${userInfo.lastName}`
+ : `${user?.name} ${user?.lastName}`}
@@ -41,7 +67,7 @@ export const AccountPage = () => {
}}
>
{
color="black"
/>
}
- onPress={() => router.push("/profile/edit")}
+ onPress={() => {
+ isRefreshNeeded.current = true;
+ router.push("/profile/edit");
+ }}
/>
{
- const { user } = useAuth();
- const { onSubmit, control, pickProfileImage, profileImage } =
- useEditProfile();
+ const {
+ onSubmit,
+ control,
+ pickProfileImage,
+ profileImage,
+ requestStatus,
+ userInfo,
+ } = useEditProfile();
- const { name, lastName } = user!;
+ if (requestStatus === ApiRequestStatus.LOADING) {
+ return ;
+ }
+
+ if (requestStatus === ApiRequestStatus.ERROR || !userInfo) {
+ return (
+
+ Something went wrong
+
+ );
+ }
+
+ const { name, lastName, imageUrl } = userInfo;
return (
{
>
diff --git a/mobile/src/route/components/generate_route_form.tsx b/mobile/src/route/components/generate_route_form.tsx
index e7f889ae0b28e24ea9800b790f20db269a0bbfb8..0cd3199cd0a66b4e63f0382325ed92555a321443 100644
--- a/mobile/src/route/components/generate_route_form.tsx
+++ b/mobile/src/route/components/generate_route_form.tsx
@@ -1,7 +1,7 @@
import { Controller, Control } from "react-hook-form";
import { View, StyleSheet } from "react-native";
import { CustomTextInput } from "../../common/components/form/text_input";
-import { GenerateRouteFormValues } from "./../hooks/useGenerateRoute";
+import { GenerateRouteFormValues } from "./../hooks/useGenerateManualRoute";
import { DateTextInput } from "../../common/components/form/date_text_input";
import { useTranslation } from "react-i18next";
diff --git a/mobile/src/route/components/multiple_selectable_list.tsx b/mobile/src/route/components/multiple_selectable_list.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..e9c2fc49713dcd8b74917257faedf56079222a9b
--- /dev/null
+++ b/mobile/src/route/components/multiple_selectable_list.tsx
@@ -0,0 +1,80 @@
+import { useRef, useState } from "react";
+import { useTranslation } from "react-i18next";
+import { ViewStyle, StyleProp, FlatList } from "react-native";
+import { LANG_CONSTANTS } from "../../lang/lang";
+import { TouchableOpacity } from "react-native-gesture-handler";
+import { LIGHT_THEME } from "../../common/const/theme";
+
+export interface MultipleSelectableListProps {
+ items: T[];
+ contentContainerStyle?: StyleProp;
+ styles?: StyleProp;
+ selectedItemColor?: string;
+ itemComponent: (item: T) => JSX.Element;
+ onSelectionChange: (selectedItems: number[]) => void;
+}
+
+export const MultipleSelectableList = ({
+ items,
+ itemComponent,
+ onSelectionChange,
+ styles,
+ contentContainerStyle,
+ selectedItemColor,
+}: MultipleSelectableListProps) => {
+ const [selectedItemsSet, setSelectedItemsSet] = useState([]);
+ const onItemPress = (index: number) => {
+ if (selectedItemsSet.includes(index)) {
+ setSelectedItemsSet(selectedItemsSet.filter((item) => item !== index));
+ } else {
+ const newSelectedItems = [...selectedItemsSet, index];
+ setSelectedItemsSet(newSelectedItems);
+ onSelectionChange(newSelectedItems);
+ }
+ };
+ return (
+ index.toString()}
+ renderItem={({ item, index }) => (
+ onItemPress(index)}
+ itemComponent={() => itemComponent(item)}
+ />
+ )}
+ >
+ );
+};
+
+interface ListItemProps {
+ selected: boolean;
+ onPress: () => void;
+ selectedItemColor: string;
+ itemComponent: () => JSX.Element;
+}
+
+export const MultipleSelectableListItem = ({
+ selected,
+ onPress,
+ itemComponent,
+ selectedItemColor,
+}: ListItemProps) => {
+ return (
+
+ {itemComponent()}
+
+ );
+};
diff --git a/mobile/src/route/domain/datasource/route_datasource.ts b/mobile/src/route/domain/datasource/route_datasource.ts
index 537e77b07ab56d7d92ea97bd3280457f014759c5..7803320808a4e31d36421b5a421716a8444c8fb1 100644
--- a/mobile/src/route/domain/datasource/route_datasource.ts
+++ b/mobile/src/route/domain/datasource/route_datasource.ts
@@ -7,6 +7,12 @@ export interface RouteDataSource {
startTime: number,
endTime: number
) => Promise;
+ generateManualRoute: (
+ townId: number,
+ startTime: number,
+ endTime: number,
+ activities: string
+ ) => Promise;
getRoute: (routeId: number) => Promise;
saveRoute: (id: number) => Promise;
}
diff --git a/mobile/src/route/domain/repositories/route_repository.ts b/mobile/src/route/domain/repositories/route_repository.ts
index dc39ebaea9d18cb5e48e2b1e58be5d7d9e0fde23..ea7fa72a1a2aba21accec3d079ce5fb41fcb9d4b 100644
--- a/mobile/src/route/domain/repositories/route_repository.ts
+++ b/mobile/src/route/domain/repositories/route_repository.ts
@@ -7,6 +7,12 @@ export interface RouteRepository {
startTime: number,
endTime: number
) => Promise;
+ generateManualRoute: (
+ townId: number,
+ startTime: number,
+ endTime: number,
+ activities: string
+ ) => Promise;
getRoute: (routeId: number) => Promise;
saveRoute: (id: number) => Promise;
}
diff --git a/mobile/src/route/hooks/useGenerateManualRoute.ts b/mobile/src/route/hooks/useGenerateManualRoute.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a2f6f94e45ce53e2e4be7975ec1999f1e7a0cb6c
--- /dev/null
+++ b/mobile/src/route/hooks/useGenerateManualRoute.ts
@@ -0,0 +1,52 @@
+import { useForm } from "react-hook-form";
+import { useDataContext } from "../../common/contexts/data_context";
+import { router } from "expo-router";
+import { useState } from "react";
+
+export interface GenerateRouteFormValues {
+ start: string;
+ end: string;
+}
+
+export const useGenerateManualRoute = (townId: number) => {
+ const { routeRepository } = useDataContext();
+ const { control, handleSubmit, setValue } = useForm({
+ defaultValues: {
+ start: "",
+ end: "",
+ },
+ });
+ const [isEnabled, setIsEnabled] = useState(false);
+ const [activitiesIds, setActivitiesIds] = useState("");
+
+ const updateActivitiesIds = (ids: string) => {
+ setActivitiesIds(ids);
+ if (ids.length > 0) {
+ setIsEnabled(true);
+ } else {
+ setIsEnabled(false);
+ }
+ };
+
+ const onValidSubmit = async (data: GenerateRouteFormValues) => {
+ const { start, end } = data;
+ const [startHours] = start.split(":");
+ const [endHours] = end.split(":");
+ try {
+ const route = await routeRepository!.generateManualRoute(
+ townId,
+ +startHours,
+ +endHours,
+ activitiesIds
+ );
+ router.push(`/routes/generate_route?routeId=${route}`);
+ } catch (error) {}
+ };
+
+ const onSubmit = async () => {
+ await handleSubmit(onValidSubmit, (errors) => {
+ console.log(errors);
+ })();
+ };
+ return { control, onSubmit, updateActivitiesIds, isEnabled };
+};
diff --git a/mobile/src/route/infrastructure/datasources/dev/route_datasource.ts b/mobile/src/route/infrastructure/datasources/dev/route_datasource.ts
index 7b6417fc6756421426119f73c1cbed3ed7a6f823..93b726a4e13e691e35845718ce951d236b57aa0e 100644
--- a/mobile/src/route/infrastructure/datasources/dev/route_datasource.ts
+++ b/mobile/src/route/infrastructure/datasources/dev/route_datasource.ts
@@ -6,6 +6,14 @@ import { RouteDataSource } from "../../../domain/datasource/route_datasource";
import { RouteEntity } from "../../../domain/entities/route";
export class RouteDataSourceDev implements RouteDataSource {
+ async generateManualRoute(
+ townId: number,
+ startTime: number,
+ endTime: number,
+ activities: string
+ ): Promise {
+ throw new Error("Method not implemented.");
+ }
async saveRoute(id: number): Promise {
return new Promise((resolve) => {
return resolve();
diff --git a/mobile/src/route/infrastructure/datasources/prod/route_datasource.ts b/mobile/src/route/infrastructure/datasources/prod/route_datasource.ts
index 9f01441c6d519336f26fe68dfcf111875a629f24..7dadb0788e8674cc5d92984c8a803e8469f905b1 100644
--- a/mobile/src/route/infrastructure/datasources/prod/route_datasource.ts
+++ b/mobile/src/route/infrastructure/datasources/prod/route_datasource.ts
@@ -12,6 +12,27 @@ export class RouteDatasourceProd implements RouteDataSource {
constructor(language: string) {
this.language = language;
}
+ async generateManualRoute(
+ townId: number,
+ startTime: number,
+ endTime: number,
+ activities: string
+ ) {
+ const { data, status } = await axios.post<{ idRoute: number }>(
+ `${API_URL}/route/custom/${townId}/${this.language}`,
+ {
+ placesIds: activities.split(",").map((activity) => +activity),
+ start: startTime,
+ end: endTime,
+ }
+ );
+ console.log(data);
+ if (status !== 201) {
+ throw new Error("Error fetching route");
+ }
+
+ return data.idRoute;
+ }
async saveRoute(id: number): Promise {
const { status } = await axios.patch(`${API_URL}/route/${id}`, {
status: "accepted",
@@ -32,7 +53,6 @@ export class RouteDatasourceProd implements RouteDataSource {
end: endTime,
}
);
- console.info("Data loaded: ", data);
if (status !== 201) {
throw new Error("Error fetching route");
}
@@ -46,7 +66,6 @@ export class RouteDatasourceProd implements RouteDataSource {
if (status !== 200) {
throw new Error("Error fetching route");
}
- console.info(data);
return {
idRoute: data.idRoute,
startDate: new Date(data.startDate),
@@ -63,7 +82,7 @@ export class RouteDatasourceProd implements RouteDataSource {
latitude: +travelPlace.place.latitude,
longitude: +travelPlace.place.longitude,
},
- tags: travelPlace.place.categories.map((category) => category.name),
+ tags: travelPlace.place.categories?.map((category) => category.name),
startTime: new Date(travelPlace.startDate),
endTime: new Date(travelPlace.endDate),
done: travelPlace.done,
diff --git a/mobile/src/route/infrastructure/repositories/route_repository.ts b/mobile/src/route/infrastructure/repositories/route_repository.ts
index a8cff848a3ac06369db0ace7b64d026438376d6b..6279d04e5e9f970aaab0540886a5e0b0e32884c0 100644
--- a/mobile/src/route/infrastructure/repositories/route_repository.ts
+++ b/mobile/src/route/infrastructure/repositories/route_repository.ts
@@ -3,6 +3,19 @@ import { RouteRepository } from "../../domain/repositories/route_repository";
export class RouteRepositoryImpl implements RouteRepository {
constructor(private routeDataSource: RouteDataSource) {}
+ async generateManualRoute(
+ townId: number,
+ startTime: number,
+ endTime: number,
+ activities: string
+ ): Promise {
+ return this.routeDataSource.generateManualRoute(
+ townId,
+ startTime,
+ endTime,
+ activities
+ );
+ }
async saveRoute(id: number): Promise {
return this.routeDataSource.saveRoute(id);
}
diff --git a/mobile/src/route/screens/generate_manual_route_screen.tsx b/mobile/src/route/screens/generate_manual_route_screen.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..a2fb8445f2724b3e57008c6cf494795b54f94729
--- /dev/null
+++ b/mobile/src/route/screens/generate_manual_route_screen.tsx
@@ -0,0 +1,78 @@
+import { useTranslation } from "react-i18next";
+import { View, Text, StyleSheet } from "react-native";
+import { LANG_CONSTANTS } from "../../lang/lang";
+import { MultipleSelectableList } from "../components/multiple_selectable_list";
+import { ActivityTile } from "../../activity/components/activity_tile";
+import { useGetActivities } from "../../activity/hooks/useGetActivities";
+import { ApiRequestStatus } from "../../common/const/api_request_states";
+import { FullPageLoader } from "../../common/components/full_page_loader";
+import { useEffect } from "react";
+import { FloatingEndActionButton } from "../../common/components/floating_end_action_button";
+import { GenerateRouteForm } from "../components/generate_route_form";
+import { useGenerateManualRoute } from "../hooks/useGenerateManualRoute";
+import { useGetOpenActivities } from "../../activity/hooks/useGetOpenActivities";
+
+interface GenerateManualRouteProps {
+ townId: number;
+}
+
+export const GenerateManualRoute = ({ townId }: GenerateManualRouteProps) => {
+ const { activities: items, requestStatus } = useGetOpenActivities(townId);
+ const { updateActivitiesIds, control, onSubmit, isEnabled } =
+ useGenerateManualRoute(townId);
+ const { t } = useTranslation();
+
+ const onSelectionChange = (selectedItems: number[]) => {
+ if (!items) {
+ return;
+ }
+ const indexes = new Set(selectedItems);
+ const ids = items
+ ?.filter((_, index) => indexes.has(index))
+ .map((item) => item.id);
+ updateActivitiesIds(ids.join(","));
+ };
+ console.log("rendering");
+
+ useEffect(() => {
+ console.log("requestStatus", requestStatus);
+ }, [requestStatus]);
+
+ if (requestStatus === ApiRequestStatus.LOADING) {
+ return ;
+ }
+ if (requestStatus === ApiRequestStatus.ERROR || !items) {
+ return Error;
+ }
+ return (
+
+
+ {t(LANG_CONSTANTS.generateManualRouteScreenDescriptionText)}
+
+
+ }
+ onSelectionChange={onSelectionChange}
+ />
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ mainContainer: {
+ flex: 1,
+ padding: 10,
+ },
+ descriptionText: {
+ fontSize: 18,
+ },
+});
diff --git a/mobile/src/route/screens/route_preview.tsx b/mobile/src/route/screens/route_preview.tsx
index 6c48b91b3759401c37395661ed5a7c0e0957345d..1e378e6ce712adb22deadd327c6cb0b27653b2da 100644
--- a/mobile/src/route/screens/route_preview.tsx
+++ b/mobile/src/route/screens/route_preview.tsx
@@ -35,6 +35,15 @@ export const RoutePreviewPage = ({
return Something went wrong;
}
+ if (route.travelPlaces.length === 0) {
+ return (
+
+
+ No activities found
+
+ );
+ }
+
return (
@@ -87,4 +96,13 @@ const styles = StyleSheet.create({
backgroundColor: "#fff",
padding: 10,
},
+ noActivitiesContainer: {
+ flex: 1,
+ justifyContent: "center",
+ alignItems: "center",
+ },
+ noActivitiesText: {
+ fontSize: 20,
+ fontWeight: "bold",
+ },
});
diff --git a/mobile/src/travel/components/travel_details_tile.tsx b/mobile/src/travel/components/travel_details_tile.tsx
index d9bc7512591328ddc6e2ea4660f5dd0631242d26..07da5bd5b25198f121ba9c6d4a22b50dbf607db1 100644
--- a/mobile/src/travel/components/travel_details_tile.tsx
+++ b/mobile/src/travel/components/travel_details_tile.tsx
@@ -14,7 +14,6 @@ export const TravelDetailsTile = ({
width,
opacity,
}: TravelDetailsTileProps) => {
- console.log(item);
return (
{
- const { travelRepository } = useDataContext();
- const { handleDownload, saveFileStatus } = useDownloadFile();
+ const { travelRepository } = useDataContext();
+ const { handleDownload, saveFileStatus } = useDownloadFile();
- const callback = async () => {
- return await travelRepository!.getVisitImage(id);
- }
- const { requestStatus: getImageStatus, data } = useGet(callback);
- const [requestStatus, setRequestStatus] = useState(ApiRequestStatus.IDLE);
+ const callback = async () => {
+ return await travelRepository!.getVisitImage(id);
+ };
+ const { requestStatus: getImageStatus, data } = useGet(callback);
+ const [requestStatus, setRequestStatus] = useState(ApiRequestStatus.IDLE);
- useEffect(() => {
- const requests = [getImageStatus, saveFileStatus];
- console.log("Requests: "+requests);
- if (requests.some(status => status === ApiRequestStatus.LOADING)) {
- setRequestStatus(ApiRequestStatus.LOADING);
- } else if (requests.some(status => status === ApiRequestStatus.ERROR)) {
- console.log("Error saving image");
- router.push("travel_history/error/save_travel_image_error");
- } else if (requests.every(status => status === ApiRequestStatus.SUCCESS)) {
- //TODO: Show success message
- router.dismissAll();
- } else if (requests.some(status => status === ApiRequestStatus.IDLE || status === ApiRequestStatus.SUCCESS)) {
- setRequestStatus(ApiRequestStatus.SUCCESS);
- }
- }, [getImageStatus, saveFileStatus]);
+ useEffect(() => {
+ const requests = [getImageStatus, saveFileStatus];
+ if (requests.some((status) => status === ApiRequestStatus.LOADING)) {
+ setRequestStatus(ApiRequestStatus.LOADING);
+ } else if (requests.some((status) => status === ApiRequestStatus.ERROR)) {
+ router.push("travel_history/error/save_travel_image_error");
+ } else if (
+ requests.every((status) => status === ApiRequestStatus.SUCCESS)
+ ) {
+ //TODO: Show success message
+ router.dismissAll();
+ } else if (
+ requests.some(
+ (status) =>
+ status === ApiRequestStatus.IDLE ||
+ status === ApiRequestStatus.SUCCESS
+ )
+ ) {
+ setRequestStatus(ApiRequestStatus.SUCCESS);
+ }
+ }, [getImageStatus, saveFileStatus]);
- return { requestStatus, data, handleDownload };
-}
\ No newline at end of file
+ return { requestStatus, data, handleDownload };
+};
diff --git a/mobile/src/travel/infrastructure/datasources/prod/travel_datasource_prod.ts b/mobile/src/travel/infrastructure/datasources/prod/travel_datasource_prod.ts
index cddd4a5fa454f1230da521451be321fd4aa9e0de..01d01d1c74af2c17efcfb17ceb1b13a48c925465 100644
--- a/mobile/src/travel/infrastructure/datasources/prod/travel_datasource_prod.ts
+++ b/mobile/src/travel/infrastructure/datasources/prod/travel_datasource_prod.ts
@@ -46,7 +46,6 @@ export class TravelDatasourceProd implements TravelDataSource {
if (status !== 200) {
throw new Error("Error fetching travel details");
}
- console.log(data);
return {
travel: {
id: data.idRoute,