diff --git a/backend/src/place/entities/place-traduction.entity.ts b/backend/src/place/entities/place-traduction.entity.ts
index 5804fa470f310041b62f70365484b40dab0086f5..a27f4da4bf5625eac81cbba8a90ab2c11fa0e66f 100644
--- a/backend/src/place/entities/place-traduction.entity.ts
+++ b/backend/src/place/entities/place-traduction.entity.ts
@@ -11,6 +11,6 @@ export class PlaceTraduction {
@ManyToOne(() => Place, (place) => place.availableDates, { nullable: false })
idPlace: number;
- @Column()
+ @Column({ length: 1024 })
description: string;
}
diff --git a/backend/src/pointOfInterest/entities/PointOfInterestTraduction.entity.ts b/backend/src/pointOfInterest/entities/PointOfInterestTraduction.entity.ts
index 858f810319934456eb796c3cccf9fdb416e1bd4b..54fb88f92dc7d0e158b95479442f6cde312688a0 100644
--- a/backend/src/pointOfInterest/entities/PointOfInterestTraduction.entity.ts
+++ b/backend/src/pointOfInterest/entities/PointOfInterestTraduction.entity.ts
@@ -10,9 +10,9 @@ export class PointOfInterestTraduction {
@PrimaryColumn()
language: LANGUAGES;
- @Column({ nullable: false })
+ @Column({ nullable: false, length: 1024 })
content: string;
- @Column({ nullable: true })
+ @Column({ nullable: true, length: 1024 })
directions: string;
@Column()
diff --git a/backend/src/town/entities/town-traduction.entity.ts b/backend/src/town/entities/town-traduction.entity.ts
index 42a4d3f4b05f5555cdba68295213eec76fc671a0..3cfae8d7cbc36dc4aa8dc2a68c3bd05b25c0e138 100644
--- a/backend/src/town/entities/town-traduction.entity.ts
+++ b/backend/src/town/entities/town-traduction.entity.ts
@@ -9,6 +9,6 @@ export class TownTraduction {
@PrimaryColumn()
language: LANGUAGES;
- @Column()
+ @Column({ length: 1024 })
description: string;
}
diff --git a/web/public/index.html b/web/public/index.html
index aa069f27cbd9d53394428171c3989fd03db73c76..babfdb0e22a07905df6b0aca16c0cf602535d4c8 100644
--- a/web/public/index.html
+++ b/web/public/index.html
@@ -2,14 +2,13 @@
-
+
-
- React App
+ Pueblos Magicos
diff --git a/web/public/logotipo.png b/web/public/logotipo.png
new file mode 100644
index 0000000000000000000000000000000000000000..35c0c715e3a96bc6456dc14ad0357c57e2ae7849
Binary files /dev/null and b/web/public/logotipo.png differ
diff --git a/web/src/App.tsx b/web/src/App.tsx
index 0c54f7ff25cb420f58ed174bc56b125909e14ef9..769ac63a34dcad037dcee3c27d99543ac2594dc2 100644
--- a/web/src/App.tsx
+++ b/web/src/App.tsx
@@ -1,11 +1,10 @@
import { RouterProvider } from "react-router-dom";
import "./App.css";
-import { AuthContextProvider } from "./context/auth_context";
-import { MessageContextProvider } from "./context/message_context";
-import { router } from "./router/router";
+import { AuthContextProvider } from "./core/context/auth_context";
+import { MessageContextProvider } from "./core/context/message_context";
+import { router } from "./core/router/router";
import { ToastContainer } from "react-toastify";
-
function App() {
return (
diff --git a/web/src/components/admin_panel_navbar/admin_navbar.tsx b/web/src/components/admin_panel_navbar/admin_navbar.tsx
deleted file mode 100644
index 81e61e3dc2d3ee73715b5016c1e107657f7806d6..0000000000000000000000000000000000000000
--- a/web/src/components/admin_panel_navbar/admin_navbar.tsx
+++ /dev/null
@@ -1,173 +0,0 @@
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faEye, faEyeSlash, faSignOut, faUser, faWindowClose } from "@fortawesome/free-solid-svg-icons";
-import { Link } from "react-router-dom";
-import './assets/styles/style.css';
-import { UserRole } from "../../constants/roles";
-import { useUserData } from "../../hooks/useUserData";
-import { Dispatch, SetStateAction, useState } from "react";
-import { useAdminChangePassword } from "../../hooks/useAdminChangePassword";
-import { usePasswoordVisibility } from "../../hooks/usePasswordVisibility";
-
-interface props{
- isWindowActive: boolean;
- setIsWindowActive: Dispatch
>;
-}
-
-export const AdminPanelNavBar = ({isWindowActive, setIsWindowActive}:props) => {
- const {user, handleLogout, setToggle, toggle, userData} = useUserData();
- const [changePasswordWindowActive, setChangePasswordWindowActive] = useState(false);
- const setChangePasswordWindowVisibility = (visibility: boolean) => {
- setIsWindowActive(visibility);
- setChangePasswordWindowActive(visibility);
- }
- const {register, handleSubmit, errors, onSubmit} = useAdminChangePassword(setChangePasswordWindowVisibility);
- const {
- values: valuesPrevPassword,
- handleClickShowPassword: handleClickShowPrevPassword,
- handleMouseDownPassword: handleMouseDownPrevPassword
- } = usePasswoordVisibility();
- const {
- values: valuesNewPassword,
- handleClickShowPassword: handleClickShowNewPassword,
- handleMouseDownPassword: handleMouseDownNewPassword
- } = usePasswoordVisibility();
- const {
- values: valuesNewPasswordConfirm,
- handleClickShowPassword: handleClickShowNewPasswordConfirm,
- handleMouseDownPassword: handleMouseDownNewPasswordConfirm
- } = usePasswoordVisibility();
-
-
- if(!user ){
- return null;
- }else{
- if(user.role !== UserRole.ADMIN && user.role !== UserRole.SUPERADMIN){
- return null;
- }
- }
-
- return (
-
-
-
{
- isWindowActive
- ?
- setToggle(false)
- :
- setToggle(!toggle)
- }}
- style={
- isWindowActive
- ?
- {cursor: "auto"}
- :
- {cursor: "pointer"}
- }
- />
- {toggle &&
-
-
-
-
-
{userData?.name}
-
-
-
-
{setChangePasswordWindowVisibility(true); setToggle(false);}} className="sub-menu-link">
-
-
Cambiar contraseña
-
-
-
-
-
Cerrar sesión
-
-
-
- }
-
- {
- changePasswordWindowActive &&
-
-
- Cambio de contraseña
- setChangePasswordWindowVisibility(false)}/>
-
-
-
-
- }
-
- );
-}
\ No newline at end of file
diff --git a/web/src/components/admin_panel_places/admin_panel_place_list/admin_panel_place_list.tsx b/web/src/components/admin_panel_places/admin_panel_place_list/admin_panel_place_list.tsx
deleted file mode 100644
index 94aa6d31ffe06ed8d4b5e1ae4bd05e650e4f8bfc..0000000000000000000000000000000000000000
--- a/web/src/components/admin_panel_places/admin_panel_place_list/admin_panel_place_list.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import DataTable, { TableColumn } from 'react-data-table-component';
-import { usePlace } from '../../../hooks/usePlace';
-import './assets/css/styles.css';
-import { Place } from '../../../infraestructure/entities/place';
-import { LoadingSpinner } from '../../loading_spinner/loading_spinner';
-import { faEdit } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Dispatch, SetStateAction, useEffect } from 'react';
-
-interface props{
- idTown: number;
- isWindowActive: boolean;
- setWindowVisibility: (visibility: boolean) => void;
- setActualPlace: Dispatch>;
- setIsRegisterPane: Dispatch>;
-}
-
-export const AdminPanelPlaceList = ({idTown, isWindowActive, setWindowVisibility, setActualPlace, setIsRegisterPane}: props) => {
- const {
- placeList,
- pending,
- updatePlacesByTown
- } = usePlace();
-
- const handleEditSelectedCategory = (place: Place) => {
- setIsRegisterPane(false);
- setActualPlace(place);
- setWindowVisibility(true);
- }
-
- useEffect(() => {
- updatePlacesByTown(idTown);
- },[]);
-
- const columns : TableColumn[] = [
- {
- name: "Identificador",
- selector: row => row.idPlace || 0
- },
- {
- name: "Nombre",
- selector: row => row.name,
- sortable: true
- },
- {
- name: "Estado",
- selector: row => row.available
- },
- {
- name: "Acciones",
- cell: (row) => {
- return (
- {
- if(!isWindowActive){
- handleEditSelectedCategory(row);
- }
- }}
- />
- );
- }
- }
- ];
-
- return (
-
-
- }
- columns={columns} data={placeList} className="data_table"/>
-
- );
-}
\ No newline at end of file
diff --git a/web/src/components/admin_panel_poi/admin_panel_poi_list/admin_panel_poi_list.tsx b/web/src/components/admin_panel_poi/admin_panel_poi_list/admin_panel_poi_list.tsx
deleted file mode 100644
index 8250307c032a81fe7f3a7ed5c39a181415b5e8c3..0000000000000000000000000000000000000000
--- a/web/src/components/admin_panel_poi/admin_panel_poi_list/admin_panel_poi_list.tsx
+++ /dev/null
@@ -1,157 +0,0 @@
-import DataTable, { TableColumn } from 'react-data-table-component';
-import { usePlace } from '../../../hooks/usePlace';
-import './assets/css/styles.css';
-import { LoadingSpinner } from '../../loading_spinner/loading_spinner';
-import { faEye } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Dispatch, SetStateAction, useEffect, useState } from 'react';
-import { usePointOfInterest } from '../../../hooks/usePointOfInterest';
-import { PointOfInterest } from '../../../infraestructure/entities/poi';
-import { LoadingScreen } from '../../loading_screen/loading_screen';
-
-interface props{
- idTown: number;
- isWindowActive: boolean;
- setActualPoint: Dispatch>;
- setWindowVisibilityViewer: (visibility: boolean) => void;
- setBinaryData: Dispatch>;
- setIsPDFViewerActive: Dispatch>;
-}
-
-export const AdminPanelPoiList = ({idTown, isWindowActive, setActualPoint, setWindowVisibilityViewer, setBinaryData,
- setIsPDFViewerActive
-}: props) => {
- const [isLoading, setIsLoading] = useState(false);
- const [isPDFLoading, setIsPDFLoading] = useState(false);
- const [printButtonActive, setPrintButtonActive] = useState(false);
- const [selectedRows, setSelectedRows] = useState([]);
- const [actualPlaceId, setActualPlaceId] = useState(0);
- const {getPdfById} = usePointOfInterest();
-
- const handleRowSelected = (selected: { allSelected: boolean; selectedCount: number; selectedRows: PointOfInterest[];}) => {
- setSelectedRows(selected.selectedRows.map((element)=>{return element.idPoint || -1}));
- }
-
- useEffect(()=>{
- if(selectedRows.length>0){
- setPrintButtonActive(true);
- }else{
- setPrintButtonActive(false);
- }
- },[selectedRows]);
-
- const handleClickPrintButton = () => {
- const fetchPdf = async () => {
- setIsPDFLoading(true);
- const res = await getPdfById(actualPlaceId, selectedRows);
- if(res!==null){
- setIsPDFViewerActive(true);
- setBinaryData(res);
- }
- setIsPDFLoading(false);
- }
- fetchPdf();
- }
-
- const {
- placeList,
- updatePlacesByTown
- } = usePlace();
-
- const {
- pending,
- updatePOIByPlace,
- poiList
- } = usePointOfInterest();
-
- const handleViewSelectedPoint = (point: PointOfInterest) => {
- setActualPoint(point);
- setWindowVisibilityViewer(true);
- }
-
- const columns : TableColumn[] = [
- {
- name: "Identificador",
- selector: row => row.idPoint || -1,
- sortable: true
- },
- {
- name: "Nombre",
- selector: row => row.name.substring(0,40),
- sortable: true
- },
- {
- name: "Acciones",
- cell: (row) => {
- return (
- {
- if(!isWindowActive){
- handleViewSelectedPoint(row);
- }
- }}
- />
- );
- }
- }
- ];
-
- useEffect(() => {
- setIsLoading(true);
- updatePlacesByTown(idTown);
- setIsLoading(false);
- },[]);
-
- const refreshList = (idPlace: number) => {
- updatePOIByPlace(idPlace)
- };
-
-
- if(isLoading) return
-
- return (
-
-
- Lugar
-
-
-
-
-
- }
- onSelectedRowsChange={handleRowSelected}
- columns={columns} data={poiList} selectableRows className="data_table"
- />
-
- {
- isPDFLoading &&
- }
-
- );
-}
\ No newline at end of file
diff --git a/web/src/components/admin_panel_poi/admin_panel_poi_register/admin_panel_poi_register.tsx b/web/src/components/admin_panel_poi/admin_panel_poi_register/admin_panel_poi_register.tsx
deleted file mode 100644
index 25b25c85ed27df302a748c63b7bb2cc4ea164e00..0000000000000000000000000000000000000000
--- a/web/src/components/admin_panel_poi/admin_panel_poi_register/admin_panel_poi_register.tsx
+++ /dev/null
@@ -1,205 +0,0 @@
-import { faWindowClose } from "@fortawesome/free-solid-svg-icons";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { useEffect, useState} from "react";
-import "./assets/css/styles.css";
-import { languaguesList } from "../../../constants/languages";
-import { LoadingScreen } from "../../loading_screen/loading_screen";
-import { usePointOfInterest } from "../../../hooks/usePointOfInterest";
-import { EmptyPointOfInterest, PointOfInterest } from "../../../infraestructure/entities/poi";
-import { ImageDropzone } from "../../image_dropzone/image_dropzone";
-import { usePlace } from "../../../hooks/usePlace";
-
-interface props {
- setWindowVisibility: (visibility: boolean) => void;
- idTown: number;
- forceRenderList: () => void;
- isRegister: boolean;
- form?: PointOfInterest;
-}
-
-export const AdminPanelPoiRegister = ({setWindowVisibility, idTown,forceRenderList, isRegister, form}: props) => {
- const {
- register,
- errors,
- setDescriptions,
- setDirections,
- descriptions,
- directions,
- setLanguageDescriptionIndexSelected,
- handleSubmit,
- onSubmitRegister,
- getPointById,
- setValue,
- languageDescriptionIndexSelected,
- languageDirectionsIndexSelected,
- setLanguageDirectionsIndexSelected,
- } = usePointOfInterest(forceRenderList, setWindowVisibility);
- const [isLoading, setIsLoading] = useState(false);
- const [preview, setPreview] = useState(null);
- const [image, setImage] = useState(null);
- const {
- placeList,
- updatePlacesByTown
- } = usePlace();
-
- useEffect(() => {
- if(image){
- setValue('image', image, {shouldValidate: true});
- }
- },[image]);
-
- useEffect(() => {
- setIsLoading(true);
- const fetchData = async () => {
- await updatePlacesByTown(idTown);
- if (!isRegister && form) {
- const pointGetted = await getPointById(form.idPoint || 0);
- if(pointGetted){
- setValue('idPoint', pointGetted.idPlace);
- setValue('name', pointGetted.name);
- setValue('contentEN', pointGetted.contentEN);
- setValue('contentES', pointGetted.contentES);
- setValue('directionsEN', pointGetted.directionsEN);
- setValue('directionsES', pointGetted.directionsES);
- }
- }
- };
- fetchData();
- setIsLoading(false);
- },[]);
-
- return (
-
-
- Registra el punto de interés
- setWindowVisibility(false)}/>
-
-
- {isLoading
- ?
-
- :
-
- }
-
-
- );
-}
\ No newline at end of file
diff --git a/web/src/components/admin_panel_poi/admin_panel_poi_screen/admin_panel_poi_screen.tsx b/web/src/components/admin_panel_poi/admin_panel_poi_screen/admin_panel_poi_screen.tsx
deleted file mode 100644
index 48c0763e4c4fc394be3db5a1c564817553f898bc..0000000000000000000000000000000000000000
--- a/web/src/components/admin_panel_poi/admin_panel_poi_screen/admin_panel_poi_screen.tsx
+++ /dev/null
@@ -1,89 +0,0 @@
-import { Dispatch, SetStateAction, useEffect, useState } from "react";
-import { AdminPanelPoiRegister } from "../admin_panel_poi_register/admin_panel_poi_register";
-import "./assets/css/styles.css";
-import { AdminPanelPoiList } from "../admin_panel_poi_list/admin_panel_poi_list";
-import { Town } from "../../../infraestructure/entities/town";
-import { PointOfInterest } from "../../../infraestructure/entities/poi";
-import { AdminPanelPoiViewer } from "../admin_panel_poi_viewer/admin_panel_poi_viewer";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faWindowClose } from "@fortawesome/free-solid-svg-icons";
-
-interface props {
- isWindowActive: boolean;
- setIsWindowActive: Dispatch>;
- town: Town | undefined;
-}
-
-export const AdminPanelPoiScreen = ({isWindowActive,setIsWindowActive, town}: props) => {
- const [renderCount, setRenderCount] = useState(0);
- const [isRegisterPane, setIsRegisterPane] = useState(true);
- const [actualPoint, setActualPoint] = useState();
- const [isRegisterWindowActive, setIsRegisterWindowActive] = useState(false);
- const [isViewerWindowActive, setIsViewerWindowActive] = useState(false);
- const [isPDFViewerActive, setIsPDFViewerActive] = useState(false);
- const [binaryData, setBinaryData] = useState('');
-
- const forceRenderList = () =>{
- setRenderCount(prevCount => prevCount + 1);
- setIsWindowActive(false);
- }
-
- const setWindowVisibilityRegister = (visibility: boolean) => {
- setIsRegisterWindowActive(visibility);
- setIsWindowActive(visibility);
- }
-
- const setWindowVisibilityViewer = (visibility: boolean) => {
- setIsViewerWindowActive(visibility);
- setIsWindowActive(visibility);
- }
-
- return (
-
-
- Administrar puntos de interés dentro de un lugar
-
-
-
- {
- isRegisterWindowActive &&
- }
- {
- isViewerWindowActive &&
- }
-
- {
- isPDFViewerActive &&
-
-
- {
- setIsPDFViewerActive(false);
- setBinaryData('');
- }}/>
-
-
-
-
-
- }
-
-
- );
-}
\ No newline at end of file
diff --git a/web/src/components/admin_panel_poi/admin_panel_poi_viewer/admin_panel_poi_viewer.tsx b/web/src/components/admin_panel_poi/admin_panel_poi_viewer/admin_panel_poi_viewer.tsx
deleted file mode 100644
index e06694a0515f91b7d57b7f7c413b713c969cadf6..0000000000000000000000000000000000000000
--- a/web/src/components/admin_panel_poi/admin_panel_poi_viewer/admin_panel_poi_viewer.tsx
+++ /dev/null
@@ -1,176 +0,0 @@
-import { useEffect, useState } from "react";
-import { PointOfInterest } from "../../../infraestructure/entities/poi";
-import { usePointOfInterest } from "../../../hooks/usePointOfInterest";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faWindowClose } from "@fortawesome/free-solid-svg-icons";
-import { LoadingScreen } from "../../loading_screen/loading_screen";
-import { languaguesList } from "../../../constants/languages";
-import { usePlace } from "../../../hooks/usePlace";
-import './assets/css/styles.css';
-
-interface props{
- pointId: number;
- setWindowVisibility: (visibility: boolean) => void;
-}
-
-export const AdminPanelPoiViewer = ({pointId, setWindowVisibility}: props) => {
- const [point, setPoint] = useState(null);
- const [isLoading, setIsLoading] = useState(false);
- const [languageDescriptionIndexSelected, setLanguageDescriptionIndexSelected] = useState(0);
- const [languageDirectionsIndexSelected, setLanguageDirectionsIndexSelected] = useState(0);
- const [descriptions, setDescriptions] = useState(new Array(languaguesList.length).fill(""));
- const [directions, setDirections] = useState(new Array(languaguesList.length).fill(""));
- const [placeName, setPlaceName] = useState('');
-
- const {
- getPointById
- } = usePointOfInterest();
-
- const{
- getPlaceById
- } = usePlace();
-
- useEffect(()=>{
- fetchData();
- },[]);
-
- const fetchData = async () => {
- setIsLoading(true);
- const result = await getPointById(pointId);
- if(result){
- setPoint(result);
- const newDescriptions = descriptions.map((element, index) => {
- if(index===0){
- return result.contentES;
- }else if(index === 1){
- return result.contentEN;
- }else{
- return element;
- }
- });
- setDescriptions(newDescriptions);
-
- const newDirections = directions.map((element, index) => {
- if(index===0){
- return result.directionsES;
- }else if(index === 1){
- return result.directionsEN;
- }else{
- return element;
- }
- });
- setDirections(newDirections);
-
- const place = await getPlaceById(result.idPlace);
- if(place){
- setPlaceName(place.name);
- }
- }
- setIsLoading(false);
- }
-
- return (
-
-
- Información del punto de interés
- setWindowVisibility(false)}/>
-
- {isLoading || point === null
- ?
-
- :
-
-
-
-
- Nombre del punto de interés
-
-
-
-
-
- Descripción del punto de interés
-
-
- {
- languaguesList.map((language, index) => {
- if(index===languageDescriptionIndexSelected){
- return (
-
- );
- }
- })
- }
-
-
-
-
- Direcciones hacia el siguiente punto de interés
-
-
- {
- languaguesList.map((language, index) => {
- if(index===languageDirectionsIndexSelected){
- return (
-
- );
- }
- })
- }
-
-
-
-
- Nombre del lugar al que pertenece el punto de interés
-
-
-
-
-
-
- Nombre del punto de interés
-
-
-
-
-
-
- }
-
- );
-}
\ No newline at end of file
diff --git a/web/src/components/admin_town_info/admin_town_info.tsx b/web/src/components/admin_town_info/admin_town_info.tsx
deleted file mode 100644
index 22b729312ed0ad65c7577c4143e2a7519d2dfdd0..0000000000000000000000000000000000000000
--- a/web/src/components/admin_town_info/admin_town_info.tsx
+++ /dev/null
@@ -1,119 +0,0 @@
-import { Dispatch, SetStateAction, useState} from "react";
-import { Town } from "../../infraestructure/entities/town";
-import "./assets/css/styles.css";
-import { faEdit, faLanguage } from "@fortawesome/free-solid-svg-icons";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { LoadingScreen } from "../loading_screen/loading_screen";
-import { useAdminTownInfo } from "../../hooks/useAdminTownInfo";
-import { SuperadminPanelTownRegister } from "../sa_panel_town/sa_panel_town_register/sa_panel_town_register";
-
-interface props {
- isWindowActive: boolean;
- setIsWindowActive: Dispatch>;
- town: Town | undefined;
- updateTown: () => Promise;
-}
-
-export const AdminTownInfo = ({updateTown, isWindowActive, setIsWindowActive, town}: props) => {
- const {
- isEnglish,
- isLoading,
- setIsEnglish,
- setIsLoading,
- renderCount,
- forceRenderList,
- statesList
- } = useAdminTownInfo(updateTown);
- const [isTownRegisterWindowActive, setIsTownRegisterWindowActive] = useState(false);
-
- const setWindowVisibility = (visibility: boolean) => {
- setIsTownRegisterWindowActive(visibility);
- setIsWindowActive(visibility);
- }
-
- if(!town){
- return (
-
- No tienes un pueblo asignado
-
- );
- }
-
- return (
-
- {isTownRegisterWindowActive &&
-
- }
-
- {isLoading &&
-
-
-
- }
-
-
-
-
-
setIsLoading(false)}/>
-
-
-
-
-
-
- {
- if(!isWindowActive){
- setIsEnglish(!isEnglish);
- }
- }
- }
- />
-
-
{
- if(!isWindowActive){
- setWindowVisibility(true);
- }
- }
- }
- />
-
- {
- isEnglish ?
-
- :
-
- }
-
- {town.state}
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/web/src/components/confirmation_dialog_box/confirmation_dialog.tsx b/web/src/components/confirmation_dialog_box/confirmation_dialog.tsx
deleted file mode 100644
index 9cc9891721d140944e1fd70e7e4d1cc541d540ce..0000000000000000000000000000000000000000
--- a/web/src/components/confirmation_dialog_box/confirmation_dialog.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import { Dispatch, SetStateAction } from 'react';
-import './assets/css/styles.css';
-
-interface props{
- hangleToClose: () => void;
- setAnswer?: Dispatch>;
- title?: string;
- message: string;
-}
-
-export const ConfirmationDialog = ({hangleToClose, setAnswer, title, message}:props) => {
- return (
-
-
-
{title || 'Confirmar acción'}
-
-
-
-
-
-
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/web/src/components/image_dropzone/image_dropzone.tsx b/web/src/components/image_dropzone/image_dropzone.tsx
deleted file mode 100644
index cb55cf7c28f5e3c85830668ba4a0f4e72c6c9b76..0000000000000000000000000000000000000000
--- a/web/src/components/image_dropzone/image_dropzone.tsx
+++ /dev/null
@@ -1,57 +0,0 @@
-import { toast } from 'react-toastify';
-import { useDropzone } from "react-dropzone";
-import './assets/css/styles.css';
-import { Dispatch, SetStateAction } from 'react';
-import "react-toastify/dist/ReactToastify.css";
-
-interface props {
- setImage: Dispatch>;
- preview : string | ArrayBuffer | null;
- setPreview: Dispatch>;
-}
-
-export const ImageDropzone = ({setImage, preview, setPreview}: props) => {
- const MAX_SIZE = 10485760;
- const {getRootProps, getInputProps} = useDropzone(
- {
- multiple: false,
- maxSize: MAX_SIZE,
- accept: {
- 'image/jpeg': [],
- 'image/png': []
- },
- onDrop(acceptedFiles, fileRejections) {
- fileRejections.map(({file, errors}) => (
- toast.error(errors[0].message, {
- position: "bottom-right",
- autoClose: 1500,
- hideProgressBar: false,
- closeOnClick: true,
- pauseOnHover: false,
- draggable: true,
- progress: undefined,
- theme: "colored"
- })));
-
- acceptedFiles.forEach((file)=>{
- const preview = URL.createObjectURL(file);
- setImage(file);
- setPreview(preview);
- });
- }
- }
- );
-
- return (
-
-
-
- {preview ? (
-
- ) : (
-
Arrastra tu imagen o seleccionala dando click aquí.
- )}
-
-
- );
-}
\ No newline at end of file
diff --git a/web/src/components/loading_screen/loading_screen.tsx b/web/src/components/loading_screen/loading_screen.tsx
deleted file mode 100644
index 42c8942612f160cd799753d6b769778db96520dd..0000000000000000000000000000000000000000
--- a/web/src/components/loading_screen/loading_screen.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import './assets/css/styles.css';
-
-export const LoadingScreen = () => {
- return (
-
- );
-}
\ No newline at end of file
diff --git a/web/src/components/multiple_images_dropzone/multiple_images_dropzone.tsx b/web/src/components/multiple_images_dropzone/multiple_images_dropzone.tsx
deleted file mode 100644
index 9b0eef099f91be3ca783728da45f6a25bed579f1..0000000000000000000000000000000000000000
--- a/web/src/components/multiple_images_dropzone/multiple_images_dropzone.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import { UseFormSetValue } from "react-hook-form";
-import { Place } from "../../infraestructure/entities/place";
-import { useDropzoneMultiplesImages } from "../../hooks/useDropzoneMultiplesImages";
-import "./assets/css/styles.css";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faTimesCircle } from "@fortawesome/free-solid-svg-icons";
-
-interface props{
- setValue: UseFormSetValue;
- imagesList?: File[] | string[];
-}
-
-export const MultipleImagesDropzone = ({setValue, imagesList}:props) => {
- const {
- getRootProps,
- getInputProps,
- imagesFiles,
- removeImage,
- } = useDropzoneMultiplesImages(setValue, imagesList);
-
- return (
-
-
-
-
Arrastra tu imagen o seleccionala dando click aquí.
-
-
- {
- imagesFiles.map((image, index) => {
- return (
-
-
-
{URL.revokeObjectURL(image.preview)}}
- />
-
-
removeImage(index)}
- />
-
- );
- })
- }
-
-
- );
-}
-
diff --git a/web/src/components/sa_panel_admin/sa_panel_admin_list/sa_panel_admin_list.tsx b/web/src/components/sa_panel_admin/sa_panel_admin_list/sa_panel_admin_list.tsx
deleted file mode 100644
index f4ca365714500030ac1a7689ebe00c8ed7694d69..0000000000000000000000000000000000000000
--- a/web/src/components/sa_panel_admin/sa_panel_admin_list/sa_panel_admin_list.tsx
+++ /dev/null
@@ -1,149 +0,0 @@
-import DataTable, { TableColumn } from 'react-data-table-component';
-import './assets/css/styles.css';
-import { LoadingSpinner } from '../../loading_spinner/loading_spinner';
-import { useState } from 'react';
-import { Admin } from '../../../infraestructure/entities/admin_form_values';
-import { State } from '../../../infraestructure/entities/state';
-import { useTown } from '../../../hooks/useTown';
-import axios, { AxiosError } from 'axios';
-import { showErrorAxios } from '../../../utils/Messages';
-import { useAdmin } from '../../../hooks/useAdmin';
-
-interface props{
- isWindowActive: boolean;
- statesList: State[];
-}
-
-export const SuperAdminPanelAdminList = ({isWindowActive, statesList}: props) => {
- const [isLoading, setIsLoading] = useState(false);
- const {
- townsList,
- getTownsByState
- } = useTown();
-
- const {
- adminList,
- getAdminListByTown
- } = useAdmin();
-
- const columns : TableColumn[] = [
- {
- name: "Email",
- selector: row => row.email,
- sortable: true
- },
- {
- name: "Nombre",
- selector: row => row.name.substring(0,40),
- sortable: true
- },
- {
- name: "Apellido",
- selector: row => row.lastName.substring(0,40),
- sortable: true
- },
- {
- name: "Estatus",
- selector: row => row.status?.substring(0,40) ?? "",
- sortable: true
- }
- ];
-
- const refreshListTown = (stateId: number, name: string) => {
- setIsLoading(true);
- const getTownsList = async () => {
- try {
- getTownsByState(stateId, name);
- } catch (error: any) {
- if (axios.isAxiosError(error)) {
- error as AxiosError;
- showErrorAxios(error);
- }
- }
- }
- getTownsList();
- setIsLoading(false);
- };
-
- const refreshListAdmins = (idTown: number) => {
- setIsLoading(true);
- const getAdminsList = async () => {
- try {
- getAdminListByTown(idTown);
- } catch (error: any) {
- if (axios.isAxiosError(error)) {
- error as AxiosError;
- showErrorAxios(error);
- }
- }
- }
- getAdminsList();
- setIsLoading(false);
- };
-
- if(isLoading) return
-
- return (
-
-
- Estado
-
-
- Pueblo mágico
-
-
-
-
-
- }
- columns={columns} data={adminList} selectableRows className="data_table"
- />
-
-
- );
-}
\ No newline at end of file
diff --git a/web/src/components/sa_panel_admin/sa_panel_admin_register/sa_panel_admin_register.tsx b/web/src/components/sa_panel_admin/sa_panel_admin_register/sa_panel_admin_register.tsx
deleted file mode 100644
index 5eb361a5fad02edaa57f48cde3028c7622e04f21..0000000000000000000000000000000000000000
--- a/web/src/components/sa_panel_admin/sa_panel_admin_register/sa_panel_admin_register.tsx
+++ /dev/null
@@ -1,158 +0,0 @@
-import { Dispatch, SetStateAction } from "react";
-import './assets/css/styles.css'
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faWindowClose, faEye, faEyeSlash } from "@fortawesome/free-solid-svg-icons";
-import { useAdmin } from "../../../hooks/useAdmin";
-import { usePasswoordVisibility } from "../../../hooks/usePasswordVisibility";
-import { State } from "../../../infraestructure/entities/state";
-import { useTown } from "../../../hooks/useTown";
-
-interface props {
- handleClickToClose: () => void;
- forceRenderList: () => void;
- statesList : State[];
-}
-
-export const SuperadminPanelAdminRegister = ({handleClickToClose, forceRenderList, statesList}:props) => {
- const {
- register,
- errors,
- handleSubmit,
- onSubmit,
- } = useAdmin(forceRenderList, handleClickToClose);
-
- const {
- values,
- handleClickShowPassword,
- handleMouseDownPassword
- } = usePasswoordVisibility();
-
- const {townsList, getTownsByState} = useTown();
-
- return (
-
-
- Registra el administrador
- handleClickToClose()}/>
-
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/web/src/components/sa_panel_admin/sa_panel_admin_screen/sa_panel_admin_screen.tsx b/web/src/components/sa_panel_admin/sa_panel_admin_screen/sa_panel_admin_screen.tsx
deleted file mode 100644
index efc62d5e6d88c288d580d9c8958390a6922d8228..0000000000000000000000000000000000000000
--- a/web/src/components/sa_panel_admin/sa_panel_admin_screen/sa_panel_admin_screen.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import { Dispatch, SetStateAction, useState } from 'react';
-import './assets/css/styles.css';
-import { SuperadminPanelAdminRegister } from '../sa_panel_admin_register/sa_panel_admin_register';
-import { State } from '../../../infraestructure/entities/state';
-import { SuperAdminPanelAdminList } from '../sa_panel_admin_list/sa_panel_admin_list';
-
-interface props {
- isWindowActive: boolean;
- setIsWindowActive: Dispatch>;
- statesList: State[];
-}
-
-export const SuperadminPanelAdminScreen = ({isWindowActive, setIsWindowActive, statesList}:props) => {
- const [showRegisterPanel, setShowRegisterPanel] = useState(false);
- const [renderCount, setRenderCount] = useState(0);
-
- const handleClickToClose = () => {
- setIsWindowActive(false);
- setShowRegisterPanel(false);
- }
-
- const forceRenderList = () =>{
- setRenderCount(prevCount => prevCount + 1);
- }
-
- return (
-
-
- Administrar administradores
-
-
-
- {showRegisterPanel
- &&
-
- }
-
-
-
- );
-}
\ No newline at end of file
diff --git a/web/src/components/sa_panel_category/sa_panel_category_list/sa_panel_category_list.tsx b/web/src/components/sa_panel_category/sa_panel_category_list/sa_panel_category_list.tsx
deleted file mode 100644
index 9d74d98673e11caad30d65daaeabe4718827397a..0000000000000000000000000000000000000000
--- a/web/src/components/sa_panel_category/sa_panel_category_list/sa_panel_category_list.tsx
+++ /dev/null
@@ -1,102 +0,0 @@
-import DataTable, { TableColumn } from 'react-data-table-component';
-import './assets/css/styles.css';
-import { LoadingSpinner } from '../../loading_spinner/loading_spinner';
-import { useCategory } from '../../../hooks/useCategory';
-import { Category } from '../../../infraestructure/entities/category';
-import { faTrash } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Dispatch, SetStateAction, useEffect, useState } from 'react';
-import { ConfirmationDialog } from '../../confirmation_dialog_box/confirmation_dialog';
-import { toast } from 'react-toastify';
-
-interface props {
- isWindowActive: boolean;
- setIsWindowActive: Dispatch>;
-}
-
-export const SuperAdminPanelCategoryList = ({isWindowActive, setIsWindowActive}:props) => {
- const {
- categoriesList,
- pending,
- deleteCategory,
- } = useCategory();
- const [isDialogOpen, setIsDialogOpen] = useState(false);
- const [dialogMessage, setDialogMessage] = useState('');
- const [categoryDeleted, setCategoryDeleted] = useState(null);
- const [deleteCategoryBool, setDeleteCategoryBool] = useState(false);
-
- const deleteSelectedCategory = (category: Category) => {
- toast.promise(
- deleteCategory(category),{
- pending: "Eliminando categoría...",
- success: "La categoría se ha eliminado correctamente",
- error: "No se pudo eliminar la categoría"
- }
- )
- }
-
- useEffect(() => {
- if(deleteCategoryBool && categoryDeleted){
- deleteSelectedCategory(categoryDeleted);
- setDeleteCategoryBool(false);
- }
- }, [deleteCategoryBool]);
-
- const handleDeleteSelectedCategory = (category: Category) => {
- setDialogMessage(`¿Desea eliminar la categoría ${category.nameES}?`)
- setCategoryDeleted(category);
- setIsDialogOpen(true);
- setIsWindowActive(true);
- }
-
- const handleToClose = () => {
- setIsWindowActive(false);
- setIsDialogOpen(false)
- }
-
- const columns : TableColumn[] = [
- {
- name: "Identificador",
- selector: row => row.idCategory || 0,
- sortable: true
- },
- {
- name: "Nombre en español",
- selector: row => row.nameES,
- sortable: true
- },
- {
- name: "Nombre en Inglés",
- selector: row => row.nameEN,
- sortable: true
- },
- {
- name: "Acciones",
- cell: (row) => {
- return (
- {
- if(!isWindowActive){
- handleDeleteSelectedCategory(row);
- }
- }}
- />
- );
- }
- }
- ];
-
- return (
-
-
- }
- disabled={isWindowActive}
- columns={columns} data={categoriesList} className="data_table"/>
- {
- isDialogOpen &&
- }
-
- );
-}
\ No newline at end of file
diff --git a/web/src/components/sa_panel_category/sa_panel_category_register/sa_panel_category_register.tsx b/web/src/components/sa_panel_category/sa_panel_category_register/sa_panel_category_register.tsx
deleted file mode 100644
index 675d0d68f5a8359a9e090b898e32cea712bae40c..0000000000000000000000000000000000000000
--- a/web/src/components/sa_panel_category/sa_panel_category_register/sa_panel_category_register.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import { faWindowClose } from "@fortawesome/free-solid-svg-icons";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { Dispatch, SetStateAction} from "react";
-import "./assets/css/styles.css";
-import { useCategory } from "../../../hooks/useCategory";
-
-interface props {
- handleClickToClose: () => void;
- forceRenderList: () => void;
-}
-
-export const SuperAdminPanelCategoryRegister = ({handleClickToClose, forceRenderList}: props) => {
- const {
- register,
- handleSubmit,
- errors,
- onSubmit,
- } = useCategory(forceRenderList, handleClickToClose);
-
- return (
-
-
- Registra la categoría
- handleClickToClose()}/>
-
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/web/src/components/sa_panel_town/sa_panel_town_register/sa_panel_town_register.tsx b/web/src/components/sa_panel_town/sa_panel_town_register/sa_panel_town_register.tsx
deleted file mode 100644
index 5717fed50bd1ffc79fc81552c25cb6d2f874970e..0000000000000000000000000000000000000000
--- a/web/src/components/sa_panel_town/sa_panel_town_register/sa_panel_town_register.tsx
+++ /dev/null
@@ -1,163 +0,0 @@
-import { faWindowClose, faLanguage } from "@fortawesome/free-solid-svg-icons";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import './css/styles.css'
-import { ImageDropzone } from "../../image_dropzone/image_dropzone";
-import { Dispatch, SetStateAction, useEffect, useState } from "react";
-import { useTown } from "../../../hooks/useTown";
-import { State } from "../../../infraestructure/entities/state";
-import { Town } from "../../../infraestructure/entities/town";
-
-interface props {
- setWindowActive: Dispatch>,
- setActualWindowActive: Dispatch>,
- statesList: State[] | null;
- forceRenderList: () => void;
- isRegister: boolean;
- form?: Town;
-}
-
-export const SuperadminPanelTownRegister = ({setWindowActive, statesList, forceRenderList, isRegister, form,
- setActualWindowActive
-}:props) => {
- const [isEnglish, setIsEnglish] = useState(false);
- const [spanishDescription, setSpanishDescription] = useState("");
- const [englishDescription, setEnglishDescription] = useState("");
- const [preview, setPreview] = useState(null);
- const [image, setImage] = useState(null);
- const [actualWindowVisibility, setActualWindowVisibility] = useState(true);
-
- const closeActualWindow = () => {
- setWindowActive(false);
- setActualWindowActive(false);
- }
-
- const {
- register,
- setValue,
- errors,
- handleSubmit,
- onSubmitRegister,
- onSubmitUpdate,
- } = useTown(forceRenderList, closeActualWindow);
-
- useEffect(()=> {
- if(!isRegister && form){
- const setData = async () => {
- setValue('idTown', form.idTown);
- setValue('name',form.name);
- setSpanishDescription(form?.descriptionES || '');
- setValue('descriptionES',form.descriptionES);
- setEnglishDescription(form?.descriptionEN || '');
- setValue('descriptionEN',form.descriptionEN);
- setValue('idState',form.idState);
- setPreview(form.imageURL as string);
-
- const response = await fetch(form.imageURL as string);
- const blob = await response.blob();
- const file = new File([blob],'image.jpg',{type: blob.type});
- setValue('imageURL', file);
- }
- setData();
- }
- },[])
-
- useEffect(() => {
- if(image){
- setValue('imageURL', image, {shouldValidate: true});
- }
- },[image]);
-
- return (
-
-
- {isRegister ? "Registra el pueblo mágico": "Actualiza tu pueblo mágico"}
- closeActualWindow()}/>
-
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/web/src/components/sa_panel_town/sa_panel_town_screen/sa_panel_town_screen.tsx b/web/src/components/sa_panel_town/sa_panel_town_screen/sa_panel_town_screen.tsx
deleted file mode 100644
index 4deeb58a393b151ac652dcee3c2fe1db90823bad..0000000000000000000000000000000000000000
--- a/web/src/components/sa_panel_town/sa_panel_town_screen/sa_panel_town_screen.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import { Dispatch, SetStateAction, useState } from 'react';
-import { SuperadminPanelTownRegister } from '../sa_panel_town_register/sa_panel_town_register';
-import './css/styles.css'
-import { State } from '../../../infraestructure/entities/state';
-import { SuperadminPanelTownList } from '../sa_panel_town_list/sa_panel_town_list';
-
-interface props {
- windowActive: boolean;
- setWindowActive: Dispatch>;
- statesList: State[];
-}
-
-export const SuperadminPanelTownScreen = ({windowActive,setWindowActive, statesList}:props) => {
- const [showRegisterPanel, setShowRegisterPanel] = useState(false);
- const [renderCount, setRenderCount] = useState(0);
-
- const forceRenderList = () =>{
- setRenderCount(prevCount => prevCount + 1);
- }
-
- return (
-
-
- Administrar pueblos mágicos
-
-
-
-
-
- {showRegisterPanel
- &&
- }
-
-
- );
-}
\ No newline at end of file
diff --git a/web/src/components/sidebar_header/sidebar_header.tsx b/web/src/components/sidebar_header/sidebar_header.tsx
deleted file mode 100644
index f23c496b1fe84e6e2c2b3fd14bd9ddc697deb734..0000000000000000000000000000000000000000
--- a/web/src/components/sidebar_header/sidebar_header.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { UserRole } from '../../constants/roles';
-import { useAuth } from '../../context/auth_context';
-import './assets/css/styles.css';
-
-export const SidebarHeader = () => {
- const {user} = useAuth();
- if(!user){
- return null;
- }
-
- return (
-
-
-
{user.role==UserRole.SUPERADMIN ? 'Superadmin': 'Admin'} Panel
-
-
- );
-}
\ No newline at end of file
diff --git a/web/src/constants/images_nuber.ts b/web/src/constants/images_nuber.ts
deleted file mode 100644
index d9b2beb79512f6468873b47f1ec3ec27ee9af327..0000000000000000000000000000000000000000
--- a/web/src/constants/images_nuber.ts
+++ /dev/null
@@ -1 +0,0 @@
-export const MIN_NUMBER_PLACE_IMAGES = 1;
\ No newline at end of file
diff --git a/web/src/constants/languages.ts b/web/src/constants/languages.ts
deleted file mode 100644
index 770db8d7cd20d9db7aaa514acfd401930948b9db..0000000000000000000000000000000000000000
--- a/web/src/constants/languages.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export const languaguesList: string[] = [
- 'Español',
- 'Inglés',
-]
\ No newline at end of file
diff --git a/web/src/constants/roles.ts b/web/src/constants/roles.ts
deleted file mode 100644
index dd45c3294fc22de5ccefa0a6913961853b9ae1b6..0000000000000000000000000000000000000000
--- a/web/src/constants/roles.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export enum UserRole {
- ADMIN = "admin",
- SUPERADMIN = "superadmin",
-}
-
-export const SUPERADMIN_ROLE = UserRole.SUPERADMIN;
-export const ADMIN_ROLE = UserRole.ADMIN;
\ No newline at end of file
diff --git a/web/src/constants/selected_panel.ts b/web/src/constants/selected_panel.ts
deleted file mode 100644
index 0c2ef85c359beb3bf0b346ceb9ecdee12be612e7..0000000000000000000000000000000000000000
--- a/web/src/constants/selected_panel.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-export enum AdminSelectedPanel {
- TOWN_INFO = "town_info",
- PLACES = "places",
- POINT_OF_INTEREST = "point_of_interest"
-}
-
-export enum SuperAdminSelectedPanel {
- TOWNS = "towns",
- ADMINS = "admins",
- CATEGORIES = "categories"
-}
\ No newline at end of file
diff --git a/web/src/context/auth_context.tsx b/web/src/context/auth_context.tsx
deleted file mode 100644
index 0a7771410adc573089825719b2e4c4cd3add4e27..0000000000000000000000000000000000000000
--- a/web/src/context/auth_context.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import { UserEntity } from "../infraestructure/entities/user";
-import axios from "axios";
-import { UserRole } from "../constants/roles";
-import { ReactNode, createContext, useContext, useEffect, useState } from "react";
-
-type AuthContextType = {
- user: UserEntity | null;
- login: (user: UserEntity, token: string) => void;
- logout: () => void;
-};
-
-const AuthContext = createContext({
- user: null,
- login: () => {},
- logout: () => {}
-});
-
-type AuthContextProviderProps = {
- children: ReactNode;
-}
-
-export const AuthContextProvider = ({children} : AuthContextProviderProps) => {
- const [user, setUser] = useState(null);
- const saveSession = async (user: UserEntity, token: string) => {
- setUser(user);
- localStorage.setItem("token",token);
- localStorage.setItem("user",JSON.stringify(user));
- axios.defaults.headers.common["Authorization"] = `Bearer ${token}`;
- }
-
- const deleteSession = async () => {
- setUser(null);
- localStorage.removeItem("token");
- localStorage.removeItem("user");
- axios.defaults.headers.common["Authorization"] = "";
- }
-
- const login = async (user: UserEntity, token: string) => {
- await saveSession(user, token);
- }
-
- const logout = async () => {
- await deleteSession();
- }
-
- const checkSession = async () => {
- const token = localStorage.getItem("token");
- const user = localStorage.getItem("user");
- if(token && user){
- const sessionUser = JSON.parse(user);
- await saveSession({email: sessionUser.email as string, name: sessionUser.name as string, role: sessionUser.role as UserRole}, token);
- }
- }
-
- useEffect(() => {
- checkSession();
- }, []);
-
- const value = {user, login, logout};
- return {children}
-};
-
-export const useAuth = () => {
- if(AuthContext == undefined){
- throw new Error("useAuth debe se usado dentro de un AuthContextProvider");
- }
- return useContext(AuthContext);
-};
\ No newline at end of file
diff --git a/web/src/constants/api_routes.ts b/web/src/core/constants/api_routes.ts
similarity index 53%
rename from web/src/constants/api_routes.ts
rename to web/src/core/constants/api_routes.ts
index cdb28e946b242863b0fa83efea54f187a016f608..1c403b23f3ac35761134acbcfe79827f52910e9c 100644
--- a/web/src/constants/api_routes.ts
+++ b/web/src/core/constants/api_routes.ts
@@ -1,13 +1,52 @@
+/**
+ * API route for admin operations.
+ */
export const API_ROUTE_ADMIN = "/admin";
+
+/**
+ * API route for admin signup.
+ */
export const API_ROUTE_ADMIN_SIGNUP = API_ROUTE_ADMIN + "/signup";
+
+/**
+ * API route for admin signin.
+ */
export const API_ROUTE_ADMIN_SIGNIN = API_ROUTE_ADMIN + "/signin";
+
+/**
+ * API route for changing admin password.
+ */
export const API_ROUTE_ADMIN_CHANGE_PASSWORD =
API_ROUTE_ADMIN + "/change-password";
+
+/**
+ * API route for resetting admin password.
+ */
export const API_ROUTE_ADMIN_RESET_PASSWORD =
API_ROUTE_ADMIN + "/reset-password";
+
+/**
+ * API route for generating admin reset code.
+ */
export const API_ROUTE_ADMIN_GENERATE_RESET_CODE =
API_ROUTE_ADMIN + "/get-reset-code";
+
+/**
+ * API route for getting admin information.
+ */
export const API_ROUTE_ADMIN_WHOAMI = API_ROUTE_ADMIN + "/whoami";
-const API_ROUTE_STATE = "/state";
+
+/**
+ * API route for state operations.
+ */
+export const API_ROUTE_STATE = "/state";
+
+/**
+ * API route for point operations.
+ */
export const API_ROUTE_POINT = "/point";
+
+/**
+ * API route for place operations.
+ */
export const API_ROUTE_PLACE = "/place";
diff --git a/web/src/constants/api_url.ts b/web/src/core/constants/api_url.ts
similarity index 62%
rename from web/src/constants/api_url.ts
rename to web/src/core/constants/api_url.ts
index b712d33928a6ffa71f9619a88dfe2a43330182a0..463d2127f7023c3213574c302814ad3cc3668baa 100644
--- a/web/src/constants/api_url.ts
+++ b/web/src/core/constants/api_url.ts
@@ -1 +1,4 @@
+/**
+ * Base URL for the API.
+ */
export const APIUrl: String = "http://localhost:3005";
\ No newline at end of file
diff --git a/web/src/core/constants/images_nuber.ts b/web/src/core/constants/images_nuber.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e0472767db27582b81f8fc71a905c61a479f68fc
--- /dev/null
+++ b/web/src/core/constants/images_nuber.ts
@@ -0,0 +1,4 @@
+/**
+ * Minimum number of images required for a place.
+ */
+export const MIN_NUMBER_PLACE_IMAGES = 1;
\ No newline at end of file
diff --git a/web/src/core/constants/languages.ts b/web/src/core/constants/languages.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9832daad0d42cce1bad36bf77204ea6f7b398425
--- /dev/null
+++ b/web/src/core/constants/languages.ts
@@ -0,0 +1,7 @@
+/**
+ * List of supported languages.
+ */
+export const languaguesList: string[] = [
+ 'Español', // Spanish
+ 'Inglés', // English
+]
\ No newline at end of file
diff --git a/web/src/core/constants/roles.ts b/web/src/core/constants/roles.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fcc8e974b5e69dc80089ca8befcda0d7c990bd34
--- /dev/null
+++ b/web/src/core/constants/roles.ts
@@ -0,0 +1,17 @@
+/**
+ * Enum representing the different user roles.
+ */
+export enum UserRole {
+ ADMIN = "admin", // Admin role
+ SUPERADMIN = "superadmin", // Super admin role
+}
+
+/**
+ * Constant representing the super admin role.
+ */
+export const SUPERADMIN_ROLE = UserRole.SUPERADMIN;
+
+/**
+ * Constant representing the admin role.
+ */
+export const ADMIN_ROLE = UserRole.ADMIN;
\ No newline at end of file
diff --git a/web/src/core/constants/selected_panel.ts b/web/src/core/constants/selected_panel.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d9691ddb09e0f23ef56fbcb4d36001a9e65789f0
--- /dev/null
+++ b/web/src/core/constants/selected_panel.ts
@@ -0,0 +1,17 @@
+/**
+ * Enum representing the different panels available for an admin user.
+ */
+export enum AdminSelectedPanel {
+ TOWN_INFO = "town_info", // Panel for town information
+ PLACES = "places", // Panel for places
+ POINT_OF_INTEREST = "point_of_interest" // Panel for points of interest
+}
+
+/**
+ * Enum representing the different panels available for a super admin user.
+ */
+export enum SuperAdminSelectedPanel {
+ TOWNS = "towns", // Panel for towns
+ ADMINS = "admins", // Panel for admins
+ CATEGORIES = "categories" // Panel for categories
+}
\ No newline at end of file
diff --git a/web/src/core/context/auth_context.tsx b/web/src/core/context/auth_context.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..51a2373bf2a3e2467b9e6d53f9f181ffd418bac6
--- /dev/null
+++ b/web/src/core/context/auth_context.tsx
@@ -0,0 +1,92 @@
+import { UserEntity } from "../../data/datasource/api/entities/user";
+import axios from "axios";
+import { UserRole } from "../constants/roles";
+import {
+ ReactNode,
+ createContext,
+ useContext,
+ useEffect,
+ useState,
+} from "react";
+
+// Define the shape of the context state and actions
+type AuthContextType = {
+ user: UserEntity | null; // The authenticated user or null if not authenticated
+ login: (user: UserEntity, token: string) => void; // Function to log in the user
+ logout: () => void; // Function to log out the user
+};
+
+// Create the context with default values
+const AuthContext = createContext({
+ user: null,
+ login: () => {},
+ logout: () => {},
+});
+
+// Define the props for the AuthContextProvider component
+type AuthContextProviderProps = {
+ children: ReactNode; // The child components that will have access to the context
+};
+
+// Create the provider component
+export const AuthContextProvider = ({ children }: AuthContextProviderProps) => {
+ const [user, setUser] = useState(null); // State to track the authenticated user
+
+ // Function to save the session data in local storage and set the authorization header
+ const saveSession = async (user: UserEntity, token: string) => {
+ setUser(user);
+ localStorage.setItem("token", token);
+ localStorage.setItem("user", JSON.stringify(user));
+ axios.defaults.headers.common["Authorization"] = `Bearer ${token}`;
+ };
+
+ // Function to delete the session data from local storage and clear the authorization header
+ const deleteSession = async () => {
+ setUser(null);
+ localStorage.removeItem("token");
+ localStorage.removeItem("user");
+ axios.defaults.headers.common["Authorization"] = "";
+ };
+
+ // Function to log in the user by saving the session
+ const login = async (user: UserEntity, token: string) => {
+ await saveSession(user, token);
+ };
+
+ // Function to log out the user by deleting the session
+ const logout = async () => {
+ await deleteSession();
+ };
+
+ // Check if there is an existing session in local storage when the component mounts
+ useEffect(() => {
+ const checkSession = async () => {
+ const token = localStorage.getItem("token");
+ const user = localStorage.getItem("user");
+ if (token && user) {
+ const sessionUser = JSON.parse(user);
+ await saveSession(
+ {
+ email: sessionUser.email as string,
+ name: sessionUser.name as string,
+ role: sessionUser.role as UserRole,
+ },
+ token
+ );
+ }
+ };
+ checkSession();
+ }, []);
+
+ // Provide the context value to child components
+ const value = { user, login, logout };
+ return {children};
+};
+
+// Custom hook to use the AuthContext
+export const useAuth = () => {
+ if (AuthContext === undefined) {
+ throw new Error("useAuth debe se usado dentro de un AuthContextProvider");
+ }
+ return useContext(AuthContext);
+};
diff --git a/web/src/context/message_context.tsx b/web/src/core/context/message_context.tsx
similarity index 50%
rename from web/src/context/message_context.tsx
rename to web/src/core/context/message_context.tsx
index 1ce309bec77a5cb93eb182b374430ac90cb8e8bd..4bee0a31fb56801248031efa5645a4360702c06b 100644
--- a/web/src/context/message_context.tsx
+++ b/web/src/core/context/message_context.tsx
@@ -1,12 +1,14 @@
import { createContext, ReactNode, useContext, useState } from "react";
+// Define the shape of the context state and actions
type MessageContextType = {
- isVisible: boolean;
- message: string;
- showMessage: (message: string) => void;
- hideMessage: () => void;
+ isVisible: boolean; // Indicates if the message is visible
+ message: string; // The message to be displayed
+ showMessage: (message: string) => void; // Function to show the message
+ hideMessage: () => void; // Function to hide the message
}
+// Create the context with default values
const MessageContext = createContext({
isVisible: false,
message: "",
@@ -14,13 +16,17 @@ const MessageContext = createContext({
hideMessage: () => {}
});
+// Define the props for the MessageContextProvider component
type MessageContextProviderProps = {
- children: ReactNode;
+ children: ReactNode; // The child components that will have access to the context
}
+// Create the provider component
export const MessageContextProvider = ({children}: MessageContextProviderProps) => {
- const [isVisible, setIsVisible] = useState(false);
- const [message, setMessage] = useState("");
+ const [isVisible, setIsVisible] = useState(false); // State to track visibility of the message
+ const [message, setMessage] = useState(""); // State to track the message content
+
+ // Function to show the message and hide it after 3 seconds
const showMessage = (message: string) => {
setIsVisible(true);
setMessage(message);
@@ -30,17 +36,20 @@ export const MessageContextProvider = ({children}: MessageContextProviderProps)
}, 3000);
};
+ // Function to hide the message immediately
const hideMessage = () => {
setIsVisible(false);
setMessage("");
};
+ // Provide the context value to child components
const value = {isVisible, message, showMessage, hideMessage};
return {children}
}
+// Custom hook to use the MessageContext
export const useMessage = () => {
- if(MessageContext == undefined){
+ if(MessageContext === undefined){
throw new Error("useMessage debe se usado dentro de un MessageContextProvider");
}
return useContext(MessageContext);
diff --git a/web/src/core/errors/CustomError.ts b/web/src/core/errors/CustomError.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a8eeb8e7b464a4001fdddca5fa3e73c068e16e0e
--- /dev/null
+++ b/web/src/core/errors/CustomError.ts
@@ -0,0 +1,26 @@
+/**
+ * CustomErrorContent defines the structure of the error content.
+ */
+export type CustomErrorContent = {
+ message: string;
+ context?: { [key: string]: any };
+};
+
+/**
+ * CustomError is an abstract base class for custom errors.
+ * It extends the built-in Error class and adds additional properties.
+ */
+export abstract class CustomError extends Error {
+ // Abstract properties that must be implemented by subclasses
+ abstract readonly statusCode: number;
+ abstract readonly logging: boolean;
+
+ /**
+ * Constructs a new CustomError instance.
+ * @param message - The error message.
+ */
+ constructor(message: string) {
+ super(message);
+ Object.setPrototypeOf(this, CustomError.prototype);
+ }
+}
diff --git a/web/src/core/errors/UnautherizedError.ts b/web/src/core/errors/UnautherizedError.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e0316e8206f6b758fff0ee26c40484737f81303e
--- /dev/null
+++ b/web/src/core/errors/UnautherizedError.ts
@@ -0,0 +1,56 @@
+import { CustomError } from "./CustomError";
+
+/**
+ * UnauthorizedError is a custom error class that extends CustomError.
+ * It represents an error when a user is not authorized to perform an action.
+ */
+export default class UnauthorizedError extends CustomError {
+ // HTTP status code for unauthorized error
+ private static readonly _statusCode = 401;
+ private readonly _code: number;
+ private readonly _logging: boolean;
+ private readonly _context: { [key: string]: any };
+
+ /**
+ * Constructs a new UnauthorizedError instance.
+ * @param params - Optional parameters for the error.
+ * @param params.code - Custom error code (default is 401).
+ * @param params.message - Custom error message (default is "You are not authorized").
+ * @param params.logging - Flag to indicate if the error should be logged (default is false).
+ * @param params.context - Additional context for the error.
+ */
+ constructor(params?: { code?: number; message?: string; logging?: boolean; context?: { [key: string]: any } }) {
+ const { code, message, logging } = params || {};
+
+ super(message || "You are not authorized");
+ this._code = code || UnauthorizedError._statusCode;
+ this._logging = logging || false;
+ this._context = params?.context || {};
+
+ Object.setPrototypeOf(this, UnauthorizedError.prototype);
+ }
+
+ /**
+ * Returns an array of error details.
+ * @returns An array containing the error message and context.
+ */
+ get errors() {
+ return [{ message: this.message, context: this._context }];
+ }
+
+ /**
+ * Returns the HTTP status code for the error.
+ * @returns The status code.
+ */
+ get statusCode() {
+ return this._code;
+ }
+
+ /**
+ * Indicates if the error should be logged.
+ * @returns True if the error should be logged, otherwise false.
+ */
+ get logging() {
+ return this._logging;
+ }
+}
diff --git a/web/src/core/router/login_route.tsx b/web/src/core/router/login_route.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..d75a3988022a42aa5a7a09fa63311388bc8af46f
--- /dev/null
+++ b/web/src/core/router/login_route.tsx
@@ -0,0 +1,15 @@
+import { Navigate, Outlet } from "react-router-dom";
+import { useAuth } from "../context/auth_context";
+
+// Component to handle login route
+export const LoginRoute = () => {
+ const { user } = useAuth();
+
+ // If user is authenticated, redirect to home page
+ if (user) {
+ return ;
+ }
+
+ // Render the login page if user is not authenticated
+ return ;
+};
diff --git a/web/src/core/router/protected_route.tsx b/web/src/core/router/protected_route.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..8bb5c233c0e8a5ae5712008953d3568a361ff0c0
--- /dev/null
+++ b/web/src/core/router/protected_route.tsx
@@ -0,0 +1,26 @@
+import { Navigate } from "react-router-dom";
+import { useAuth } from "../context/auth_context";
+import { UserRole } from "../constants/roles";
+import { AdminHomePage } from "../../presentation/admin/panel/admin_home_page";
+import { SuperAdminHomePage } from "../../presentation/superadmin/panel/super_admin_home_page";
+
+// Component to handle protected routes based on user role
+export const ProtectedRoute = () => {
+ const { user, logout } = useAuth();
+
+ // If no user is authenticated, redirect to login page
+ if (!user) {
+ return ;
+ } else {
+ // Redirect based on user role
+ if (user.role === UserRole.ADMIN) {
+ return ;
+ } else if (user.role === UserRole.SUPERADMIN) {
+ return ;
+ } else {
+ // Logout and redirect to login if user role is not recognized
+ logout();
+ return ;
+ }
+ }
+};
diff --git a/web/src/core/router/router.tsx b/web/src/core/router/router.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..50a1eec748adcb3e10d5e9734dfb6cc7eb3884c4
--- /dev/null
+++ b/web/src/core/router/router.tsx
@@ -0,0 +1,30 @@
+import { createBrowserRouter } from "react-router-dom";
+import { ProtectedRoute } from "./protected_route";
+import { LoginPage } from "../../presentation/login/login_page";
+
+// Create the router configuration
+export const router = createBrowserRouter([
+ {
+ // Protected routes that require authentication
+ element: ,
+ children: [
+ {
+ // Default route for authenticated users
+ index: true,
+ path: "/",
+ element:
+ }
+ ]
+ },
+ {
+ // Public route for login page
+ element: ,
+ children: [
+ {
+ // Route for login page
+ path: "/login",
+ element:
+ }
+ ]
+ }
+]);
\ No newline at end of file
diff --git a/web/src/core/utils/Messages.ts b/web/src/core/utils/Messages.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5418bbc4931d411485ca7853e34360b4eb5cd863
--- /dev/null
+++ b/web/src/core/utils/Messages.ts
@@ -0,0 +1,36 @@
+import axios, { AxiosError } from "axios";
+import { toast } from "react-toastify";
+
+/**
+ * Displays an error message using react-toastify based on the type of Axios error.
+ *
+ * @param {AxiosError} error - The error object returned by Axios.
+ */
+export const showErrorAxios = (error: AxiosError) => {
+ let message = "";
+
+ // Determine the error message based on the error code
+ switch(error.code){
+ case(axios.AxiosError.ERR_BAD_REQUEST):
+ message = "Acceso no autorizado"; // Unauthorized access
+ break;
+ case(axios.AxiosError.ERR_NETWORK):
+ message = "Conexión con el servidor fallida"; // Server connection failed
+ break;
+ default:
+ message = error.message; // Default to the error message provided by Axios
+ break;
+ }
+
+ // Display the error message using react-toastify
+ toast.error(message, {
+ position: "bottom-right", // Position the toast at the bottom-right corner
+ autoClose: 1500, // Automatically close the toast after 1.5 seconds
+ hideProgressBar: false, // Show the progress bar
+ closeOnClick: true, // Close the toast when clicked
+ pauseOnHover: false, // Do not pause the toast on hover
+ draggable: true, // Allow the toast to be draggable
+ progress: undefined, // Use the default progress bar behavior
+ theme: "colored" // Use the colored theme for the toast
+ });
+}
\ No newline at end of file
diff --git a/web/src/data/datasource/admin_datasource.ts b/web/src/data/datasource/admin_datasource.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0bf0b76cf5595b31feebd7a3e2dcfb494c3c34b7
--- /dev/null
+++ b/web/src/data/datasource/admin_datasource.ts
@@ -0,0 +1,55 @@
+import { Admin, AdminFormValues } from "./api/entities/admin_form_values";
+import { ResetPasswordValues } from "./api/entities/reset_password_values";
+
+/**
+ * Interface representing a data source for handling admin-related operations.
+ */
+export interface AdminDatasourceInf {
+ /**
+ * Registers a new admin.
+ * @param form - The admin data to register.
+ * @returns A promise that resolves when the registration is complete.
+ */
+ registerAdmin(form: AdminFormValues): Promise;
+
+ /**
+ * Retrieves admin information based on a token.
+ * @param token - The authentication token.
+ * @returns A promise that resolves to an Admin object.
+ */
+ getAdminInfo(token: string): Promise;
+
+ /**
+ * Changes the password for an admin.
+ * @param token - The authentication token.
+ * @param prevPassword - The previous password.
+ * @param newPassword - The new password.
+ * @returns A promise that resolves when the password change is complete.
+ */
+ changePassword(
+ token: string,
+ prevPassword: string,
+ newPassword: string
+ ): Promise;
+
+ /**
+ * Retrieves a list of admins by town.
+ * @param idTown - The ID of the town.
+ * @returns A promise that resolves to an array of Admin objects.
+ */
+ getAdminsByTown(idTown: number): Promise;
+
+ /**
+ * Generates a reset code for the given email.
+ * @param email - The email address to send the reset code to.
+ * @returns A promise that resolves when the reset code is generated.
+ */
+ generateResetCode(email: string): Promise;
+
+ /**
+ * Resets the password using the provided reset form values.
+ * @param form - The reset password form values.
+ * @returns A promise that resolves when the password reset is complete.
+ */
+ resetPassword(form: ResetPasswordValues): Promise;
+}
diff --git a/web/src/data/datasources/prod/admin_datasource.ts b/web/src/data/datasource/api/admin_datasource.ts
similarity index 58%
rename from web/src/data/datasources/prod/admin_datasource.ts
rename to web/src/data/datasource/api/admin_datasource.ts
index a89fa78c9a293249a00c939c433a443daefcd0ee..2e4ac80d5044386ee0ff535acfce5b96fb6386d6 100644
--- a/web/src/data/datasources/prod/admin_datasource.ts
+++ b/web/src/data/datasource/api/admin_datasource.ts
@@ -1,12 +1,9 @@
import axios from "axios";
-import { AdminDatasourceInf } from "../../../infraestructure/datasources/admin_datasource";
-import {
- Admin,
- AdminFormValues,
-} from "../../../infraestructure/entities/admin_form_values";
-import { APIUrl } from "../../../constants/api_url";
-import { AdminModel } from "../../models/prod/AdminModel";
-import { UserRole } from "../../../constants/roles";
+import { AdminDatasourceInf } from "../admin_datasource";
+import { Admin, AdminFormValues } from "./entities/admin_form_values";
+import { APIUrl } from "../../../core/constants/api_url";
+import { AdminModel } from "../../../domain/model/AdminModel";
+import { UserRole } from "../../../core/constants/roles";
import {
API_ROUTE_ADMIN,
API_ROUTE_ADMIN_CHANGE_PASSWORD,
@@ -14,10 +11,15 @@ import {
API_ROUTE_ADMIN_RESET_PASSWORD,
API_ROUTE_ADMIN_SIGNUP,
API_ROUTE_ADMIN_WHOAMI,
-} from "../../../constants/api_routes";
-import { ResetPasswordValues } from "../../../infraestructure/entities/reset_password_values";
+} from "../../../core/constants/api_routes";
+import { ResetPasswordValues } from "./entities/reset_password_values";
export class AdminDatasourceProd implements AdminDatasourceInf {
+ /**
+ * Registers a new admin using the provided form data.
+ * @param form - The admin data to be registered.
+ * @returns A promise that resolves when the admin is successfully registered.
+ */
async registerAdmin(form: AdminFormValues): Promise {
await axios.post(APIUrl + API_ROUTE_ADMIN_SIGNUP, {
email: form.email,
@@ -28,6 +30,11 @@ export class AdminDatasourceProd implements AdminDatasourceInf {
});
}
+ /**
+ * Retrieves admin information using the provided token.
+ * @param token - The token of the admin.
+ * @returns A promise that resolves to an Admin object.
+ */
async getAdminInfo(token: string): Promise {
const { data } = await axios.get(
APIUrl + API_ROUTE_ADMIN_WHOAMI,
@@ -50,6 +57,13 @@ export class AdminDatasourceProd implements AdminDatasourceInf {
return admin;
}
+ /**
+ * Changes the password of the admin.
+ * @param token - The token of the admin.
+ * @param prevPassword - The previous password.
+ * @param newPassword - The new password.
+ * @returns A promise that resolves when the password is successfully changed.
+ */
async changePassword(
token: string,
prevPassword: string,
@@ -69,6 +83,11 @@ export class AdminDatasourceProd implements AdminDatasourceInf {
);
}
+ /**
+ * Retrieves a list of admins by town ID.
+ * @param idTown - The ID of the town.
+ * @returns A promise that resolves to an array of Admin objects.
+ */
async getAdminsByTown(idTown: number): Promise {
const { data } = await axios.get(
APIUrl + API_ROUTE_ADMIN + `/${idTown}`
@@ -85,12 +104,22 @@ export class AdminDatasourceProd implements AdminDatasourceInf {
return admins;
}
+ /**
+ * Generates a reset code for the given email.
+ * @param email - The email of the admin.
+ * @returns A promise that resolves when the reset code is successfully generated.
+ */
async generateResetCode(email: string): Promise {
await axios.post(APIUrl + API_ROUTE_ADMIN_GENERATE_RESET_CODE, {
email: email,
});
}
+ /**
+ * Resets the password using the provided form data.
+ * @param form - The reset password form values.
+ * @returns A promise that resolves when the password is successfully reset.
+ */
async resetPassword(form: ResetPasswordValues): Promise {
await axios.post(APIUrl + API_ROUTE_ADMIN_RESET_PASSWORD, {
email: form.email,
diff --git a/web/src/data/datasource/api/category_datasource.ts b/web/src/data/datasource/api/category_datasource.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e364270db39d0f8ad3be6c7200fcddbea3ea14b7
--- /dev/null
+++ b/web/src/data/datasource/api/category_datasource.ts
@@ -0,0 +1,62 @@
+import axios from "axios";
+import { CategoryDatasourceInf } from "../category_datasource";
+import { Category, CategoryFormValues } from "./entities/category";
+import { APIUrl } from "../../../core/constants/api_url";
+import { CategoryModel } from "../../../domain/model/CategoryModel";
+
+export class CategoryDatasourceProd implements CategoryDatasourceInf {
+ /**
+ * Registers a new category using the provided form data.
+ * @param form - The category data to be registered.
+ * @returns A promise that resolves when the category is successfully registered.
+ */
+ async registerCategory(form: CategoryFormValues): Promise {
+ await axios.post(APIUrl + "/category", {
+ nameES: form.nameES,
+ nameEN: form.nameEN,
+ });
+ }
+
+ /**
+ * Retrieves a list of categories.
+ * @returns A promise that resolves to an array of Category objects.
+ */
+ async getCategories(): Promise {
+ const { data: dataES } = await axios.get(
+ APIUrl + "/category/",
+ {
+ params: {
+ lang: "ES",
+ },
+ }
+ );
+ const { data: dataEN } = await axios.get(
+ APIUrl + "/category/",
+ {
+ params: {
+ lang: "EN",
+ },
+ }
+ );
+ const categories: Category[] = [];
+ for (let i = 0; i < dataES.length; i++) {
+ const category: Category = {
+ idCategory: dataES[i].idCategory,
+ nameES: dataES[i].name,
+ nameEN: dataEN[i].name,
+ };
+ categories.push(category);
+ }
+
+ return categories;
+ }
+
+ /**
+ * Deletes a category by its ID.
+ * @param category - The category to be deleted.
+ * @returns A promise that resolves when the category is successfully deleted.
+ */
+ async deleteCategory(category: Category): Promise {
+ await axios.delete(APIUrl + `/category/${category.idCategory}`);
+ }
+}
diff --git a/web/src/infraestructure/entities/admin_form_values.ts b/web/src/data/datasource/api/entities/admin_form_values.ts
similarity index 61%
rename from web/src/infraestructure/entities/admin_form_values.ts
rename to web/src/data/datasource/api/entities/admin_form_values.ts
index 69786190480db24a6bcf326fa1d2969895d251de..f6c71772e25f03dfa8b448fc0a9fe17c5159417c 100644
--- a/web/src/infraestructure/entities/admin_form_values.ts
+++ b/web/src/data/datasource/api/entities/admin_form_values.ts
@@ -1,5 +1,8 @@
-import { UserRole } from "../../constants/roles";
+import { UserRole } from "../../../../core/constants/roles";
+/**
+ * Interface representing the values of an admin form.
+ */
export interface AdminFormValues {
email: string;
name: string;
@@ -9,6 +12,9 @@ export interface AdminFormValues {
townAdmin: number;
}
+/**
+ * Interface representing an admin.
+ */
export interface Admin {
email: string;
name: string;
@@ -18,8 +24,11 @@ export interface Admin {
status?: string;
}
+/**
+ * Interface representing the values required to change an admin's password.
+ */
export interface AdminPasswordValues {
prevPassword: string;
newPassword: string;
newPasswordConfirm: string;
-}
\ No newline at end of file
+}
diff --git a/web/src/data/datasource/api/entities/category.ts b/web/src/data/datasource/api/entities/category.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3edd3dc1dde0e496894ce3a7284b9475a52fc409
--- /dev/null
+++ b/web/src/data/datasource/api/entities/category.ts
@@ -0,0 +1,16 @@
+/**
+ * Interface representing a category.
+ */
+export interface Category {
+ idCategory: number;
+ nameES: string;
+ nameEN: string;
+}
+
+/**
+ * Interface representing the values of a category form.
+ */
+export interface CategoryFormValues {
+ nameES: string;
+ nameEN: string;
+}
\ No newline at end of file
diff --git a/web/src/data/datasource/api/entities/image.ts b/web/src/data/datasource/api/entities/image.ts
new file mode 100644
index 0000000000000000000000000000000000000000..960b6ecfbee427e78d3e757d90cb8c146ea4a909
--- /dev/null
+++ b/web/src/data/datasource/api/entities/image.ts
@@ -0,0 +1,7 @@
+/**
+ * Interface representing an image.
+ */
+export interface Image {
+ file: File;
+ preview: string;
+}
\ No newline at end of file
diff --git a/web/src/data/datasource/api/entities/login_form_values.ts b/web/src/data/datasource/api/entities/login_form_values.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f7c8007d557227b2edb7637c022ea158c4399a89
--- /dev/null
+++ b/web/src/data/datasource/api/entities/login_form_values.ts
@@ -0,0 +1,7 @@
+/**
+ * Interface representing the values of a login form.
+ */
+export interface LoginFormValues {
+ email: string;
+ password: string;
+}
\ No newline at end of file
diff --git a/web/src/infraestructure/entities/place.ts b/web/src/data/datasource/api/entities/place.ts
similarity index 69%
rename from web/src/infraestructure/entities/place.ts
rename to web/src/data/datasource/api/entities/place.ts
index ca43cd0878319bf21a1843fda064e7ef1a12f835..2abb1e14b3901f890617e5bf752f31f465df611c 100644
--- a/web/src/infraestructure/entities/place.ts
+++ b/web/src/data/datasource/api/entities/place.ts
@@ -1,6 +1,9 @@
-export interface Place{
+/**
+ * Interface representing a place.
+ */
+export interface Place {
idTown: number;
- idPlace? : number;
+ idPlace?: number;
name: string;
categoriesId: number[];
descriptions?: string[];
@@ -15,6 +18,9 @@ export interface Place{
address: string;
}
+/**
+ * Enum representing available days.
+ */
export enum AvailableDays {
WEEKEND = 'weekend',
ALL_DAYS = 'all_days',
@@ -22,19 +28,28 @@ export enum AvailableDays {
CUSTOM = 'custom'
}
-export interface availableDaysOption{
+/**
+ * Interface representing an available days option.
+ */
+export interface availableDaysOption {
option: AvailableDays;
name: string;
}
-export const availableDaysList= [
+/**
+ * List of available days options.
+ */
+export const availableDaysList = [
{ option: AvailableDays.WEEKEND, name: 'Fines de semana' },
{ option: AvailableDays.ALL_DAYS, name: 'Todos los dias' },
{ option: AvailableDays.WEEKDAYS, name: 'Lunes a Viernes' },
{ option: AvailableDays.CUSTOM, name: 'Personalizado' }
];
-export const EmptyPlace : Place = {
+/**
+ * Constant representing an empty place.
+ */
+export const EmptyPlace: Place = {
idTown: -1,
idPlace: -1,
name: "",
@@ -46,4 +61,4 @@ export const EmptyPlace : Place = {
closeAt: 0,
available: AvailableDays.WEEKEND,
address: ''
-}
\ No newline at end of file
+};
\ No newline at end of file
diff --git a/web/src/infraestructure/entities/poi.ts b/web/src/data/datasource/api/entities/poi.ts
similarity index 76%
rename from web/src/infraestructure/entities/poi.ts
rename to web/src/data/datasource/api/entities/poi.ts
index cdbfcb7ee5ed20cacbdc3ece14cb0f0a6e59800b..ad5936a32d5e55509fce5e07612baf32697b8253 100644
--- a/web/src/infraestructure/entities/poi.ts
+++ b/web/src/data/datasource/api/entities/poi.ts
@@ -1,3 +1,6 @@
+/**
+ * Interface representing a point of interest.
+ */
export interface PointOfInterest {
idPoint?: number;
idPlace: number;
@@ -9,6 +12,9 @@ export interface PointOfInterest {
directionsES: string;
}
+/**
+ * Constant representing an empty point of interest.
+ */
export const EmptyPointOfInterest: PointOfInterest = {
idPoint: 0,
idPlace: 0,
@@ -18,4 +24,4 @@ export const EmptyPointOfInterest: PointOfInterest = {
contentES: '',
directionsEN: '',
directionsES: ''
-}
\ No newline at end of file
+};
\ No newline at end of file
diff --git a/web/src/infraestructure/entities/reset_password_values.ts b/web/src/data/datasource/api/entities/reset_password_values.ts
similarity index 62%
rename from web/src/infraestructure/entities/reset_password_values.ts
rename to web/src/data/datasource/api/entities/reset_password_values.ts
index a353e140ff733c68c05146bd8b4f4f2c8e7aa02d..e7da0355d861547ed5026d56e33788c5e93061c8 100644
--- a/web/src/infraestructure/entities/reset_password_values.ts
+++ b/web/src/data/datasource/api/entities/reset_password_values.ts
@@ -1,3 +1,6 @@
+/**
+ * Interface representing the values required to reset a password.
+ */
export interface ResetPasswordValues {
email: string;
code: number;
diff --git a/web/src/data/datasource/api/entities/state.ts b/web/src/data/datasource/api/entities/state.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2451f5fe68d26595a6577124afdf604b7333b1cb
--- /dev/null
+++ b/web/src/data/datasource/api/entities/state.ts
@@ -0,0 +1,8 @@
+/**
+ * Interface representing a state.
+ */
+export interface State {
+ stateId: number;
+ name: string;
+ imageURL: string;
+}
\ No newline at end of file
diff --git a/web/src/data/datasource/api/entities/town.ts b/web/src/data/datasource/api/entities/town.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c3b08f744ea392c1a6bd92386ef5405180a48a92
--- /dev/null
+++ b/web/src/data/datasource/api/entities/town.ts
@@ -0,0 +1,12 @@
+/**
+ * Interface representing a town.
+ */
+export interface Town {
+ idTown: number;
+ name: string;
+ descriptionES?: string;
+ descriptionEN?: string;
+ idState: number;
+ state: string;
+ imageURL?: string | File;
+}
\ No newline at end of file
diff --git a/web/src/data/datasource/api/entities/user.ts b/web/src/data/datasource/api/entities/user.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a824fa72ebae1fb9c587567efb47219e33b91012
--- /dev/null
+++ b/web/src/data/datasource/api/entities/user.ts
@@ -0,0 +1,223 @@
+import { UserRole } from "../../../../core/constants/roles";
+
+/**
+ * Interface representing a user entity.
+ */
+export interface UserEntity {
+ name: string;
+ lastName?: string;
+ email: string;
+ role: UserRole;
+}
+
+/**
+ * Interface representing a logged-in user.
+ */
+export interface LoggedInUser {
+ user: UserEntity;
+ token: string;
+}
+
+/**
+ * Interface representing user information.
+ */
+export interface UserInfo {
+ name?: string;
+ lastName?: string;
+ email: string;
+}
+
+/**
+ * Class for converting JSON strings to UserEntity objects and vice versa.
+ */
+export class Convert {
+ public static toUser(json: string): UserEntity {
+ return cast(JSON.parse(json), r("User"));
+ }
+
+ public static userToJson(value: UserEntity): string {
+ return JSON.stringify(uncast(value, r("User")), null, 2);
+ }
+}
+
+function invalidValue(typ: any, val: any, key: any, parent: any = ""): never {
+ const prettyTyp = prettyTypeName(typ);
+ const parentText = parent ? ` on ${parent}` : "";
+ const keyText = key ? ` for key "${key}"` : "";
+ throw Error(
+ `Invalid value${keyText}${parentText}. Expected ${prettyTyp} but got ${JSON.stringify(
+ val
+ )}`
+ );
+}
+
+function prettyTypeName(typ: any): string {
+ if (Array.isArray(typ)) {
+ if (typ.length === 2 && typ[0] === undefined) {
+ return `an optional ${prettyTypeName(typ[1])}`;
+ } else {
+ return `one of [${typ
+ .map((a) => {
+ return prettyTypeName(a);
+ })
+ .join(", ")}]`;
+ }
+ } else if (typeof typ === "object" && typ.literal !== undefined) {
+ return typ.literal;
+ } else {
+ return typeof typ;
+ }
+}
+
+function jsonToJSProps(typ: any): any {
+ if (typ.jsonToJS === undefined) {
+ const map: any = {};
+ typ.props.forEach((p: any) => (map[p.json] = { key: p.js, typ: p.typ }));
+ typ.jsonToJS = map;
+ }
+ return typ.jsonToJS;
+}
+
+function jsToJSONProps(typ: any): any {
+ if (typ.jsToJSON === undefined) {
+ const map: any = {};
+ typ.props.forEach((p: any) => (map[p.js] = { key: p.json, typ: p.typ }));
+ typ.jsToJSON = map;
+ }
+ return typ.jsToJSON;
+}
+
+function transform(
+ val: any,
+ typ: any,
+ getProps: any,
+ key: any = "",
+ parent: any = ""
+): any {
+ function transformPrimitive(typ: string, val: any): any {
+ if (typeof typ === typeof val) return val;
+ return invalidValue(typ, val, key, parent);
+ }
+
+ function transformUnion(typs: any[], val: any): any {
+ // val must validate against one typ in typs
+ const l = typs.length;
+ for (let i = 0; i < l; i++) {
+ const typ = typs[i];
+ try {
+ return transform(val, typ, getProps);
+ } catch (_) {}
+ }
+ return invalidValue(typs, val, key, parent);
+ }
+
+ function transformEnum(cases: string[], val: any): any {
+ if (cases.indexOf(val) !== -1) return val;
+ return invalidValue(
+ cases.map((a) => {
+ return l(a);
+ }),
+ val,
+ key,
+ parent
+ );
+ }
+
+ function transformArray(typ: any, val: any): any {
+ // val must be an array with no invalid elements
+ if (!Array.isArray(val)) return invalidValue(l("array"), val, key, parent);
+ return val.map((el) => transform(el, typ, getProps));
+ }
+
+ function transformDate(val: any): any {
+ if (val === null) {
+ return null;
+ }
+ const d = new Date(val);
+ if (isNaN(d.valueOf())) {
+ return invalidValue(l("Date"), val, key, parent);
+ }
+ return d;
+ }
+
+ function transformObject(
+ props: { [k: string]: any },
+ additional: any,
+ val: any
+ ): any {
+ if (val === null || typeof val !== "object" || Array.isArray(val)) {
+ return invalidValue(l(ref || "object"), val, key, parent);
+ }
+ const result: any = {};
+ Object.getOwnPropertyNames(props).forEach((key) => {
+ const prop = props[key];
+ const v = Object.prototype.hasOwnProperty.call(val, key)
+ ? val[key]
+ : undefined;
+ result[prop.key] = transform(v, prop.typ, getProps, key, ref);
+ });
+ Object.getOwnPropertyNames(val).forEach((key) => {
+ if (!Object.prototype.hasOwnProperty.call(props, key)) {
+ result[key] = transform(val[key], additional, getProps, key, ref);
+ }
+ });
+ return result;
+ }
+
+ if (typ === "any") return val;
+ if (typ === null) {
+ if (val === null) return val;
+ return invalidValue(typ, val, key, parent);
+ }
+ if (typ === false) return invalidValue(typ, val, key, parent);
+ let ref: any = undefined;
+ while (typeof typ === "object" && typ.ref !== undefined) {
+ ref = typ.ref;
+ typ = typeMap[typ.ref];
+ }
+ if (Array.isArray(typ)) return transformEnum(typ, val);
+ if (typeof typ === "object") {
+ return typ.hasOwnProperty("unionMembers")
+ ? transformUnion(typ.unionMembers, val)
+ : typ.hasOwnProperty("arrayItems")
+ ? transformArray(typ.arrayItems, val)
+ : typ.hasOwnProperty("props")
+ ? transformObject(getProps(typ), typ.additional, val)
+ : invalidValue(typ, val, key, parent);
+ }
+ // Numbers can be parsed by Date but shouldn't be.
+ if (typ === Date && typeof val !== "number") return transformDate(val);
+ return transformPrimitive(typ, val);
+}
+
+function cast(val: any, typ: any): T {
+ return transform(val, typ, jsonToJSProps);
+}
+
+function uncast(val: T, typ: any): any {
+ return transform(val, typ, jsToJSONProps);
+}
+
+function l(typ: any) {
+ return { literal: typ };
+}
+
+
+function o(props: any[], additional: any) {
+ return { props, additional };
+}
+
+function r(name: string) {
+ return { ref: name };
+}
+
+const typeMap: any = {
+ User: o(
+ [
+ { json: "email", js: "email", typ: "" },
+ { json: "name", js: "name", typ: "" },
+ { json: "role", js: "role", typ: "" },
+ ],
+ false
+ ),
+};
diff --git a/web/src/data/datasource/api/login_datasource.ts b/web/src/data/datasource/api/login_datasource.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9040460bb4953aec927eeb0b72871dbb746fdf62
--- /dev/null
+++ b/web/src/data/datasource/api/login_datasource.ts
@@ -0,0 +1,41 @@
+import { LoginDatasource } from "../login_datasource";
+import { LoginFormValues } from "./entities/login_form_values";
+import axios from "axios";
+import { LoggedInUser } from "./entities/user";
+import { LoggedInUserModel } from "../../../domain/model/LoggedInUserModel";
+import { UserRole } from "../../../core/constants/roles";
+import { APIUrl } from "../../../core/constants/api_url";
+
+/**
+ * Implementation of the LoginDatasource interface.
+ */
+export class LoginDatasourceImpl implements LoginDatasource {
+ /**
+ * Retrieves a token for the given login form values.
+ * @param form - The login form values.
+ * @returns A promise that resolves to a logged-in user.
+ */
+ async getToken(form: LoginFormValues): Promise {
+ const { email, password } = form;
+ const { data } = await axios.post(
+ APIUrl + "/admin/signin",
+ {
+ email: email,
+ password: password,
+ }
+ );
+ const user: LoggedInUser = {
+ user: {
+ email: data.email,
+ name: data.name,
+ role:
+ data.role === UserRole.SUPERADMIN
+ ? UserRole.SUPERADMIN
+ : UserRole.ADMIN,
+ },
+ token: data.token,
+ };
+
+ return user;
+ }
+}
diff --git a/web/src/data/datasource/api/place_datasource.ts b/web/src/data/datasource/api/place_datasource.ts
new file mode 100644
index 0000000000000000000000000000000000000000..88145c21edde7d41481f40eae57c7e6c2f0f6494
--- /dev/null
+++ b/web/src/data/datasource/api/place_datasource.ts
@@ -0,0 +1,128 @@
+import axios from "axios";
+import { APIUrl } from "../../../core/constants/api_url";
+import { PlaceDatasourceInf } from "../place_datasource";
+import { AvailableDays, Place } from "./entities/place";
+import {
+ PlaceModel,
+ placeModelToEntity,
+} from "../../../domain/model/PlaceModel";
+
+export class PlaceDatasourceProd implements PlaceDatasourceInf {
+ /**
+ * Registers a new place using the provided form data.
+ * @param form - The place data to be registered.
+ * @returns A promise that resolves when the place is successfully registered.
+ */
+ async registerPlace(form: Place): Promise {
+ const formToSend = new FormData();
+ formToSend.append("available", form.available);
+ formToSend.append("idTown", String(form.idTown));
+ formToSend.append("name", form.name);
+ formToSend.append("categoriesId", form.categoriesId.join(","));
+ formToSend.append("descriptionES", form.descriptions?.[0] ?? "");
+ formToSend.append("descriptionEN", form.descriptions?.[1] ?? "");
+ formToSend.append("image", form.imagesList?.[0] ?? "");
+ formToSend.append("latitude", String(form.latitude));
+ formToSend.append("longitude", String(form.longitude));
+ formToSend.append("openAt", String(form.openAt));
+ formToSend.append("closeAt", String(form.closeAt));
+ formToSend.append("address", form.address);
+
+ if (form.available === AvailableDays.CUSTOM) {
+ formToSend.append("startDate", String(form.startDate));
+ formToSend.append("endDate", String(form.endDate));
+ }
+
+ const headers = {
+ "Content-Type": "multipart/form-data",
+ };
+
+ await axios.post(APIUrl + "/place", formToSend, { headers });
+ }
+
+ /**
+ * Retrieves a list of places by town ID.
+ * @param idTown - The ID of the town.
+ * @returns A promise that resolves to an array of Place objects.
+ */
+ async getPlacesByTown(idTown: number): Promise {
+ const { data } = await axios.get(
+ APIUrl + `/place/town/${idTown}/place`,
+ {
+ params: {
+ lang: "ES",
+ },
+ }
+ );
+
+ const places = data.map((dataES) => {
+ return placeModelToEntity(dataES);
+ });
+
+ return places;
+ }
+
+ /**
+ * Retrieves a place by its ID.
+ * @param idPlace - The ID of the place.
+ * @returns A promise that resolves to a Place object.
+ */
+ async getPlaceById(idPlace: number): Promise {
+ const { data: dataES } = await axios.get(
+ APIUrl + `/place/${idPlace}`,
+ {
+ params: {
+ lang: "ES",
+ },
+ }
+ );
+
+ const { data: dataEN } = await axios.get(
+ APIUrl + `/place/${idPlace}`,
+ {
+ params: {
+ lang: "EN",
+ },
+ }
+ );
+
+ const place: Place = placeModelToEntity(dataES);
+ place.descriptions?.push(dataEN.description);
+
+ return place;
+ }
+
+ /**
+ * Updates an existing place using the provided form data.
+ * @param place - The place data to be updated.
+ * @returns A promise that resolves when the place is successfully updated.
+ */
+ async updatePlace(place: Place): Promise {
+ const formToSend = new FormData();
+ formToSend.append("available", place.available);
+ formToSend.append("idTown", String(place.idTown));
+ formToSend.append("name", place.name);
+ formToSend.append("categoriesId", place.categoriesId.join(","));
+ formToSend.append("descriptionES", place.descriptions?.[0] ?? "");
+ formToSend.append("descriptionEN", place.descriptions?.[1] ?? "");
+ formToSend.append("image", place.imagesList?.[0] ?? "");
+ formToSend.append("latitude", String(place.latitude));
+ formToSend.append("longitude", String(place.longitude));
+ formToSend.append("openAt", String(place.openAt));
+ formToSend.append("closeAt", String(place.closeAt));
+ formToSend.append("address", place.address);
+
+ if (place.available === AvailableDays.CUSTOM) {
+ formToSend.append("startDate", String(place.startDate));
+ formToSend.append("endDate", String(place.endDate));
+ }
+
+ const headers = {
+ "Content-Type": "multipart/form-data",
+ };
+
+ await axios.patch(APIUrl + `/place/${place.idPlace}`, formToSend, {
+ headers,
+ });
+ }
+}
diff --git a/web/src/data/datasource/api/poi_datasource.ts b/web/src/data/datasource/api/poi_datasource.ts
new file mode 100644
index 0000000000000000000000000000000000000000..206f5c92f139422541c2f5cd8e71337157188f3e
--- /dev/null
+++ b/web/src/data/datasource/api/poi_datasource.ts
@@ -0,0 +1,106 @@
+import axios from "axios";
+import { Buffer } from "buffer";
+import { PoiDatasourceInf } from "../poi_datasource";
+import { PointOfInterest } from "./entities/poi";
+import { APIUrl } from "../../../core/constants/api_url";
+import {
+ API_ROUTE_PLACE,
+ API_ROUTE_POINT,
+} from "../../../core/constants/api_routes";
+import { POIModel, POIModelToEntity } from "../../../domain/model/POIModel";
+
+export class POIDatasourceProd implements PoiDatasourceInf {
+ /**
+ * Registers a new point of interest using the provided form data.
+ * @param form - The point of interest data to be registered.
+ * @returns A promise that resolves when the point is successfully registered.
+ */
+ async registerPoint(form: PointOfInterest): Promise {
+ const formToSend = new FormData();
+ formToSend.append("idPlace", String(form.idPlace));
+ formToSend.append("name", form.name);
+ formToSend.append("image", form.image);
+ formToSend.append("contentEN", form.contentEN);
+ formToSend.append("contentES", form.contentES);
+ formToSend.append("directionsEN", form.directionsEN);
+ formToSend.append("directionsES", form.directionsES);
+
+ const headers = {
+ "Content-Type": "multipart/form-data",
+ };
+
+ await axios.post(APIUrl + API_ROUTE_POINT, formToSend, { headers });
+ }
+
+ /**
+ * Retrieves a list of points of interest by place ID.
+ * @param idPlace - The ID of the place.
+ * @returns A promise that resolves to an array of PointOfInterest objects.
+ */
+ async getPOIsByPlace(idPlace: number): Promise {
+ const { data: dataES } = await axios.get(
+ APIUrl + API_ROUTE_PLACE + `/${idPlace}` + API_ROUTE_POINT,
+ {
+ params: {
+ lang: "ES",
+ },
+ }
+ );
+
+ const poiList = dataES.map((dataESModel) => {
+ return POIModelToEntity(dataESModel);
+ });
+
+ return poiList;
+ }
+
+ /**
+ * Retrieves a point of interest by its ID.
+ * @param idPoint - The ID of the point of interest.
+ * @returns A promise that resolves to a PointOfInterest object.
+ */
+ async getPOIById(idPoint: number): Promise {
+ const { data: dataES } = await axios.get(
+ APIUrl + API_ROUTE_POINT + `/${idPoint}`,
+ {
+ params: {
+ lang: "ES",
+ },
+ }
+ );
+
+ const { data: dataEN } = await axios.get(
+ APIUrl + API_ROUTE_POINT + `/${idPoint}`,
+ {
+ params: {
+ lang: "EN",
+ },
+ }
+ );
+
+ const poi: PointOfInterest = POIModelToEntity(dataES);
+ poi.contentEN = dataEN.content;
+ poi.directionsEN = dataEN.directions;
+
+ return poi;
+ }
+
+ /**
+ * Generates a PDF for the specified points of interest.
+ * @param idPlace - The ID of the place.
+ * @param pointsId - An array of point IDs to include in the PDF.
+ * @returns A promise that resolves to a base64-encoded string of the PDF.
+ */
+ async getPDFByPoints(idPlace: number, pointsId: number[]): Promise {
+ const { data } = await axios.get(
+ APIUrl + API_ROUTE_PLACE + `/${idPlace}` + API_ROUTE_POINT + "/generate",
+ {
+ params: {
+ pointsId: pointsId.join(","),
+ },
+ responseType: "arraybuffer",
+ }
+ );
+ return Buffer.from(data, "binary").toString("base64");
+ }
+}
diff --git a/web/src/data/datasource/api/town_datasource.ts b/web/src/data/datasource/api/town_datasource.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b99259e9bb5269a66c0c2be34c556742edddc670
--- /dev/null
+++ b/web/src/data/datasource/api/town_datasource.ts
@@ -0,0 +1,115 @@
+import axios from "axios";
+import { APIUrl } from "../../../core/constants/api_url";
+import { TownDatasourceInf } from "../town_datasource";
+import { State } from "./entities/state";
+import { Town } from "./entities/town";
+import { StateModel } from "../../../domain/model/StateModel";
+import { TownModelTrad, TownModel } from "../../../domain/model/TownModel";
+import { API_ROUTE_STATE } from "../../../core/constants/api_routes";
+
+export class TownDatasourceProd implements TownDatasourceInf {
+ /**
+ * Retrieves a list of states from the API.
+ * @returns A promise that resolves to an array of State objects.
+ */
+ async getStates(): Promise {
+ const { data } = await axios.get(APIUrl + API_ROUTE_STATE);
+ const states = data.map((value) => {
+ const state: State = {
+ stateId: value.stateId,
+ name: value.name,
+ imageURL: value.imageURL,
+ };
+ return state;
+ });
+
+ return states;
+ }
+
+ /**
+ * Registers a new town using the provided form data.
+ * @param form - The town data to be registered.
+ * @returns A promise that resolves when the town is successfully registered.
+ */
+ async registerTown(form: Town): Promise {
+ const formToSend = new FormData();
+ formToSend.append("name", form.name);
+ formToSend.append("descriptionES", form.descriptionES || "");
+ formToSend.append("descriptionEN", form.descriptionEN || "");
+ formToSend.append("state", String(form.idState));
+ formToSend.append("image", form.imageURL || "");
+
+ const headers = {
+ "Content-Type": "multipart/form-data",
+ };
+
+ await axios.post(APIUrl + "/town", formToSend, { headers });
+ }
+
+ /**
+ * Retrieves a list of towns by state ID and state name.
+ * @param idState - The ID of the state.
+ * @param stateName - The name of the state.
+ * @returns A promise that resolves to an array of Town objects.
+ */
+ async getTownsByState(idState: number, stateName: string): Promise {
+ const { data } = await axios.get(
+ APIUrl + `/state/${idState}/town`,
+ {
+ params: {
+ lang: "ES",
+ },
+ }
+ );
+ const towns = data.map((value) => {
+ const town: Town = {
+ idTown: value.townId,
+ name: value.name,
+ idState: value.stateId,
+ state: stateName,
+ };
+ return town;
+ });
+
+ return towns;
+ }
+
+ /**
+ * Retrieves a town by its ID.
+ * @param idTown - The ID of the town.
+ * @returns A promise that resolves to a Town object.
+ */
+ async getTown(idTown: number): Promise {
+ const { data } = await axios.get(APIUrl + `/town/${idTown}`);
+ const town: Town = {
+ idTown: data.townId,
+ name: data.name,
+ idState: data.stateId,
+ descriptionES: data.descriptionES,
+ descriptionEN: data.descriptionEN,
+ state: "",
+ imageURL: data.imageName,
+ };
+ return town;
+ }
+
+ /**
+ * Updates an existing town using the provided form data.
+ * @param form - The town data to be updated.
+ * @returns A promise that resolves when the town is successfully updated.
+ */
+ async updateTown(form: Town): Promise {
+ const formToSend = new FormData();
+ formToSend.append("name", form.name);
+ formToSend.append("descriptionES", form.descriptionES || "");
+ formToSend.append("descriptionEN", form.descriptionEN || "");
+ formToSend.append("image", form.imageURL || "");
+ formToSend.append("state", String(form.idState));
+
+ const headers = {
+ "Content-Type": "multipart/form-data",
+ };
+
+ await axios.patch(APIUrl + `/town/${form.idTown}`, formToSend, { headers });
+ }
+}
diff --git a/web/src/data/datasource/category_datasource.ts b/web/src/data/datasource/category_datasource.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1f20724732ef10f2f974e1684e18952f1cb8668a
--- /dev/null
+++ b/web/src/data/datasource/category_datasource.ts
@@ -0,0 +1,26 @@
+import { Category, CategoryFormValues } from "./api/entities/category";
+
+/**
+ * Interface representing a data source for handling category-related operations.
+ */
+export interface CategoryDatasourceInf {
+ /**
+ * Registers a new category.
+ * @param form - The category data to register.
+ * @returns A promise that resolves when the registration is complete.
+ */
+ registerCategory(form: CategoryFormValues): Promise;
+
+ /**
+ * Retrieves a list of categories.
+ * @returns A promise that resolves to an array of Category objects.
+ */
+ getCategories(): Promise;
+
+ /**
+ * Deletes an existing category.
+ * @param category - The category to delete.
+ * @returns A promise that resolves when the deletion is complete.
+ */
+ deleteCategory(category: Category): Promise;
+}
diff --git a/web/src/data/datasource/login_datasource.ts b/web/src/data/datasource/login_datasource.ts
new file mode 100644
index 0000000000000000000000000000000000000000..69a04888ad17e5fefa86a47d1ddc684874871c27
--- /dev/null
+++ b/web/src/data/datasource/login_datasource.ts
@@ -0,0 +1,14 @@
+import { LoginFormValues } from "./api/entities/login_form_values";
+import { LoggedInUser } from "./api/entities/user";
+
+/**
+ * Interface representing a data source for handling login operations.
+ */
+export interface LoginDatasource {
+ /**
+ * Retrieves a token for the given login form values.
+ * @param form - The login form values.
+ * @returns A promise that resolves to a LoggedInUser object containing the token.
+ */
+ getToken(form: LoginFormValues): Promise;
+}
diff --git a/web/src/data/datasource/place_datasource.ts b/web/src/data/datasource/place_datasource.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3b787935f20b9adada613a0f14fc7e43268a54c4
--- /dev/null
+++ b/web/src/data/datasource/place_datasource.ts
@@ -0,0 +1,34 @@
+import { Place } from "./api/entities/place";
+
+/**
+ * Interface representing a data source for handling place-related operations.
+ */
+export interface PlaceDatasourceInf {
+ /**
+ * Registers a new place.
+ * @param form - The place data to register.
+ * @returns A promise that resolves when the registration is complete.
+ */
+ registerPlace(form: Place): Promise;
+
+ /**
+ * Retrieves a list of places by town.
+ * @param idTown - The ID of the town.
+ * @returns A promise that resolves to an array of Place objects.
+ */
+ getPlacesByTown(idTown: number): Promise;
+
+ /**
+ * Retrieves a place by its ID.
+ * @param idPlace - The ID of the place.
+ * @returns A promise that resolves to a Place object.
+ */
+ getPlaceById(idPlace: number): Promise;
+
+ /**
+ * Updates an existing place.
+ * @param place - The updated place data.
+ * @returns A promise that resolves when the update is complete.
+ */
+ updatePlace(place: Place): Promise;
+}
diff --git a/web/src/data/datasource/poi_datasource.ts b/web/src/data/datasource/poi_datasource.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b03b50dd651b2308f721918326cc330c38d192d2
--- /dev/null
+++ b/web/src/data/datasource/poi_datasource.ts
@@ -0,0 +1,35 @@
+import { PointOfInterest } from "./api/entities/poi";
+
+/**
+ * Interface representing a data source for handling points of interest (POI) operations.
+ */
+export interface PoiDatasourceInf {
+ /**
+ * Registers a new point of interest.
+ * @param form - The POI data to register.
+ * @returns A promise that resolves when the registration is complete.
+ */
+ registerPoint(form: PointOfInterest): Promise;
+
+ /**
+ * Retrieves a list of POIs by place.
+ * @param idPlace - The ID of the place.
+ * @returns A promise that resolves to an array of PointOfInterest objects.
+ */
+ getPOIsByPlace(idPlace: number): Promise;
+
+ /**
+ * Retrieves a POI by its ID.
+ * @param idPoint - The ID of the POI.
+ * @returns A promise that resolves to a PointOfInterest object.
+ */
+ getPOIById(idPoint: number): Promise;
+
+ /**
+ * Retrieves a PDF document containing information about specified POIs.
+ * @param idPlace - The ID of the place.
+ * @param pointsId - An array of POI IDs.
+ * @returns A promise that resolves to a string representing the PDF document.
+ */
+ getPDFByPoints(idPlace: number, pointsId: number[]): Promise;
+}
diff --git a/web/src/data/datasource/town_datasource.ts b/web/src/data/datasource/town_datasource.ts
new file mode 100644
index 0000000000000000000000000000000000000000..87fa51c53251c389e3960855f0014ee274d426f4
--- /dev/null
+++ b/web/src/data/datasource/town_datasource.ts
@@ -0,0 +1,41 @@
+import { State } from "./api/entities/state";
+import { Town } from "./api/entities/town";
+
+/**
+ * Interface representing a data source for handling town-related operations.
+ */
+export interface TownDatasourceInf {
+ /**
+ * Retrieves a list of states.
+ * @returns A promise that resolves to an array of State objects.
+ */
+ getStates(): Promise;
+
+ /**
+ * Registers a new town.
+ * @param form - The town data to register.
+ */
+ registerTown(form: Town): void;
+
+ /**
+ * Retrieves a list of towns by state.
+ * @param idState - The ID of the state.
+ * @param stateName - The name of the state.
+ * @returns A promise that resolves to an array of Town objects.
+ */
+ getTownsByState(idState: number, stateName: string): Promise;
+
+ /**
+ * Retrieves a town by its ID.
+ * @param idTown - The ID of the town.
+ * @returns A promise that resolves to a Town object.
+ */
+ getTown(idTown: number): Promise;
+
+ /**
+ * Updates an existing town.
+ * @param form - The updated town data.
+ * @returns A promise that resolves when the update is complete.
+ */
+ updateTown(form: Town): Promise;
+}
diff --git a/web/src/data/datasources/prod/category_datasource.ts b/web/src/data/datasources/prod/category_datasource.ts
deleted file mode 100644
index 41271b557657b07009c4f0b6a82e72db9cb056c0..0000000000000000000000000000000000000000
--- a/web/src/data/datasources/prod/category_datasource.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import axios from "axios";
-import { CategoryDatasourceInf } from "../../../infraestructure/datasources/category_datasource";
-import { Category, CategoryFormValues } from "../../../infraestructure/entities/category";
-import { CategoryModel } from "../../models/prod/CategoryModel";
-import { APIUrl } from "../../../constants/api_url";
-
-export class CategoryDatasourceProd implements CategoryDatasourceInf{
- async registerCategory(form: CategoryFormValues): Promise {
- await axios.post(
- APIUrl + "/category",
- {
- nameES: form.nameES,
- nameEN: form.nameEN
- }
- );
- }
-
- async getCategories(): Promise {
- const {data: dataES} = await axios.get(APIUrl+'/category/', {
- params: {
- lang: 'ES'
- }
- });
- const {data: dataEN} = await axios.get(APIUrl+'/category/', {
- params: {
- lang: 'EN'
- }
- });
- const categories : Category[] = [];
- for(let i=0; i {
- await axios.delete(APIUrl + `/category/${category.idCategory}`)
- }
-}
\ No newline at end of file
diff --git a/web/src/data/datasources/prod/login_datasource.ts b/web/src/data/datasources/prod/login_datasource.ts
deleted file mode 100644
index c2d7f535a1381fe04aef54278c4b8ce15fb32ed3..0000000000000000000000000000000000000000
--- a/web/src/data/datasources/prod/login_datasource.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { LoginDatasourceInf } from "../../../infraestructure/datasources/login_datasource";
-import { LoginFormValues } from "../../../infraestructure/entities/login_form_values";
-import axios from "axios";
-import { LoggedInUser } from "../../../infraestructure/entities/user";
-import { LoggedInUserModel } from "../../models/prod/LoggedInUserModel";
-import { UserRole } from "../../../constants/roles";
-import { APIUrl } from "../../../constants/api_url";
-
-export class LoginDatasourceProd implements LoginDatasourceInf{
- async getToken(form: LoginFormValues): Promise {
- const {email, password} = form;
- const {data} = await axios.post(
- APIUrl + "/admin/signin",
- {
- email: email,
- password: password,
- }
- );
- const user: LoggedInUser = {
- user: {
- email: data.email,
- name: data.name,
- role: data.role === UserRole.SUPERADMIN ? UserRole.SUPERADMIN : UserRole.ADMIN,
- },
- token: data.token,
- };
-
- return user;
- }
-}
\ No newline at end of file
diff --git a/web/src/data/datasources/prod/place_datasource.ts b/web/src/data/datasources/prod/place_datasource.ts
deleted file mode 100644
index 299676c58e61b8a8222115d7ee25e0d362b6803c..0000000000000000000000000000000000000000
--- a/web/src/data/datasources/prod/place_datasource.ts
+++ /dev/null
@@ -1,94 +0,0 @@
-import axios from "axios";
-import { APIUrl } from "../../../constants/api_url";
-import { PlaceDatasourceInf } from "../../../infraestructure/datasources/place_datasource";
-import { AvailableDays, Place } from "../../../infraestructure/entities/place";
-import { PlaceModel, placeModelToEntity } from "../../models/prod/PlaceModel";
-
-export class PlaceDatasourceProd implements PlaceDatasourceInf{
- async registerPlace(form: Place): Promise {
- const formToSend = new FormData();
- formToSend.append('available', form.available);
- formToSend.append('idTown', String(form.idTown));
- formToSend.append('name', form.name);
- formToSend.append('categoriesId', form.categoriesId.join(','));
- formToSend.append('descriptionES', form.descriptions?.[0] ?? '');
- formToSend.append('descriptionEN', form.descriptions?.[1] ?? '');
- formToSend.append('image', form.imagesList?.[0] ?? '');
- formToSend.append('latitude', String(form.latitude));
- formToSend.append('longitude', String(form.longitude));
- formToSend.append('openAt', String(form.openAt));
- formToSend.append('closeAt', String(form.closeAt));
- formToSend.append('address', form.address);
-
- if(form.available === AvailableDays.CUSTOM){
- formToSend.append('startDate', String(form.startDate));
- formToSend.append('endDate', String(form.endDate));
- }
-
- const headers = {
- 'Content-Type': 'multipart/form-data'
- };
-
- await axios.post(APIUrl + '/place', formToSend,{headers});
- }
-
- async getPlacesByTown(idTown: number): Promise {
- const {data} = await axios.get(APIUrl+`/place/town/${idTown}/place`, {
- params: {
- lang: 'ES'
- }
- });
-
- const places = data.map((dataES) => {
- return placeModelToEntity(dataES);
- })
-
- return places;
- }
-
- async getPlaceById(idPlace: number): Promise {
- const {data: dataES} = await axios.get(APIUrl+`/place/${idPlace}`, {
- params: {
- lang: 'ES'
- }
- });
-
- const {data: dataEN} = await axios.get(APIUrl+`/place/${idPlace}`, {
- params: {
- lang: 'EN'
- }
- });
-
- const place: Place = placeModelToEntity(dataES);
- place.descriptions?.push(dataEN.description);
-
- return place;
- }
-
- async updatePlace(place: Place): Promise {
- const formToSend = new FormData();
- formToSend.append('available', place.available);
- formToSend.append('idTown', String(place.idTown));
- formToSend.append('name', place.name);
- formToSend.append('categoriesId', place.categoriesId.join(','));
- formToSend.append('descriptionES', place.descriptions?.[0] ?? '');
- formToSend.append('descriptionEN', place.descriptions?.[1] ?? '');
- formToSend.append('image', place.imagesList?.[0] ?? '');
- formToSend.append('latitude', String(place.latitude));
- formToSend.append('longitude', String(place.longitude));
- formToSend.append('openAt', String(place.openAt));
- formToSend.append('closeAt', String(place.closeAt));
- formToSend.append('address', place.address);
-
- if(place.available === AvailableDays.CUSTOM){
- formToSend.append('startDate', String(place.startDate));
- formToSend.append('endDate', String(place.endDate));
- }
-
- const headers = {
- 'Content-Type': 'multipart/form-data'
- };
-
- await axios.patch(APIUrl+`/place/${place.idPlace}`, formToSend,{headers});
- }
-}
\ No newline at end of file
diff --git a/web/src/data/datasources/prod/poi_datasource.ts b/web/src/data/datasources/prod/poi_datasource.ts
deleted file mode 100644
index f1d6b92b9c38cf6dd3c3b95a323c46431f2e01e7..0000000000000000000000000000000000000000
--- a/web/src/data/datasources/prod/poi_datasource.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-import axios from "axios";
-import { Buffer } from "buffer";
-import { PoiDatasourceInf } from "../../../infraestructure/datasources/poi_datasource";
-import { PointOfInterest } from "../../../infraestructure/entities/poi";
-import { APIUrl } from "../../../constants/api_url";
-import { API_ROUTE_PLACE, API_ROUTE_POINT } from "../../../constants/api_routes";
-import { POIModel, POIModelToEntity } from "../../models/prod/POIModel";
-
-export class POIDatasourceProd implements PoiDatasourceInf{
- async registerPoint(form: PointOfInterest): Promise {
- const formToSend = new FormData();
- formToSend.append('idPlace', String(form.idPlace));
- formToSend.append('name', form.name);
- formToSend.append('image', form.image);
- formToSend.append('contentEN', form.contentEN);
- formToSend.append('contentES', form.contentES);
- formToSend.append('directionsEN', form.directionsEN);
- formToSend.append('directionsES', form.directionsES);
-
- const headers = {
- 'Content-Type': 'multipart/form-data'
- };
-
- await axios.post(APIUrl + API_ROUTE_POINT, formToSend,{headers});
- }
-
- async getPOIsByPlace(idPlace: number): Promise {
- const {data: dataES} = await axios.get(APIUrl + API_ROUTE_PLACE + `/${idPlace}` + API_ROUTE_POINT, {
- params: {
- lang: 'ES'
- }
- });
-
- const poiList = dataES.map((dataESModel) => {
- return POIModelToEntity(dataESModel);
- })
-
- return poiList;
- }
-
- async getPOIById(idPoint: number): Promise {
- const {data: dataES} = await axios.get(APIUrl + API_ROUTE_POINT + `/${idPoint}`, {
- params: {
- lang: 'ES'
- }
- });
-
- const {data: dataEN} = await axios.get(APIUrl + API_ROUTE_POINT + `/${idPoint}`, {
- params: {
- lang: 'EN'
- }
- });
-
- const poi: PointOfInterest = POIModelToEntity(dataES);
- poi.contentEN = dataEN.content;
- poi.directionsEN = dataEN.directions;
-
- return poi;
- }
-
- async getPDFByPoints(idPlace: number, pointsId: number[]): Promise {
- const {data} = await axios.get(APIUrl + API_ROUTE_PLACE + `/${idPlace}` + API_ROUTE_POINT + '/generate', {
- params: {
- pointsId: pointsId.join(',')
- },
- responseType: 'arraybuffer'
- });
- return Buffer.from(data, 'binary').toString('base64');
- }
-}
\ No newline at end of file
diff --git a/web/src/data/datasources/prod/town_datasource.ts b/web/src/data/datasources/prod/town_datasource.ts
deleted file mode 100644
index fcbba15e6ace83328665a0c2dd752c1c528b8e91..0000000000000000000000000000000000000000
--- a/web/src/data/datasources/prod/town_datasource.ts
+++ /dev/null
@@ -1,86 +0,0 @@
-import axios from "axios";
-import { APIUrl } from "../../../constants/api_url";
-import { TownDatasourceInf } from "../../../infraestructure/datasources/town_datasource";
-import { StateModel } from "../../models/prod/StateModel";
-import { State } from "../../../infraestructure/entities/state";
-import { Town } from "../../../infraestructure/entities/town";
-import { TownModel, TownModelTrad } from "../../models/prod/TownModel";
-
-export class TownDatasourceProd implements TownDatasourceInf{
- async getStates(): Promise {
- const {data} = await axios.get(APIUrl+"/state");
- const states = data.map((value) => {
- const state: State = {
- stateId : value.stateId,
- name: value.name,
- imageURL: value.imageURL
- }
- return state;
- })
-
- return states;
- }
-
- async registerTown(form: Town): Promise {
- const formToSend = new FormData();
- formToSend.append('name',form.name);
- formToSend.append('descriptionES',form.descriptionES || '');
- formToSend.append('descriptionEN',form.descriptionEN || '');
- formToSend.append('state', String(form.idState));
- formToSend.append('image',form.imageURL || '');
-
- const headers = {
- 'Content-Type': 'multipart/form-data'
- };
-
- await axios.post(APIUrl + '/town', formToSend,{headers});
- }
-
- async getTownsByState(idState: number, stateName:string): Promise {
- const {data} = await axios.get(APIUrl+`/state/${idState}/town`, {
- params: {
- lang: 'ES'
- }
- });
- const towns = data.map((value) => {
- const town: Town = {
- idTown : value.townId,
- name: value.name,
- idState: value.stateId,
- state: stateName
- }
- return town;
- })
-
- return towns;
- }
-
- async getTown(idTown: number): Promise {
- const {data} = await axios.get(APIUrl+`/town/${idTown}`);
- const town: Town = {
- idTown : data.townId,
- name: data.name,
- idState: data.stateId,
- descriptionES: data.descriptionES,
- descriptionEN: data.descriptionEN,
- state: '',
- imageURL: data.imageName
- }
- return town;
- }
-
- async updateTown(form: Town): Promise {
- const formToSend = new FormData();
- formToSend.append('name',form.name);
- formToSend.append('descriptionES',form.descriptionES || '');
- formToSend.append('descriptionEN',form.descriptionEN || '');
- formToSend.append('image',form.imageURL || '');
- formToSend.append('state', String(form.idState));
-
- const headers = {
- 'Content-Type': 'multipart/form-data'
- };
-
- await axios.patch(APIUrl+`/town/${form.idTown}`, formToSend,{headers});
- }
-}
\ No newline at end of file
diff --git a/web/src/data/models/prod/AdminModel.ts b/web/src/data/models/prod/AdminModel.ts
deleted file mode 100644
index 2ac2cfc40cdf758298e4d82b4da8a5b3822936b5..0000000000000000000000000000000000000000
--- a/web/src/data/models/prod/AdminModel.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-export interface AdminModel {
- email: string;
- name: string;
- lastName: string;
- role?: string;
- idTown?: number;
- status: string;
-}
\ No newline at end of file
diff --git a/web/src/data/models/prod/CategoryModel.ts b/web/src/data/models/prod/CategoryModel.ts
deleted file mode 100644
index 2846a79c2a999d69c11c5ae2109be3bd8fce90da..0000000000000000000000000000000000000000
--- a/web/src/data/models/prod/CategoryModel.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-export interface CategoryModel{
- idCategory: number;
- name: string;
-}
-
-export interface CategoryModelLan {
- idCategory: number;
- language: string;
- name: string;
-}
\ No newline at end of file
diff --git a/web/src/data/models/prod/LoggedInUserModel.ts b/web/src/data/models/prod/LoggedInUserModel.ts
deleted file mode 100644
index 8f73d45b187796cdfa6ae2c154a6d1b5a252db0c..0000000000000000000000000000000000000000
--- a/web/src/data/models/prod/LoggedInUserModel.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-export interface LoggedInUserModel {
- email: string;
- name: string;
- role: string;
- token: string;
-}
\ No newline at end of file
diff --git a/web/src/data/models/prod/POIModel.ts b/web/src/data/models/prod/POIModel.ts
deleted file mode 100644
index 603b8bd4091c3850456452fb827aea67f3deb0e0..0000000000000000000000000000000000000000
--- a/web/src/data/models/prod/POIModel.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { PointOfInterest } from "../../../infraestructure/entities/poi";
-
-export interface POIModel {
- idPoint: number;
- idPlace: number;
- name: string;
- imageName: string;
- content: string;
- directions: string;
-}
-
-export const POIModelToEntity = (model: POIModel) =>{
- const poi: PointOfInterest = {
- idPoint : model.idPoint,
- idPlace : model.idPlace,
- name: model.name,
- image: model.imageName,
- contentES: model.content,
- contentEN: model.content,
- directionsES: model.directions,
- directionsEN: model.directions
- }
- return poi;
-}
\ No newline at end of file
diff --git a/web/src/data/models/prod/PlaceModel.ts b/web/src/data/models/prod/PlaceModel.ts
deleted file mode 100644
index 5e4ea3e456073befd560a1646e5624033007bc20..0000000000000000000000000000000000000000
--- a/web/src/data/models/prod/PlaceModel.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import { AvailableDays, Place } from "../../../infraestructure/entities/place";
-import { CategoryModelLan } from "./CategoryModel";
-
-export interface PlaceModel {
- idTown: number;
- idPlace: number;
- available: string;
- description: string;
- latitude: number;
- longitude: number;
- imageName: string;
- name: string;
- categories: CategoryModelLan[]
- openAt: number;
- closeAt: number;
- startDate?: Date;
- endDate?: Date;
- address: string;
-}
-
-export const placeModelToEntity = (model: PlaceModel) =>{
- let availableDays = AvailableDays.WEEKEND;
- switch(model.available){
- case AvailableDays.ALL_DAYS:
- availableDays = AvailableDays.ALL_DAYS;
- break;
- case AvailableDays.CUSTOM:
- availableDays = AvailableDays.CUSTOM;
- break;
- case AvailableDays.WEEKDAYS:
- availableDays = AvailableDays.WEEKDAYS;
- break;
- default:
- availableDays = AvailableDays.WEEKEND;
- break;
- }
-
- const place: Place = {
- idTown : model.idTown,
- idPlace : model.idPlace,
- available : availableDays,
- latitude: model.latitude,
- longitude: model.longitude,
- descriptions: [model.description],
- imagesList: [model.imageName],
- name: model.name,
- categoriesId: model.categories.map((category)=> category.idCategory),
- openAt: model.openAt,
- closeAt: model.closeAt,
- startDate: model.startDate,
- endDate: model.endDate,
- address: model.address
- }
- return place;
-}
\ No newline at end of file
diff --git a/web/src/data/models/prod/StateModel.ts b/web/src/data/models/prod/StateModel.ts
deleted file mode 100644
index 41f2f827dc54b2db15f33fded38c282ff90df57a..0000000000000000000000000000000000000000
--- a/web/src/data/models/prod/StateModel.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export interface StateModel {
- stateId: number;
- name: string;
- imageURL: string;
-}
\ No newline at end of file
diff --git a/web/src/data/models/prod/TownModel.ts b/web/src/data/models/prod/TownModel.ts
deleted file mode 100644
index 4b79e916283ad4958eaae9e0ca2395dc43da8e6b..0000000000000000000000000000000000000000
--- a/web/src/data/models/prod/TownModel.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-export interface TownModelTrad {
- townId: number;
- name : string;
- imageName : string;
- description : string;
- stateId : number;
-}
-
-export interface TownModel {
- townId: number;
- name : string;
- descriptionEN : string;
- descriptionES : string;
- imageName : string;
- stateId : number;
-}
\ No newline at end of file
diff --git a/web/src/data/repositories/prod/admin_repository.ts b/web/src/data/repositories/prod/admin_repository.ts
deleted file mode 100644
index b86b4da13577619fce2dc148620fac518c39a73c..0000000000000000000000000000000000000000
--- a/web/src/data/repositories/prod/admin_repository.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { AdminDatasourceInf } from "../../../infraestructure/datasources/admin_datasource";
-import {
- Admin,
- AdminFormValues,
-} from "../../../infraestructure/entities/admin_form_values";
-import { ResetPasswordValues } from "../../../infraestructure/entities/reset_password_values";
-import { AdminRepositoryInf } from "../../../infraestructure/repositories/admin_repository";
-
-export class AdminRepositoryProd implements AdminRepositoryInf {
- constructor(private datasource: AdminDatasourceInf) {}
-
- async registerAdmin(form: AdminFormValues): Promise {
- return this.datasource.registerAdmin(form);
- }
-
- async getAdminInfo(token: string): Promise {
- return this.datasource.getAdminInfo(token);
- }
-
- async changePassword(
- token: string,
- prevPassword: string,
- newPassword: string
- ): Promise {
- return this.datasource.changePassword(token, prevPassword, newPassword);
- }
-
- async getAdminsByTown(idTown: number): Promise {
- return this.datasource.getAdminsByTown(idTown);
- }
-
- async generateResetCode(email: string): Promise {
- return this.datasource.generateResetCode(email);
- }
-
- async resetPassword(form: ResetPasswordValues): Promise {
- return this.datasource.resetPassword(form);
- }
-}
diff --git a/web/src/data/repositories/prod/category_repository.ts b/web/src/data/repositories/prod/category_repository.ts
deleted file mode 100644
index ab0055ebf05862a28fb74fc01613fc46cfd69053..0000000000000000000000000000000000000000
--- a/web/src/data/repositories/prod/category_repository.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { CategoryDatasourceInf } from "../../../infraestructure/datasources/category_datasource";
-import { Category, CategoryFormValues } from "../../../infraestructure/entities/category";
-import { CategoryRepositoryInf } from "../../../infraestructure/repositories/category_repository";
-
-export class CategoryRepositoryProd implements CategoryRepositoryInf{
- constructor(
- private datasource: CategoryDatasourceInf
- ){}
-
- async registerCategory(form: CategoryFormValues): Promise {
- return this.datasource.registerCategory(form);
- }
-
- async getCategories(): Promise {
- return this.datasource.getCategories();
- }
-
- async deleteCategory(category: Category): Promise {
- return this.datasource.deleteCategory(category);
- }
-}
\ No newline at end of file
diff --git a/web/src/data/repositories/prod/login_repository.ts b/web/src/data/repositories/prod/login_repository.ts
deleted file mode 100644
index 69c0694fa4ea1ac50aa3890d7c8aedc305f364c1..0000000000000000000000000000000000000000
--- a/web/src/data/repositories/prod/login_repository.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { LoginDatasourceInf } from "../../../infraestructure/datasources/login_datasource";
-import { LoginFormValues } from "../../../infraestructure/entities/login_form_values";
-import { LoggedInUser } from "../../../infraestructure/entities/user";
-import { LoginRepositoryInf } from "../../../infraestructure/repositories/login_repository";
-
-export class LoginRepositoryProd implements LoginRepositoryInf{
- constructor(
- private datasource: LoginDatasourceInf
- ){}
- async getToken(form: LoginFormValues): Promise {
- return this.datasource.getToken(form);
- }
-}
\ No newline at end of file
diff --git a/web/src/data/repositories/prod/place_repository.ts b/web/src/data/repositories/prod/place_repository.ts
deleted file mode 100644
index 843fdb8df607bdc26a72a957a3f57012b5a46d59..0000000000000000000000000000000000000000
--- a/web/src/data/repositories/prod/place_repository.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { PlaceDatasourceInf } from "../../../infraestructure/datasources/place_datasource";
-import { Place } from "../../../infraestructure/entities/place";
-import { PlaceRepositoryInf } from "../../../infraestructure/repositories/place_repository";
-
-export class PlaceRepositoryProd implements PlaceRepositoryInf{
- constructor(
- private datasouce: PlaceDatasourceInf
- ){}
- async registerPlace(form: Place): Promise {
- return this.datasouce.registerPlace(form);
- }
-
- async getPlacesByTown(idTown: number): Promise {
- return this.datasouce.getPlacesByTown(idTown);
- }
-
- async getPlaceById(idPlace: number): Promise {
- return this.datasouce.getPlaceById(idPlace);
- }
-
- async updatePlace(place: Place): Promise {
- return this.datasouce.updatePlace(place);
- }
-}
\ No newline at end of file
diff --git a/web/src/data/repositories/prod/poi_repository.ts b/web/src/data/repositories/prod/poi_repository.ts
deleted file mode 100644
index 86a180bdf70812f1c5fc95cdcb7b019ca3e31bf2..0000000000000000000000000000000000000000
--- a/web/src/data/repositories/prod/poi_repository.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { PoiDatasourceInf } from "../../../infraestructure/datasources/poi_datasource";
-import { PointOfInterest } from "../../../infraestructure/entities/poi";
-import { PoiRepositoryInf } from "../../../infraestructure/repositories/poi_repository";
-
-export class POIRepositoryProd implements PoiRepositoryInf{
- constructor(
- private datasouce: PoiDatasourceInf
- ){}
-
- async registerPoint(form: PointOfInterest): Promise {
- return this.datasouce.registerPoint(form);
- }
-
- async getPOIById(idPoint: number): Promise {
- return this.datasouce.getPOIById(idPoint);
- }
-
- async getPOIsByPlace(idPlace: number): Promise {
- return this.datasouce.getPOIsByPlace(idPlace);
- }
-
- async getPDFByPoints(idPlace: number, pointsId: number[]): Promise {
- return this.datasouce.getPDFByPoints(idPlace, pointsId);
- }
-}
\ No newline at end of file
diff --git a/web/src/data/repositories/prod/town_repository.ts b/web/src/data/repositories/prod/town_repository.ts
deleted file mode 100644
index 72996d2909614e4bcc66b58131093371b510eeef..0000000000000000000000000000000000000000
--- a/web/src/data/repositories/prod/town_repository.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { TownDatasourceInf } from "../../../infraestructure/datasources/town_datasource";
-import { State } from "../../../infraestructure/entities/state";
-import { Town } from "../../../infraestructure/entities/town";
-import { TownRepositoryInf } from "../../../infraestructure/repositories/town_repository";
-
-export class TownRepositoryProd implements TownRepositoryInf{
- constructor(
- private datasource: TownDatasourceInf
- ){}
- async getStates(): Promise {
- return this.datasource.getStates();
- }
- async registerTown(form: Town): Promise {
- return this.datasource.registerTown(form);
- }
- async getTownsByState(idState: number, stateName:string) : Promise {
- return this.datasource.getTownsByState(idState, stateName);
- }
- async getTown(idTown: number): Promise {
- return this.datasource.getTown(idTown);
- }
- async updateTown(form: Town): Promise {
- return this.datasource.updateTown(form);
- }
-}
\ No newline at end of file
diff --git a/web/src/data/repository/admin_repository.ts b/web/src/data/repository/admin_repository.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e2ceedcb46a536a6b7322af1f9756008997a99c6
--- /dev/null
+++ b/web/src/data/repository/admin_repository.ts
@@ -0,0 +1,74 @@
+import { AdminDatasourceInf } from "../datasource/admin_datasource";
+import {
+ Admin,
+ AdminFormValues,
+} from "../datasource/api/entities/admin_form_values";
+import { ResetPasswordValues } from "../datasource/api/entities/reset_password_values";
+import { AdminRepositoryInf } from "../../domain/repository/admin_repository";
+
+/**
+ * Implementation of the AdminRepository interface for production.
+ */
+export class AdminRepositoryProd implements AdminRepositoryInf {
+ constructor(private datasource: AdminDatasourceInf) {}
+
+ /**
+ * Registers a new admin.
+ * @param form - The admin data to register.
+ * @returns A promise that resolves when the admin is registered.
+ */
+ async registerAdmin(form: AdminFormValues): Promise {
+ return this.datasource.registerAdmin(form);
+ }
+
+ /**
+ * Retrieves admin information by token.
+ * @param token - The token of the admin.
+ * @returns A promise that resolves to an Admin object.
+ */
+ async getAdminInfo(token: string): Promise {
+ return this.datasource.getAdminInfo(token);
+ }
+
+ /**
+ * Changes the password of an admin.
+ * @param token - The token of the admin.
+ * @param prevPassword - The previous password.
+ * @param newPassword - The new password.
+ * @returns A promise that resolves when the password is changed.
+ */
+ async changePassword(
+ token: string,
+ prevPassword: string,
+ newPassword: string
+ ): Promise {
+ return this.datasource.changePassword(token, prevPassword, newPassword);
+ }
+
+ /**
+ * Retrieves admins by town.
+ * @param idTown - The ID of the town.
+ * @returns A promise that resolves to an array of Admin objects.
+ */
+ async getAdminsByTown(idTown: number): Promise {
+ return this.datasource.getAdminsByTown(idTown);
+ }
+
+ /**
+ * Generates a reset code for the given email.
+ * @param email - The email to generate the reset code for.
+ * @returns A promise that resolves when the reset code is generated.
+ */
+ async generateResetCode(email: string): Promise {
+ return this.datasource.generateResetCode(email);
+ }
+
+ /**
+ * Resets the password using the given form values.
+ * @param form - The reset password form values.
+ * @returns A promise that resolves when the password is reset.
+ */
+ async resetPassword(form: ResetPasswordValues): Promise {
+ return this.datasource.resetPassword(form);
+ }
+}
diff --git a/web/src/data/repository/category_repository.ts b/web/src/data/repository/category_repository.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d35aa5fa9e83107114d8aaa440ed30bc41e277d5
--- /dev/null
+++ b/web/src/data/repository/category_repository.ts
@@ -0,0 +1,39 @@
+import { CategoryDatasourceInf } from "../datasource/category_datasource";
+import {
+ Category,
+ CategoryFormValues,
+} from "../datasource/api/entities/category";
+import { CategoryRepositoryInf } from "../../domain/repository/category_repository";
+
+/**
+ * Implementation of the CategoryRepository interface for production.
+ */
+export class CategoryRepositoryProd implements CategoryRepositoryInf {
+ constructor(private datasource: CategoryDatasourceInf) {}
+
+ /**
+ * Registers a new category.
+ * @param form - The category data to register.
+ * @returns A promise that resolves when the category is registered.
+ */
+ async registerCategory(form: CategoryFormValues): Promise {
+ return this.datasource.registerCategory(form);
+ }
+
+ /**
+ * Retrieves all categories.
+ * @returns A promise that resolves to an array of Category objects.
+ */
+ async getCategories(): Promise {
+ return this.datasource.getCategories();
+ }
+
+ /**
+ * Deletes an existing category.
+ * @param category - The category to delete.
+ * @returns A promise that resolves when the category is deleted.
+ */
+ async deleteCategory(category: Category): Promise {
+ return this.datasource.deleteCategory(category);
+ }
+}
diff --git a/web/src/data/repository/login_repository.ts b/web/src/data/repository/login_repository.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4dac9d5b0e081f950c29fa3f14f253f1f26c21c4
--- /dev/null
+++ b/web/src/data/repository/login_repository.ts
@@ -0,0 +1,20 @@
+import { LoginDatasource } from "../datasource/login_datasource";
+import { LoginFormValues } from "../datasource/api/entities/login_form_values";
+import { LoggedInUser } from "../datasource/api/entities/user";
+import { LoginRepository } from "../../domain/repository/login_repository";
+
+/**
+ * Implementation of the LoginRepository interface.
+ */
+export class LoginRepositoryImpl implements LoginRepository {
+ constructor(private datasource: LoginDatasource) {}
+
+ /**
+ * Retrieves a token for the given login form values.
+ * @param form - The login form values.
+ * @returns A promise that resolves to a logged-in user.
+ */
+ async getToken(form: LoginFormValues): Promise {
+ return this.datasource.getToken(form);
+ }
+}
diff --git a/web/src/data/repository/place_repository.ts b/web/src/data/repository/place_repository.ts
new file mode 100644
index 0000000000000000000000000000000000000000..aa120f13dc1f8de2d44a3c43e7e36e3931534b8f
--- /dev/null
+++ b/web/src/data/repository/place_repository.ts
@@ -0,0 +1,46 @@
+import { PlaceDatasourceInf } from "../datasource/place_datasource";
+import { Place } from "../datasource/api/entities/place";
+import { PlaceRepositoryInf } from "../../domain/repository/place_repository";
+
+/**
+ * Implementation of the PlaceRepository interface for production.
+ */
+export class PlaceRepositoryProd implements PlaceRepositoryInf {
+ constructor(private datasouce: PlaceDatasourceInf) {}
+
+ /**
+ * Registers a new place.
+ * @param form - The place data to register.
+ * @returns A promise that resolves when the place is registered.
+ */
+ async registerPlace(form: Place): Promise {
+ return this.datasouce.registerPlace(form);
+ }
+
+ /**
+ * Retrieves places by town.
+ * @param idTown - The ID of the town.
+ * @returns A promise that resolves to an array of Place objects.
+ */
+ async getPlacesByTown(idTown: number): Promise {
+ return this.datasouce.getPlacesByTown(idTown);
+ }
+
+ /**
+ * Retrieves a place by its ID.
+ * @param idPlace - The ID of the place.
+ * @returns A promise that resolves to a Place object.
+ */
+ async getPlaceById(idPlace: number): Promise {
+ return this.datasouce.getPlaceById(idPlace);
+ }
+
+ /**
+ * Updates an existing place.
+ * @param place - The place data to update.
+ * @returns A promise that resolves when the place is updated.
+ */
+ async updatePlace(place: Place): Promise {
+ return this.datasouce.updatePlace(place);
+ }
+}
diff --git a/web/src/data/repository/poi_repository.ts b/web/src/data/repository/poi_repository.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9a59ed06091faaa6d39374c8ff55cd9ee0a6cafa
--- /dev/null
+++ b/web/src/data/repository/poi_repository.ts
@@ -0,0 +1,47 @@
+import { PoiDatasourceInf } from "../datasource/poi_datasource";
+import { PointOfInterest } from "../datasource/api/entities/poi";
+import { PoiRepositoryInf } from "../../domain/repository/poi_repository";
+
+/**
+ * Implementation of the PoiRepository interface for production.
+ */
+export class POIRepositoryProd implements PoiRepositoryInf {
+ constructor(private datasouce: PoiDatasourceInf) {}
+
+ /**
+ * Registers a new point of interest.
+ * @param form - The point of interest data to register.
+ * @returns A promise that resolves when the point of interest is registered.
+ */
+ async registerPoint(form: PointOfInterest): Promise {
+ return this.datasouce.registerPoint(form);
+ }
+
+ /**
+ * Retrieves a point of interest by its ID.
+ * @param idPoint - The ID of the point of interest.
+ * @returns A promise that resolves to a PointOfInterest object.
+ */
+ async getPOIById(idPoint: number): Promise {
+ return this.datasouce.getPOIById(idPoint);
+ }
+
+ /**
+ * Retrieves points of interest by place.
+ * @param idPlace - The ID of the place.
+ * @returns A promise that resolves to an array of PointOfInterest objects.
+ */
+ async getPOIsByPlace(idPlace: number): Promise {
+ return this.datasouce.getPOIsByPlace(idPlace);
+ }
+
+ /**
+ * Retrieves a PDF document for the given points of interest.
+ * @param idPlace - The ID of the place.
+ * @param pointsId - An array of point IDs.
+ * @returns A promise that resolves to a string representing the PDF document.
+ */
+ async getPDFByPoints(idPlace: number, pointsId: number[]): Promise {
+ return this.datasouce.getPDFByPoints(idPlace, pointsId);
+ }
+}
diff --git a/web/src/data/repository/town_repository.ts b/web/src/data/repository/town_repository.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1eb7ddb85df0f42bc47bbc6d2510c1f823597fff
--- /dev/null
+++ b/web/src/data/repository/town_repository.ts
@@ -0,0 +1,56 @@
+import { TownDatasourceInf } from "../datasource/town_datasource";
+import { State } from "../datasource/api/entities/state";
+import { Town } from "../datasource/api/entities/town";
+import { TownRepositoryInf } from "../../domain/repository/town_repository";
+
+/**
+ * Implementation of the TownRepository interface for production.
+ */
+export class TownRepositoryProd implements TownRepositoryInf {
+ constructor(private datasource: TownDatasourceInf) {}
+
+ /**
+ * Retrieves all states.
+ * @returns A promise that resolves to an array of State objects.
+ */
+ async getStates(): Promise {
+ return this.datasource.getStates();
+ }
+
+ /**
+ * Registers a new town.
+ * @param form - The town data to register.
+ * @returns A promise that resolves when the town is registered.
+ */
+ async registerTown(form: Town): Promise {
+ return this.datasource.registerTown(form);
+ }
+
+ /**
+ * Retrieves towns by state.
+ * @param idState - The ID of the state.
+ * @param stateName - The name of the state.
+ * @returns A promise that resolves to an array of Town objects.
+ */
+ async getTownsByState(idState: number, stateName: string): Promise {
+ return this.datasource.getTownsByState(idState, stateName);
+ }
+
+ /**
+ * Retrieves a town by its ID.
+ * @param idTown - The ID of the town.
+ * @returns A promise that resolves to a Town object.
+ */
+ async getTown(idTown: number): Promise {
+ return this.datasource.getTown(idTown);
+ }
+
+ /**
+ * Updates an existing town.
+ * @param form - The town data to update.
+ * @returns A promise that resolves when the town is updated.
+ */
+ async updateTown(form: Town): Promise {
+ return this.datasource.updateTown(form);
+ }
+}
diff --git a/web/src/domain/model/AdminModel.ts b/web/src/domain/model/AdminModel.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6cea6275db149796445fd602aae8e09a847220db
--- /dev/null
+++ b/web/src/domain/model/AdminModel.ts
@@ -0,0 +1,17 @@
+/**
+ * Interface representing an admin model.
+ */
+export interface AdminModel {
+ /** Email of the admin */
+ email: string;
+ /** First name of the admin */
+ name: string;
+ /** Last name of the admin */
+ lastName: string;
+ /** Optional role of the admin */
+ role?: string;
+ /** Optional unique identifier for the town */
+ idTown?: number;
+ /** Status of the admin */
+ status: string;
+}
\ No newline at end of file
diff --git a/web/src/domain/model/CategoryModel.ts b/web/src/domain/model/CategoryModel.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3e780c91e37eb2b03464ec476b7d8c13ffedfa0e
--- /dev/null
+++ b/web/src/domain/model/CategoryModel.ts
@@ -0,0 +1,21 @@
+/**
+ * Interface representing a category model.
+ */
+export interface CategoryModel {
+ /** Unique identifier for the category */
+ idCategory: number;
+ /** Name of the category */
+ name: string;
+}
+
+/**
+ * Interface representing a category model with language information.
+ */
+export interface CategoryModelLan {
+ /** Unique identifier for the category */
+ idCategory: number;
+ /** Language of the category name */
+ language: string;
+ /** Name of the category */
+ name: string;
+}
\ No newline at end of file
diff --git a/web/src/domain/model/LoggedInUserModel.ts b/web/src/domain/model/LoggedInUserModel.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0db9d5a23aad005c8bc24aed84b3c8090764bed1
--- /dev/null
+++ b/web/src/domain/model/LoggedInUserModel.ts
@@ -0,0 +1,24 @@
+/**
+ * Interface representing a logged-in user model.
+ */
+export interface LoggedInUserModel {
+ /**
+ * The email address of the logged-in user.
+ */
+ email: string;
+
+ /**
+ * The name of the logged-in user.
+ */
+ name: string;
+
+ /**
+ * The role of the logged-in user (e.g., admin, user).
+ */
+ role: string;
+
+ /**
+ * The authentication token for the logged-in user.
+ */
+ token: string;
+}
\ No newline at end of file
diff --git a/web/src/domain/model/POIModel.ts b/web/src/domain/model/POIModel.ts
new file mode 100644
index 0000000000000000000000000000000000000000..72d1408084dcc60dd22d13e9cdc3e752bf365aea
--- /dev/null
+++ b/web/src/domain/model/POIModel.ts
@@ -0,0 +1,38 @@
+import { PointOfInterest } from "../../data/datasource/api/entities/poi";
+
+/**
+ * Interface representing a Point of Interest (POI) model.
+ */
+export interface POIModel {
+ /** Unique identifier for the point of interest */
+ idPoint: number;
+ /** Unique identifier for the place */
+ idPlace: number;
+ /** Name of the point of interest */
+ name: string;
+ /** Image name associated with the point of interest */
+ imageName: string;
+ /** Content description of the point of interest */
+ content: string;
+ /** Directions to the point of interest */
+ directions: string;
+}
+
+/**
+ * Converts a POIModel to a PointOfInterest entity.
+ * @param model - The POIModel to convert.
+ * @returns The converted PointOfInterest entity.
+ */
+export const POIModelToEntity = (model: POIModel): PointOfInterest => {
+ const poi: PointOfInterest = {
+ idPoint: model.idPoint,
+ idPlace: model.idPlace,
+ name: model.name,
+ image: model.imageName,
+ contentES: model.content,
+ contentEN: model.content,
+ directionsES: model.directions,
+ directionsEN: model.directions,
+ };
+ return poi;
+};
diff --git a/web/src/domain/model/PlaceModel.ts b/web/src/domain/model/PlaceModel.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e9d83406ce53a34136a86a569b8e24f7bf362c4f
--- /dev/null
+++ b/web/src/domain/model/PlaceModel.ts
@@ -0,0 +1,77 @@
+import { AvailableDays, Place } from "../../data/datasource/api/entities/place";
+import { CategoryModelLan } from "./CategoryModel";
+
+/**
+ * Interface representing a place model.
+ */
+export interface PlaceModel {
+ /** Unique identifier for the town */
+ idTown: number;
+ /** Unique identifier for the place */
+ idPlace: number;
+ /** Availability of the place */
+ available: string;
+ /** Description of the place */
+ description: string;
+ /** Latitude coordinate of the place */
+ latitude: number;
+ /** Longitude coordinate of the place */
+ longitude: number;
+ /** Image name associated with the place */
+ imageName: string;
+ /** Name of the place */
+ name: string;
+ /** Categories associated with the place */
+ categories: CategoryModelLan[];
+ /** Opening time of the place */
+ openAt: number;
+ /** Closing time of the place */
+ closeAt: number;
+ /** Optional start date for the place */
+ startDate?: Date;
+ /** Optional end date for the place */
+ endDate?: Date;
+ /** Address of the place */
+ address: string;
+}
+
+/**
+ * Converts a PlaceModel to a Place entity.
+ * @param model - The PlaceModel to convert.
+ * @returns The converted Place entity.
+ */
+export const placeModelToEntity = (model: PlaceModel): Place => {
+ let availableDays = AvailableDays.WEEKEND;
+ switch (model.available) {
+ case AvailableDays.ALL_DAYS:
+ availableDays = AvailableDays.ALL_DAYS;
+ break;
+ case AvailableDays.CUSTOM:
+ availableDays = AvailableDays.CUSTOM;
+ break;
+ case AvailableDays.WEEKDAYS:
+ availableDays = AvailableDays.WEEKDAYS;
+ break;
+ default:
+ availableDays = AvailableDays.WEEKEND;
+ break;
+ }
+
+ const place: Place = {
+ idTown: model.idTown,
+ idPlace: model.idPlace,
+ available: availableDays,
+ latitude: model.latitude,
+ longitude: model.longitude,
+ descriptions: [model.description],
+ imagesList: [model.imageName],
+ name: model.name,
+ categoriesId: model.categories.map((category) => category.idCategory),
+ openAt: model.openAt,
+ closeAt: model.closeAt,
+ startDate: model.startDate,
+ endDate: model.endDate,
+ address: model.address,
+ };
+ return place;
+};
diff --git a/web/src/domain/model/StateModel.ts b/web/src/domain/model/StateModel.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fc76339ad59ac342bcd317ab272c493a9792e2f0
--- /dev/null
+++ b/web/src/domain/model/StateModel.ts
@@ -0,0 +1,11 @@
+/**
+ * Interface representing a state model.
+ */
+export interface StateModel {
+ /** Unique identifier for the state */
+ stateId: number;
+ /** Name of the state */
+ name: string;
+ /** URL of the image representing the state */
+ imageURL: string;
+}
diff --git a/web/src/domain/model/TownModel.ts b/web/src/domain/model/TownModel.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5f5cd4b5ab58af50e569de75eec6c96a827c91eb
--- /dev/null
+++ b/web/src/domain/model/TownModel.ts
@@ -0,0 +1,33 @@
+/**
+ * Interface representing a traditional town model.
+ */
+export interface TownModelTrad {
+ /** Unique identifier for the town */
+ townId: number;
+ /** Name of the town */
+ name: string;
+ /** Image name associated with the town */
+ imageName: string;
+ /** Description of the town */
+ description: string;
+ /** Unique identifier for the state */
+ stateId: number;
+}
+
+/**
+ * Interface representing a town model with multilingual descriptions.
+ */
+export interface TownModel {
+ /** Unique identifier for the town */
+ townId: number;
+ /** Name of the town */
+ name: string;
+ /** English description of the town */
+ descriptionEN: string;
+ /** Spanish description of the town */
+ descriptionES: string;
+ /** Image name associated with the town */
+ imageName: string;
+ /** Unique identifier for the state */
+ stateId: number;
+}
\ No newline at end of file
diff --git a/web/src/domain/repository/admin_repository.ts b/web/src/domain/repository/admin_repository.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3c2d9144f77c320026daf6a657d9a0978fbc6396
--- /dev/null
+++ b/web/src/domain/repository/admin_repository.ts
@@ -0,0 +1,58 @@
+import {
+ Admin,
+ AdminFormValues,
+} from "../../data/datasource/api/entities/admin_form_values";
+import { ResetPasswordValues } from "../../data/datasource/api/entities/reset_password_values";
+
+/**
+ * Interface representing a repository for handling admin-related operations.
+ */
+export interface AdminRepositoryInf {
+ /**
+ * Registers a new admin.
+ * @param form - The admin data to register.
+ * @returns A promise that resolves when the admin is registered.
+ */
+ registerAdmin(form: AdminFormValues): Promise;
+
+ /**
+ * Retrieves admin information based on a token.
+ * @param token - The token to authenticate the request.
+ * @returns A promise that resolves to an Admin object.
+ */
+ getAdminInfo(token: string): Promise;
+
+ /**
+ * Changes the password for an admin.
+ * @param token - The token to authenticate the request.
+ * @param prevPassword - The previous password.
+ * @param newPassword - The new password.
+ * @returns A promise that resolves when the password is changed.
+ */
+ changePassword(
+ token: string,
+ prevPassword: string,
+ newPassword: string
+ ): Promise;
+
+ /**
+ * Retrieves a list of admins by town.
+ * @param idTown - The ID of the town.
+ * @returns A promise that resolves to an array of Admin objects.
+ */
+ getAdminsByTown(idTown: number): Promise;
+
+ /**
+ * Generates a reset code for password recovery.
+ * @param email - The email address to send the reset code to.
+ * @returns A promise that resolves when the reset code is generated.
+ */
+ generateResetCode(email: string): Promise;
+
+ /**
+ * Resets the password using the provided form values.
+ * @param form - The reset password form values.
+ * @returns A promise that resolves when the password is reset.
+ */
+ resetPassword(form: ResetPasswordValues): Promise;
+}
diff --git a/web/src/domain/repository/category_repository.ts b/web/src/domain/repository/category_repository.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4425ee7bc000de5c80df14bc1adc4aa348aa93c4
--- /dev/null
+++ b/web/src/domain/repository/category_repository.ts
@@ -0,0 +1,29 @@
+import {
+ Category,
+ CategoryFormValues,
+} from "../../data/datasource/api/entities/category";
+
+/**
+ * Interface representing a repository for handling category-related operations.
+ */
+export interface CategoryRepositoryInf {
+ /**
+ * Registers a new category.
+ * @param form - The category data to register.
+ * @returns A promise that resolves when the category is registered.
+ */
+ registerCategory(form: CategoryFormValues): Promise;
+
+ /**
+ * Retrieves a list of categories.
+ * @returns A promise that resolves to an array of Category objects.
+ */
+ getCategories(): Promise;
+
+ /**
+ * Deletes an existing category.
+ * @param category - The category to delete.
+ * @returns A promise that resolves when the category is deleted.
+ */
+ deleteCategory(category: Category): Promise;
+}
diff --git a/web/src/domain/repository/login_repository.ts b/web/src/domain/repository/login_repository.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5fe2435a4a30a92af3b602813031a6357b32592e
--- /dev/null
+++ b/web/src/domain/repository/login_repository.ts
@@ -0,0 +1,14 @@
+import { LoginFormValues } from "../../data/datasource/api/entities/login_form_values";
+import { LoggedInUser } from "../../data/datasource/api/entities/user";
+
+/**
+ * Interface representing a repository for handling login operations.
+ */
+export interface LoginRepository {
+ /**
+ * Retrieves a token for the given login form values.
+ * @param form - The login form values.
+ * @returns A promise that resolves to a logged-in user.
+ */
+ getToken(form: LoginFormValues): Promise;
+}
diff --git a/web/src/domain/repository/place_repository.ts b/web/src/domain/repository/place_repository.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a89108d1903a79beb0ad4117d9b449fb7fd182b0
--- /dev/null
+++ b/web/src/domain/repository/place_repository.ts
@@ -0,0 +1,34 @@
+import { Place } from "../../data/datasource/api/entities/place";
+
+/**
+ * Interface representing a repository for handling place-related operations.
+ */
+export interface PlaceRepositoryInf {
+ /**
+ * Registers a new place.
+ * @param form - The place data to register.
+ * @returns A promise that resolves when the place is registered.
+ */
+ registerPlace(form: Place): Promise;
+
+ /**
+ * Retrieves a list of places by town.
+ * @param idTown - The ID of the town.
+ * @returns A promise that resolves to an array of Place objects.
+ */
+ getPlacesByTown(idTown: number): Promise;
+
+ /**
+ * Retrieves a place by its ID.
+ * @param idPlace - The ID of the place.
+ * @returns A promise that resolves to a Place object.
+ */
+ getPlaceById(idPlace: number): Promise;
+
+ /**
+ * Updates an existing place.
+ * @param place - The place data to update.
+ * @returns A promise that resolves when the place is updated.
+ */
+ updatePlace(place: Place): Promise;
+}
diff --git a/web/src/domain/repository/poi_repository.ts b/web/src/domain/repository/poi_repository.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e99c809242e8520e634d478f751f252c34ef733d
--- /dev/null
+++ b/web/src/domain/repository/poi_repository.ts
@@ -0,0 +1,35 @@
+import { PointOfInterest } from "../../data/datasource/api/entities/poi";
+
+/**
+ * Interface representing a repository for handling points of interest (POI) operations.
+ */
+export interface PoiRepositoryInf {
+ /**
+ * Registers a new point of interest.
+ * @param form - The POI data to register.
+ * @returns A promise that resolves when the POI is registered.
+ */
+ registerPoint(form: PointOfInterest): Promise;
+
+ /**
+ * Retrieves a list of POIs by place.
+ * @param idPlace - The ID of the place.
+ * @returns A promise that resolves to an array of PointOfInterest objects.
+ */
+ getPOIsByPlace(idPlace: number): Promise;
+
+ /**
+ * Retrieves a POI by its ID.
+ * @param idPoint - The ID of the POI.
+ * @returns A promise that resolves to a PointOfInterest object.
+ */
+ getPOIById(idPoint: number): Promise;
+
+ /**
+ * Retrieves a PDF containing information about specified POIs.
+ * @param idPlace - The ID of the place.
+ * @param pointsId - An array of POI IDs.
+ * @returns A promise that resolves to a string representing the PDF.
+ */
+ getPDFByPoints(idPlace: number, pointsId: number[]): Promise;
+}
diff --git a/web/src/domain/repository/town_repository.ts b/web/src/domain/repository/town_repository.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b8d8ab4aa4d3951c4976abb3755581c96fca293f
--- /dev/null
+++ b/web/src/domain/repository/town_repository.ts
@@ -0,0 +1,42 @@
+import { State } from "../../data/datasource/api/entities/state";
+import { Town } from "../../data/datasource/api/entities/town";
+
+/**
+ * Interface representing a repository for handling town-related operations.
+ */
+export interface TownRepositoryInf {
+ /**
+ * Retrieves a list of states.
+ * @returns A promise that resolves to an array of State objects.
+ */
+ getStates(): Promise;
+
+ /**
+ * Registers a new town.
+ * @param form - The town data to register.
+ * @returns A promise that resolves when the town is registered.
+ */
+ registerTown(form: Town): Promise;
+
+ /**
+ * Retrieves a list of towns by state.
+ * @param idState - The ID of the state.
+ * @param stateName - The name of the state.
+ * @returns A promise that resolves to an array of Town objects.
+ */
+ getTownsByState(idState: number, stateName: string): Promise;
+
+ /**
+ * Retrieves a town by its ID.
+ * @param idTown - The ID of the town.
+ * @returns A promise that resolves to a Town object.
+ */
+ getTown(idTown: number): Promise;
+
+ /**
+ * Updates an existing town.
+ * @param form - The town data to update.
+ * @returns A promise that resolves when the town is updated.
+ */
+ updateTown(form: Town): Promise;
+}
diff --git a/web/src/hooks/useAdmin.tsx b/web/src/domain/useCase/useAdmin.ts
similarity index 74%
rename from web/src/hooks/useAdmin.tsx
rename to web/src/domain/useCase/useAdmin.ts
index 42745b10d627e4b56e50e368a163d6fba395e2bb..9e3e0bb35e1d03e5bc7700490fd7d45abedeb629 100644
--- a/web/src/hooks/useAdmin.tsx
+++ b/web/src/domain/useCase/useAdmin.ts
@@ -1,35 +1,51 @@
+// Import necessary modules and components
import { FieldErrors, Resolver, SubmitHandler, useForm } from "react-hook-form";
-import { AdminDatasourceProd } from "../data/datasources/prod/admin_datasource";
-import { AdminRepositoryProd } from "../data/repositories/prod/admin_repository";
+import { AdminDatasourceProd } from "../../data/datasource/api/admin_datasource";
+import { AdminRepositoryProd } from "../../data/repository/admin_repository";
import {
Admin,
AdminFormValues,
-} from "../infraestructure/entities/admin_form_values";
+} from "../../data/datasource/api/entities/admin_form_values";
import { toast } from "react-toastify";
import axios, { AxiosError } from "axios";
import { useState } from "react";
-import { showErrorAxios } from "../utils/Messages";
+import { showErrorAxios } from "../../core/utils/Messages";
+// Initialize data source and repository for admin
const adminDatasource = new AdminDatasourceProd();
const adminRepository = new AdminRepositoryProd(adminDatasource);
+// Define resolver for form validation
const resolver: Resolver = async (data) => {
const errors: FieldErrors = {};
+ // Validate name
if (!data.name) {
errors.name = {
type: "required",
message: "El nombre del administrador es requerido",
};
+ } else if (data.name.length > 255) {
+ errors.name = {
+ type: "maxLength",
+ message: "El tamaño no debe sobrepasar los 255 caracteres",
+ };
}
+ // Validate last name
if (!data.lastName) {
errors.lastName = {
type: "required",
message: "El apellido del administrador es requerido",
};
+ } else if (data.lastName.length > 255) {
+ errors.lastName = {
+ type: "maxLength",
+ message: "El tamaño no debe sobrepasar los 255 caracteres",
+ };
}
+ // Validate email
if (!data.email) {
errors.email = {
type: "required",
@@ -43,8 +59,16 @@ const resolver: Resolver = async (data) => {
message: "El correo electronico no es válido",
};
}
+
+ if (data.email.length > 255) {
+ errors.email = {
+ type: "maxLength",
+ message: "El tamaño no debe sobrepasar los 255 caracteres",
+ };
+ }
}
+ // Validate password
if (!data.password) {
errors.password = {
type: "required",
@@ -57,6 +81,7 @@ const resolver: Resolver = async (data) => {
};
}
+ // Validate password confirmation
if (!data.confirmPassword) {
errors.confirmPassword = {
type: "required",
@@ -64,6 +89,7 @@ const resolver: Resolver = async (data) => {
};
}
+ // Validate town admin selection
if (!data.townAdmin) {
errors.townAdmin = {
type: "required",
@@ -77,10 +103,12 @@ const resolver: Resolver = async (data) => {
};
};
+// Custom hook for managing admin functionalities
export const useAdmin = (
forceRenderList?: () => void,
handleClickToClose?: () => void
) => {
+ // Initialize form handling and state management
const {
register,
handleSubmit,
@@ -89,6 +117,7 @@ export const useAdmin = (
} = useForm({ resolver });
const [adminList, setAdminList] = useState([]);
+ // Handle form submission
const onSubmit: SubmitHandler = (data: AdminFormValues) => {
const fetch = async () => {
try {
@@ -104,7 +133,10 @@ export const useAdmin = (
switch (error.code) {
case axios.AxiosError.ERR_BAD_REQUEST:
errorMessage = "Acceso no autorizado";
- setError("email", { type: "validate", message: "El email ya existe" });
+ setError("email", {
+ type: "validate",
+ message: "El email ya existe",
+ });
break;
case axios.AxiosError.ERR_NETWORK:
errorMessage = "Conexión con el servidor fallida";
@@ -125,6 +157,7 @@ export const useAdmin = (
});
};
+ // Fetch admin information
const getAdminInfo = async (): Promise => {
const token = localStorage.getItem("token");
if (token) {
@@ -142,6 +175,7 @@ export const useAdmin = (
return null;
};
+ // Fetch admin list by town
const getAdminListByTown = async (idTown: number) => {
try {
const adminList = await adminRepository.getAdminsByTown(idTown);
diff --git a/web/src/domain/useCase/useAdminChangePassword.ts b/web/src/domain/useCase/useAdminChangePassword.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9742949e54461218109ec04edad3e7210ea25f2f
--- /dev/null
+++ b/web/src/domain/useCase/useAdminChangePassword.ts
@@ -0,0 +1,106 @@
+// Import necessary modules and components
+import { FieldErrors, Resolver, SubmitHandler, useForm } from "react-hook-form";
+import { AdminDatasourceProd } from "../../data/datasource/api/admin_datasource";
+import { AdminRepositoryProd } from "../../data/repository/admin_repository";
+import { AdminPasswordValues } from "../../data/datasource/api/entities/admin_form_values";
+import { useState } from "react";
+import axios, { AxiosError } from "axios";
+import { toast } from "react-toastify";
+
+// Initialize data source and repository for admin
+const adminDatasource = new AdminDatasourceProd();
+const adminRepository = new AdminRepositoryProd(adminDatasource);
+
+// Define resolver for form validation
+const resolver: Resolver = async (data) => {
+ const errors: FieldErrors = {};
+
+ // Validate previous password
+ if (!data.prevPassword) {
+ errors.prevPassword = {
+ type: "required",
+ message: "Se requiere la contraseña actual",
+ };
+ }
+
+ // Validate new password
+ if (!data.newPassword) {
+ errors.newPassword = {
+ type: "required",
+ message: "Se requiere una nueva contraseña",
+ };
+ } else if (data.newPasswordConfirm !== data.newPassword) {
+ errors.newPasswordConfirm = {
+ type: "validate",
+ message: "Las contraseñas deben coincidir",
+ };
+ }
+
+ // Validate new password confirmation
+ if (!data.newPasswordConfirm) {
+ errors.newPasswordConfirm = {
+ type: "required",
+ message: "Se requiere confirmar la nueva contraseña",
+ };
+ }
+
+ return {
+ values: Object.keys(errors).length > 0 ? {} : data,
+ errors: errors,
+ };
+};
+
+// Custom hook for changing admin password
+export const useAdminChangePassword = (
+ setChangePasswordWindowVisibility: (visibility: boolean) => void
+) => {
+ // Initialize form handling and state management
+ const {
+ register,
+ handleSubmit,
+ formState: { errors },
+ setError,
+ } = useForm({ resolver });
+ const [errorMessage, setErrorMessage] = useState("");
+
+ // Handle form submission
+ const onSubmit: SubmitHandler = (
+ data: AdminPasswordValues
+ ) => {
+ const fetch = async () => {
+ try {
+ const token = localStorage.getItem("token") || "";
+ await adminRepository.changePassword(
+ token,
+ data.prevPassword,
+ data.newPassword
+ );
+ setChangePasswordWindowVisibility(false);
+ } catch (error: any) {
+ if (axios.isAxiosError(error)) {
+ error as AxiosError;
+ switch (error.code) {
+ case axios.AxiosError.ERR_BAD_REQUEST:
+ setErrorMessage("Acceso no autorizado");
+ setError("prevPassword", {
+ type: "validate",
+ message: "Contraseña incorrecta",
+ });
+ break;
+ case axios.AxiosError.ERR_NETWORK:
+ setErrorMessage("Conexión con el servidor fallida");
+ break;
+ }
+ }
+ throw new Error();
+ }
+ };
+ toast.promise(fetch(), {
+ pending: "Actualizando contraseña...",
+ success: "La contraseña se ha actualizado",
+ error: errorMessage,
+ });
+ };
+
+ return { register, handleSubmit, errors, onSubmit };
+};
diff --git a/web/src/hooks/useAdminHomePage.tsx b/web/src/domain/useCase/useAdminHomePage.ts
similarity index 57%
rename from web/src/hooks/useAdminHomePage.tsx
rename to web/src/domain/useCase/useAdminHomePage.ts
index 2aa0190c58679e9b205ef413ec36431ec084500b..e50539fccc5331dcd49eb303b49207cf9ac60a4a 100644
--- a/web/src/hooks/useAdminHomePage.tsx
+++ b/web/src/domain/useCase/useAdminHomePage.ts
@@ -1,40 +1,45 @@
+// Import necessary modules and hooks
import { useEffect, useState } from "react";
-import { AdminSelectedPanel } from "../constants/selected_panel";
-import { useAuth } from "../context/auth_context";
-import { Town } from "../infraestructure/entities/town";
+import { AdminSelectedPanel } from "../../core/constants/selected_panel";
+import { useAuth } from "../../core/context/auth_context";
+import { Town } from "../../data/datasource/api/entities/town";
import { useWindowShow } from "./useWindowShow";
import { useAdmin } from "./useAdmin";
-import { UserRole } from "../constants/roles";
+import { UserRole } from "../../core/constants/roles";
import { useTown } from "./useTown";
+// Custom hook for managing the admin home page
export const useAdminHomePage = () => {
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(false);
const [errorMessage, setErrorMessage] = useState("");
const [collapsed, setCollapsed] = useState(true);
- const {setIsWindowActive, isWindowActive} = useWindowShow();
- const [selectedPanel, setSelectedPanel] = useState(AdminSelectedPanel.TOWN_INFO);
- const {getTownById} = useTown();
+ const { setIsWindowActive, isWindowActive } = useWindowShow();
+ const [selectedPanel, setSelectedPanel] = useState(
+ AdminSelectedPanel.TOWN_INFO
+ );
+ const { getTownById } = useTown();
const [town, setTown] = useState();
- const {user, logout} = useAuth();
- const {getAdminInfo} = useAdmin();
+ const { user, logout } = useAuth();
+ const { getAdminInfo } = useAdmin();
+ // Check admin role and fetch town info on component mount
useEffect(() => {
const checkAdminRole = async () => {
const admin = await getAdminInfo();
- if(admin){
- if(admin.role!==UserRole.ADMIN){
+ if (admin) {
+ if (admin.role !== UserRole.ADMIN) {
setError(true);
- setErrorMessage("No estas autorizado para acceder a esta página")
+ setErrorMessage("No estas autorizado para acceder a esta página");
setTimeout(() => {
logout();
}, 5000);
return;
}
- if(admin.idTown){
+ if (admin.idTown) {
const townFetched = await getTownById(admin.idTown);
- if(townFetched){
+ if (townFetched) {
setTown(townFetched);
}
}
@@ -42,16 +47,18 @@ export const useAdminHomePage = () => {
setIsLoading(false);
};
checkAdminRole();
- },[user]);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [user]);
- const updateTown = async () =>{
- if(town){
+ // Update town information
+ const updateTown = async () => {
+ if (town) {
const townFetched = await getTownById(town.idTown);
- if(townFetched){
+ if (townFetched) {
setTown(townFetched);
}
}
- }
+ };
return {
collapsed,
@@ -64,6 +71,6 @@ export const useAdminHomePage = () => {
error,
errorMessage,
town,
- updateTown
+ updateTown,
};
-}
\ No newline at end of file
+};
diff --git a/web/src/hooks/useAdminTownInfo.tsx b/web/src/domain/useCase/useAdminTownInfo.ts
similarity index 73%
rename from web/src/hooks/useAdminTownInfo.tsx
rename to web/src/domain/useCase/useAdminTownInfo.ts
index 6a47fe307b886e13d465329fb3bc9253b75bfb87..5b1fed5160670da71237391c3ca20c8d1fd60278 100644
--- a/web/src/hooks/useAdminTownInfo.tsx
+++ b/web/src/domain/useCase/useAdminTownInfo.ts
@@ -1,19 +1,24 @@
+// Import necessary modules and hooks
import { useEffect, useState } from "react";
import { useGetStatesList } from "./useGetStatesList";
+// Custom hook for managing town information in admin panel
export const useAdminTownInfo = (updateTown: ()=>void) => {
const [isEnglish, setIsEnglish] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [renderCount, setRenderCount] = useState(0);
const {getStates, statesList} = useGetStatesList();
+ // Function to force re-render the list
const forceRenderList = () =>{
updateTown();
setRenderCount(prevCount => prevCount + 1);
}
+ // Fetch states on component mount
useEffect(()=> {
getStates();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
},[]);
return {
diff --git a/web/src/domain/useCase/useCategory.ts b/web/src/domain/useCase/useCategory.ts
new file mode 100644
index 0000000000000000000000000000000000000000..dcd6447a38e456f3649e10f63dd902792bdc4438
--- /dev/null
+++ b/web/src/domain/useCase/useCategory.ts
@@ -0,0 +1,155 @@
+// Import necessary modules and components
+import { FieldErrors, Resolver, SubmitHandler, useForm } from "react-hook-form";
+import { CategoryDatasourceProd } from "../../data/datasource/api/category_datasource";
+import { CategoryRepositoryProd } from "../../data/repository/category_repository";
+import {
+ Category,
+ CategoryFormValues,
+} from "../../data/datasource/api/entities/category";
+import axios, { AxiosError } from "axios";
+import { toast } from "react-toastify";
+import { useEffect, useState } from "react";
+import { showErrorAxios } from "../../core/utils/Messages";
+
+// Initialize data source and repository for categories
+const categoryDatasource = new CategoryDatasourceProd();
+const categoryRepository = new CategoryRepositoryProd(categoryDatasource);
+
+// Define resolver for form validation
+const resolver: Resolver = async (data) => {
+ const errors: FieldErrors = {};
+
+ // Validate Spanish name
+ if (!data.nameES) {
+ errors.nameES = {
+ type: "required",
+ message: "El nombre en español de la categoría es requerido",
+ };
+ } else if (data.nameES.length > 255) {
+ errors.nameES = {
+ type: "maxLength",
+ message: "El tamaño no debe sobrepasar los 255 caracteres",
+ };
+ }
+
+ // Validate English name
+ if (!data.nameEN) {
+ errors.nameEN = {
+ type: "required",
+ message: "El nombre en inglés de la categoría es requerido",
+ };
+ } else if (data.nameEN.length > 255) {
+ errors.nameEN = {
+ type: "maxLength",
+ message: "El tamaño no debe sobrepasar los 255 caracteres",
+ };
+ }
+
+ return {
+ values: Object.keys(errors).length > 0 ? {} : data,
+ errors: errors,
+ };
+};
+
+// Custom hook for category management
+export const useCategory = (
+ forceRenderList?: () => void,
+ handleClickToClose?: () => void
+) => {
+ // Initialize form handling and state management
+ const {
+ register,
+ handleSubmit,
+ setValue,
+ formState: { errors },
+ clearErrors,
+ resetField,
+ } = useForm({ resolver });
+ const [pending, setPending] = useState(false);
+ const [isLoading, setIsLoading] = useState(false);
+ const [categoriesList, setCategoriesList] = useState([]);
+
+ // Handle form submission
+ const onSubmit: SubmitHandler = (
+ data: CategoryFormValues
+ ) => {
+ const fetch = async () => {
+ try {
+ await categoryRepository.registerCategory(data).then(() => {
+ if (forceRenderList && handleClickToClose) {
+ updateCategoriesList();
+ forceRenderList();
+ handleClickToClose();
+ }
+ });
+ } catch (error: any) {
+ let errorMessage: string = "Ha ocurrido un error";
+ if (axios.isAxiosError(error)) {
+ error as AxiosError;
+ switch (error.code) {
+ case axios.AxiosError.ERR_BAD_REQUEST:
+ errorMessage = "Acceso no autorizado";
+ break;
+ case axios.AxiosError.ERR_NETWORK:
+ errorMessage = "Conexión con el servidor fallida";
+ break;
+ }
+ }
+ throw new Error(errorMessage);
+ }
+ };
+ toast.promise(fetch(), {
+ pending: "Subiendo datos...",
+ success: "Los datos se han subido correctamente",
+ error: {
+ render({ data }) {
+ return (data as Error).message;
+ },
+ },
+ });
+ };
+
+ // Update the list of categories
+ const updateCategoriesList = async () => {
+ setPending(true);
+ try {
+ const categories = await categoryRepository.getCategories();
+ setCategoriesList(categories);
+ setPending(false);
+ } catch (error: any) {
+ if (axios.isAxiosError(error)) {
+ error as AxiosError;
+ showErrorAxios(error);
+ }
+ setPending(false);
+ }
+ };
+
+ // Fetch categories on component mount
+ useEffect(() => {
+ setIsLoading(true);
+ updateCategoriesList();
+ setIsLoading(false);
+ }, []);
+
+ // Delete a category
+ const deleteCategory = async (category: Category) => {
+ await categoryRepository.deleteCategory(category);
+ await updateCategoriesList();
+ };
+
+ return {
+ register,
+ handleSubmit,
+ errors,
+ onSubmit,
+ setValue,
+ clearErrors,
+ resetField,
+ updateCategoriesList,
+ categoriesList,
+ pending,
+ deleteCategory,
+ isLoading,
+ };
+};
diff --git a/web/src/domain/useCase/useDropzoneMultiplesImages.ts b/web/src/domain/useCase/useDropzoneMultiplesImages.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9a45d987479be5cdc8987d2af8eeb860daf73c61
--- /dev/null
+++ b/web/src/domain/useCase/useDropzoneMultiplesImages.ts
@@ -0,0 +1,102 @@
+// Import necessary modules and hooks from libraries
+import { useEffect, useState } from "react";
+import { useDropzone } from "react-dropzone";
+import { toast } from "react-toastify";
+import { Image } from "../../data/datasource/api/entities/image";
+import { UseFormSetValue } from "react-hook-form";
+import { Place } from "../../data/datasource/api/entities/place";
+
+// Custom hook for handling multiple image uploads using dropzone
+export const useDropzoneMultiplesImages = (
+ setValue: UseFormSetValue,
+ imagesList?: File[] | string[]
+) => {
+ const [imagesFiles, setImagesFiles] = useState([]);
+ const [files, setFiles] = useState([]);
+ const MAX_SIZE = 10485760; // Maximum file size (10MB)
+ const { getRootProps, getInputProps } = useDropzone({
+ maxSize: MAX_SIZE,
+ accept: {
+ "image/*": [],
+ },
+ onDrop(acceptedFiles, fileRejections) {
+ // Handle file rejections
+ fileRejections.map(({ file, errors }) =>
+ toast.error("Error: " + errors[0].message + " : " + file.name, {
+ position: "bottom-right",
+ autoClose: 1500,
+ hideProgressBar: false,
+ closeOnClick: true,
+ pauseOnHover: false,
+ draggable: true,
+ progress: undefined,
+ theme: "colored",
+ })
+ );
+
+ const backupImages = [...imagesFiles];
+ const backupFiles = [...files];
+ acceptedFiles.forEach((file, index) => {
+ if (imagesFiles.length <= 10 && index < 10) {
+ const preview = URL.createObjectURL(file);
+ backupImages.push({ file, preview });
+ backupFiles.push(file);
+ } else {
+ toast.error("Error: Ha superado los 10 archivos", {
+ position: "bottom-right",
+ autoClose: 1500,
+ hideProgressBar: false,
+ closeOnClick: true,
+ pauseOnHover: false,
+ draggable: true,
+ progress: undefined,
+ theme: "colored",
+ });
+ }
+ });
+ setImagesFiles(backupImages);
+ setFiles(backupFiles);
+ setValue("imagesList", backupFiles, { shouldValidate: true });
+ },
+ });
+
+ // Function to remove an image by index
+ const removeImage = (index: number) => {
+ const backupImages = [...imagesFiles];
+ const backupFiles = [...files];
+ backupImages.splice(index, 1);
+ backupFiles.splice(index, 1);
+ setImagesFiles(backupImages);
+ setFiles(backupFiles);
+ setValue("imagesList", backupFiles, { shouldValidate: true });
+ };
+
+ // Effect to process initial images list
+ useEffect(() => {
+ if (imagesList && imagesList.length > 0) {
+ const backupImages: Image[] = [];
+ const backupFiles: File[] = [];
+ const processImages = async () => {
+ const promises = imagesList.map(async (imageURL) => {
+ if (typeof imageURL === "string") {
+ const response = await fetch(imageURL);
+ const blob = await response.blob();
+ const file = new File([blob], "image.jpg", { type: blob.type });
+ backupImages.push({ file, preview: imageURL });
+ backupFiles.push(file);
+ }
+ });
+ await Promise.all(promises);
+ };
+
+ processImages().then(() => {
+ setImagesFiles(backupImages);
+ setFiles(backupFiles);
+ setValue("imagesList", backupFiles);
+ });
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [imagesList]);
+
+ return { getInputProps, getRootProps, imagesFiles, removeImage };
+};
diff --git a/web/src/domain/useCase/useGetStatesList.ts b/web/src/domain/useCase/useGetStatesList.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8256b9c97df8cd663b1718fdd7c907a538b928e2
--- /dev/null
+++ b/web/src/domain/useCase/useGetStatesList.ts
@@ -0,0 +1,51 @@
+// Import necessary modules and hooks from libraries
+import { useState } from "react";
+import { State } from "../../data/datasource/api/entities/state";
+import axios, { AxiosError } from "axios";
+import { TownDatasourceProd } from "../../data/datasource/api/town_datasource";
+import { TownRepositoryProd } from "../../data/repository/town_repository";
+import { showErrorAxios } from "../../core/utils/Messages";
+
+// Initialize the town datasource and repository
+const townDatasource = new TownDatasourceProd();
+const townRepository = new TownRepositoryProd(townDatasource);
+
+// Custom hook for fetching states list
+export const useGetStatesList = () => {
+ const [statesList, setStatesList] = useState([]);
+
+ // Function to fetch all states
+ const getStates = async () => {
+ try {
+ const states = await townRepository.getStates();
+ setStatesList(states);
+ } catch (error: any) {
+ if (axios.isAxiosError(error)) {
+ error as AxiosError;
+ showErrorAxios(error);
+ }
+ }
+ };
+
+ // Function to fetch a state by its ID
+ const getStateById = async (idState: number): Promise => {
+ let stateReturned: State | null = null;
+ try {
+ const states = await townRepository.getStates();
+ states.forEach((state) => {
+ if (state.stateId === idState) {
+ stateReturned = state;
+ return;
+ }
+ });
+ } catch (error: any) {
+ if (axios.isAxiosError(error)) {
+ error as AxiosError;
+ showErrorAxios(error);
+ }
+ }
+ return stateReturned;
+ };
+
+ return { getStates, statesList, getStateById };
+};
diff --git a/web/src/domain/useCase/useLogin.ts b/web/src/domain/useCase/useLogin.ts
new file mode 100644
index 0000000000000000000000000000000000000000..047058926c517f22eeae1260e318fa7c3ce67000
--- /dev/null
+++ b/web/src/domain/useCase/useLogin.ts
@@ -0,0 +1,104 @@
+// Import necessary modules and hooks from libraries
+import { FieldErrors, Resolver, SubmitHandler, useForm } from "react-hook-form";
+import { Dispatch, SetStateAction, useEffect } from "react";
+import { useNavigate } from "react-router-dom";
+import axios, { AxiosError } from "axios";
+import { useAuth } from "../../core/context/auth_context";
+import { LoginFormValues } from "../../data/datasource/api/entities/login_form_values";
+import { LoginDatasourceImpl } from "../../data/datasource/api/login_datasource";
+import { LoginRepositoryImpl } from "../../data/repository/login_repository";
+
+// Initialize the login datasource and repository
+const loginDatasource = new LoginDatasourceImpl();
+const loginRepository = new LoginRepositoryImpl(loginDatasource);
+
+// Define the resolver function for form validation
+const resolver: Resolver = async (data) => {
+ const errors: FieldErrors = {};
+
+ // Validate email field
+ if (!data.email) {
+ errors.email = {
+ type: "required",
+ message: "El correo electronico es requerido",
+ };
+ } else {
+ const emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
+ if (!emailPattern.test(data.email)) {
+ errors.email = {
+ type: "validate",
+ message: "El correo electronico no es válido",
+ };
+ }
+ }
+
+ // Validate password field
+ if (!data.password) {
+ errors.password = {
+ type: "required",
+ message: "La contraseña es requerida",
+ };
+ }
+
+ return {
+ values: Object.keys(errors).length > 0 ? {} : data,
+ errors: errors,
+ };
+};
+
+// Custom hook for handling login logic
+export const useLogin = (setIsLoading: Dispatch>) => {
+ const { login, user } = useAuth();
+ const navigate = useNavigate();
+ const {
+ register,
+ handleSubmit,
+ setError,
+ formState: { errors },
+ clearErrors,
+ } = useForm({ resolver });
+
+ // Function to handle form submission
+ const onSubmit: SubmitHandler = (data: LoginFormValues) => {
+ const authenticate = async () => {
+ setIsLoading(true);
+ try {
+ // Attempt to get token and login
+ const { user, token } = await loginRepository.getToken(data);
+ await login(user, token);
+ navigate("/");
+ } catch (error: any) {
+ // Handle different types of errors
+ if (axios.isAxiosError(error)) {
+ error as AxiosError;
+ switch (error.code) {
+ case axios.AxiosError.ERR_BAD_REQUEST:
+ setError("root.serverError", {
+ type: "401",
+ message: "Correo electrónico o contraseña incorrectos",
+ });
+ break;
+ case axios.AxiosError.ERR_NETWORK:
+ setError("root.serverError", {
+ type: "500",
+ message: "Conexión con el servidor fallida",
+ });
+ break;
+ }
+ }
+ }
+ setIsLoading(false);
+ };
+ authenticate();
+ };
+
+ // Redirect if user is already logged in
+ useEffect(() => {
+ if (user) {
+ navigate("/");
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ return { register, handleSubmit, onSubmit, errors, clearErrors };
+};
diff --git a/web/src/hooks/usePasswordVisibility.tsx b/web/src/domain/useCase/usePasswordVisibility.ts
similarity index 61%
rename from web/src/hooks/usePasswordVisibility.tsx
rename to web/src/domain/useCase/usePasswordVisibility.ts
index 23108a824cab3a048531926308d79f2c05edafc4..d21b67ce7bba5e288b6786a594e4c6476e1421a1 100644
--- a/web/src/hooks/usePasswordVisibility.tsx
+++ b/web/src/domain/useCase/usePasswordVisibility.ts
@@ -1,10 +1,14 @@
-import { useState } from "react"
+// Import useState hook from React
+import { useState } from "react";
+// Custom hook for managing password visibility
export const usePasswoordVisibility = () => {
+ // Initialize state for password visibility
const [values, setValues] = useState({
showPassword: false,
});
+ // Toggle password visibility
const handleClickShowPassword = () => {
setValues({
...values,
@@ -12,9 +16,11 @@ export const usePasswoordVisibility = () => {
});
};
+ // Prevent default action on mouse down event
const handleMouseDownPassword = (event: any) => {
event.preventDefault();
};
+ // Return state and handlers
return {values, handleClickShowPassword, handleMouseDownPassword};
}
\ No newline at end of file
diff --git a/web/src/hooks/usePlace.tsx b/web/src/domain/useCase/usePlace.ts
similarity index 83%
rename from web/src/hooks/usePlace.tsx
rename to web/src/domain/useCase/usePlace.ts
index 60bf3ce0225a04cd4b7f7bbf4bbde787ac9a8d39..eec0430ea4c6263b5304f53d286c58f78f99beb8 100644
--- a/web/src/hooks/usePlace.tsx
+++ b/web/src/domain/useCase/usePlace.ts
@@ -1,20 +1,24 @@
+// Import necessary modules and components
import { FieldErrors, Resolver, SubmitHandler, useForm } from "react-hook-form";
-import { Place, AvailableDays } from "../infraestructure/entities/place";
+import { Place, AvailableDays } from "../../data/datasource/api/entities/place";
import { toast } from "react-toastify";
import { useEffect, useState } from "react";
import axios, { AxiosError } from "axios";
-import { languaguesList } from "../constants/languages";
-import { MIN_NUMBER_PLACE_IMAGES } from "../constants/images_nuber";
-import { PlaceDatasourceProd } from "../data/datasources/prod/place_datasource";
-import { PlaceRepositoryProd } from "../data/repositories/prod/place_repository";
-import { showErrorAxios } from "../utils/Messages";
+import { languaguesList } from "../../core/constants/languages";
+import { MIN_NUMBER_PLACE_IMAGES } from "../../core/constants/images_nuber";
+import { PlaceDatasourceProd } from "../../data/datasource/api/place_datasource";
+import { PlaceRepositoryProd } from "../../data/repository/place_repository";
+import { showErrorAxios } from "../../core/utils/Messages";
+// Initialize data source and repository
const placeDatasouce = new PlaceDatasourceProd();
const placeRepository = new PlaceRepositoryProd(placeDatasouce);
+// Define resolver for form validation
const resolver: Resolver = async (data) => {
const errors: FieldErrors = {};
+ // Validate idTown
if (!data.idTown) {
errors.idTown = {
type: "required",
@@ -22,6 +26,7 @@ const resolver: Resolver = async (data) => {
};
}
+ // Validate openAt
if (!data.openAt && data.openAt !== 0) {
errors.openAt = {
type: "required",
@@ -29,6 +34,7 @@ const resolver: Resolver = async (data) => {
};
}
+ // Validate closeAt
if (!data.closeAt && data.closeAt !== 0) {
errors.closeAt = {
type: "required",
@@ -36,6 +42,7 @@ const resolver: Resolver = async (data) => {
};
}
+ // Ensure openAt is before closeAt
if (data.openAt > data.closeAt) {
errors.closeAt = {
type: "required",
@@ -43,13 +50,20 @@ const resolver: Resolver = async (data) => {
};
}
+ // Validate name
if (!data.name) {
errors.name = {
type: "required",
message: "El nombre del lugar es requerido",
};
+ } else if (data.name.length > 255) {
+ errors.name = {
+ type: "maxLength",
+ message: "El tamaño no debe sobrepasar los 255 caracteres",
+ };
}
+ // Validate categoriesId
if (!data.categoriesId) {
errors.categoriesId = {
type: "required",
@@ -64,6 +78,7 @@ const resolver: Resolver = async (data) => {
}
}
+ // Validate descriptions for each language
for (var index = languaguesList.length - 1; index >= 0; index--) {
if (!data.descriptions || !data.descriptions[index]) {
errors.descriptions = {
@@ -73,9 +88,15 @@ const resolver: Resolver = async (data) => {
languaguesList[index] +
" es requerida",
};
+ } else if (data.descriptions[index].length > 1024) {
+ errors.descriptions = {
+ type: "maxLength",
+ message: "El tamaño no debe sobrepasar los 1024 caracteres",
+ };
}
}
+ // Validate available days
if (!data.available) {
errors.available = {
type: "required",
@@ -111,6 +132,7 @@ const resolver: Resolver = async (data) => {
}
}
+ // Validate latitude and longitude
if (!data.latitude || !data.longitude) {
errors.latitude = {
type: "required",
@@ -118,6 +140,7 @@ const resolver: Resolver = async (data) => {
};
}
+ // Validate imagesList
if (!data.imagesList || data.imagesList.length < MIN_NUMBER_PLACE_IMAGES) {
errors.imagesList = {
type: "required",
@@ -128,16 +151,19 @@ const resolver: Resolver = async (data) => {
};
}
+ // Return validation results
return {
values: Object.keys(errors).length > 0 ? {} : data,
errors: errors,
};
};
+// Custom hook for managing place-related form and state
export const usePlace = (
forceRenderList?: () => void,
setIsWindowActive?: (visibility: boolean) => void
) => {
+ // Initialize form methods and state
const {
register,
handleSubmit,
@@ -162,13 +188,16 @@ export const usePlace = (
const [categoriesId, setCategoriesId] = useState([]);
const [updatedCategories, setUpdatedCategories] = useState(false);
+ // Update categoriesId when it changes
useEffect(() => {
setValue("categoriesId", categoriesId, {
shouldValidate: updatedCategories,
});
setUpdatedCategories(true);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
}, [categoriesId]);
+ // Handle form submission for registering a place
const onSubmitRegister: SubmitHandler = (data: Place) => {
const fetch = async () => {
try {
@@ -205,6 +234,7 @@ export const usePlace = (
});
};
+ // Handle form submission for updating a place
const onSubmitUpdate: SubmitHandler = (data: Place) => {
const fetch = async () => {
try {
@@ -240,6 +270,7 @@ export const usePlace = (
});
};
+ // Update form time fields
const updateTimeForm = (time: string, isOpeningHour: boolean) => {
const timeSplitted = time.split(":");
let hours = Number(timeSplitted[0]);
@@ -253,6 +284,7 @@ export const usePlace = (
}
};
+ // Fetch places by town ID
const updatePlacesByTown = async (idTown: number) => {
setPending(true);
try {
@@ -268,6 +300,7 @@ export const usePlace = (
}
};
+ // Fetch place by ID
const getPlaceById = async (idPlace: number): Promise => {
try {
const place = await placeRepository.getPlaceById(idPlace);
@@ -281,6 +314,7 @@ export const usePlace = (
return null;
};
+ // Return hook methods and state
return {
register,
handleSubmit,
diff --git a/web/src/domain/useCase/usePointOfInterest.ts b/web/src/domain/useCase/usePointOfInterest.ts
new file mode 100644
index 0000000000000000000000000000000000000000..656e827a252fde71a0ff6b0c7746fd0e6dfa40f6
--- /dev/null
+++ b/web/src/domain/useCase/usePointOfInterest.ts
@@ -0,0 +1,246 @@
+// Import necessary modules and components
+import { FieldErrors, Resolver, SubmitHandler, useForm } from "react-hook-form";
+import { POIDatasourceProd } from "../../data/datasource/api/poi_datasource";
+import { POIRepositoryProd } from "../../data/repository/poi_repository";
+import { PointOfInterest } from "../../data/datasource/api/entities/poi";
+import { useState } from "react";
+import axios, { AxiosError } from "axios";
+import { toast } from "react-toastify";
+import { languaguesList } from "../../core/constants/languages";
+import { showErrorAxios } from "../../core/utils/Messages";
+
+// Initialize data source and repository
+const POIDatasouce = new POIDatasourceProd();
+const POIRepository = new POIRepositoryProd(POIDatasouce);
+
+// Define resolver for form validation
+const resolver: Resolver = async (data) => {
+ const errors: FieldErrors = {};
+
+ // Validate idPlace
+ if (!data.idPlace) {
+ errors.idPlace = {
+ type: "required",
+ message: "Debe seleccionar el lugar al que pertenece el punto de interés",
+ };
+ }
+
+ // Validate name
+ if (!data.name) {
+ errors.name = {
+ type: "required",
+ message: "El nombre del punto de interés es requerido",
+ };
+ } else if (data.name.length > 255) {
+ errors.name = {
+ type: "maxLength",
+ message: "El tamaño no debe sobrepasar los 255 caracteres",
+ };
+ }
+
+ // Validate content in English
+ if (!data.contentEN) {
+ errors.contentEN = {
+ type: "required",
+ message: "La descripción del punto de interés en inglés es requerida",
+ };
+ } else if (data.contentEN.length > 1024) {
+ errors.contentEN = {
+ type: "maxLength",
+ message: "El tamaño no debe sobrepasar los 1024 caracteres",
+ };
+ }
+
+ // Validate content in Spanish
+ if (!data.contentES) {
+ errors.contentES = {
+ type: "required",
+ message: "La descripción del punto de interés en español es requerida",
+ };
+ } else if (data.contentES.length > 1024) {
+ errors.contentES = {
+ type: "maxLength",
+ message: "El tamaño no debe sobrepasar los 1024 caracteres",
+ };
+ }
+
+ // Validate directions in English
+ if (!data.directionsEN) {
+ errors.directionsEN = {
+ type: "required",
+ message: "Las direcciones del punto de interés en inglés son requeridas",
+ };
+ } else if (data.directionsEN.length > 1024) {
+ errors.directionsEN = {
+ type: "maxLength",
+ message: "El tamaño no debe sobrepasar los 1024 caracteres",
+ };
+ }
+
+ // Validate directions in Spanish
+ if (!data.directionsES) {
+ errors.directionsES = {
+ type: "required",
+ message: "Las direcciones del punto de interés en español son requeridas",
+ };
+ } else if (data.directionsES.length > 1024) {
+ errors.directionsES = {
+ type: "maxLength",
+ message: "El tamaño no debe sobrepasar los 1024 caracteres",
+ };
+ }
+
+ // Validate image
+ if (!data.image) {
+ errors.image = {
+ type: "required",
+ message: "Debe de haber al menos 1 imagen representativa del lugar",
+ };
+ }
+
+ return {
+ values: Object.keys(errors).length > 0 ? {} : data,
+ errors: errors,
+ };
+};
+
+// Custom hook for point of interest functionality
+export const usePointOfInterest = (
+ forceRenderList?: () => void,
+ setIsWindowActive?: (visibility: boolean) => void
+) => {
+ const {
+ register,
+ handleSubmit,
+ setValue,
+ formState: { errors },
+ clearErrors,
+ resetField,
+ } = useForm