diff --git a/backend/backend.code-workspace b/backend/backend.code-workspace new file mode 100644 index 0000000000000000000000000000000000000000..4952e97c5d7ed0214b123b29a349ff2594040cfe --- /dev/null +++ b/backend/backend.code-workspace @@ -0,0 +1,9 @@ +{ + "folders": [ + { + "name": "backend", + "path": "." + } +], + "settings": {} +} diff --git a/backend/src/database-seeder/database-seeder.service.ts b/backend/src/database-seeder/database-seeder.service.ts index 2d6e0d961cb571da27ba97058b7c0c0abf525759..c1239d465811d435d631aca7e83e866488ee9120 100644 --- a/backend/src/database-seeder/database-seeder.service.ts +++ b/backend/src/database-seeder/database-seeder.service.ts @@ -49,8 +49,7 @@ export class DatabaseSeederService implements OnModuleInit { role: ADMIN_ROLE.ADMIN, status: UserStatus.ACTIVE, }; - let tokenSuper = '', - tokenAdmin = ''; + let tokenSuper = ''; try { tokenSuper = await this.authAdminService.signUp({ ...createSuperAdmin }); } catch (error) { @@ -61,24 +60,11 @@ export class DatabaseSeederService implements OnModuleInit { }) ).token; } - try { - tokenAdmin = await this.authAdminService.signUp({ ...createAdmin }); - } catch (error) { - tokenAdmin = ( - await this.authAdminService.signIn({ - email: createAdmin.email, - password: createSuperAdmin.password, - }) - ).token; - } + console.log( `Super Admin created with email: ${createSuperAdmin.email} , password: ${createSuperAdmin.password}, and token: ${tokenSuper}`, ); - console.log( - `Admin created with email: ${createAdmin.email}, - password: ${createAdmin.password}, and token: ${tokenAdmin}`, - ); } async insertTowns() { diff --git a/backend/src/route/route.controller.ts b/backend/src/route/route.controller.ts index d73c626685cf29fddc298defb68013c8d1d77f03..aedd78d98c1fbeae8f404ba9af12c5ae121b1237 100644 --- a/backend/src/route/route.controller.ts +++ b/backend/src/route/route.controller.ts @@ -32,14 +32,21 @@ export class RouteController { @Param('idTown') idTown: number, @Body() createRouteReq: CreateRouteReq, ) { - const { email } = req.user; - return await this.routeService.recommend( - idTown, - email, - lang as LANGUAGES, - createRouteReq.start, - createRouteReq.end, - ); + try { + const { email } = req.user; + const res = await this.routeService.recommend( + idTown, + email, + lang as LANGUAGES, + createRouteReq.start, + createRouteReq.end, + ); + console.log('HELOOO: ', res); + return res; + } catch (error) { + console.log(error); + return error; + } } @Get('') diff --git a/backend/src/route/route.service.ts b/backend/src/route/route.service.ts index 00de4a4383329d95a9db518222568b7860005a9a..3d1c2538cde0cc0e1b5e302daaafc54a189d88ef 100644 --- a/backend/src/route/route.service.ts +++ b/backend/src/route/route.service.ts @@ -96,7 +96,8 @@ export class RouteService { async getRouteAndPlacesByUser(email: string, routeStatus: RouteStatus) { const res = await this.routeRepository.find({ relations: ['travelPlace'], - where: { user: { email }, status: routeStatus, endDate: MoreThan(new Date()) }, + where: { user: { email }, status: routeStatus }, + order: { endDate: 'DESC' }, }); return res; @@ -138,6 +139,8 @@ export class RouteService { }); } + console.log(res); + return res; } diff --git a/mobile/app/(tabs)/_layout.tsx b/mobile/app/(tabs)/_layout.tsx index 191bf08582bb9de3b8724288b23e35a9f267950d..a5374f3725b3b5ebb1b07880bf840ca058ba4bbe 100644 --- a/mobile/app/(tabs)/_layout.tsx +++ b/mobile/app/(tabs)/_layout.tsx @@ -7,18 +7,22 @@ import { useTranslation } from "react-i18next"; import { useSetUp } from "../../src/common/contexts/set_up_context"; export default function Layout() { - const { user } = useAuth(); + const { user, isVerified } = useAuth(); const { isFirstTime } = useSetUp(); const LANG = useTranslation(); - console.log(user); + if (!user) { - return ; + return ; + } + + if (!isVerified) { + return ; } if (isFirstTime) { - return ; + return ; } - + return ( ; }, }} - /> + /> ); } diff --git a/mobile/app/_layout.tsx b/mobile/app/_layout.tsx index 126debba14d27f32722810a88d1c631a2a318160..5d190e5d771da581115df1eab37272f4fffc87d0 100644 --- a/mobile/app/_layout.tsx +++ b/mobile/app/_layout.tsx @@ -8,9 +8,12 @@ import { import { ActivityIndicator } from "react-native"; import { GestureHandlerRootView } from "react-native-gesture-handler"; import { AudioContextProvider } from "../src/common/contexts/audio_context"; -import { I18nextProvider } from "react-i18next"; +import { I18nextProvider, useTranslation } from "react-i18next"; import i18n from "../src/lang/translations"; -import { SetUpContextProvider, useSetUp } from "../src/common/contexts/set_up_context"; +import { + SetUpContextProvider, + useSetUp, +} from "../src/common/contexts/set_up_context"; export default function Root() { return ( @@ -32,6 +35,7 @@ export default function Root() { const MainLayout = () => { const { isLoading } = useAuth(); + const { t } = useTranslation(); if (isLoading) { return ; @@ -76,7 +80,7 @@ const MainLayout = () => { { statusBarColor: LIGHT_THEME.color.primary, }} /> + (); - if (!townId) { - return Invalid town id; - } - return ; -} \ No newline at end of file + const { routeId } = useLocalSearchParams<{ routeId: string }>(); + if (!routeId) { + return Invalid town id; + } + return ; +} diff --git a/mobile/app/state/[stateId]/town/_layout.tsx b/mobile/app/state/[stateId]/town/_layout.tsx index 856496f472365281afa1765076a077b1a3144376..891a48c487513dded04edf3ba0875ec419c4dd16 100644 --- a/mobile/app/state/[stateId]/town/_layout.tsx +++ b/mobile/app/state/[stateId]/town/_layout.tsx @@ -1,23 +1,31 @@ import { Stack } from "expo-router"; import { LIGHT_THEME } from "../../../../src/common/const/theme"; +import { useTranslation } from "react-i18next"; export default function Layout() { - return ( - - - - - ); -} \ No newline at end of file + const { t } = useTranslation(); + return ( + + + + + ); +} diff --git a/mobile/app/state/_layout.tsx b/mobile/app/state/_layout.tsx index 39f450c3f460b9624647fc1696eb4b07868ed47e..d37e1dbdef1b1860b7e86b83eb8be39334628cf5 100644 --- a/mobile/app/state/_layout.tsx +++ b/mobile/app/state/_layout.tsx @@ -1,13 +1,15 @@ import { Stack } from "expo-router"; import { LIGHT_THEME } from "../../src/common/const/theme"; +import { useTranslation } from "react-i18next"; export default function Layout() { + const { t } = useTranslation(); return ( ; +} diff --git a/mobile/assets/guide.gif b/mobile/assets/guide.gif new file mode 100644 index 0000000000000000000000000000000000000000..718bc4049c6abff8c00d17721893aeb5f97d4886 Binary files /dev/null and b/mobile/assets/guide.gif differ diff --git a/mobile/src/activity/components/activity_bottom_sheet.tsx b/mobile/src/activity/components/activity_bottom_sheet.tsx index 827408e9db46032d0d61f9ea4dcfbdc7c3aa3351..17054a27843cbc5911c0cb279c829cbaf6437810 100644 --- a/mobile/src/activity/components/activity_bottom_sheet.tsx +++ b/mobile/src/activity/components/activity_bottom_sheet.tsx @@ -14,6 +14,7 @@ import { import { LIGHT_THEME } from "../../common/const/theme"; import { router } from "expo-router"; import { ActivityInfoEntity } from "../domain/entities/activity_info_entity"; +import { useTranslation } from "react-i18next"; interface ActivityBottomSheetProps { startSnapPoint: number; @@ -30,6 +31,7 @@ export const ActivityBottomSheet = ({ }: ActivityBottomSheetProps) => { const sheetRef = useRef(null); const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false); + const { t } = useTranslation(); return ( - Do Activity + + {t("activityInfoScreen.doActivityButton")} + {activity.tags && ( @@ -78,7 +82,9 @@ export const ActivityBottomSheet = ({ /> )} - Description + + {t("activityInfoScreen.descriptionText")} + {activity.description} @@ -86,7 +92,9 @@ export const ActivityBottomSheet = ({ onPress={() => setIsDescriptionExpanded(!isDescriptionExpanded)} > - {isDescriptionExpanded ? "Show less" : "Show more..."} + {isDescriptionExpanded + ? t("activityInfoScreen.showLessButton") + : t("activityInfoScreen.showMoreButton")} @@ -103,6 +111,7 @@ const styles = StyleSheet.create({ padding: 20, }, activity_name_container: { + width: "100%", flexDirection: "row", justifyContent: "space-between", }, @@ -117,6 +126,7 @@ const styles = StyleSheet.create({ backgroundColor: LIGHT_THEME.color.primary, padding: 10, borderRadius: 20, + marginLeft: -40, justifyContent: "center", alignItems: "center", paddingHorizontal: 20, diff --git a/mobile/src/activity/domain/entities/activity_place_entity.ts b/mobile/src/activity/domain/entities/activity_place_entity.ts index e23bfddc0c1c5ef69394600056386b18d1f06ce8..98560ec2a0748ae6af1cd0948e6703baabde52c4 100644 --- a/mobile/src/activity/domain/entities/activity_place_entity.ts +++ b/mobile/src/activity/domain/entities/activity_place_entity.ts @@ -1,19 +1,19 @@ export interface ActivityPlaceEntity { - idPlaceActivity: number; - name: string; - number: number; - idPlace: number; - imageUrl: string; - directions?: DirectionEntity; - content: ContentEntity + idPlaceActivity: number; + name: string; + number: number; + idPlace: number; + imageUrl: string; + directions?: DirectionEntity; + content: ContentEntity; } interface DirectionEntity { - content: string; - speakUrl: string; + content: string; + speakUrl: string; } interface ContentEntity { - content: string; - speakUrl: string; -} \ No newline at end of file + content: string; + speakUrl: string; +} diff --git a/mobile/src/activity/hooks/useRankActivity.ts b/mobile/src/activity/hooks/useRankActivity.ts index 22166e35446f5c7b12df97451f1df4bac3c0ef17..9baf6d805b3de8ec24b0d9e9b6e9613c82069b86 100644 --- a/mobile/src/activity/hooks/useRankActivity.ts +++ b/mobile/src/activity/hooks/useRankActivity.ts @@ -4,40 +4,41 @@ import { ApiRequestStatus } from "../../common/const/api_request_states"; import { router } from "expo-router"; export const useRankActivity = (activityId: number) => { - const { activityRepository } = useDataContext(); - const [requestStatus, setRequestStatus] = useState( - ApiRequestStatus.IDLE - ); - const [ratingModal, setRatingModal] = useState(false); + const { activityRepository } = useDataContext(); + const [requestStatus, setRequestStatus] = useState( + ApiRequestStatus.IDLE + ); + const [ratingModal, setRatingModal] = useState(false); - const setLoading = async () => { - setRequestStatus(ApiRequestStatus.LOADING); - }; + const setLoading = async () => { + setRequestStatus(ApiRequestStatus.LOADING); + }; - const rankActivity = async (rank: number) => { - try { - await setLoading(); - await activityRepository!.rankActivity(activityId, rank); - setRequestStatus(ApiRequestStatus.SUCCESS); - closeRatingModal(); - } catch (error) { - setRequestStatus(ApiRequestStatus.ERROR); - } - }; + const rankActivity = async (rank: number) => { + try { + await setLoading(); + await activityRepository!.rankActivity(activityId, rank); + setRequestStatus(ApiRequestStatus.SUCCESS); + closeRatingModal(); + router.back(); + } catch (error) { + setRequestStatus(ApiRequestStatus.ERROR); + } + }; - const closeRatingModal = () => { - setRatingModal(false); - }; + const closeRatingModal = () => { + setRatingModal(false); + }; - const openRatingModal = () => { - setRatingModal(true); - }; + const openRatingModal = () => { + setRatingModal(true); + }; - return { - rankActivity, - requestStatus, - ratingModal, - closeRatingModal, - openRatingModal, - }; + return { + rankActivity, + requestStatus, + ratingModal, + closeRatingModal, + openRatingModal, + }; }; diff --git a/mobile/src/activity/infrastructure/datasources/prod/activity_datasource.ts b/mobile/src/activity/infrastructure/datasources/prod/activity_datasource.ts index f6fedd3bd2cf6339100b992ce76e805372891b87..2d2fb3d871d553a7045da9609557960195bc2c35 100644 --- a/mobile/src/activity/infrastructure/datasources/prod/activity_datasource.ts +++ b/mobile/src/activity/infrastructure/datasources/prod/activity_datasource.ts @@ -7,21 +7,30 @@ import { API_URL } from "../../../../common/const/api"; import { ActivityPlaceEntity } from "../../../domain/entities/activity_place_entity"; export class ActivityDatasourceProd implements ActivityDataSource { - constructor(private lang: string = Languages.SPANISH) {} - async rankActivity(activityId: number, rank: number): Promise { - const {status} = await axios.post(`${API_URL}/visited`, { - idPlace: activityId, - rating: rank - }); - if (status !== 201) { - throw new Error("Error al calificar la actividad"); - } + constructor(private lang: string = Languages.SPANISH) {} + async rankActivity(activityId: number, rank: number): Promise { + const { status } = await axios.post(`${API_URL}/visited`, { + idPlace: activityId, + rating: rank, + }); + console.info("rankActivity", status); + if (status !== 201) { + throw new Error("Error al calificar la actividad"); } - async getPlaceActivity(activityId: number, townId: number, stateId: number, placeNumber: number): Promise { - const { data, status } = await axios.get(`${API_URL}/point/${placeNumber}?lang=${this.lang}`); - if (status !== 200) { - throw new Error("Error al obtener la información del lugar"); - } - return activityPlaceModelToEntity(data); + } + async getPlaceActivity( + activityId: number, + townId: number, + stateId: number, + placeNumber: number + ): Promise { + const { data, status } = await axios.get( + `${API_URL}/point/${placeNumber}?lang=${this.lang}` + ); + if (status !== 200) { + throw new Error("Error al obtener la información del lugar"); } -} \ No newline at end of file + console.log(data); + return activityPlaceModelToEntity(data); + } +} diff --git a/mobile/src/activity/infrastructure/models/activity_place_model.ts b/mobile/src/activity/infrastructure/models/activity_place_model.ts index d69d3f4e1f59cd1577d4095817e84877cc0c37c5..ce21453e572b522b32cb81bda4f3815ecdbbc2a0 100644 --- a/mobile/src/activity/infrastructure/models/activity_place_model.ts +++ b/mobile/src/activity/infrastructure/models/activity_place_model.ts @@ -4,5 +4,5 @@ export interface ActivityPlaceModel { name: string; imageName: string; content: string; - directions: string; + directions?: string; } diff --git a/mobile/src/activity/infrastructure/utils/activity_utils.ts b/mobile/src/activity/infrastructure/utils/activity_utils.ts index b16a10929a13385ce933be30a8bb4d8933667916..13a9e3e538d190f13c26debbeb5d2f31d84f4f21 100644 --- a/mobile/src/activity/infrastructure/utils/activity_utils.ts +++ b/mobile/src/activity/infrastructure/utils/activity_utils.ts @@ -1,20 +1,24 @@ import { ActivityPlaceEntity } from "../../domain/entities/activity_place_entity"; import { ActivityPlaceModel } from "../models/activity_place_model"; -export const activityPlaceModelToEntity = (activity: ActivityPlaceModel): ActivityPlaceEntity => { - return { - idPlaceActivity: activity.idPoint, - name: activity.name, - number: activity.idPlace, - idPlace: activity.idPlace, - imageUrl: activity.imageName, - directions: { - content: activity.directions, - speakUrl: "" - }, - content: { - content: activity.content, - speakUrl: "" +export const activityPlaceModelToEntity = ( + activity: ActivityPlaceModel +): ActivityPlaceEntity => { + return { + idPlaceActivity: activity.idPoint, + name: activity.name, + number: activity.idPlace, + idPlace: activity.idPlace, + imageUrl: activity.imageName, + directions: activity.directions + ? { + content: activity.directions, + speakUrl: "", } - } -}; \ No newline at end of file + : undefined, + content: { + content: activity.content, + speakUrl: "", + }, + }; +}; diff --git a/mobile/src/activity/screens/activity_description_page.tsx b/mobile/src/activity/screens/activity_description_page.tsx index d7e79aa87463021673a5f0ab57bf685c2cf2180d..ab6d642cb614dba7085cb117d019833e7335c1d4 100644 --- a/mobile/src/activity/screens/activity_description_page.tsx +++ b/mobile/src/activity/screens/activity_description_page.tsx @@ -68,7 +68,6 @@ export const ActivityDescriptionPage = ({ }).start(); }; - //TODO: Check if description is available if (!activityInfo.description) { activityInfo.description = "No description available"; } diff --git a/mobile/src/activity/screens/activity_point.tsx b/mobile/src/activity/screens/activity_point.tsx index eaa617fc251fe085a5812281bfd03c5614d4bd39..415f5fc0fc3c248b198e2c24bc8bfb56a1d3a49a 100644 --- a/mobile/src/activity/screens/activity_point.tsx +++ b/mobile/src/activity/screens/activity_point.tsx @@ -13,6 +13,8 @@ import { FullPageRating } from "../../common/components/rating_page/full_page_ra import { useRankActivity } from "../hooks/useRankActivity"; import { useGetActivityPoint } from "../hooks/useGetActivityPoint"; +const touristGuide = require("../../../assets/guide.gif"); + interface ActivityPointScreenProps { stateId: number; townId: number; @@ -30,9 +32,11 @@ export const ActivityPointScreen = memo( }); const { onUnmount } = useAudio(); - const { openRatingModal, closeRatingModal, ratingModal, rankActivity } = useRankActivity(activityId); + const { openRatingModal, closeRatingModal, ratingModal, rankActivity } = + useRankActivity(activityId); useEffect(() => { + onUnmount(); const backAction = () => { onUnmount(); router.back(); @@ -47,6 +51,10 @@ export const ActivityPointScreen = memo( return () => backHandler.remove(); }, []); + const doNextActivity = () => { + router.replace("/scan"); + }; + if (requestStatus === ApiRequestStatus.LOADING) { return ; } @@ -65,6 +73,11 @@ export const ActivityPointScreen = memo( style={styles.image} resizeMode="contain" /> + {data.directions.content} + + + + Next Activity + + )} {!data.directions && ( - End Activity + + End Activity + )} - {!data.directions && ratingModal && } + {!data.directions && ratingModal && ( + + )} ); } @@ -121,15 +153,23 @@ const styles = StyleSheet.create({ imageContainer: { width: "100%", height: 300, - backgroundColor: "lightgrey", + backgroundColor: "white", padding: 10, borderRadius: 10, overflow: "hidden", borderWidth: 2, }, image: { - height: "70%", - width: "70%", + height: "60%", + width: "60%", + zIndex: 1, + }, + touristGuide: { + height: "60%", + width: "60%", + position: "absolute", + bottom: 10, + right: 10, }, endActivityButton: { backgroundColor: LIGHT_THEME.color.primary, diff --git a/mobile/src/auth/components/code_form.tsx b/mobile/src/auth/components/code_form.tsx index 6f6b1e78645f676c80ac4e88ee4f3771b1e38394..36eb702e59ff6e65bd4b1a445860720607434ca0 100644 --- a/mobile/src/auth/components/code_form.tsx +++ b/mobile/src/auth/components/code_form.tsx @@ -3,29 +3,31 @@ import { MultipleDigitsCode } from "./multiple_digits_code"; import { Control, UseFormSetValue } from "react-hook-form"; import { ResetPasswordFormValues } from "../hooks/useResetPassword"; - interface CodeFormProps { - setValue: UseFormSetValue; + setValue: UseFormSetValue; + getNewResetCode?: () => void; } -export const CodeForm = ({ setValue }: CodeFormProps) => { - const onTextChange = (value: string) => { - console.log("CodeForm onTextChange", value); - setValue("code", value); - }; +export const CodeForm = ({ setValue, getNewResetCode }: CodeFormProps) => { + const onTextChange = (value: string) => { + setValue("code", value); + }; - return ( - - Introduce el código de verificación - - - ); + return ( + + Introduce el código de verificación + + {getNewResetCode && ( + Obtener nuevo código + )} + + ); }; const styles = StyleSheet.create({ - container: { - flex: 1, - width: "100%", - gap: 20, - }, -}); \ No newline at end of file + container: { + flex: 1, + width: "100%", + gap: 20, + }, +}); diff --git a/mobile/src/auth/components/login_form.tsx b/mobile/src/auth/components/login_form.tsx index b6910399baa962e2823ebe9ab23b68cc87c27939..c96d403d12bda2272c300bfda6880726dd4f99f6 100644 --- a/mobile/src/auth/components/login_form.tsx +++ b/mobile/src/auth/components/login_form.tsx @@ -1,129 +1,143 @@ import { Control, Controller, FieldValues } from "react-hook-form"; -import { Button, ScrollView, StyleSheet, Text, TouchableOpacity, View } from "react-native"; +import { + Button, + ScrollView, + StyleSheet, + Text, + TouchableOpacity, + View, +} from "react-native"; import { CustomTextInput } from "../../common/components/form/text_input"; import { LIGHT_THEME } from "../../common/const/theme"; import { LoginFormValues } from "../hooks/useLoggin"; import { OrDivision } from "../../common/components/form/or_division"; -import { AntDesign } from '@expo/vector-icons'; +import { AntDesign } from "@expo/vector-icons"; import { Link, router } from "expo-router"; import { useTranslation } from "react-i18next"; import { LanguageIcon } from "../../lang/components/language_icon"; interface LoginFormProps { - control: Control; - onSubmit: () => void; + control: Control; + onSubmit: () => void; } export const LoginForm = ({ control, onSubmit }: LoginFormProps) => { const LANG = useTranslation(); - console.log("Lang"+LANG); - return ( - - - {LANG.t('loginScreen.title')} - - ( - - )} - rules={{ - required: "Email is required", - pattern: { value: /\S+@\S+\.\S+/, message: "Invalid email" }, + return ( + + + {LANG.t("loginScreen.title")} + + ( + - ( - - )} - rules={{ required: "Password is required" }} + )} + rules={{ + required: "Email is required", + pattern: { value: /\S+@\S+\.\S+/, message: "Invalid email" }, + }} + /> + ( + - - - Recuperar contraseña - - - - - {LANG.t('loginScreen.loginButton')} - + )} + rules={{ required: "Password is required" }} + /> + + + Recuperar contraseña + + + + + + {LANG.t("loginScreen.loginButton")} + + - + -