Compare commits

..

19 Commits

Author SHA1 Message Date
4e22290e88 icons right 2024-10-18 18:18:55 +02:00
e813b5abce Change editor and format with prettier 2024-09-28 10:33:29 +02:00
f58f69b84c icons 2024-09-11 16:40:22 +02:00
e3f1f5f807 add modal registro correcto 2024-09-11 15:35:48 +02:00
284f66345b crear listas y scroll 2024-09-10 13:09:53 +02:00
13730eecbc registro completado 2024-09-09 19:01:33 +02:00
060bb8385e all dialogs 2024-09-09 15:56:38 +02:00
1fa94762b0 change por headless ui 2024-09-08 15:20:50 +02:00
8fa8066e88 change flowbite x preline 2024-09-07 16:23:11 +02:00
a2d44137cd url pocket in store 2024-09-07 12:30:31 +02:00
0e302223e9 rutas se mantienen en / 2024-09-04 18:17:21 +02:00
efbe1d2d69 no change url with buttons back and forward 2024-09-04 13:48:20 +02:00
86ee49bf80 modals olvido contraseña 2024-09-03 13:29:52 +02:00
09e7be2479 page registro 2024-08-31 07:15:18 +02:00
00c95ad65b olvidó contraseña 2024-08-31 06:14:39 +02:00
9c9c8cbc67 filtra listas 2024-08-30 19:58:45 +02:00
f4bd38e5a2 listas items 2024-08-30 01:12:26 +02:00
0d4e5e5cfd login page 2024-08-29 17:25:07 +02:00
dc93c8c951 first commit 2024-08-29 17:22:02 +02:00
14 changed files with 1209 additions and 85 deletions

109
package-lock.json generated
View File

@@ -8,16 +8,20 @@
"name": "frontend",
"version": "0.0.0",
"dependencies": {
"@headlessui/vue": "^1.7.22",
"pinia": "^2.1.7",
"pocketbase": "^0.21.5",
"vue": "^3.4.29",
"vue-router": "^4.3.3"
},
"devDependencies": {
"@tailwindcss/forms": "^0.5.9",
"@vitejs/plugin-vue": "^5.0.5",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.41",
"tailwindcss": "^3.4.10",
"vite": "^5.3.1"
"vite": "^5.3.1",
"vite-plugin-remove-console": "^2.2.0"
}
},
"node_modules/@alloc/quick-lru": {
@@ -33,6 +37,21 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@headlessui/vue": {
"version": "1.7.22",
"resolved": "https://registry.npmjs.org/@headlessui/vue/-/vue-1.7.22.tgz",
"integrity": "sha512-Hoffjoolq1rY+LOfJ+B/OvkhuBXXBFgd8oBlN+l1TApma2dB0En0ucFZrwQtb33SmcCqd32EQd0y07oziXWNYg==",
"license": "MIT",
"dependencies": {
"@tanstack/vue-virtual": "^3.0.0-beta.60"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"vue": "^3.2.0"
}
},
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
@@ -152,6 +171,52 @@
"node": ">=14"
}
},
"node_modules/@tailwindcss/forms": {
"version": "0.5.9",
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.9.tgz",
"integrity": "sha512-tM4XVr2+UVTxXJzey9Twx48c1gcxFStqn1pQz0tRsX8o3DvxhN5oY5pvyAbUx7VTaZxpej4Zzvc6h+1RJBzpIg==",
"dev": true,
"license": "MIT",
"dependencies": {
"mini-svg-data-uri": "^1.2.3"
},
"peerDependencies": {
"tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20"
}
},
"node_modules/@tanstack/virtual-core": {
"version": "3.10.7",
"resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.10.7.tgz",
"integrity": "sha512-ND5dfsU0n9F4gROzwNNDJmg6y8n9pI8YWxtgbfJ5UcNn7Hx+MxEXtXcQ189tS7sh8pmCObgz2qSiyRKTZxT4dg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@tanstack/vue-virtual": {
"version": "3.10.7",
"resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.10.7.tgz",
"integrity": "sha512-OSK1fkvz4GaBhF80KVmBsJZoMI9ncVaUU//pI8OqTdBnepw467zcuF2Y+Ia1VC0CPYfUEALyS8n4Ar0RI/7ASg==",
"license": "MIT",
"dependencies": {
"@tanstack/virtual-core": "3.10.7"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"vue": "^2.7.0 || ^3.0.0"
}
},
"node_modules/@types/estree": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"dev": true,
"license": "MIT"
},
"node_modules/@vitejs/plugin-vue": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.1.2.tgz",
@@ -512,6 +577,12 @@
"node": ">=6"
}
},
"node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"license": "MIT"
},
"node_modules/fast-glob": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
@@ -821,6 +892,16 @@
"node": ">=8.6"
}
},
"node_modules/mini-svg-data-uri": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
"integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==",
"dev": true,
"license": "MIT",
"bin": {
"mini-svg-data-uri": "cli.js"
}
},
"node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
@@ -1056,6 +1137,12 @@
"node": ">= 6"
}
},
"node_modules/pocketbase": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/pocketbase/-/pocketbase-0.21.5.tgz",
"integrity": "sha512-bnI/uinnQps+ElSlzxkc4yvwuSFfKcoszDtXH/4QT2FhGq2mJVUvDlxn+rjRXVntUjPfmMG5LEPZ1eGqV6ssog==",
"license": "MIT"
},
"node_modules/postcss": {
"version": "8.4.41",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz",
@@ -1679,6 +1766,13 @@
}
}
},
"node_modules/vite-plugin-remove-console": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/vite-plugin-remove-console/-/vite-plugin-remove-console-2.2.0.tgz",
"integrity": "sha512-qgjh5pz75MdE9Kzs8J0kBwaCfifHV0ezRbB9rpGsIOxam+ilcGV7WOk91vFJXquzRmiKrFh3Hxlh0JJWAmXTbQ==",
"dev": true,
"license": "MIT"
},
"node_modules/vite/node_modules/@esbuild/aix-ppc64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
@@ -2294,13 +2388,6 @@
"win32"
]
},
"node_modules/vite/node_modules/@types/estree": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"dev": true,
"license": "MIT"
},
"node_modules/vite/node_modules/esbuild": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
@@ -2577,12 +2664,6 @@
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/vue/node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"license": "MIT"
},
"node_modules/vue/node_modules/magic-string": {
"version": "0.30.11",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz",

View File

@@ -9,15 +9,19 @@
"preview": "vite preview"
},
"dependencies": {
"@headlessui/vue": "^1.7.22",
"pinia": "^2.1.7",
"pocketbase": "^0.21.5",
"vue": "^3.4.29",
"vue-router": "^4.3.3"
},
"devDependencies": {
"@tailwindcss/forms": "^0.5.9",
"@vitejs/plugin-vue": "^5.0.5",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.41",
"tailwindcss": "^3.4.10",
"vite": "^5.3.1"
"vite": "^5.3.1",
"vite-plugin-remove-console": "^2.2.0"
}
}

17
src/components/404.vue Normal file
View File

@@ -0,0 +1,17 @@
<template>
<div class="h-screen w-screen bg-gray-50 flex items-center">
<div class="container flex flex-col md:flex-row items-center justify-between px-5 text-gray-700">
<div class="w-full lg:w-1/2 mx-8">
<div class="text-7xl text-green-500 font-dark font-extrabold mb-8"> 404</div>
<p class="text-2xl md:text-3xl font-light leading-normal mb-8">
Sorry we couldn't find the page you're looking for
</p>
<a href="/" class="px-5 inline py-3 text-sm font-medium leading-5 shadow-2xl text-white transition-all duration-400 border border-transparent rounded-lg focus:outline-none bg-green-600 active:bg-red-600 hover:bg-red-700">back to homepage</a>
</div>
<div class="w-full lg:flex lg:justify-end lg:w-1/2 mx-5 my-12">
<img src="https://user-images.githubusercontent.com/43953425/166269493-acd08ccb-4df3-4474-95c7-ad1034d3c070.svg" class="" alt="Page not found">
</div>
</div>
</div></template>

121
src/components/Lista.vue Normal file
View File

@@ -0,0 +1,121 @@
<template>
<head>
<link rel="stylesheet"
href="https://horizon-tailwind-react-git-tailwind-components-horizon-ui.vercel.app/static/css/main.ad49aa9b.css" />
<link rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@48,400,1,0" />
</head>
<body>
<div class="flex flex-col justify-center items-center h-[100vh]">
<div
class="!z-5 relative flex flex-col rounded-[20px] max-w-[300px] bg-white bg-clip-border shadow-3xl shadow-shadow-500 flex flex-col w-full !p-4 3xl:p-![18px] bg-white undefined">
<div class="relative flex flex-row justify-between">
<div class="flex items-center">
<div
class="flex h-9 w-9 items-center justify-center rounded-full bg-indigo-100 dark:bg-indigo-100 dark:bg-white/5">
<span class="material-symbols-rounded h-6 w-6 text-brand-500 dark:text-white">
check_circle
</span>
</div>
<h4 class="ml-4 text-xl font-bold text-navy-700 dark:text-white">
{{ nombreLista }}
</h4>
</div>
<button
class='flex items-center text-xl hover:cursor-pointer bg-lightPrimary p-2 text-brand-500 hover:bg-gray-100 dark:bg-navy-700 dark:text-white dark:hover:bg-white/20 dark:active:bg-white/10 rounded-lg'>
<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 16 16"
class="h-6 w-6" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg">
<path
d="M3 9.5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3z">
</path>
</svg>
</button>
</div>
<div class="h-full w-full">
<div class="mt-2 flex items-center justify-between" v-for="item in items">
<div class="flex items-center justify-center gap-7">
<input type="checkbox"
class="defaultCheckbox relative flex h-[20px] min-h-[20px] w-[20px] min-w-[20px] appearance-none items-center
justify-center rounded-md border border-gray-300 text-white/0 outline-none transition duration-[0.2s]
checked:border-none checked:text-white hover:cursor-pointer dark:border-white/10 checked:bg-brand-500 dark:checked:bg-brand-400"
name="weekly" />
<p class="text-base text-navy-700 dark:text-white" :class="{ 'line-through': item.completado, 'font-bold': !item.completado }">
{{ item.nombre }}
</p>
</div>
<span class="material-symbols-rounded h-6 w-6 text-navy-700 dark:text-white cursor-pointer">
drag_indicator
</span>
</div>
</div>
</div>
</div>
</body>
<div>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import PocketBase from 'pocketbase';
import { useUserStore } from '@/stores/user'
// access the `store` variable anywhere in the component ✨
const storeUser = useUserStore()
const route = useRoute()
let id_lista = route.params.id
let items = ref()
let pb = null
let nombreLista = ref('')
onMounted(async () => {
pb = new PocketBase(storeUser.urlPocketbase);
items.value = await pb.collection('item').getFullList( {
filter: `field.id = '${id_lista}'`,
});
ordenarItems()
const lista = await pb.collection('listas').getOne(id_lista, {
expand: 'nombre',
});
nombreLista.value = lista.nombre
console.log(nombreLista.value)
})
/**
* Sorts the items in descending order of their last updated date.
*/
const ordenarItems = () => {
items.value.sort((a, b) => {
if (a.updated < b.updated) {
return 1
}
if (a.updated > b.updated) {
return -1
}
return 0
})
items.value.sort((a, b) => {
if (a.completado < b.completado) {
return -1
}
if (a.completado > b.completado) {
return 1
}
return 0
})
}
</script>

View File

@@ -1,3 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@tailwind utilities;

View File

@@ -1,14 +1,16 @@
import './index.css'
import "./index.css";
import { createApp } from "vue";
import { createPinia } from "pinia";
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
const app = createApp(App)
import App from "./App.vue";
import router from "./router";
app.use(createPinia())
const app = createApp(App);
app.use(createPinia());
app.use(router)
app.mount('#app')
app.mount("#app");

View File

@@ -1,22 +1,66 @@
import { createRouter, createWebHistory } from 'vue-router'
import { createRouter, createWebHistory } from "vue-router";
import { useUserStore } from "@/stores/user";
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'Login',
component: () => import('../views/LoginView.vue')
path: "/",
name: "Login",
component: () => import("../views/LoginView.vue"),
},
{
path: '/about',
name: 'about',
{
path: "/registro",
name: "Registro",
component: () => import("../views/RegistroView.vue"),
},
{
path: "/listas",
name: "listas",
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('../views/AboutView.vue')
}
]
})
component: () => import("../views/ListasView.vue"),
meta: { requiresAuth: true },
},
{
path: "/lista/:id",
name: "lista",
component: () => import("../components/Lista.vue"),
meta: { requiresAuth: true },
},
{
path: "/:pathMatch(.*)*",
name: "NotFound",
component: () => import("../components/404.vue"),
},
],
});
export default router
router.beforeEach((to, from) => {
// ✅ This will work because the router starts its navigation after
// the router is installed and pinia will be installed too
const storeUser = useUserStore();
if (!from.meta.requiresAuth && to.meta.requiresAuth && !storeUser.isValid) {
console.log("caso 1", from.name);
if (from.name) {
console.log("1a");
return false;
} else {
console.log("1b");
return "/";
}
}
if (to.fullPath == "/") {
if (from.fullPath!="/registro") {
console.log("caso 2a");
storeUser.user = "";
storeUser.isValid = false;
} else {
console.log('caso 2b')
}
}
});
export default router;

10
src/stores/user.js Normal file
View File

@@ -0,0 +1,10 @@
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', () => {
const user = ref()
const isValid = ref(false)
const urlPocketbase = "http://127.0.0.1:8090"
return { user, isValid, urlPocketbase }
})

View File

@@ -1,14 +0,0 @@
<template>
<h1 class="text-3xl font-bold underline">
About Page Gooooo
{{ storeCounter.count }}
</h1>
<router-link to="/">Go to Login</router-link>
</template>
<script setup>
import { useCounterStore } from '@/stores/counter'
// access the `store` variable anywhere in the component ✨
const storeCounter = useCounterStore()
</script>

219
src/views/ListasView.vue Normal file
View File

@@ -0,0 +1,219 @@
<template>
<div class="bg-emerald-700 flex flex-col gap-4 h-screen items-center justify-center">
<div id="fijo" class="absolute top-0 my-6 flex flex-col gap-4">
<h1 class="text-2xl font-bold mb-2 text-gray-900 mx-10">
Las listas de {{ storeUser.user.name }}
</h1>
<!-- input -->
<div class="relative mx-auto">
<label for="newListaName" class="sr-only"> Nueva lista </label>
<input type="text" id="newListaName" placeholder="Nueva Lista"
class="w-full rounded-md border-gray-200 py-2.5 pe-10 shadow-sm sm:text-sm" v-model="newListaName" />
<span class="absolute inset-y-0 end-0 grid w-10 place-content-center">
<button type="button" class="text-gray-600 hover:text-gray-700" @click="createList">
<span class="sr-only">Search</span>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
</svg>
</button>
</span>
</div>
</div>
<!-- Card 1 -->
<div class="bg-emerald-700 overflow-auto w-full absolute my-6 flex flex-col top-40">
<template v-for="lista in listas">
<div
class="my-2 mx-auto rounded-md max-w-lg min-w-5 grid grid-cols-6 bg-emerald-100 shadow p-3 items-center hover:shadow-lg transition delay-150 duration-300 ease-in-out hover:scale-105 transform">
<!-- Title -->
<router-link :to="`/lista/${lista.id}`">
<div class="col-span-11 xl:-ml-1">
<p class="text-indigo-500 hover:text-indigo-700 font-semibold">
{{ lista.nombre }}
</p>
</div>
<!-- Description -->
<div class="md:col-start-9 col-span-5 xl:-ml-0">
<div class="text-sm text-gray-800 font-light">
{{ lista.descripcion }}
</div>
</div>
</router-link><!-- Icons -->
<div class="grid grid-cols-12 gap-6">
<div class="col-start-10 grid">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="size-6" @click="editarLista">
<path stroke-linecap="round" stroke-linejoin="round"
d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" />
</svg>
</div>
<div><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="size-6" @click="borrarLista">
<path stroke-linecap="round" stroke-linejoin="round"
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
</svg>
</div>
</div>
</div>
</template>
</div>
<!-- Modal de nueva lista no puede estar vacía -->
<TransitionRoot appear :show="isOpenModalEmptyList" as="template">
<Dialog as="div" @close="isOpenModalEmptyList = false" class="relative z-10">
<TransitionChild as="template" enter="duration-300 ease-out" enter-from="opacity-0" enter-to="opacity-100"
leave="duration-200 ease-in" leave-from="opacity-100" leave-to="opacity-0">
<div class="fixed inset-0 bg-black/25" />
</TransitionChild>
<div class="fixed inset-0 overflow-y-auto">
<div class="flex min-h-full items-center justify-center p-4 text-center">
<TransitionChild as="template" enter="duration-300 ease-out" enter-from="opacity-0 scale-95"
enter-to="opacity-100 scale-100" leave="duration-200 ease-in" leave-from="opacity-100 scale-100"
leave-to="opacity-0 scale-95">
<DialogPanel
class="w-full max-w-md transform overflow-hidden rounded-2xl bg-emerald-300 p-6 text-left align-middle shadow-xl transition-all">
<DialogTitle as="h3" class="text-lg font-medium leading-6 text-red-600">
Error
</DialogTitle>
<div class="mt-2">
<p class="text-gray-500 text-md">
No puede estar el nombre de la lista vacía
</p>
</div>
<div class="mt-4">
<button type="button"
class="inline-flex justify-center rounded-md border border-transparent bg-blue-100 px-4 py-2 text-sm font-medium text-blue-900 hover:bg-blue-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
@click="isOpenModalEmptyList = false">
Aceptar
</button>
</div>
</DialogPanel>
</TransitionChild>
</div>
</div>
</Dialog>
</TransitionRoot>
<!-- Modal de pedir descripción -->
<TransitionRoot appear :show="isOpenModalDescription" as="template">
<Dialog as="div" @close="cancelarLista" class="relative z-10">
<TransitionChild as="template" enter="duration-300 ease-out" enter-from="opacity-0" enter-to="opacity-100"
leave="duration-200 ease-in" leave-from="opacity-100" leave-to="opacity-0">
<div class="fixed inset-0 bg-black/25" />
</TransitionChild>
<div class="fixed inset-0 overflow-y-auto">
<div class="flex min-h-full items-center justify-center p-4 text-center">
<TransitionChild as="template" enter="duration-300 ease-out" enter-from="opacity-0 scale-95"
enter-to="opacity-100 scale-100" leave="duration-200 ease-in" leave-from="opacity-100 scale-100"
leave-to="opacity-0 scale-95">
<DialogPanel
class="w-full max-w-md transform overflow-hidden rounded-2xl bg-emerald-300 p-6 text-left align-middle shadow-xl transition-all">
<DialogTitle as="h3" class="text-lg font-medium leading-6 text-gray-900">
Descripción de la lista
</DialogTitle>
<div class="mt-2">
<input
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline mb-2"
id="descripción" type="text" placeholder="Breve descripción" v-model="descripcion" />
</div>
<div class="mt-4 justify-between flex">
<button type="button"
class="inline-flex justify-center rounded-md border border-transparent bg-red-100 px-4 py-2 text-sm font-medium text-red-900 hover:bg-red-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-red-500 focus-visible:ring-offset-2"
@click="cancelarLista">
Cancelar
</button>
<button type="button"
class="inline-flex justify-center rounded-md border border-transparent bg-blue-100 px-4 py-2 text-sm font-medium text-blue-900 hover:bg-blue-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
@click="guardarLista">
Aceptar
</button>
</div>
</DialogPanel>
</TransitionChild>
</div>
</div>
</Dialog>
</TransitionRoot>
</div>
</template>
<script setup>
import { useUserStore } from "@/stores/user";
import { onMounted, ref } from "vue";
import PocketBase from "pocketbase";
import {
TransitionRoot,
TransitionChild,
Dialog,
DialogPanel,
DialogTitle,
} from "@headlessui/vue";
// access the `store` variable anywhere in the component ✨
const storeUser = useUserStore();
let pb = ref();
let listas = ref();
let newListaName = ref();
let descripcion = ref();
let isOpenModalEmptyList = ref(false);
let isOpenModalDescription = ref(false);
onMounted(async () => {
pb = new PocketBase(storeUser.urlPocketbase);
listas.value = await pb.collection("listas").getFullList({
sort: "-created",
});
});
const createList = () => {
if (newListaName.value) {
isOpenModalDescription.value = true;
// modal pidiendo descripción
} else {
isOpenModalEmptyList.value = true;
// modal no puede estar vacío
}
};
const guardarLista = async () => {
isOpenModalDescription.value = false;
const newLista = {
nombre: newListaName.value,
descripcion: descripcion.value,
field: storeUser.user.id,
};
const record = await pb
.collection("listas")
.create(newLista)
.then(function (record) {
console.log(record);
listas.value.unshift(record);
newListaName.value = "";
descripcion.value = "";
})
.catch(function (error) {
console.log(error);
});
};
const cancelarLista = () => {
isOpenModalDescription.value = false;
descripcion.value = "";
newListaName.value = "";
};
const editarLista = () => {
console.log('editar caca')
}
const borrarLista = () => {
console.log('borrar caca')
}
</script>

View File

@@ -1,32 +1,393 @@
<template>
<h1 class="text-3xl font-bold underline">
Login Page Gooooo
</h1>
<router-link to="/about">Go to About</router-link>
</template>
<!-- https://www.creative-tim.com/twcomponents/component/simple-login-form-3 -->
<div
class="min-h-screen flex items-center justify-center w-full bg-emerald-700"
>
<div
class="bg-emerald-200 shadow-md rounded-lg px-8 py-6 ancho min-w-[300px]"
>
<h1 class="text-2xl font-bold text-center mb-4 dark:text-gray">
Logueate!
</h1>
<form action="#">
<div class="mb-4">
<label
for="email"
class="block text-sm font-medium text-gray-700 dark:text-gray-600 mb-2"
>Correo electrónico</label
>
<input
type="email"
id="email"
v-model="email"
class="shadow-sm rounded-md w-full px-3 py-2 border border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
placeholder="your@email.com"
required
/>
</div>
<div class="mb-4">
<label
for="password"
class="block text-sm font-medium text-gray-700 dark:text-gray-600 mb-2"
>Contraseña</label
>
<input
type="password"
id="password"
v-model="password"
class="shadow-sm rounded-md w-full px-3 py-2 border border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
placeholder="Enter your password"
required
/>
<a
href="#"
class="text-xs text-blue-400 hover:text-indigo-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
@click="isOpenModalRecuPContras = true"
>Olvidó la contraseña?</a
>
</div>
<div class="flex items-center justify-between mb-4">
<div class="flex items-center"></div>
<router-link
to="/registro"
class="text-xs text-indigo-500 hover:text-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>Crear cuenta</router-link
>
</div>
<button
@click="login"
type="button"
class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Login
</button>
</form>
</div>
</div>
<!-- Modal de correo electrónico enviado -->
<TransitionRoot appear :show="isOpenModalSend" as="template">
<Dialog as="div" @close="isOpenModalSend = false" class="relative z-10">
<TransitionChild
as="template"
enter="duration-300 ease-out"
enter-from="opacity-0"
enter-to="opacity-100"
leave="duration-200 ease-in"
leave-from="opacity-100"
leave-to="opacity-0"
>
<div class="fixed inset-0 bg-black/25" />
</TransitionChild>
<div class="fixed inset-0 overflow-y-auto">
<div
class="flex min-h-full items-center justify-center p-4 text-center"
>
<TransitionChild
as="template"
enter="duration-300 ease-out"
enter-from="opacity-0 scale-95"
enter-to="opacity-100 scale-100"
leave="duration-200 ease-in"
leave-from="opacity-100 scale-100"
leave-to="opacity-0 scale-95"
>
<DialogPanel
class="w-full max-w-md transform overflow-hidden rounded-2xl bg-emerald-300 p-6 text-left align-middle shadow-xl transition-all"
>
<DialogTitle
as="h3"
class="text-lg font-medium leading-6 text-green-600"
>
Enviado
</DialogTitle>
<div class="mt-2">
<p class="text-gray-500 text-md">Correo electrónico enviado</p>
</div>
<div class="mt-4">
<button
type="button"
class="inline-flex justify-center rounded-md border border-transparent bg-blue-100 px-4 py-2 text-sm font-medium text-blue-900 hover:bg-blue-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
@click="isOpenModalSend = false"
>
Aceptar
</button>
</div>
</DialogPanel>
</TransitionChild>
</div>
</div>
</Dialog>
</TransitionRoot>
<!-- Modal de correo invalido -->
<TransitionRoot appear :show="isOpenModalWrongPass" as="template">
<Dialog
as="div"
@close="isOpenModalWrongPass = false"
class="relative z-10"
>
<TransitionChild
as="template"
enter="duration-300 ease-out"
enter-from="opacity-0"
enter-to="opacity-100"
leave="duration-200 ease-in"
leave-from="opacity-100"
leave-to="opacity-0"
>
<div class="fixed inset-0 bg-black/25" />
</TransitionChild>
<div class="fixed inset-0 overflow-y-auto">
<div
class="flex min-h-full items-center justify-center p-4 text-center"
>
<TransitionChild
as="template"
enter="duration-300 ease-out"
enter-from="opacity-0 scale-95"
enter-to="opacity-100 scale-100"
leave="duration-200 ease-in"
leave-from="opacity-100 scale-100"
leave-to="opacity-0 scale-95"
>
<DialogPanel
class="w-full max-w-md transform overflow-hidden rounded-2xl bg-emerald-300 p-6 text-left align-middle shadow-xl transition-all"
>
<DialogTitle
as="h3"
class="text-lg font-medium leading-6 text-red-600"
>
Error
</DialogTitle>
<div class="mt-2">
<p class="text-gray-500 text-md">
Error en el correo electrónico
</p>
</div>
<div class="mt-4">
<button
type="button"
class="inline-flex justify-center rounded-md border border-transparent bg-blue-100 px-4 py-2 text-sm font-medium text-blue-900 hover:bg-blue-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
@click="isOpenModalWrongPass = false"
>
Aceptar
</button>
</div>
</DialogPanel>
</TransitionChild>
</div>
</div>
</Dialog>
</TransitionRoot>
<!-- Modal de recuperar contraseña -->
<TransitionRoot appear :show="isOpenModalRecuPContras" as="template">
<Dialog
as="div"
@close="isOpenModalRecuPContras = false"
class="relative z-10"
>
<TransitionChild
as="template"
enter="duration-300 ease-out"
enter-from="opacity-0"
enter-to="opacity-100"
leave="duration-200 ease-in"
leave-from="opacity-100"
leave-to="opacity-0"
>
<div class="fixed inset-0 bg-black/25" />
</TransitionChild>
<div class="fixed inset-0 overflow-y-auto">
<div
class="flex min-h-full items-center justify-center p-4 text-center"
>
<TransitionChild
as="template"
enter="duration-300 ease-out"
enter-from="opacity-0 scale-95"
enter-to="opacity-100 scale-100"
leave="duration-200 ease-in"
leave-from="opacity-100 scale-100"
leave-to="opacity-0 scale-95"
>
<DialogPanel
class="w-full max-w-md transform overflow-hidden rounded-2xl bg-emerald-300 p-6 text-left align-middle shadow-xl transition-all"
>
<DialogTitle
as="h3"
class="text-lg font-medium leading-6 text-gray-900"
>
Recuperar contraseña
</DialogTitle>
<div class="mt-2">
<input
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline mb-2"
id="username"
type="text"
placeholder="email@example.com"
v-model="email"
/>
<p class="text-gray-500 text-sm">
Si el email existe en nuestra base de datos, le enviaremos un
correo electrónico de recuperación.
</p>
</div>
<div class="mt-4 justify-between flex">
<button
type="button"
class="inline-flex justify-center rounded-md border border-transparent bg-red-100 px-4 py-2 text-sm font-medium text-red-900 hover:bg-red-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-red-500 focus-visible:ring-offset-2"
@click="isOpenModalRecuPContras = false"
>
Cancelar
</button>
<button
type="button"
class="inline-flex justify-center rounded-md border border-transparent bg-blue-100 px-4 py-2 text-sm font-medium text-blue-900 hover:bg-blue-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
@click="requestPasswordReset"
>
Aceptar
</button>
</div>
</DialogPanel>
</TransitionChild>
</div>
</div>
</Dialog>
</TransitionRoot>
<!-- Modal de login incorrecto -->
<TransitionRoot appear :show="isOpenModalWrongLogin" as="template">
<Dialog
as="div"
@close="isOpenModalWrongLogin = false"
class="relative z-10"
>
<TransitionChild
as="template"
enter="duration-300 ease-out"
enter-from="opacity-0"
enter-to="opacity-100"
leave="duration-200 ease-in"
leave-from="opacity-100"
leave-to="opacity-0"
>
<div class="fixed inset-0 bg-black/25" />
</TransitionChild>
<div class="fixed inset-0 overflow-y-auto">
<div
class="flex min-h-full items-center justify-center p-4 text-center"
>
<TransitionChild
as="template"
enter="duration-300 ease-out"
enter-from="opacity-0 scale-95"
enter-to="opacity-100 scale-100"
leave="duration-200 ease-in"
leave-from="opacity-100 scale-100"
leave-to="opacity-0 scale-95"
>
<DialogPanel
class="w-full max-w-md transform overflow-hidden rounded-2xl bg-emerald-300 p-6 text-left align-middle shadow-xl transition-all"
>
<DialogTitle
as="h3"
class="text-lg font-medium leading-6 text-red-600"
>
Error
</DialogTitle>
<div class="mt-2">
<p class="text-gray-500 text-md">Parámetros Incorrectos</p>
</div>
<div class="mt-4">
<button
type="button"
class="inline-flex justify-center rounded-md border border-transparent bg-blue-100 px-4 py-2 text-sm font-medium text-blue-900 hover:bg-blue-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
@click="isOpenModalWrongLogin = false"
>
Aceptar
</button>
</div>
</DialogPanel>
</TransitionChild>
</div>
</div>
</Dialog>
</TransitionRoot>
</template>
<script setup>
import { useCounterStore } from '@/stores/counter'
import { computed, ref } from 'vue'
import { useUserStore } from "@/stores/user";
import { onMounted, ref } from "vue";
import PocketBase from "pocketbase";
import router from "@/router";
import {
TransitionRoot,
TransitionChild,
Dialog,
DialogPanel,
DialogTitle,
} from "@headlessui/vue";
// access the `store` variable anywhere in the component ✨
const storeCounter = useCounterStore()
const storeUser = useUserStore();
let count = ref(0)
let pb = null;
let email = ref("p40store@gmail.com");
let password = ref("p40store");
let isOpenModalRecuPContras = ref(false);
let isOpenModalWrongPass = ref(false);
let isOpenModalSend = ref(false);
let isOpenModalWrongLogin = ref(false);
onMounted(() => {
pb = new PocketBase(storeUser.urlPocketbase);
});
console.log(storeCounter.count) // 0
const login = async () => {
const authData = await pb
.collection("users")
.authWithPassword(email.value, password.value)
.then(function (result) {
console.log(pb.authStore.isValid);
storeUser.user = pb.authStore.model;
storeUser.isValid = pb.authStore.isValid;
console.log(storeUser.user);
if (storeUser.isValid) {
router.push("listas");
}
})
.catch(function () {
isOpenModalWrongLogin.value = true;
});
};
storeCounter.increment()
console.log(storeCounter.count) // 1
const requestPasswordReset = async () => {
const authData = await pb
.collection("users")
.requestPasswordReset(email.value)
.then(function (result) {
isOpenModalSend.value = true;
})
.catch(function (error) {
console.log(error);
isOpenModalWrongPass.value = true;
});
isOpenModalRecuPContras.value = false;
email.value = "";
password.value = "";
};
count = computed(() => storeCounter.doubleCount)
console.log(count.value) // 2
console.log(storeCounter.count) // 1
storeCounter.changeCount(19)
console.log(storeCounter.count) // 10
storeCounter.count = computed(() => storeCounter.doubleCount)
console.log(storeCounter.count) // 20 */
</script>
const cerrar = () => {
window.HSOverlay.close("#hs-vertically-centered-modal");
};
</script>
<style>
.ancho {
width: 30%;
}
</style>

276
src/views/RegistroView.vue Normal file
View File

@@ -0,0 +1,276 @@
<template>
<!-- https://www.creative-tim.com/twcomponents/component/simple-login-form-3 -->
<div
class="min-h-screen flex items-center justify-center w-full dark:bg-emerald-700"
>
<div
class="bg-white dark:bg-emerald-200 shadow-md rounded-lg px-8 py-6 ancho min-w-[300px]"
>
<h1 class="text-2xl font-bold text-center mb-4 dark:text-gray">
Crea una cuenta!
</h1>
<form action="#">
<div class="mb-4">
<label
for="email"
class="block text-sm font-medium text-gray-700 dark:text-gray-600 mb-2"
>Correo electrónico</label
>
<input
type="email"
id="email"
v-model="email"
class="shadow-sm rounded-md w-full px-3 py-2 border border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
placeholder="your@email.com"
required
/>
</div>
<div class="mb-4">
<label
for="email"
class="block text-sm font-medium text-gray-700 dark:text-gray-600 mb-2"
>Nombre</label
>
<input
type="text"
id="nombre"
v-model="nombre"
class="shadow-sm rounded-md w-full px-3 py-2 border border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
placeholder="Nombre completo"
required
/>
</div>
<div class="mb-4">
<label
for="password"
class="block text-sm font-medium text-gray-700 dark:text-gray-600 mb-2"
>Contraseña</label
>
<input
type="password"
id="password"
v-model="password"
class="shadow-sm rounded-md w-full px-3 py-2 border border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 mb-4"
placeholder="Enter your password"
required
/>
<label
for="passwordRepeat"
class="block text-sm font-medium text-gray-700 dark:text-gray-600 mb-2"
>Repita su contraseña</label
>
<input
type="password"
id="passwordRepeat"
v-model="passwordRepeat"
class="shadow-sm rounded-md w-full px-3 py-2 border border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
placeholder="Enter your password again"
required
/>
</div>
<div class="flex items-center justify-between mb-4">
<div class="flex items-center"></div>
<router-link
to="/"
class="text-xs text-indigo-500 hover:text-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>Logueate</router-link
>
</div>
<button
@click="registro"
type="button"
class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Registrar
</button>
</form>
</div>
</div>
<!-- modal no se ha podido registrar -->
<TransitionRoot appear :show="isOpenModalWrongRegister" as="template">
<Dialog
as="div"
@close="isOpenModalWrongRegister = false"
class="relative z-10"
>
<TransitionChild
as="template"
enter="duration-300 ease-out"
enter-from="opacity-0"
enter-to="opacity-100"
leave="duration-200 ease-in"
leave-from="opacity-100"
leave-to="opacity-0"
>
<div class="fixed inset-0 bg-black/25" />
</TransitionChild>
<div class="fixed inset-0 overflow-y-auto">
<div
class="flex min-h-full items-center justify-center p-4 text-center"
>
<TransitionChild
as="template"
enter="duration-300 ease-out"
enter-from="opacity-0 scale-95"
enter-to="opacity-100 scale-100"
leave="duration-200 ease-in"
leave-from="opacity-100 scale-100"
leave-to="opacity-0 scale-95"
>
<DialogPanel
class="w-full max-w-md transform overflow-hidden rounded-2xl bg-emerald-300 p-6 text-left align-middle shadow-xl transition-all"
>
<DialogTitle
as="h3"
class="text-lg font-medium leading-6 text-red-600"
>
Error
</DialogTitle>
<div class="mt-2">
<p class="text-gray-500 text-md">
{{
password === passwordRepeat
? "Ha sido imposible crear al nuevo usuario!!"
: "Las contraseñas no coinciden"
}}
</p>
</div>
<div class="mt-4">
<button
type="button"
class="inline-flex justify-center rounded-md border border-transparent bg-blue-100 px-4 py-2 text-sm font-medium text-blue-900 hover:bg-blue-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
@click="isOpenModalWrongRegister = false"
>
Aceptar
</button>
</div>
</DialogPanel>
</TransitionChild>
</div>
</div>
</Dialog>
</TransitionRoot>
<!-- Modal de registro correcto -->
<TransitionRoot appear :show="isOpenModalOkRegister" as="template">
<Dialog as="div" @close="cerrarModalOkRegister" class="relative z-10">
<TransitionChild
as="template"
enter="duration-300 ease-out"
enter-from="opacity-0"
enter-to="opacity-100"
leave="duration-200 ease-in"
leave-from="opacity-100"
leave-to="opacity-0"
>
<div class="fixed inset-0 bg-black/25" />
</TransitionChild>
<div class="fixed inset-0 overflow-y-auto">
<div
class="flex min-h-full items-center justify-center p-4 text-center"
>
<TransitionChild
as="template"
enter="duration-300 ease-out"
enter-from="opacity-0 scale-95"
enter-to="opacity-100 scale-100"
leave="duration-200 ease-in"
leave-from="opacity-100 scale-100"
leave-to="opacity-0 scale-95"
>
<DialogPanel
class="w-full max-w-md transform overflow-hidden rounded-2xl bg-emerald-300 p-6 text-left align-middle shadow-xl transition-all"
>
<DialogTitle
as="h3"
class="text-lg font-medium leading-6 text-green-600"
>
Aviso
</DialogTitle>
<div class="mt-2">
<p class="text-gray-500 text-md">
Registro realizado correctamente
</p>
</div>
<div class="mt-4">
<button
type="button"
class="inline-flex justify-center rounded-md border border-transparent bg-blue-100 px-4 py-2 text-sm font-medium text-blue-900 hover:bg-blue-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
@click="cerrarModalOkRegister"
>
Aceptar
</button>
</div>
</DialogPanel>
</TransitionChild>
</div>
</div>
</Dialog>
</TransitionRoot>
</template>
<script setup>
import { useUserStore } from "@/stores/user";
import { onMounted, ref } from "vue";
import PocketBase from "pocketbase";
import router from "@/router";
import {
TransitionRoot,
TransitionChild,
Dialog,
DialogPanel,
DialogTitle,
} from "@headlessui/vue";
// access the `store` variable anywhere in the component ✨
const storeUser = useUserStore();
let pb = null;
let email = ref("");
let password = ref("");
let passwordRepeat = ref("");
let nombre = ref("");
let isOpenModalWrongRegister = ref(false);
let isOpenModalOkRegister = ref(false);
onMounted(() => {
pb = new PocketBase(storeUser.urlPocketbase);
});
const registro = async () => {
const newUser = {
username: email.value.split("@")[0],
email: email.value,
emailVisibility: true,
password: password.value,
passwordConfirm: passwordRepeat.value,
name: nombre.value,
};
const record = await pb
.collection("users")
.create(newUser)
.then(function (record) {
console.log("New user created with ID: ", record.id);
isOpenModalOkRegister.value = true;
})
.catch(function (error) {
isOpenModalWrongRegister.value = true;
email = ref("");
password = ref("");
passwordRepeat = ref("");
nombre = ref("");
});
};
const cerrarModalOkRegister = () => {
console.log("cerrado modal");
router.push("/");
};
</script>
<style>
.ancho {
width: 30%;
}
</style>

View File

@@ -6,5 +6,6 @@ module.exports = {
theme: {
extend: {},
},
plugins: [],
}
plugins: [
require("@tailwindcss/forms")],
};

View File

@@ -1,16 +1,18 @@
import { fileURLToPath, URL } from 'node:url'
import { fileURLToPath, URL } from "node:url";
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import removeConsole from "vite-plugin-remove-console";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
],
plugins: [vue(), removeConsole()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
esbuild: { // disable logs in production?
drop: ["console", "debugger"],
},
});