feature-comercial-module-ts #13
2
components.d.ts
vendored
2
components.d.ts
vendored
@ -20,8 +20,10 @@ declare module 'vue' {
|
||||
Column: typeof import('primevue/column')['default']
|
||||
DataTable: typeof import('primevue/datatable')['default']
|
||||
HelloWorld: typeof import('./src/components/HelloWorld.vue')['default']
|
||||
IconField: typeof import('primevue/iconfield')['default']
|
||||
InputGroup: typeof import('primevue/inputgroup')['default']
|
||||
InputGroupAddon: typeof import('primevue/inputgroupaddon')['default']
|
||||
InputIcon: typeof import('primevue/inputicon')['default']
|
||||
InputText: typeof import('primevue/inputtext')['default']
|
||||
KpiCard: typeof import('./src/components/shared/KpiCard.vue')['default']
|
||||
Menu: typeof import('primevue/menu')['default']
|
||||
|
||||
280
package-lock.json
generated
280
package-lock.json
generated
@ -12,6 +12,7 @@
|
||||
"@primevue/auto-import-resolver": "^4.4.1",
|
||||
"@tailwindcss/vite": "^4.1.16",
|
||||
"@vueuse/core": "^14.0.0",
|
||||
"axios": "^1.13.2",
|
||||
"primeicons": "^7.0.0",
|
||||
"primevue": "^4.4.1",
|
||||
"tailwindcss-primeui": "^0.6.1",
|
||||
@ -1448,6 +1449,36 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
|
||||
"integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.4",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
@ -1463,6 +1494,18 @@
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/confbox": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz",
|
||||
@ -1492,6 +1535,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||
@ -1501,6 +1553,20 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
"version": "5.18.3",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz",
|
||||
@ -1526,6 +1592,51 @@
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
|
||||
@ -1596,6 +1707,42 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.11",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
@ -1610,12 +1757,109 @@
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-tostringtag": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/jiti": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
|
||||
@ -1900,6 +2144,36 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mlly": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz",
|
||||
@ -2052,6 +2326,12 @@
|
||||
"node": ">=12.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/quansync": {
|
||||
"version": "0.2.11",
|
||||
"resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz",
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
"@primevue/auto-import-resolver": "^4.4.1",
|
||||
"@tailwindcss/vite": "^4.1.16",
|
||||
"@vueuse/core": "^14.0.0",
|
||||
"axios": "^1.13.2",
|
||||
"primeicons": "^7.0.0",
|
||||
"primevue": "^4.4.1",
|
||||
"tailwindcss-primeui": "^0.6.1",
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useLayout } from "../../composables/useLayout";
|
||||
import { useAuth } from "../../stores/auth";
|
||||
import { useAuth } from "../../modules/auth/composables/useAuth";
|
||||
|
||||
const router = useRouter();
|
||||
const { isDarkMode, toggleDarkMode } = useLayout();
|
||||
|
||||
@ -7,7 +7,7 @@ import StyleClass from "primevue/styleclass";
|
||||
import { createApp } from "vue";
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
import { useAuth } from "./stores/auth";
|
||||
import { useAuth } from "./modules/auth/composables/useAuth";
|
||||
|
||||
// Crear un preset personalizado basado en Aura con color azul
|
||||
const MyPreset = definePreset(Aura, {
|
||||
|
||||
195
src/modules/auth/components/Login.vue
Normal file
195
src/modules/auth/components/Login.vue
Normal file
@ -0,0 +1,195 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import { useAuth } from '../composables/useAuth';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const router = useRouter();
|
||||
const { login, isLoading } = useAuth();
|
||||
|
||||
const email = ref('');
|
||||
const password = ref('');
|
||||
const remember = ref(false);
|
||||
const showPassword = ref(false);
|
||||
const errorMessage = ref('');
|
||||
|
||||
const isFormValid = computed(() => {
|
||||
return email.value.trim() !== '' && password.value.trim() !== '';
|
||||
});
|
||||
|
||||
const handleLogin = async () => {
|
||||
errorMessage.value = '';
|
||||
|
||||
const result = await login({
|
||||
email: email.value,
|
||||
password: password.value
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
router.push('/');
|
||||
} else {
|
||||
errorMessage.value = result.error || 'Error al iniciar sesión';
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyPress = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Enter' && isFormValid.value) {
|
||||
handleLogin();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex min-h-screen bg-surface-50 dark:bg-surface-950">
|
||||
<!-- Left Column: Image Panel -->
|
||||
<div
|
||||
class="relative hidden lg:flex lg:w-1/2 flex-col justify-between p-8 text-white bg-cover bg-center bg-no-repeat"
|
||||
style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuBTdTKvMQYppS4BwZhrgylD8oKxLJa13js2PIRUwAQF5XbEqqNU2VCYyvJEQTWdKdoCTYHiAm_RGoobkJ3Rt4xd-XJUcshYKfCBrF0fZcBJOFR9UJ0Vh2WuLIA8g_xmKA8Fn7hdh4KPqJC7X_IOg5UPi5RRzqxB-Xn2tMbaEk-n1h8mYXfnKSIV7C0up-YreeZdP9GeznZN6DCzjy8TLyxw03gXdmhHziZC6hzahwWemMrtS8W5sX028-G1tnxivu1v2o8oETFyh5Wi')"
|
||||
>
|
||||
<!-- Overlay oscuro -->
|
||||
<div class="absolute inset-0 bg-black/50"></div>
|
||||
|
||||
<!-- Logo y nombre -->
|
||||
<div class="relative z-10 flex items-center gap-3">
|
||||
<i class="pi pi-box text-4xl"></i>
|
||||
<span class="text-xl font-bold">Golscontrols ERP</span>
|
||||
</div>
|
||||
|
||||
<!-- Texto principal -->
|
||||
<div class="relative z-10">
|
||||
<h1 class="text-4xl font-black leading-tight tracking-tight">
|
||||
Sistema Integral de Gestión Empresarial
|
||||
</h1>
|
||||
<p class="mt-2 max-w-md text-lg text-white/80">
|
||||
Optimiza tus operaciones, gestiona inventarios y aumenta la eficiencia desde un solo panel.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Column: Form Panel -->
|
||||
<div class="flex w-full lg:w-1/2 flex-col items-center justify-center px-4 py-12">
|
||||
<div class="w-full max-w-md space-y-8">
|
||||
<!-- Header -->
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold tracking-tight text-surface-900 dark:text-white">
|
||||
Bienvenido de nuevo
|
||||
</h1>
|
||||
<p class="mt-2 text-base text-surface-600 dark:text-surface-300">
|
||||
Ingresa tus credenciales para acceder a tu cuenta.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Mensaje de error -->
|
||||
<Message
|
||||
v-if="errorMessage"
|
||||
severity="error"
|
||||
:closable="true"
|
||||
@close="errorMessage = ''"
|
||||
>
|
||||
{{ errorMessage }}
|
||||
</Message>
|
||||
|
||||
<!-- Info de prueba (solo para desarrollo) -->
|
||||
<Message severity="info" :closable="false">
|
||||
<div class="text-sm">
|
||||
<p class="font-semibold mb-1">Credenciales de prueba:</p>
|
||||
<p><strong>Email:</strong> admin@gols.com</p>
|
||||
<p><strong>Password:</strong> admin123</p>
|
||||
</div>
|
||||
</Message>
|
||||
|
||||
<!-- Formulario -->
|
||||
<form @submit.prevent="handleLogin" class="space-y-6">
|
||||
<!-- Email -->
|
||||
<div class="flex flex-col gap-2">
|
||||
<label for="email" class="text-sm font-medium text-surface-900 dark:text-surface-200">
|
||||
Correo Electrónico
|
||||
</label>
|
||||
<IconField>
|
||||
<InputIcon class="pi pi-user" />
|
||||
<InputText
|
||||
id="email"
|
||||
v-model="email"
|
||||
type="email"
|
||||
placeholder="you@example.com"
|
||||
:disabled="isLoading"
|
||||
@keypress="handleKeyPress"
|
||||
class="w-full"
|
||||
autocomplete="email"
|
||||
size="large"
|
||||
/>
|
||||
</IconField>
|
||||
</div>
|
||||
|
||||
<!-- Password -->
|
||||
<div class="flex flex-col gap-2">
|
||||
<label for="password" class="text-sm font-medium text-surface-900 dark:text-surface-200">
|
||||
Contraseña
|
||||
</label>
|
||||
<IconField>
|
||||
<InputIcon class="pi pi-lock" />
|
||||
<InputText
|
||||
id="password"
|
||||
v-model="password"
|
||||
:type="showPassword ? 'text' : 'password'"
|
||||
placeholder="Ingresa tu contraseña"
|
||||
:disabled="isLoading"
|
||||
@keypress="handleKeyPress"
|
||||
class="w-full pr-10"
|
||||
autocomplete="current-password"
|
||||
size="large"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
@click="showPassword = !showPassword"
|
||||
class="absolute right-3 top-1/2 -translate-y-1/2 text-surface-400 hover:text-surface-600 dark:hover:text-surface-200"
|
||||
tabindex="-1"
|
||||
>
|
||||
<i :class="showPassword ? 'pi pi-eye-slash' : 'pi pi-eye'"></i>
|
||||
</button>
|
||||
</IconField>
|
||||
</div>
|
||||
|
||||
<!-- Remember me y Forgot password -->
|
||||
<!-- <div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<Checkbox
|
||||
inputId="remember-me"
|
||||
v-model="remember"
|
||||
:binary="true"
|
||||
/>
|
||||
<label for="remember-me" class="text-sm text-surface-700 dark:text-surface-300 cursor-pointer">
|
||||
Remember Me
|
||||
</label>
|
||||
</div>
|
||||
<a href="#" class="text-sm font-medium text-primary hover:text-primary/80">
|
||||
Forgot Password?
|
||||
</a>
|
||||
</div> -->
|
||||
|
||||
<!-- Botón de Login -->
|
||||
<Button
|
||||
type="submit"
|
||||
label="Iniciar Sesión"
|
||||
:loading="isLoading"
|
||||
:disabled="!isFormValid || isLoading"
|
||||
class="w-full"
|
||||
size="large"
|
||||
severity="primary"
|
||||
/>
|
||||
</form>
|
||||
|
||||
<!-- Copyright -->
|
||||
<p class="text-center text-sm text-surface-500 dark:text-surface-400">
|
||||
© 2025 Grupo Golsystems, Todos los derechos reservados.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* Ajuste para el botón de toggle password */
|
||||
.p-iconfield {
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
135
src/modules/auth/services/authService.ts
Normal file
135
src/modules/auth/services/authService.ts
Normal file
@ -0,0 +1,135 @@
|
||||
import api from '../../../services/api';
|
||||
import type { User, LoginCredentials } from '../composables/useAuth';
|
||||
|
||||
export interface LoginResponse {
|
||||
user: User;
|
||||
token: string;
|
||||
}
|
||||
|
||||
export interface RegisterData {
|
||||
name: string;
|
||||
email: string;
|
||||
password: string;
|
||||
password_confirmation: string;
|
||||
}
|
||||
|
||||
class AuthService {
|
||||
/**
|
||||
* Iniciar sesión
|
||||
*/
|
||||
async login(credentials: LoginCredentials): Promise<LoginResponse> {
|
||||
try {
|
||||
// En producción, esto haría una llamada real al backend
|
||||
// const response = await api.post<LoginResponse>('/auth/login', credentials);
|
||||
|
||||
// Simulación para desarrollo
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
if (credentials.email === 'admin@gols.com' && credentials.password === 'admin123') {
|
||||
resolve({
|
||||
user: {
|
||||
id: 1,
|
||||
name: 'John Doe',
|
||||
email: credentials.email,
|
||||
avatar: '',
|
||||
role: 'admin'
|
||||
},
|
||||
token: 'mock-jwt-token-' + Date.now()
|
||||
});
|
||||
} else {
|
||||
reject(new Error('Credenciales inválidas'));
|
||||
}
|
||||
}, 1000);
|
||||
});
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response?.data?.message || 'Error al iniciar sesión');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registrar nuevo usuario
|
||||
*/
|
||||
async register(data: RegisterData): Promise<LoginResponse> {
|
||||
try {
|
||||
const response = await api.post<LoginResponse>('/auth/register', data);
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response?.data?.message || 'Error al registrar usuario');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cerrar sesión
|
||||
*/
|
||||
async logout(): Promise<void> {
|
||||
try {
|
||||
// Notificar al backend que se cerró sesión
|
||||
await api.post('/auth/logout');
|
||||
} catch (error) {
|
||||
console.error('Error al cerrar sesión:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtener usuario actual
|
||||
*/
|
||||
async getCurrentUser(): Promise<User> {
|
||||
try {
|
||||
const response = await api.get<User>('/auth/me');
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response?.data?.message || 'Error al obtener usuario');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Actualizar perfil de usuario
|
||||
*/
|
||||
async updateProfile(data: Partial<User>): Promise<User> {
|
||||
try {
|
||||
const response = await api.put<User>('/auth/profile', data);
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response?.data?.message || 'Error al actualizar perfil');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cambiar contraseña
|
||||
*/
|
||||
async changePassword(currentPassword: string, newPassword: string): Promise<void> {
|
||||
try {
|
||||
await api.post('/auth/change-password', {
|
||||
current_password: currentPassword,
|
||||
new_password: newPassword
|
||||
});
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response?.data?.message || 'Error al cambiar contraseña');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Solicitar recuperación de contraseña
|
||||
*/
|
||||
async forgotPassword(email: string): Promise<void> {
|
||||
try {
|
||||
await api.post('/auth/forgot-password', { email });
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response?.data?.message || 'Error al solicitar recuperación');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resetear contraseña con token
|
||||
*/
|
||||
async resetPassword(token: string, password: string): Promise<void> {
|
||||
try {
|
||||
await api.post('/auth/reset-password', { token, password });
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response?.data?.message || 'Error al resetear contraseña');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const authService = new AuthService();
|
||||
export default authService;
|
||||
@ -1,210 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import { useAuth } from '../../stores/auth';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const router = useRouter();
|
||||
const { login, isLoading } = useAuth();
|
||||
|
||||
const email = ref('');
|
||||
const password = ref('');
|
||||
const remember = ref(false);
|
||||
const showPassword = ref(false);
|
||||
const errorMessage = ref('');
|
||||
|
||||
const isFormValid = computed(() => {
|
||||
return email.value.trim() !== '' && password.value.trim() !== '';
|
||||
});
|
||||
|
||||
const handleLogin = async () => {
|
||||
errorMessage.value = '';
|
||||
|
||||
const result = await login({
|
||||
email: email.value,
|
||||
password: password.value
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
router.push('/');
|
||||
} else {
|
||||
errorMessage.value = result.error || 'Error al iniciar sesión';
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyPress = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Enter' && isFormValid.value) {
|
||||
handleLogin();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-screen flex items-center justify-center bg-surface-50 dark:bg-surface-950 p-4">
|
||||
<div class="w-full max-w-md">
|
||||
<!-- Card de Login -->
|
||||
<div class="bg-surface-0 dark:bg-surface-900 rounded-2xl shadow-xl border border-surface-200 dark:border-surface-800 overflow-hidden">
|
||||
<!-- Header con logo y título -->
|
||||
<div class="bg-linear-to-br from-primary to-primary-600 p-8 text-center">
|
||||
<div class="flex items-center justify-center mb-4">
|
||||
<div class="w-16 h-16 bg-white/20 backdrop-blur-sm rounded-2xl flex items-center justify-center">
|
||||
<i class="pi pi-chart-line text-4xl text-white"></i>
|
||||
</div>
|
||||
</div>
|
||||
<h1 class="text-3xl font-bold text-white mb-2">GOLS Control</h1>
|
||||
<p class="text-primary-100">Sistema de Gestión Empresarial</p>
|
||||
</div>
|
||||
|
||||
<!-- Formulario -->
|
||||
<div class="p-8">
|
||||
<h2 class="text-2xl font-semibold text-surface-900 dark:text-surface-0 mb-2">
|
||||
Iniciar Sesión
|
||||
</h2>
|
||||
<p class="text-surface-600 dark:text-surface-400 mb-6">
|
||||
Ingresa tus credenciales para continuar
|
||||
</p>
|
||||
|
||||
<!-- Mensaje de error -->
|
||||
<Message
|
||||
v-if="errorMessage"
|
||||
severity="error"
|
||||
:closable="false"
|
||||
class="mb-4"
|
||||
>
|
||||
{{ errorMessage }}
|
||||
</Message>
|
||||
|
||||
<!-- Info de prueba -->
|
||||
<Message severity="info" :closable="false" class="mb-6">
|
||||
<div class="text-sm">
|
||||
<p class="font-semibold mb-1">Credenciales de prueba:</p>
|
||||
<p><strong>Email:</strong> admin@gols.com</p>
|
||||
<p><strong>Password:</strong> admin123</p>
|
||||
</div>
|
||||
</Message>
|
||||
|
||||
<form @submit.prevent="handleLogin" class="space-y-6">
|
||||
<!-- Email -->
|
||||
<div class="space-y-2">
|
||||
<label for="email" class="block text-sm font-medium text-surface-700 dark:text-surface-300">
|
||||
Correo Electrónico
|
||||
</label>
|
||||
<InputGroup>
|
||||
<InputGroupAddon>
|
||||
<i class="pi pi-envelope"></i>
|
||||
</InputGroupAddon>
|
||||
<InputText
|
||||
id="email"
|
||||
v-model="email"
|
||||
type="email"
|
||||
placeholder="tu@email.com"
|
||||
:disabled="isLoading"
|
||||
@keypress="handleKeyPress"
|
||||
class="w-full"
|
||||
autocomplete="email"
|
||||
/>
|
||||
</InputGroup>
|
||||
</div>
|
||||
|
||||
<!-- Password -->
|
||||
<div class="space-y-2">
|
||||
<label for="password" class="block text-sm font-medium text-surface-700 dark:text-surface-300">
|
||||
Contraseña
|
||||
</label>
|
||||
<InputGroup>
|
||||
<InputGroupAddon>
|
||||
<i class="pi pi-lock"></i>
|
||||
</InputGroupAddon>
|
||||
<InputText
|
||||
id="password"
|
||||
v-model="password"
|
||||
:type="showPassword ? 'text' : 'password'"
|
||||
placeholder="••••••••"
|
||||
:disabled="isLoading"
|
||||
@keypress="handleKeyPress"
|
||||
class="w-full"
|
||||
autocomplete="current-password"
|
||||
/>
|
||||
<InputGroupAddon class="cursor-pointer" @click="showPassword = !showPassword">
|
||||
<i :class="showPassword ? 'pi pi-eye-slash' : 'pi pi-eye'"></i>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
</div>
|
||||
|
||||
<!-- Recordar sesión -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<Checkbox
|
||||
inputId="remember"
|
||||
v-model="remember"
|
||||
:binary="true"
|
||||
/>
|
||||
<label for="remember" class="text-sm text-surface-700 dark:text-surface-300 cursor-pointer">
|
||||
Recordar sesión
|
||||
</label>
|
||||
</div>
|
||||
<a href="#" class="text-sm text-primary hover:text-primary-700 font-medium">
|
||||
¿Olvidaste tu contraseña?
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Botón de login -->
|
||||
<Button
|
||||
type="submit"
|
||||
label="Iniciar Sesión"
|
||||
icon="pi pi-sign-in"
|
||||
:loading="isLoading"
|
||||
:disabled="!isFormValid || isLoading"
|
||||
class="w-full"
|
||||
size="large"
|
||||
/>
|
||||
</form>
|
||||
|
||||
<!-- Separador -->
|
||||
<div class="relative my-6">
|
||||
<div class="absolute inset-0 flex items-center">
|
||||
<div class="w-full border-t border-surface-300 dark:border-surface-700"></div>
|
||||
</div>
|
||||
<div class="relative flex justify-center text-sm">
|
||||
<span class="px-4 bg-surface-0 dark:bg-surface-900 text-surface-500 dark:text-surface-400">
|
||||
O continuar con
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Botones sociales -->
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<Button
|
||||
label="Google"
|
||||
icon="pi pi-google"
|
||||
outlined
|
||||
severity="secondary"
|
||||
class="w-full"
|
||||
/>
|
||||
<Button
|
||||
label="Microsoft"
|
||||
icon="pi pi-microsoft"
|
||||
outlined
|
||||
severity="secondary"
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="px-8 py-6 bg-surface-50 dark:bg-surface-950 border-t border-surface-200 dark:border-surface-800 text-center">
|
||||
<p class="text-sm text-surface-600 dark:text-surface-400">
|
||||
¿No tienes una cuenta?
|
||||
<a href="#" class="text-primary hover:text-primary-700 font-medium">
|
||||
Solicitar acceso
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Copyright -->
|
||||
<p class="text-center text-sm text-surface-500 dark:text-surface-400 mt-8">
|
||||
© 2025 GOLS Control. Todos los derechos reservados.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -1,8 +1,8 @@
|
||||
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router';
|
||||
import { useAuth } from '../stores/auth';
|
||||
import { useAuth } from '../modules/auth/composables/useAuth';
|
||||
|
||||
// Importar vistas
|
||||
import Login from '../pages/Auth/Login.vue';
|
||||
import Login from '../modules/auth/components/Login.vue';
|
||||
import MainLayout from '../MainLayout.vue';
|
||||
import WarehouseDashboard from '../modules/warehouse/components/WarehouseDashboard.vue';
|
||||
|
||||
|
||||
@ -1,120 +1,45 @@
|
||||
// Servicio API base para todas las peticiones HTTP
|
||||
import axios from 'axios';
|
||||
import type { InternalAxiosRequestConfig } from 'axios';
|
||||
|
||||
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:3000/api';
|
||||
const api = axios.create({
|
||||
baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000',
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
interface RequestOptions extends RequestInit {
|
||||
params?: Record<string, string>;
|
||||
}
|
||||
|
||||
class ApiService {
|
||||
private baseUrl: string;
|
||||
|
||||
constructor(baseUrl: string) {
|
||||
this.baseUrl = baseUrl;
|
||||
// Interceptor para agregar el Bearer Token a cada petición
|
||||
api.interceptors.request.use(
|
||||
(config: InternalAxiosRequestConfig) => {
|
||||
const token = localStorage.getItem('auth_token'); // Ajusta según dónde guardes el token
|
||||
if (token && config.headers) {
|
||||
config.headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Construye la URL con query params
|
||||
*/
|
||||
private buildUrl(endpoint: string, params?: Record<string, string>): string {
|
||||
const url = new URL(`${this.baseUrl}${endpoint}`);
|
||||
if (params) {
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
url.searchParams.append(key, value);
|
||||
});
|
||||
}
|
||||
return url.toString();
|
||||
// Interceptor para manejar errores de forma global
|
||||
api.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
if (error.response) {
|
||||
// El servidor respondió con un código de estado fuera del rango 2xx
|
||||
console.error(error.response.data);
|
||||
|
||||
// Manejar error 401 (no autorizado) - redirigir al login
|
||||
if (error.response.status === 401) {
|
||||
localStorage.removeItem('auth_token');
|
||||
localStorage.removeItem('auth_user');
|
||||
window.location.href = '/login';
|
||||
}
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Maneja la respuesta de la API
|
||||
*/
|
||||
private async handleResponse<T>(response: Response): Promise<T> {
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ message: 'Error desconocido' }));
|
||||
throw new Error(error.message || `HTTP Error: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* GET request
|
||||
*/
|
||||
async get<T>(endpoint: string, options?: RequestOptions): Promise<T> {
|
||||
const url = this.buildUrl(endpoint, options?.params);
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options?.headers,
|
||||
},
|
||||
});
|
||||
return this.handleResponse<T>(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST request
|
||||
*/
|
||||
async post<T>(endpoint: string, data?: any, options?: RequestOptions): Promise<T> {
|
||||
const url = this.buildUrl(endpoint, options?.params);
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options?.headers,
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
return this.handleResponse<T>(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT request
|
||||
*/
|
||||
async put<T>(endpoint: string, data?: any, options?: RequestOptions): Promise<T> {
|
||||
const url = this.buildUrl(endpoint, options?.params);
|
||||
const response = await fetch(url, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options?.headers,
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
return this.handleResponse<T>(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE request
|
||||
*/
|
||||
async delete<T>(endpoint: string, options?: RequestOptions): Promise<T> {
|
||||
const url = this.buildUrl(endpoint, options?.params);
|
||||
const response = await fetch(url, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options?.headers,
|
||||
},
|
||||
});
|
||||
return this.handleResponse<T>(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* PATCH request
|
||||
*/
|
||||
async patch<T>(endpoint: string, data?: any, options?: RequestOptions): Promise<T> {
|
||||
const url = this.buildUrl(endpoint, options?.params);
|
||||
const response = await fetch(url, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options?.headers,
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
return this.handleResponse<T>(response);
|
||||
}
|
||||
}
|
||||
|
||||
// Exportar instancia singleton
|
||||
export const api = new ApiService(API_BASE_URL);
|
||||
export default api;
|
||||
Loading…
x
Reference in New Issue
Block a user