diff --git a/web/package-lock.json b/web/package-lock.json
index 8d194350e6765bdc34341ef8a3abe07c7899b848..dd8e9e47bb7a6a9d80a02df029dbc72644a66fd1 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -20,10 +20,12 @@
"axios": "^1.6.8",
"react": "^18.2.0",
"react-dom": "^18.2.0",
+ "react-dropzone": "^14.2.3",
"react-hook-form": "^7.51.2",
"react-pro-sidebar": "^1.1.0",
"react-router-dom": "^6.22.3",
"react-scripts": "5.0.1",
+ "react-toastify": "^10.0.5",
"typescript": "^4.9.5",
"web-vitals": "^2.1.4"
},
@@ -5394,6 +5396,14 @@
"node": ">= 4.0.0"
}
},
+ "node_modules/attr-accept": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz",
+ "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/autoprefixer": {
"version": "10.4.18",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.18.tgz",
@@ -6209,6 +6219,14 @@
"wrap-ansi": "^7.0.0"
}
},
+ "node_modules/clsx": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz",
+ "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/co": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@@ -8439,6 +8457,17 @@
"webpack": "^4.0.0 || ^5.0.0"
}
},
+ "node_modules/file-selector": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz",
+ "integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==",
+ "dependencies": {
+ "tslib": "^2.4.0"
+ },
+ "engines": {
+ "node": ">= 12"
+ }
+ },
"node_modules/filelist": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
@@ -15080,6 +15109,22 @@
"react": "^18.2.0"
}
},
+ "node_modules/react-dropzone": {
+ "version": "14.2.3",
+ "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.2.3.tgz",
+ "integrity": "sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug==",
+ "dependencies": {
+ "attr-accept": "^2.2.2",
+ "file-selector": "^0.6.0",
+ "prop-types": "^15.8.1"
+ },
+ "engines": {
+ "node": ">= 10.13"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8 || 18.0.0"
+ }
+ },
"node_modules/react-error-overlay": {
"version": "6.0.11",
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz",
@@ -15230,6 +15275,18 @@
}
}
},
+ "node_modules/react-toastify": {
+ "version": "10.0.5",
+ "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-10.0.5.tgz",
+ "integrity": "sha512-mNKt2jBXJg4O7pSdbNUfDdTsK9FIdikfsIE/yUCxbAEXl4HMyJaivrVFcn3Elvt5xvCQYhUZm+hqTIu1UXM3Pw==",
+ "dependencies": {
+ "clsx": "^2.1.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ }
+ },
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
diff --git a/web/package.json b/web/package.json
index f36efbd589ff32f2155b6d383a848966e61fb095..3ac96a940a595f0fd22374d9975027b12a631ed6 100644
--- a/web/package.json
+++ b/web/package.json
@@ -15,10 +15,12 @@
"axios": "^1.6.8",
"react": "^18.2.0",
"react-dom": "^18.2.0",
+ "react-dropzone": "^14.2.3",
"react-hook-form": "^7.51.2",
"react-pro-sidebar": "^1.1.0",
"react-router-dom": "^6.22.3",
"react-scripts": "5.0.1",
+ "react-toastify": "^10.0.5",
"typescript": "^4.9.5",
"web-vitals": "^2.1.4"
},
diff --git a/web/src/components/admin_panel_navbar/admin_navbar.tsx b/web/src/components/admin_panel_navbar/admin_navbar.tsx
index 424bd0633d14451ce363068a75b14018fdda6fbf..6a65a776c3c60b79d224a95655c0e873160c506d 100644
--- a/web/src/components/admin_panel_navbar/admin_navbar.tsx
+++ b/web/src/components/admin_panel_navbar/admin_navbar.tsx
@@ -5,7 +5,11 @@ import { useAuth } from "../../context/auth_context";
import { Link } from "react-router-dom";
import './assets/styles/style.css';
-export const AdminPanelNavBar = () => {
+interface props{
+ windowActive: boolean;
+}
+
+export const AdminPanelNavBar = ({windowActive}:props) => {
const {user, logout} = useAuth();
const [toggle, setToggle] = useState(false);
@@ -17,7 +21,21 @@ export const AdminPanelNavBar = () => {
setToggle(!toggle)}/>
+ onClick={() => {
+ windowActive
+ ?
+ setToggle(false)
+ :
+ setToggle(!toggle)
+ }}
+ style={
+ windowActive
+ ?
+ {cursor: "auto"}
+ :
+ {cursor: "pointer"}
+ }
+ />
{toggle &&
diff --git a/web/src/components/admin_panel_navbar/assets/styles/style.css b/web/src/components/admin_panel_navbar/assets/styles/style.css
index 0762a6709c9e7e0d467a5cf68bd373d6b71dfb5c..7e82ea4711c167bc0a8caa4c0bc7ef3f948e2a04 100644
--- a/web/src/components/admin_panel_navbar/assets/styles/style.css
+++ b/web/src/components/admin_panel_navbar/assets/styles/style.css
@@ -15,12 +15,12 @@
.profile{
position: absolute;
right: 20px;
+ user-select: none;
}
.user-pic{
width: 30px;
border-radius: 50%;
- cursor: pointer;
background: white;
}
@@ -30,6 +30,7 @@
right: 10%;
max-width: 400px;
overflow: hidden;
+ z-index: 1000;
transition: max-height 0.5s;
}
diff --git a/web/src/components/image_dropzone/assets/css/styles.css b/web/src/components/image_dropzone/assets/css/styles.css
new file mode 100644
index 0000000000000000000000000000000000000000..e5e00a1a63f78f37498b76c2dc5cd5afa7956c78
--- /dev/null
+++ b/web/src/components/image_dropzone/assets/css/styles.css
@@ -0,0 +1,27 @@
+.image_dropzone_container {
+ width: 90%;
+}
+
+.image_dropzone {
+ cursor: pointer;
+ border-color: #eeeeee;
+ border-style: dashed;
+ background-color: #fafafa;
+ outline: none;
+ color: #bdbdbd;
+ aspect-ratio: 1/1;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.image_dropzone:hover {
+ border-color: rgb(106, 188, 226);
+ color: rgb(43, 38, 38);
+}
+
+.image_dropzone img {
+ height: 100%;
+ width: 100%;
+ object-fit: contain;
+}
\ 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
new file mode 100644
index 0000000000000000000000000000000000000000..add3c59c09ccc42081bc1dc4eee2d42ea9d9f212
--- /dev/null
+++ b/web/src/components/image_dropzone/image_dropzone.tsx
@@ -0,0 +1,73 @@
+import { ToastContainer, toast } from 'react-toastify';
+import './assets/css/styles.css'
+import { useDropzone } from "react-dropzone";
+import { useState } from 'react';
+import "react-toastify/dist/ReactToastify.css";
+import { UseFormSetValue } from 'react-hook-form';
+import { TownFormValues } from '../../infraestructure/entities/town_form_values';
+
+interface props {
+ setValue : UseFormSetValue
+}
+
+export const ImageDropzone = ({setValue}: props) => {
+ const MAX_SIZE = 10485760;
+ const [preview, setPreview] = useState(null);
+ const {fileRejections,getRootProps, getInputProps} = useDropzone(
+ {
+ multiple: false,
+ maxSize: MAX_SIZE,
+ accept: {
+ 'image/jpeg': [],
+ 'image/png': []
+ },
+ onDrop(acceptedFiles, fileRejections) {
+ const file = new FileReader;
+
+ 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.map((file)=>{
+ {setValue('imageURL',file)}
+ });
+
+ file.onload = () => {
+ setPreview(file.result);
+ };
+
+ file.readAsDataURL(acceptedFiles[0]);
+ }
+ }
+ );
+
+ return (
+
+
+
+ {preview ? (
+
+ ) : (
+
Arrastra tu imagen o seleccionala dando click aquí.
+ )}
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/web/src/components/sa_panel_town/sa_panel_town_register/css/styles.css b/web/src/components/sa_panel_town/sa_panel_town_register/css/styles.css
new file mode 100644
index 0000000000000000000000000000000000000000..2445be2fb3d51e905731dcbe9f677e562c51e221
--- /dev/null
+++ b/web/src/components/sa_panel_town/sa_panel_town_register/css/styles.css
@@ -0,0 +1,155 @@
+*{
+ user-select: none;
+}
+
+.town_register_wrap {
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ margin: auto;
+ width: 70vw;
+ height: 90vh;
+ background: green;
+ display: flex;
+ flex-direction: column;
+}
+
+.town_register_header {
+ display: flex;
+ width: 100%;
+ align-items: center;
+ justify-content: center;
+ padding: 5px;
+}
+
+.town_register_close_btn{
+ display: inline-block;
+ cursor: pointer;
+ position: absolute;
+ right: 5px;
+}
+
+.town_register_content {
+ background: white;
+ width: 100%;
+ flex-grow: 1;
+ display: flex;
+ flex-direction: column;
+}
+
+.town_register_content form{
+ display: flex;
+ flex-direction: column;
+ flex-grow: 1;
+}
+
+.town_description_image_cont {
+ width: 100%;
+ background: lightgray;
+ flex-grow: 1;
+ display: flex;
+ flex-direction: row;
+}
+
+.town_desc{
+ height: 100%;
+ width: 55%;
+ display: flex;
+ flex-direction: column;
+ padding: 20px;
+}
+
+.image_container {
+ height: 100%;
+ width: 45%;
+ background: white;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+}
+
+.image_container div {
+ justify-content: center;
+ align-items: center;
+}
+
+.town_desc div{
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.towm_desc_input_cnt{
+ height: 100%;
+ width: 100%;
+ flex-grow: 1;
+}
+
+.towm_desc_input_cnt textarea{
+ width: 100%;;
+ height: 100%;
+ padding: 5px;
+ overflow-y: auto;
+ resize: none;
+}
+
+.state_select{
+ display: flex;
+ flex-direction: column;
+ width: 40%;
+ justify-content: center;
+ align-items: center;
+}
+
+.state_select label {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin: 0 auto;
+}
+
+.state_select select{
+ width: 60%;
+}
+
+.town_name_state_input {
+ display: flex;
+ flex-direction: row;
+ margin-top: 5px;
+ margin-bottom: 5px;
+}
+
+.town_name_input{
+ width: 40%;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+.town_name_input input{
+ width: 70%;
+ border-radius: 3px;
+}
+
+.town_name_input .form_input::focus {
+ border-width: 1px;
+}
+
+.language_change_cnt{
+ width: 20%;
+}
+
+.change_lang_btn{
+ cursor: pointer;
+}
+
+.language_change_cnt {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ align-items: center;
+}
\ 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
new file mode 100644
index 0000000000000000000000000000000000000000..9f796dd5564228db3e8222e7b6ebcaa0fa12968a
--- /dev/null
+++ b/web/src/components/sa_panel_town/sa_panel_town_register/sa_panel_town_register.tsx
@@ -0,0 +1,111 @@
+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, useState } from "react";
+import { useTownRegister } from "../../../hooks/useTownRegister";
+
+interface props {
+ setWindowActive: Dispatch>,
+ setShowRegisterPanel: Dispatch>
+}
+
+export const SuperadminPanelTownRegister = ({setWindowActive, setShowRegisterPanel}:props) => {
+ const {
+ statesList,
+ register,
+ setValue,
+ handleSubmit,
+ errors,
+ onSubmit
+ } = useTownRegister();
+ const [isEnglish, setIsEnglish] = useState(false);
+ const [spanishDescription, setSpanishDescription] = useState("");
+ const [englishDescription, setEnglishDescription] = useState("");
+
+ return (
+
+
+ Registra el pueblo mágico
+ {setShowRegisterPanel(false); setWindowActive(false)}}/>
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/web/src/components/sa_panel_town/sa_panel_town_screen/css/styles.css b/web/src/components/sa_panel_town/sa_panel_town_screen/css/styles.css
index 2713080dda323b253a843a595638d986d34072a9..d7bbeb9caa5af76aa0fac0fac015124fc3a932b5 100644
--- a/web/src/components/sa_panel_town/sa_panel_town_screen/css/styles.css
+++ b/web/src/components/sa_panel_town/sa_panel_town_screen/css/styles.css
@@ -11,10 +11,12 @@
width: 100%;
align-items: center;
justify-content: center;
+ padding: 5px;
}
-.town_add_btn{
+.town_panel_header .town_add_btn{
+ display: inline-block;
+ cursor: pointer;
position: absolute;
- top: 50%;
right: 5px;
}
\ 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
index dc7bab0f97a32cc17ce7c26352c254d2283f7e46..5d70ed4bdc40b5ec5be78d8291cbecd2febd993e 100644
--- 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
@@ -1,14 +1,32 @@
+import { Dispatch, SetStateAction, useState } from 'react';
+import { SuperadminPanelTownRegister } from '../sa_panel_town_register/sa_panel_town_register';
import './css/styles.css'
-export const SuperadminPanelTownScreen = () => {
+interface props {
+ windowActive: boolean;
+ setWindowActive: Dispatch>;
+}
+
+export const SuperadminPanelTownScreen = ({windowActive,setWindowActive}:props) => {
+ const [showRegisterPanel, setShowRegisterPanel] = useState(false);
+
return (
Administrar pueblos mágicos
-
+
-
+ {showRegisterPanel
+ &&
+ }
);
diff --git a/web/src/components/sidebar_header/assets/css/styles.css b/web/src/components/sidebar_header/assets/css/styles.css
index f47166b7c4d5fa5d20181f23a77c6be4905e9d06..e8c7881755f8b23fd1ea55cdbf948a2cd7ce0842 100644
--- a/web/src/components/sidebar_header/assets/css/styles.css
+++ b/web/src/components/sidebar_header/assets/css/styles.css
@@ -4,8 +4,4 @@
display: flex;
align-items: center;
padding: 0 20px;
-}
-
-.sidebar_header_content{
-
}
\ 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
new file mode 100644
index 0000000000000000000000000000000000000000..f398dcff56eaff5f7e835f4c5f69db8c3150d319
--- /dev/null
+++ b/web/src/data/datasources/prod/town_datasource.ts
@@ -0,0 +1,43 @@
+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 { TownFormValues } from "../../../infraestructure/entities/town_form_values";
+import UnauthorizedError from "../../../errors/UnautherizedError";
+
+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: TownFormValues): Promise {
+ const formToSend = new FormData;
+ formToSend.append('name',form.name);
+ formToSend.append('descriptionES',form.descriptionES);
+ formToSend.append('descriptionEN',form.descriptionEN);
+ formToSend.append('state', form.state);
+ formToSend.append('image',form.imageURL);
+
+ const headers = {
+ 'Content-Type': 'multipart/form-data'
+ };
+
+ const {status, data} = await axios.post(APIUrl + '/town', formToSend,{headers});
+ if (status === 401) {
+ throw new UnauthorizedError({code: 401, message: 'Unauthorized'});
+ }else {
+ console.log(data);
+ }
+ }
+}
\ No newline at end of file
diff --git a/web/src/data/models/prod/StateModel.ts b/web/src/data/models/prod/StateModel.ts
new file mode 100644
index 0000000000000000000000000000000000000000..41f2f827dc54b2db15f33fded38c282ff90df57a
--- /dev/null
+++ b/web/src/data/models/prod/StateModel.ts
@@ -0,0 +1,5 @@
+export interface StateModel {
+ stateId: number;
+ name: string;
+ imageURL: string;
+}
\ 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
new file mode 100644
index 0000000000000000000000000000000000000000..2ca49d14c33a5fd22a7f2932246a8044d985c091
--- /dev/null
+++ b/web/src/data/repositories/prod/town_repository.ts
@@ -0,0 +1,16 @@
+import { TownDatasourceInf } from "../../../infraestructure/datasources/town_datasource";
+import { State } from "../../../infraestructure/entities/state";
+import { TownFormValues } from "../../../infraestructure/entities/town_form_values";
+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: TownFormValues): Promise {
+ return this.datasource.registerTown(form);
+ }
+}
\ No newline at end of file
diff --git a/web/src/errors/CustomError.ts b/web/src/errors/CustomError.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1c8c5434043babf69e7edee7e55f38c13999e9be
--- /dev/null
+++ b/web/src/errors/CustomError.ts
@@ -0,0 +1,15 @@
+export type CustomErrorContent = {
+ message: string;
+ context?: { [key: string]: any };
+};
+
+export abstract class CustomError extends Error {
+ abstract readonly statusCode: number;
+ abstract readonly logging: boolean;
+
+ constructor(message: string) {
+ super(message);
+ Object.setPrototypeOf(this, CustomError.prototype);
+ }
+}
+
\ No newline at end of file
diff --git a/web/src/errors/UnautherizedError.ts b/web/src/errors/UnautherizedError.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4b772a6082326832b7e804c4bcea3a3ac79e03b6
--- /dev/null
+++ b/web/src/errors/UnautherizedError.ts
@@ -0,0 +1,31 @@
+import { CustomError } from "./CustomError";
+
+export default class UnauthorizedError extends CustomError {
+ private static readonly _statusCode = 401;
+ private readonly _code: number;
+ private readonly _logging: boolean;
+ private readonly _context: { [key: string]: any };
+
+ 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);
+ }
+
+ get errors() {
+ return [{ message: this.message, context: this._context }];
+ }
+
+ get statusCode() {
+ return this._code;
+ }
+
+ get logging() {
+ return this._logging;
+ }
+}
diff --git a/web/src/hooks/useTownRegister.tsx b/web/src/hooks/useTownRegister.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..1c5bf209f5a02f4ce022c8b771b14f2db261aa57
--- /dev/null
+++ b/web/src/hooks/useTownRegister.tsx
@@ -0,0 +1,41 @@
+import { useEffect, useState } from "react";
+import { TownDatasourceProd } from "../data/datasources/prod/town_datasource"
+import { TownRepositoryProd } from "../data/repositories/prod/town_repository";
+import { State } from "../infraestructure/entities/state";
+import { SubmitHandler, useForm } from "react-hook-form";
+import { TownFormValues } from "../infraestructure/entities/town_form_values";
+
+const townDatasource = new TownDatasourceProd();
+const townRepository = new TownRepositoryProd(townDatasource);
+
+export const useTownRegister = () => {
+ const [statesList, setStatesList] = useState(null);
+ const {
+ register,
+ handleSubmit,
+ setError,
+ formState: {errors},
+ setValue
+ } = useForm();
+
+ useEffect(() => {
+ const getStates = async () => {
+ const states = await townRepository.getStates();
+ setStatesList(states);
+ }
+ getStates();
+ }, []);
+
+ const onSubmit: SubmitHandler = (data: TownFormValues) => {
+ const fetch = async () => {
+ try{
+ await townRepository.registerTown(data);
+ }catch(error: any){
+
+ }
+ }
+ fetch();
+ }
+
+ return {statesList, register, handleSubmit, errors, onSubmit, setValue};
+}
\ No newline at end of file
diff --git a/web/src/infraestructure/datasources/town_datasource.ts b/web/src/infraestructure/datasources/town_datasource.ts
new file mode 100644
index 0000000000000000000000000000000000000000..229d5124b4e7d74595dd897d33bf7d8c126a5fef
--- /dev/null
+++ b/web/src/infraestructure/datasources/town_datasource.ts
@@ -0,0 +1,7 @@
+import { State } from "../entities/state";
+import { TownFormValues } from "../entities/town_form_values";
+
+export interface TownDatasourceInf{
+ getStates(): Promise;
+ registerTown(form: TownFormValues): void;
+}
\ No newline at end of file
diff --git a/web/src/infraestructure/entities/state.ts b/web/src/infraestructure/entities/state.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7079ff293ca95fac28d82ebc3401243eddb15399
--- /dev/null
+++ b/web/src/infraestructure/entities/state.ts
@@ -0,0 +1,5 @@
+export interface State {
+ stateId?: number,
+ name?: string,
+ imageURL?: string
+}
\ No newline at end of file
diff --git a/web/src/infraestructure/entities/town_form_values.ts b/web/src/infraestructure/entities/town_form_values.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ffd11e36424a648627fa6b0ff01bc99346134c31
--- /dev/null
+++ b/web/src/infraestructure/entities/town_form_values.ts
@@ -0,0 +1,7 @@
+export interface TownFormValues {
+ name : string
+ descriptionES : string
+ descriptionEN : string
+ state : string
+ imageURL : File
+}
\ No newline at end of file
diff --git a/web/src/infraestructure/repositories/town_repository.ts b/web/src/infraestructure/repositories/town_repository.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6a919e9f31a2aed31cb5b221b439be2bc8b9d091
--- /dev/null
+++ b/web/src/infraestructure/repositories/town_repository.ts
@@ -0,0 +1,7 @@
+import { State } from "../entities/state";
+import { TownFormValues } from "../entities/town_form_values";
+
+export interface TownRepositoryInf{
+ getStates(): Promise;
+ registerTown(form: TownFormValues): void;
+}
\ No newline at end of file
diff --git a/web/src/pages/home/super_admin_page/super_admin_home_page.tsx b/web/src/pages/home/super_admin_page/super_admin_home_page.tsx
index 9c6d16fa5d76cebaa584699c42590c6d0c0c65db..8fc5769f514fddd4de374f892df09b508d692a56 100644
--- a/web/src/pages/home/super_admin_page/super_admin_home_page.tsx
+++ b/web/src/pages/home/super_admin_page/super_admin_home_page.tsx
@@ -3,8 +3,6 @@ import './assets/styles/style.css';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faMonument, faSignOut, faUser, faUserTie } from "@fortawesome/free-solid-svg-icons";
import { useState } from "react";
-import { useAuth } from "../../../context/auth_context";
-import { Link } from "react-router-dom";
import { AdminPanelNavBar } from "../../../components/admin_panel_navbar/admin_navbar";
import { SidebarHeader } from "../../../components/sidebar_header/sidebar_header";
import { SuperadminPanelTownScreen } from "../../../components/sa_panel_town/sa_panel_town_screen/sa_panel_town_screen";
@@ -12,28 +10,47 @@ import { SuperadminPanelAdminScreen } from "../../../components/sa_panel_admin/s
export const SuperAdminHomePage = () => {
const [collapsed, setCollapsed] = useState(true);
+ const [windowActive, setWindowActive] = useState(false);
const [townPanel, setTownPanel] = useState(true);
return (
setCollapsed(false)}
- onMouseOut={() => setCollapsed(true)}>
+ onMouseOver={() => {
+ windowActive ?
+ setCollapsed(true)
+ :
+ setCollapsed(false)
+ }}
+ onMouseOut={() => setCollapsed(true)}
+
+ >
-
+
- {townPanel ? : }
+ {townPanel
+ ?
+
+ :
+ }