diff --git a/mobile/src/common/components/audio_player.tsx b/mobile/src/common/components/audio_player.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..ad8fe1a72f0cc2fd7d3ad4404c72dc0d3f499791
--- /dev/null
+++ b/mobile/src/common/components/audio_player.tsx
@@ -0,0 +1,77 @@
+import { TouchableOpacity, View, StyleSheet, Text } from "react-native";
+import { FontAwesome, Feather } from "@expo/vector-icons";
+import Slider from "@react-native-community/slider";
+import { millisecondsToHourFormat } from "../../utils/time";
+import { LIGTHT_THEME } from "../../constants/theme";
+import { useAudio } from "../../contexts/audio_context";
+import { useEffect } from "react";
+
+const audio = require("./../../../assets/audio_prueba.mp3");
+
+interface AudioPlayerProps {
+ audioUrl: string;
+ title: string;
+ description: string;
+}
+
+export const AudioPlayer = () => {
+ const { loadAudio, position, togglePlay, isPlaying, duration, onValueChange } =
+ useAudio();
+
+ useEffect(() => {
+ loadAudio(audio);
+ }, []);
+
+ return (
+
+
+
+ {millisecondsToHourFormat(position)}
+
+
+
+ {millisecondsToHourFormat(duration)}
+
+
+
+
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ height: 100,
+ width: "100%",
+ justifyContent: "center",
+ alignItems: "center",
+ backgroundColor: LIGTHT_THEME.color.primary,
+ gap: 10,
+ },
+ sliderContainer: {
+ width: "100%",
+ display: "flex",
+ flexDirection: "row",
+ paddingHorizontal: 20,
+ },
+ slider: {
+ flex: 1,
+ },
+ hour_text: {
+ color: LIGTHT_THEME.color.white,
+ fontWeight: "bold",
+ },
+});
diff --git a/mobile/src/common/components/caroussel/caroussel.tsx b/mobile/src/common/components/caroussel/caroussel.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..b78eee7555a84506652f13e3214f9fdd5465727d
--- /dev/null
+++ b/mobile/src/common/components/caroussel/caroussel.tsx
@@ -0,0 +1,92 @@
+import { View, Animated, Text, Dimensions, Image, NativeSyntheticEvent, NativeScrollEvent } from "react-native";
+import { PlaceInfoEntity } from "../../../domain/entities/place_info_entity";
+import { useRef } from "react";
+import { StateDataSource } from "../../../domain/datasources/state_datasource";
+import { CarousselTile } from "./caroussel_tile";
+import { router } from "expo-router";
+
+interface CarousselProps {
+ data: PlaceInfoEntity[];
+ onPress?: (id: number) => void;
+ onIndexChange?: (index: number) => void;
+}
+
+const ITEM_WIDTH = Dimensions.get("window").width * 0.7;
+const BLANK_ITEM_WIDTH = Dimensions.get("window").width * 0.15;
+
+export const Caroussel = ({ data, onPress, onIndexChange }: CarousselProps) => {
+ const scrollX = useRef(new Animated.Value(0)).current;
+ const finalData = [
+ {
+ id: -1,
+ name: "empty-left",
+ },
+ ...data,
+ {
+ id: -2,
+ name: "empty-right",
+ },
+ ];
+
+ scrollX.addListener(({ value }) => {
+ const index = Math.round(value / ITEM_WIDTH);
+ if (index < 0 || index >= data.length) {
+ return;
+ }
+ onIndexChange && onIndexChange(index);
+ });
+ return (
+ item.id.toString()}
+ horizontal
+ showsHorizontalScrollIndicator={false}
+ decelerationRate={0}
+ initialNumToRender={1}
+
+ bounces={false}
+ snapToInterval={ITEM_WIDTH}
+ onScroll={Animated.event([{ nativeEvent: { contentOffset: { x: scrollX } } }], {
+ useNativeDriver: true,
+ })}
+ renderItem={({ item, index }) => {
+ if (item.id === -1 || item.id === -2) {
+ return ;
+ }
+ const inputRange = [
+ (index - 2) * ITEM_WIDTH,
+ (index - 1) * ITEM_WIDTH,
+ index * ITEM_WIDTH,
+ ];
+
+ const translateY = scrollX.interpolate({
+ inputRange,
+ outputRange: [100, 0, 100],
+ });
+
+ const opacity = scrollX.interpolate({
+ inputRange,
+ outputRange: [0.6, 1, 0.6],
+ });
+ const scale = scrollX.interpolate({
+ inputRange,
+ outputRange: [0.8, 1, 0.8],
+ });
+ return (
+ {
+ if (item.id !== -1 && item.id !== -2) {
+ onPress && onPress(item.id);
+ }
+ }
+ }
+ />
+ );
+ }}
+ />
+ );
+};
diff --git a/mobile/src/common/components/caroussel/caroussel_tile.tsx b/mobile/src/common/components/caroussel/caroussel_tile.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..65a61985b1f67f3e6baf10c4c84eb179028374b0
--- /dev/null
+++ b/mobile/src/common/components/caroussel/caroussel_tile.tsx
@@ -0,0 +1,77 @@
+import { memo } from "react";
+import {
+ Animated,
+ Dimensions,
+ Image,
+ Pressable,
+ Text,
+ TouchableOpacity,
+} from "react-native";
+import { PlaceInfoEntity } from "../../../domain/entities/place_info_entity";
+import { Link } from "expo-router";
+
+interface CarousselTileProps {
+ onPress?: () => void;
+ item: PlaceInfoEntity;
+ translateY: Animated.AnimatedInterpolation;
+ scale: Animated.AnimatedInterpolation;
+ opacity: Animated.AnimatedInterpolation;
+}
+
+const ITEM_WIDTH = Dimensions.get("window").width * 0.7;
+const BLANK_ITEM_WIDTH = Dimensions.get("window").width * 0.15;
+const ITEM_HEIGHT = Dimensions.get("window").height * 0.7;
+
+export const CarousselTile = memo(
+ ({ translateY, scale, opacity, item, onPress }: CarousselTileProps) => {
+ return (
+
+
+
+
+
+ {item.name}
+
+
+
+
+ );
+ }
+);
diff --git a/mobile/src/common/components/custom_tile_button.tsx b/mobile/src/common/components/custom_tile_button.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..93719b020358251452cf9f544f17a8d0ed2a2b83
--- /dev/null
+++ b/mobile/src/common/components/custom_tile_button.tsx
@@ -0,0 +1,66 @@
+import { ReactNode } from "react";
+import { TouchableOpacity, View, Text, StyleSheet } from "react-native";
+import { MaterialIcons } from "@expo/vector-icons";
+import { LIGTHT_THEME } from "../../constants/theme";
+
+interface CustomTileButtonProps {
+ leadingIcon?: ReactNode;
+ title: string;
+ subtitle?: string;
+ onPress?: () => void;
+}
+
+export const CustomTileButton = ({
+ title,
+ onPress,
+ leadingIcon,
+ subtitle
+}: CustomTileButtonProps) => {
+ return (
+
+
+ {leadingIcon}
+
+ {title}
+ {
+ subtitle && {subtitle}
+ }
+
+
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ account_option: {
+ flexDirection: "row",
+ alignItems: "center",
+ justifyContent: "space-between",
+ height: 60,
+ paddingHorizontal: 10,
+ borderTopWidth: 2,
+ borderTopColor: "#ccc",
+ backgroundColor: LIGTHT_THEME.color.white,
+ borderRadius: 5,
+ width: "100%",
+ },
+ titleContainer: {
+ height: "100%",
+ alignItems: "flex-start",
+ justifyContent: "space-between",
+ paddingVertical: 10,
+ maxWidth: "85%",
+
+ },
+ title: {
+ fontSize: 16,
+ fontWeight: "700",
+ },
+ subtitle: {
+ fontSize: 14,
+ color: "#777",
+ fontWeight: "500",
+ textAlign: "left",
+ }
+});
diff --git a/mobile/src/common/components/form/date_text_input.tsx b/mobile/src/common/components/form/date_text_input.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..dae598e5002489b7babda2d2631a7c4b01cbf1cf
--- /dev/null
+++ b/mobile/src/common/components/form/date_text_input.tsx
@@ -0,0 +1,42 @@
+import { useEffect, useState } from "react";
+import { CustomTextInput } from "./text_input";
+import { TouchableOpacity, View } from "react-native";
+import DateTimePickerModal from "react-native-modal-datetime-picker";
+
+interface DateInputProps {
+ label: string;
+ onChangeText: (text: string) => void;
+ value: string;
+ onBlur?: () => void;
+ errors?: string;
+}
+
+export const DateTextInput = ({label, onChangeText, value, onBlur, errors}: DateInputProps) => {
+ const [isVisible, setIsVisible] = useState(false);
+ useEffect(() => {
+ console.log(isVisible);
+ }, [isVisible]);
+ return (
+ setIsVisible(true)}>
+ {}}
+ onBlur={onBlur}
+ errors={errors}
+ editable={false}
+ />
+ {
+ onChangeText(data.toDateString());
+ setIsVisible(false);
+ }}
+ onCancel={() => {
+ setIsVisible(false);
+ }}
+ isVisible={isVisible}
+ />
+
+ );
+};
diff --git a/mobile/src/common/components/form/text_input.tsx b/mobile/src/common/components/form/text_input.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..55e7e733c64b5bd033b88ecdf07d40a1c9e6db11
--- /dev/null
+++ b/mobile/src/common/components/form/text_input.tsx
@@ -0,0 +1,98 @@
+import { useEffect, useRef } from "react";
+import { TextInput, Text, StyleSheet, View, Animated, TouchableOpacity } from "react-native";
+import { LIGTHT_THEME } from "../../../constants/theme";
+
+interface TextInputProps {
+ isPassword?: boolean;
+ label: string;
+ onChangeText: (text: string) => void;
+ value: string;
+ onBlur?: () => void;
+ errors?: string;
+ type?: any;
+ editable?: boolean;
+}
+
+export const CustomTextInput = (props: TextInputProps) => {
+ const { isPassword, label, type, onBlur, errors, value, editable,...rest } = props;
+ const labelFocusAnimation = useRef(new Animated.Value(value.length > 0 ? 1 : 0)).current;
+ const inputRef = useRef(null);
+
+ const handleFocus = () => {
+ Animated.timing(labelFocusAnimation, {
+ toValue: 1,
+ duration: 200,
+ useNativeDriver: false,
+ }).start();
+ };
+
+ const handleBlur = () => {
+ if (!props.value || props.value === "") {
+ Animated.timing(labelFocusAnimation, {
+ toValue: 0,
+ duration: 200,
+ useNativeDriver: false,
+ }).start();
+ }
+ onBlur && onBlur();
+ };
+
+ return (
+
+
+
+ {label}
+
+
+
+ {
+ errors ? {errors} :
+ }
+
+ );
+};
+
+const styles = StyleSheet.create({
+ main_container: {
+ width: "100%",
+ },
+ container: {
+ width: "100%",
+ borderWidth: 1,
+ borderRadius: 5,
+ borderColor: "black",
+ height: 40,
+ alignItems: "center",
+ justifyContent: "center",
+ },
+ input: {
+ width: "100%",
+ height: "100%",
+ paddingHorizontal: 10,
+ },
+ label: {
+ position: "absolute",
+ left: 10,
+ zIndex: 1,
+ backgroundColor: LIGTHT_THEME.color.white,
+ },
+});
diff --git a/mobile/src/common/components/full_page_loader.tsx b/mobile/src/common/components/full_page_loader.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..858792083c12ad8c06a0374bae24c49434ab2ded
--- /dev/null
+++ b/mobile/src/common/components/full_page_loader.tsx
@@ -0,0 +1,9 @@
+import { ActivityIndicator, View } from "react-native";
+
+export const FullPageLoader = () => {
+ return (
+
+
+
+ );
+}
\ No newline at end of file