Compare commits
34 Commits
cedf74fcfe
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| f1f09029ba | |||
| 221f89c1e4 | |||
| 1753c6ee33 | |||
| 14f7d0e67e | |||
| 109ad712bf | |||
| ee21c44afb | |||
| b17f4e7f1e | |||
| 0da30c0563 | |||
| 6029364240 | |||
| 6e60580f9d | |||
| 4f329ba46a | |||
| 62a225633c | |||
| 9fdc68296b | |||
| 8ccd713672 | |||
| 6fd6b82440 | |||
| 51b1086341 | |||
| 525f3f4da7 | |||
| 5a2bffe57a | |||
| c59133df8d | |||
| 0bbe44b739 | |||
| 80237f72c9 | |||
| 5464b71e3d | |||
| 7cc30ee302 | |||
| fabb6a7c98 | |||
| 2d7b1dee75 | |||
| 2b6a40f795 | |||
| a6dc62496e | |||
| 4291ccbe63 | |||
| 347d6ab147 | |||
| 60d295a5c5 | |||
| a9a9124513 | |||
| e029307aac | |||
| 0595570cf0 | |||
| 1396fe531a |
19
Dockerfile
Normal file
@@ -0,0 +1,19 @@
|
||||
# develop stage
|
||||
FROM node:14.21.2-alpine as develop-stage
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm install -g npm@latest
|
||||
RUN npm i -g @quasar/cli
|
||||
COPY . .
|
||||
|
||||
# build stage
|
||||
FROM develop-stage as build-stage
|
||||
RUN npm i
|
||||
RUN quasar build
|
||||
|
||||
# production stage
|
||||
FROM nginx:1.17.5-alpine as production-stage
|
||||
COPY --from=build-stage /app/dist/spa /usr/share/nginx/html
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
EXPOSE 80
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
43
README.md
@@ -1,41 +1,20 @@
|
||||
# PocketBase (frontend-pocketbase)
|
||||
Quasar App (ejercicio_quasar)
|
||||
|
||||
frontend of pocketbase backend
|
||||
### Crear la imagen
|
||||
|
||||
## Install the dependencies
|
||||
```bash
|
||||
yarn
|
||||
# or
|
||||
npm install
|
||||
```
|
||||
docker build -t dockerize-quasar-listas .
|
||||
```
|
||||
|
||||
### Start the app in development mode (hot-code reloading, error reporting, etc.)
|
||||
```bash
|
||||
quasar dev
|
||||
### borrar y crear contenedor
|
||||
|
||||
```
|
||||
docker rm -f listas && docker run -d --name=listas --restart unless-stopped -p 33329:80 dockerize-quasar-listas
|
||||
```
|
||||
|
||||
### purgar docker
|
||||
|
||||
### Lint the files
|
||||
```bash
|
||||
yarn lint
|
||||
# or
|
||||
npm run lint
|
||||
```
|
||||
|
||||
|
||||
### Format the files
|
||||
```bash
|
||||
yarn format
|
||||
# or
|
||||
npm run format
|
||||
docker system prune
|
||||
docker image prune -a
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Build the app for production
|
||||
```bash
|
||||
quasar build
|
||||
```
|
||||
|
||||
### Customize the configuration
|
||||
See [Configuring quasar.config.js](https://v2.quasar.dev/quasar-cli-vite/quasar-config-js).
|
||||
|
||||
6
dockerizar.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
git pull
|
||||
docker build -t dockerize-quasar-listas .
|
||||
docker rm -f listas && docker run -d --name=listas --restart unless-stopped -p 33329:80 dockerize-quasar-listas
|
||||
docker system prune
|
||||
docker image prune -a
|
||||
46
nginx.conf
Normal file
@@ -0,0 +1,46 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
#charset koi8-r;
|
||||
#access_log /var/log/nginx/host.access.log main;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
#error_page 404 /404.html;
|
||||
|
||||
# redirect server error pages to the static page /50x.html
|
||||
#
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
|
||||
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
|
||||
#
|
||||
#location ~ \.php$ {
|
||||
# proxy_pass http://127.0.0.1;
|
||||
#}
|
||||
|
||||
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
|
||||
#
|
||||
#location ~ \.php$ {
|
||||
# root html;
|
||||
# fastcgi_pass 127.0.0.1:9000;
|
||||
# fastcgi_index index.php;
|
||||
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
|
||||
# include fastcgi_params;
|
||||
#}
|
||||
|
||||
# deny access to .htaccess files, if Apache's document root
|
||||
# concurs with nginx's one
|
||||
#
|
||||
#location ~ /\.ht {
|
||||
# deny all;
|
||||
#}
|
||||
}
|
||||
|
||||
3460
package-lock.json
generated
@@ -26,7 +26,14 @@
|
||||
"eslint-config-prettier": "^8.1.0",
|
||||
"eslint-plugin-vue": "^9.0.0",
|
||||
"postcss": "^8.4.14",
|
||||
"prettier": "^2.5.1"
|
||||
"prettier": "^2.5.1",
|
||||
"workbox-build": "^6.5.4",
|
||||
"workbox-cacheable-response": "^6.5.4",
|
||||
"workbox-core": "^6.5.4",
|
||||
"workbox-expiration": "^6.5.4",
|
||||
"workbox-precaching": "^6.5.4",
|
||||
"workbox-routing": "^6.5.4",
|
||||
"workbox-strategies": "^6.5.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18 || ^16 || ^14.19",
|
||||
|
||||
159
pb_schema.json
Normal file
@@ -0,0 +1,159 @@
|
||||
[
|
||||
{
|
||||
"id": "_pb_users_auth_",
|
||||
"name": "users",
|
||||
"type": "auth",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"id": "users_name",
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"system": false,
|
||||
"required": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "users_avatar",
|
||||
"name": "avatar",
|
||||
"type": "file",
|
||||
"system": false,
|
||||
"required": false,
|
||||
"options": {
|
||||
"maxSelect": 1,
|
||||
"maxSize": 5242880,
|
||||
"mimeTypes": [
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/svg+xml",
|
||||
"image/gif",
|
||||
"image/webp"
|
||||
],
|
||||
"thumbs": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
"CREATE INDEX `__pb_users_auth__created_idx` ON `users` (`created`)"
|
||||
],
|
||||
"listRule": "",
|
||||
"viewRule": "id = @request.auth.id",
|
||||
"createRule": "",
|
||||
"updateRule": "id = @request.auth.id",
|
||||
"deleteRule": null,
|
||||
"options": {
|
||||
"allowEmailAuth": true,
|
||||
"allowOAuth2Auth": true,
|
||||
"allowUsernameAuth": true,
|
||||
"exceptEmailDomains": null,
|
||||
"manageRule": null,
|
||||
"minPasswordLength": 8,
|
||||
"onlyEmailDomains": null,
|
||||
"requireEmail": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "wuqkbeb48hlovkc",
|
||||
"name": "lista",
|
||||
"type": "base",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"id": "pmy5olxm",
|
||||
"name": "nombre",
|
||||
"type": "text",
|
||||
"system": false,
|
||||
"required": true,
|
||||
"options": {
|
||||
"min": 2,
|
||||
"max": 50,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "6is0rpp0",
|
||||
"name": "usuarios",
|
||||
"type": "relation",
|
||||
"system": false,
|
||||
"required": true,
|
||||
"options": {
|
||||
"collectionId": "_pb_users_auth_",
|
||||
"cascadeDelete": true,
|
||||
"minSelect": null,
|
||||
"maxSelect": null,
|
||||
"displayFields": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "a6udx3z4",
|
||||
"name": "items",
|
||||
"type": "relation",
|
||||
"system": false,
|
||||
"required": false,
|
||||
"options": {
|
||||
"collectionId": "g4g4sojia3p5qr2",
|
||||
"cascadeDelete": true,
|
||||
"minSelect": null,
|
||||
"maxSelect": null,
|
||||
"displayFields": [
|
||||
"nombre"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
"CREATE INDEX `_wuqkbeb48hlovkc_created_idx` ON `lista` (`created`)"
|
||||
],
|
||||
"listRule": "usuarios.id ?= @request.auth.id",
|
||||
"viewRule": null,
|
||||
"createRule": "@request.auth.id != \"\"",
|
||||
"updateRule": null,
|
||||
"deleteRule": "usuarios.id ?= @request.auth.id",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"id": "g4g4sojia3p5qr2",
|
||||
"name": "items",
|
||||
"type": "base",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"id": "hjfkfx3u",
|
||||
"name": "nombre",
|
||||
"type": "text",
|
||||
"system": false,
|
||||
"required": true,
|
||||
"options": {
|
||||
"min": 2,
|
||||
"max": 50,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "4jogigzv",
|
||||
"name": "cantidad",
|
||||
"type": "number",
|
||||
"system": false,
|
||||
"required": true,
|
||||
"options": {
|
||||
"min": 1,
|
||||
"max": 50
|
||||
}
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
"CREATE INDEX `_g4g4sojia3p5qr2_created_idx` ON `items` (`created`)",
|
||||
"CREATE UNIQUE INDEX \"idx_unique_hjfkfx3u\" on \"items\" (\"nombre\")"
|
||||
],
|
||||
"listRule": null,
|
||||
"viewRule": null,
|
||||
"createRule": null,
|
||||
"updateRule": null,
|
||||
"deleteRule": null,
|
||||
"options": {}
|
||||
}
|
||||
]
|
||||
BIN
public/icons/apple-icon-120x120.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
public/icons/apple-icon-152x152.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
public/icons/apple-icon-167x167.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
public/icons/apple-icon-180x180.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
public/icons/icon-128x128.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/icons/icon-192x192.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
public/icons/icon-256x256.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
public/icons/icon-384x384.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
public/icons/icon-512x512.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
public/icons/ms-icon-144x144.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
1
public/icons/safari-pinned-tab.svg
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
30
src-pwa/custom-service-worker.js
Normal file
@@ -0,0 +1,30 @@
|
||||
/* eslint-env serviceworker */
|
||||
|
||||
/*
|
||||
* This file (which will be your service worker)
|
||||
* is picked up by the build system ONLY if
|
||||
* quasar.config.js > pwa > workboxMode is set to "injectManifest"
|
||||
*/
|
||||
|
||||
import { clientsClaim } from 'workbox-core'
|
||||
import { precacheAndRoute, cleanupOutdatedCaches, createHandlerBoundToURL } from 'workbox-precaching'
|
||||
import { registerRoute, NavigationRoute } from 'workbox-routing'
|
||||
|
||||
self.skipWaiting()
|
||||
clientsClaim()
|
||||
|
||||
// Use with precache injection
|
||||
precacheAndRoute(self.__WB_MANIFEST)
|
||||
|
||||
cleanupOutdatedCaches()
|
||||
|
||||
// Non-SSR fallback to index.html
|
||||
// Production SSR fallback to offline.html (except for dev)
|
||||
if (process.env.MODE !== 'ssr' || process.env.PROD) {
|
||||
registerRoute(
|
||||
new NavigationRoute(
|
||||
createHandlerBoundToURL(process.env.PWA_FALLBACK_HTML),
|
||||
{ denylist: [/sw\.js$/, /workbox-(.)*\.js$/] }
|
||||
)
|
||||
)
|
||||
}
|
||||
32
src-pwa/manifest.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"orientation": "portrait",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#027be3",
|
||||
"icons": [
|
||||
{
|
||||
"src": "icons/icon-128x128.png",
|
||||
"sizes": "128x128",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "icons/icon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "icons/icon-256x256.png",
|
||||
"sizes": "256x256",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "icons/icon-384x384.png",
|
||||
"sizes": "384x384",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "icons/icon-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
}
|
||||
10
src-pwa/pwa-flag.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
/* eslint-disable */
|
||||
// THIS FEATURE-FLAG FILE IS AUTOGENERATED,
|
||||
// REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING
|
||||
import "quasar/dist/types/feature-flag";
|
||||
|
||||
declare module "quasar/dist/types/feature-flag" {
|
||||
interface QuasarFeatureFlags {
|
||||
pwa: true;
|
||||
}
|
||||
}
|
||||
41
src-pwa/register-service-worker.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import { register } from 'register-service-worker'
|
||||
|
||||
// The ready(), registered(), cached(), updatefound() and updated()
|
||||
// events passes a ServiceWorkerRegistration instance in their arguments.
|
||||
// ServiceWorkerRegistration: https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration
|
||||
|
||||
register(process.env.SERVICE_WORKER_FILE, {
|
||||
// The registrationOptions object will be passed as the second argument
|
||||
// to ServiceWorkerContainer.register()
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register#Parameter
|
||||
|
||||
// registrationOptions: { scope: './' },
|
||||
|
||||
ready (/* registration */) {
|
||||
// console.log('Service worker is active.')
|
||||
},
|
||||
|
||||
registered (/* registration */) {
|
||||
// console.log('Service worker has been registered.')
|
||||
},
|
||||
|
||||
cached (/* registration */) {
|
||||
// console.log('Content has been cached for offline use.')
|
||||
},
|
||||
|
||||
updatefound (/* registration */) {
|
||||
// console.log('New content is downloading.')
|
||||
},
|
||||
|
||||
updated (/* registration */) {
|
||||
// console.log('New content is available; please refresh.')
|
||||
},
|
||||
|
||||
offline () {
|
||||
// console.log('No internet connection found. App is running in offline mode.')
|
||||
},
|
||||
|
||||
error (/* err */) {
|
||||
// console.error('Error during service worker registration:', err)
|
||||
}
|
||||
})
|
||||
@@ -1,49 +0,0 @@
|
||||
<template>
|
||||
<q-item
|
||||
clickable
|
||||
tag="a"
|
||||
target="_blank"
|
||||
:href="link"
|
||||
>
|
||||
<q-item-section
|
||||
v-if="icon"
|
||||
avatar
|
||||
>
|
||||
<q-icon :name="icon" />
|
||||
</q-item-section>
|
||||
|
||||
<q-item-section>
|
||||
<q-item-label>{{ title }}</q-item-label>
|
||||
<q-item-label caption>{{ caption }}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'EssentialLink',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
|
||||
caption: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
|
||||
link: {
|
||||
type: String,
|
||||
default: '#'
|
||||
},
|
||||
|
||||
icon: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -2,16 +2,10 @@
|
||||
<q-layout view="lHh Lpr lFf">
|
||||
<q-header elevated>
|
||||
<q-toolbar>
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
round
|
||||
icon="menu"
|
||||
aria-label="Menu"
|
||||
@click="toggleLeftDrawer"
|
||||
/>
|
||||
|
||||
<q-toolbar-title> Quasar App </q-toolbar-title>
|
||||
<q-toolbar-title>
|
||||
{{ listaStore.pb.authStore.model.name }} -
|
||||
{{ lista }}
|
||||
</q-toolbar-title>
|
||||
|
||||
<div>
|
||||
<q-btn @click="listaStore.logout()">Cerrar</q-btn>
|
||||
@@ -19,18 +13,6 @@
|
||||
</q-toolbar>
|
||||
</q-header>
|
||||
|
||||
<q-drawer v-model="leftDrawerOpen" show-if-above bordered>
|
||||
<q-list>
|
||||
<q-item-label header> Essential Links </q-item-label>
|
||||
|
||||
<EssentialLink
|
||||
v-for="link in essentialLink"
|
||||
:key="link.title"
|
||||
v-bind="link"
|
||||
/>
|
||||
</q-list>
|
||||
</q-drawer>
|
||||
|
||||
<q-page-container>
|
||||
<router-view />
|
||||
</q-page-container>
|
||||
@@ -38,58 +20,29 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import EssentialLink from "components/EssentialLink.vue";
|
||||
import { ref, computed } from "vue";
|
||||
import { useListaStore } from "../stores/lista.js";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
const $router = useRouter();
|
||||
const listaStore = useListaStore();
|
||||
|
||||
const essentialLink = [
|
||||
{
|
||||
title: "Docs",
|
||||
caption: "quasar.dev",
|
||||
icon: "school",
|
||||
link: "https://quasar.dev",
|
||||
},
|
||||
{
|
||||
title: "Github",
|
||||
caption: "github.com/quasarframework",
|
||||
icon: "code",
|
||||
link: "https://github.com/quasarframework",
|
||||
},
|
||||
{
|
||||
title: "Discord Chat Channel",
|
||||
caption: "chat.quasar.dev",
|
||||
icon: "chat",
|
||||
link: "https://chat.quasar.dev",
|
||||
},
|
||||
{
|
||||
title: "Forum",
|
||||
caption: "forum.quasar.dev",
|
||||
icon: "record_voice_over",
|
||||
link: "https://forum.quasar.dev",
|
||||
},
|
||||
{
|
||||
title: "Twitter",
|
||||
caption: "@quasarframework",
|
||||
icon: "rss_feed",
|
||||
link: "https://twitter.quasar.dev",
|
||||
},
|
||||
{
|
||||
title: "Facebook",
|
||||
caption: "@QuasarFramework",
|
||||
icon: "public",
|
||||
link: "https://facebook.quasar.dev",
|
||||
},
|
||||
{
|
||||
title: "Quasar Awesome",
|
||||
caption: "Community Quasar projects",
|
||||
icon: "favorite",
|
||||
link: "https://awesome.quasar.dev",
|
||||
},
|
||||
];
|
||||
const leftDrawerOpen = ref(false);
|
||||
|
||||
const toggleLeftDrawer = () => {
|
||||
leftDrawerOpen.value = !leftDrawerOpen.value;
|
||||
const nombreLista = ref("");
|
||||
const cargarLista = async () => {
|
||||
const result = await listaStore.pb
|
||||
.collection("lista")
|
||||
.getOne($router.currentRoute.value.params.id, { $autoCancel: false });
|
||||
nombreLista.value = result.nombre;
|
||||
};
|
||||
|
||||
const lista = computed(() => {
|
||||
if ($router.currentRoute.value.path == "/") {
|
||||
return "Listas";
|
||||
} else if ($router.currentRoute.value.path == "/perfil") {
|
||||
return "Perfil";
|
||||
} else if ($router.currentRoute.value.path.includes("/lista/")) {
|
||||
cargarLista();
|
||||
return nombreLista.value;
|
||||
} else return "";
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,74 +1,206 @@
|
||||
<template>
|
||||
<q-page class="flex flex-center">
|
||||
<div class="column q-pa-md q-gutter-sm">
|
||||
<div class="row">
|
||||
<q-avatar size="96px">
|
||||
<img
|
||||
:src="
|
||||
listaStore.pb.authStore.model.avatar.length > 0
|
||||
? listaStore.pb.getFileUrl(
|
||||
listaStore.pb.authStore.model,
|
||||
listaStore.pb.authStore.model.avatar
|
||||
)
|
||||
: `https://cdn.quasar.dev/img/avatar.png`
|
||||
"
|
||||
/>
|
||||
</q-avatar>
|
||||
</div>
|
||||
<div class="row" style="max-width: 200px">
|
||||
<q-file class="full-width" outlined v-model="imagen" label="Avatar" />
|
||||
</div>
|
||||
<div class="row">
|
||||
<q-input outlined v-model="name" label="Name" />
|
||||
</div>
|
||||
<div class="row">
|
||||
<q-input outlined v-model="username" label="Username" />
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="row q-mt-md q-mx-md">
|
||||
<div class="col">
|
||||
<q-btn
|
||||
round
|
||||
color="deep-orange"
|
||||
icon="keyboard_return"
|
||||
@click="
|
||||
cargarDatos();
|
||||
$router.push('/');
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-12 flex flex-center">
|
||||
<q-avatar size="96px">
|
||||
<q-img :src="fuenteImagen" />
|
||||
</q-avatar>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-2"></div>
|
||||
<div class="col">
|
||||
<q-form class="q-gutter-md" @click.once="recibeDatos()">
|
||||
<q-file
|
||||
outlined
|
||||
v-model="imagen"
|
||||
label="Avatar"
|
||||
accept=".jpg, image/*"
|
||||
max-total-size="5120000"
|
||||
@rejected="noValidos"
|
||||
class="q-py-md"
|
||||
/>
|
||||
<q-input
|
||||
outlined
|
||||
v-model="name"
|
||||
label="Name"
|
||||
:rules="[
|
||||
(val) => (val != null && val.length >= 3) || 'Mínimo 3 caracteres',
|
||||
]"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
outlined
|
||||
v-model="username"
|
||||
label="Username"
|
||||
:rules="[
|
||||
(val) => (val != null && val.length >= 3) || 'Mínimo 3 caracteres',
|
||||
(val) => comprobarUsername || 'Ya existe en la BD',
|
||||
(val) => /^[A-Z0-9]+$/i.test(val) || 'Sólo letras o números',
|
||||
]"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
outlined
|
||||
v-model="listaStore.pb.authStore.model.email"
|
||||
label="email"
|
||||
disable
|
||||
/>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<q-btn round color="primary" icon="cancel" />
|
||||
</div>
|
||||
<div class="col">
|
||||
<q-btn round color="secondary" icon="save" @click="updateUser()" />
|
||||
</div>
|
||||
</q-form>
|
||||
<div class="row q-mt-md q-mx-auto">
|
||||
<q-btn
|
||||
class="col-6"
|
||||
flat
|
||||
color="primary"
|
||||
label="Cancelar"
|
||||
@click="cargarDatos()"
|
||||
/>
|
||||
<q-btn
|
||||
class="col-6"
|
||||
flat
|
||||
:disable="btnSaveDisable()"
|
||||
color="secondary"
|
||||
label="Guardar"
|
||||
@click="alertSave = true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</q-page>
|
||||
<div class="col-2"></div>
|
||||
</div>
|
||||
|
||||
<q-dialog v-model="alertSave" persistent>
|
||||
<q-card>
|
||||
<q-card-section class="row items-center">
|
||||
<q-avatar icon="warning" color="warning" text-color="white" />
|
||||
<span class="q-ml-sm">Está seguro de guardar los datos?</span>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-actions align="right">
|
||||
<q-btn
|
||||
flat
|
||||
label="No"
|
||||
color="negative"
|
||||
v-close-popup
|
||||
@click="cargarDatos()"
|
||||
/>
|
||||
<q-btn
|
||||
flat
|
||||
label="Guardar"
|
||||
color="accent"
|
||||
v-close-popup
|
||||
@click="updateUser()"
|
||||
/>
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import { useListaStore } from "../stores/lista.js";
|
||||
import { useQuasar } from "quasar";
|
||||
|
||||
const $q = useQuasar();
|
||||
const listaStore = useListaStore();
|
||||
|
||||
const imagen = ref(listaStore.pb.authStore.model.username.avatar);
|
||||
const imagen = ref(null);
|
||||
const fuenteImagen = ref(null);
|
||||
const username = ref(listaStore.pb.authStore.model.username);
|
||||
const name = ref(listaStore.pb.authStore.model.name);
|
||||
const updateUser = () => {
|
||||
const alertSave = ref(false);
|
||||
const usuarios = ref([]);
|
||||
|
||||
const updateUser = async () => {
|
||||
const formData = new FormData();
|
||||
if (username.value) {
|
||||
formData.append("username", username.value);
|
||||
if (username.value.trim().length > 0) {
|
||||
formData.append("username", username.value.trim());
|
||||
}
|
||||
if (name.value) {
|
||||
formData.append("name", name.value);
|
||||
if (name.value.trim().length > 0) {
|
||||
formData.append("name", name.value.trim());
|
||||
}
|
||||
if (imagen.value != null) {
|
||||
formData.append("avatar", imagen.value);
|
||||
}
|
||||
const record = listaStore.pb
|
||||
await listaStore.pb
|
||||
.collection("users")
|
||||
.update(listaStore.pb.authStore.model.id, formData)
|
||||
.then((r) => {
|
||||
imagen.value = listaStore.pb.authStore.model.username.avatar;
|
||||
cargarDatos();
|
||||
});
|
||||
console.log(record);
|
||||
recibeDatos();
|
||||
};
|
||||
const cargarDatos = () => {
|
||||
refrescarImagen();
|
||||
imagen.value = null;
|
||||
username.value = listaStore.pb.authStore.model.username.trim();
|
||||
name.value = listaStore.pb.authStore.model.name.trim();
|
||||
};
|
||||
|
||||
const recibeDatos = () => {
|
||||
listaStore.getUsers().then(function (item) {
|
||||
usuarios.value = item;
|
||||
});
|
||||
};
|
||||
|
||||
const comprobarUsername = computed(() => {
|
||||
let filtro = usuarios.value?.filter(
|
||||
(user) => user.username.toLowerCase() == username.value.trim().toLowerCase()
|
||||
);
|
||||
if (filtro.length == 0) {
|
||||
return true;
|
||||
} else if (
|
||||
filtro.length == 1 &&
|
||||
filtro[0].username == listaStore.pb.authStore.model.username
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
const btnSaveDisable = () => {
|
||||
if (
|
||||
username.value == null ||
|
||||
username.value.length < 3 ||
|
||||
!comprobarUsername.value ||
|
||||
!/^[A-Z0-9]+$/i.test(username.value) ||
|
||||
name.value == null ||
|
||||
name.value.length < 3
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const refrescarImagen = () => {
|
||||
if (listaStore.pb.authStore.model.avatar.length > 0) {
|
||||
fuenteImagen.value =
|
||||
listaStore.pb.getFileUrl(
|
||||
listaStore.pb.authStore.model,
|
||||
listaStore.pb.authStore.model.avatar
|
||||
) + "?thumb=100x100";
|
||||
} else {
|
||||
fuenteImagen.value = `https://cdn.quasar.dev/img/avatar.png`;
|
||||
}
|
||||
};
|
||||
|
||||
const noValidos = () => {
|
||||
$q.dialog({
|
||||
type: "negative",
|
||||
message: `Sólo se permiten archivos de imágenes de 5 MB como máximo`,
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
refrescarImagen();
|
||||
});
|
||||
</script>
|
||||
|
||||
131
src/pages/ListasPage.vue
Normal file
@@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-center bg-teal-7 q-ma-md cursor-pointer rounded-borders"
|
||||
@click="irPerfil()"
|
||||
>
|
||||
<h3 style="font-weight: 615">
|
||||
{{ listaStore.pb.authStore.model.username }}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="flex flex-center">
|
||||
<q-btn
|
||||
outline
|
||||
rounded
|
||||
color="primary"
|
||||
label="Nueva Lista"
|
||||
@click="crearLista()"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-center q-mt-xl">
|
||||
<div
|
||||
class="full-width text-center q-px-md"
|
||||
v-for="lista in listas"
|
||||
:key="lista.id"
|
||||
>
|
||||
<p class="bg-cyan-7 q-pa-md text-weight-bold text-h6 rounded-borders">
|
||||
<router-link :to="`/lista/${lista.id}`">{{ lista.nombre }}</router-link
|
||||
><q-icon
|
||||
class="float-right cursor-pointer q-mt-xs"
|
||||
name="delete"
|
||||
@click="eliminarLista(lista)"
|
||||
/>
|
||||
<q-badge
|
||||
color="cyan-5"
|
||||
text-color="black"
|
||||
:label="lista.items.length"
|
||||
class="float-right q-ma-sm"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue";
|
||||
import { useListaStore } from "../stores/lista.js";
|
||||
import { useRouter } from "vue-router";
|
||||
import { useQuasar } from "quasar";
|
||||
|
||||
const $router = useRouter();
|
||||
const listaStore = useListaStore();
|
||||
const $q = useQuasar();
|
||||
|
||||
const listas = ref(null);
|
||||
|
||||
const listListas = async () => {
|
||||
listas.value = await listaStore.pb.collection("lista").getFullList({
|
||||
sort: "-created",
|
||||
});
|
||||
};
|
||||
|
||||
const irPerfil = () => {
|
||||
$router.push("perfil");
|
||||
};
|
||||
|
||||
const crearLista = () => {
|
||||
$q.dialog({
|
||||
title: "Nueva lista",
|
||||
message: "Nombre de la lista",
|
||||
prompt: {
|
||||
model: "",
|
||||
isValid: (val) => val.length > 2,
|
||||
type: "text", // optional
|
||||
},
|
||||
cancel: true,
|
||||
persistent: true,
|
||||
})
|
||||
.onOk((nombreLista) => {
|
||||
console.log(">>>> OK, received", nombreLista);
|
||||
const data = {
|
||||
nombre: nombreLista.trim(),
|
||||
usuarios: [listaStore.pb.authStore.model.id],
|
||||
items: [],
|
||||
};
|
||||
listaStore.pb
|
||||
.collection("lista")
|
||||
.create(data)
|
||||
.then((r) => {
|
||||
console.log("creada");
|
||||
listListas();
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
});
|
||||
})
|
||||
.onCancel((e) => {
|
||||
console.log(e);
|
||||
});
|
||||
};
|
||||
|
||||
const eliminarLista = (l) => {
|
||||
console.log(l.id);
|
||||
$q.dialog({
|
||||
title: l.nombre,
|
||||
message: "Está seguro de querer eliminar " + l.nombre + "?",
|
||||
cancel: true,
|
||||
persistent: true,
|
||||
}).onOk(async () => {
|
||||
if (l.usuarios.length == 1) {
|
||||
await listaStore.pb.collection("lista").delete(l.id);
|
||||
} else {
|
||||
const resultado = l.usuarios.filter(
|
||||
(user) => user != listaStore.pb.authStore.model.id
|
||||
);
|
||||
l.usuarios = resultado;
|
||||
await listaStore.pb.collection("lista").update(l.id, l);
|
||||
}
|
||||
|
||||
listListas();
|
||||
});
|
||||
};
|
||||
onMounted(() => {
|
||||
listListas();
|
||||
});
|
||||
</script>
|
||||
<style scoped>
|
||||
a:link,
|
||||
a:visited,
|
||||
a:active {
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
</style>
|
||||
125
src/pages/listas/indexPage.vue
Normal file
@@ -0,0 +1,125 @@
|
||||
<template>
|
||||
<div class="row">
|
||||
<div class="col q-ma-md">
|
||||
<q-btn
|
||||
round
|
||||
color="deep-orange"
|
||||
icon="keyboard_return"
|
||||
@click="$router.push('/')"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-10">
|
||||
<div class="q-ma-md rounded-borders">
|
||||
<q-input
|
||||
outlined
|
||||
v-model="producto"
|
||||
label="Item"
|
||||
@keyup.enter="cargarProducto()"
|
||||
:rules="[
|
||||
(val) =>
|
||||
(val != null && val.length >= 3) ||
|
||||
(val != null && val.length == 0) ||
|
||||
'Mínimo 3 caracteres',
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-center q-mt-xl">
|
||||
<div
|
||||
v-for="item in items"
|
||||
:key="item.id"
|
||||
class="full-width text-center q-px-md"
|
||||
>
|
||||
<p
|
||||
class="bg-cyan-7 q-pa-md q-mx-md text-weight-bold text-h6 rounded-borders"
|
||||
>
|
||||
{{ item.nombre
|
||||
}}<q-icon
|
||||
class="float-right cursor-pointer q-mt-xs"
|
||||
name="delete"
|
||||
@click="eliminarItem(item)"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue";
|
||||
import { useListaStore } from "../../stores/lista.js";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
const listaStore = useListaStore();
|
||||
const $router = useRouter();
|
||||
|
||||
const lista = ref(null);
|
||||
const items = ref(null);
|
||||
const producto = ref("");
|
||||
|
||||
const cargarProducto = async () => {
|
||||
if (producto.value.length >= 3) {
|
||||
// Busca en todos, si no está crea y añade
|
||||
const res = await listaStore.pb.collection("items").getFullList();
|
||||
const busqueda = res.filter(
|
||||
(element) => element.nombre == producto.value.trim()
|
||||
);
|
||||
if (busqueda.length == 0) {
|
||||
const data = {
|
||||
nombre: producto.value.trim(),
|
||||
cantidad: 1,
|
||||
};
|
||||
const item = await listaStore.pb.collection("items").create(data);
|
||||
lista.value.items.push(item.id);
|
||||
await listaStore.pb
|
||||
.collection("lista")
|
||||
.update(lista.value.id, lista.value);
|
||||
} else {
|
||||
// Si está, comprueba que no esté en la lista y lo añade
|
||||
if (lista.value.items.indexOf(busqueda[0].id) == -1) {
|
||||
lista.value.items.push(busqueda[0].id);
|
||||
await listaStore.pb
|
||||
.collection("lista")
|
||||
.update(lista.value.id, lista.value);
|
||||
}
|
||||
}
|
||||
const arrayIdItem = lista.value.items;
|
||||
const result = await listaStore.pb.collection("items").getFullList();
|
||||
items.value = result.filter(
|
||||
(element) => arrayIdItem.indexOf(element.id) != -1
|
||||
);
|
||||
producto.value = "";
|
||||
}
|
||||
};
|
||||
|
||||
const eliminarItem = async (i) => {
|
||||
//Lo saca de la lista
|
||||
const orden = lista.value.items.indexOf(i.id);
|
||||
lista.value.items.splice(orden, 1);
|
||||
await listaStore.pb.collection("lista").update(lista.value.id, lista.value);
|
||||
const arrayIdItem = lista.value.items;
|
||||
const result = await listaStore.pb.collection("items").getFullList();
|
||||
items.value = result.filter(
|
||||
(element) => arrayIdItem.indexOf(element.id) != -1
|
||||
);
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
lista.value = await listaStore.pb
|
||||
.collection("lista")
|
||||
.getOne($router.currentRoute.value.params.id);
|
||||
const arrayIdItem = lista.value.items;
|
||||
const result = await listaStore.pb.collection("items").getFullList();
|
||||
items.value = result.filter(
|
||||
(element) => arrayIdItem.indexOf(element.id) != -1
|
||||
);
|
||||
});
|
||||
</script>
|
||||
<style scoped>
|
||||
a:link,
|
||||
a:visited,
|
||||
a:active {
|
||||
text-decoration: none;
|
||||
color: gray;
|
||||
}
|
||||
</style>
|
||||
@@ -5,9 +5,19 @@ const routes = [
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
component: () => import("pages/ListasPage.vue"),
|
||||
meta: { auth: true },
|
||||
},
|
||||
{
|
||||
path: "perfil",
|
||||
component: () => import("pages/IndexPage.vue"),
|
||||
meta: { auth: true },
|
||||
},
|
||||
{
|
||||
path: 'lista/:id',
|
||||
component: () => import("pages/listas/indexPage.vue"),
|
||||
meta: { auth: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -25,7 +25,6 @@ export const useListaStore = defineStore("lista", () => {
|
||||
});
|
||||
}
|
||||
async function register(data) {
|
||||
console.log(data);
|
||||
return await pb
|
||||
.collection("users")
|
||||
.create(data)
|
||||
@@ -47,30 +46,3 @@ export const useListaStore = defineStore("lista", () => {
|
||||
register,
|
||||
};
|
||||
});
|
||||
|
||||
/*
|
||||
--- Pinia
|
||||
|
||||
export const useListaStore = defineStore("lista", () => {
|
||||
const counter = ref(3);
|
||||
function increment() {
|
||||
counter.value++;
|
||||
}
|
||||
function doubleCount() {
|
||||
return counter.value * 2;
|
||||
}
|
||||
return { counter, increment, doubleCount };
|
||||
});
|
||||
|
||||
|
||||
--- vue
|
||||
|
||||
listaStore.counter = 9;
|
||||
console.log(listaStore.counter);
|
||||
console.log(listaStore.doubleCount());
|
||||
console.log(listaStore.counter);
|
||||
listaStore.increment();
|
||||
console.log(listaStore.counter);
|
||||
|
||||
|
||||
*/
|
||||
|
||||