Compare commits

...

20 Commits

Author SHA1 Message Date
90bf54e991 Update list display layout and responsiveness 2025-08-31 18:12:34 +02:00
6885df22e8 Add @vueuse/core dependency 2025-08-31 18:12:19 +02:00
84e1ca555c Add default avatar for users without profile image 2025-08-16 15:58:10 +02:00
0fc0aa95d3 Add error handling to login and improve user profile
The login changes add try/catch error handling with a user-friendly
message, while the profile page now displays first and last names
separately. Also includes a fix for optional chaining in index.vue.
2025-08-16 15:07:09 +02:00
d65fe6e353 Fix image upload validation and clearing 2025-08-16 12:56:58 +02:00
021bd8aae2 Enhance list items UI and progress display 2025-08-16 12:00:33 +02:00
82fb58c785 Add user email and improve nav title formatting 2025-08-16 12:00:18 +02:00
52cfe98a1b Fix file input clear and improve upload handling
The change adds image clearing functionality and improves input
validation by adding null checks and clearable state to the file input
component.
2025-08-15 11:32:42 +02:00
36f8bac508 Update NavBar styling and theme controls 2025-08-15 11:32:31 +02:00
a04a66a176 Add minimal and soft color themes 2025-08-15 11:32:21 +02:00
6dc8f5c39e Update list item color and background styles 2025-08-15 10:05:11 +02:00
8b55efa318 Use theme prop instead of class for v-main background 2025-08-15 10:04:59 +02:00
28beddb785 Add theme customization and component defaults 2025-08-15 10:04:45 +02:00
2abfd38dd7 Add list view component to homepage 2025-08-14 16:37:52 +02:00
1cdfafc90f Add padding to navigation drawer profile item 2025-08-14 16:37:42 +02:00
b3d4e4e565 Set Vuetify default theme to light mode 2025-08-14 15:27:58 +02:00
c624b3ccf6 Change file input label to Spanish 2025-08-14 15:27:48 +02:00
1ec98f2bf3 Sort imports and add spacing in index.vue 2025-08-14 15:27:30 +02:00
f7715bb494 Add theme toggle button to navigation bar 2025-08-14 15:27:10 +02:00
af91640c2b Add theme-based background color to main content 2025-08-14 15:26:46 +02:00
8 changed files with 333 additions and 49 deletions

View File

@@ -1,18 +1,25 @@
<template>
<v-app>
<NavBar />
<v-app>
<NavBar />
<!-- Main content of the application -->
<v-main>
<NuxtPage />
<!-- Main content of the application -->
<v-main :theme="$vuetify.theme.current.dark ? 'dark' : 'light'">
<NuxtPage />
</v-main>
</v-main>
<AppFooter />
</v-app>
<AppFooter />
</v-app>
</template>
<script>
<script setup>
import { onMounted } from "vue";
import { useTheme } from "vuetify";
const theme = useTheme();
onMounted(() => {
console.log(theme.name);
});
</script>
<style></style>

View File

@@ -1,16 +1,27 @@
<template>
<!-- Toolbar at the top of the screen -->
<v-app-bar
color="rgba(27, 94, 32, 0.8)"
app
color="primary-darken-1"
class="text-white"
height="48"
v-if="status !== 'unauthenticated'"
>
<!-- Title of the application with adjusted margin and font size -->
<v-app-bar-title class="text-h6 ms-3 text-white">
<v-icon icon="mdi-apps" @click="drawer = !drawer"></v-icon>
<v-icon
color="info"
icon="mdi-apps"
@click="drawer = !drawer"
></v-icon>
<span class="ms-1 text-white"
>Listas de Front - {{ capitalize($route.name) }}</span
<span class="ms-1 text-info"
>{{ data?.email ? data?.email : "" }} -
{{
capitalize($route.name) == "Index"
? "Listas"
: capitalize($route.name)
}}</span
>
</v-app-bar-title>
@@ -19,15 +30,23 @@
<!-- Menu icons on the right side of the toolbar -->
<template v-slot:append>
<small class="text-white"> Logout </small>
<v-btn icon="mdi-account" class="text-white" @click="logout">
</v-btn>
<v-btn
color="info"
@click="theme.toggle()"
text="Light / Dark"
></v-btn>
<v-btn icon="mdi-logout" color="warning" @click="logout"> </v-btn>
</template>
</v-app-bar>
<v-navigation-drawer v-model="drawer" temporary>
<v-list-item
:prepend-avatar="data && data.image ? data.image : ''"
class="ma-4 ml-8"
:prepend-avatar="
data && data.image
? data.image
: 'https://cdn.vuetifyjs.com/images/logos/v-alt.svg'
"
:title="data && data.name ? data.name : ''"
></v-list-item>
@@ -55,7 +74,9 @@
<script setup>
import { ref, capitalize } from "vue";
import { useSystemStore } from "~/stores/system";
import { useTheme } from "vuetify";
const theme = useTheme();
const drawer = ref(false);
const { data, status, signOut } = useAuth();
const systemStore = useSystemStore();

View File

@@ -123,11 +123,16 @@ const passwordRules = [
"La contraseña debe tener al menos 5 caracteres",
];
// TODO - cuando hace login cambiar el último login del usuario
const login = async () => {
await signIn(
{ username: "admin", password: "admin" },
{ callbackUrl: "/" },
);
try {
await signIn(
{ username: "joseko", password: "kpdrlp00" },
{ callbackUrl: "/" },
);
} catch (error) {
alert("Ha ocurrido un error. Comprueba tus credenciales");
}
};
</script>

View File

@@ -1,12 +1,58 @@
<template>
<v-container
class="fill-height d-flex flex-column align-center justify-center text-center"
class="d-flex flex-column align-center justify-center text-center"
>
<div class="mt-3">My Application's Home Page</div>
<div>
{{ listasStore.listas }}
{{ data }}
</div>
<div class="mt-3">Listas</div>
<v-card
class="ma-5 mx-auto"
v-if="listasStore.listas.length > 0"
max-width="100%"
width="1000%"
>
<v-list-item
class="text-black ma-10 pa-5 rounded-lg full-height flex-column"
:class="
lista.owner == data?.email
? 'bg-green-lighten-2'
: 'bg-green-darken-1'
"
v-for="lista in listasStore.listas"
:key="lista.id"
:subtitle="lista.descripcion"
:title="lista.name"
>
<template v-slot:prepend v-if="width > 600">
<div class="d-flex flex-column full-height">
<small class="ml-2 text-red">{{ lista.owner }}</small>
<template v-if="lista.sharedListas.length > 0">
<small
v-for="shared in lista.sharedListas"
:key="shared"
class="ml-2 text-orange-darken-3"
>{{ shared }}</small
></template
>
</div>
</template>
<!-- TODO template número de compartidos -->
<template v-slot:append>
<v-badge
class="mr-3"
location="top right"
color="grey-lighten-2"
:content="`${lista.countItems}/${lista.completedItems}`"
>
<v-btn
color="black"
icon="mdi-counter"
variant="text"
></v-btn>
</v-badge>
</template> </v-list-item
></v-card>
<div></div>
</v-container>
</template>
@@ -14,14 +60,18 @@
definePageMeta({
auth: true,
});
import { useWindowSize } from "@vueuse/core";
const { data } = useAuth();
import { ref, onMounted } from "vue";
import { useSystemStore } from "~/stores/system";
import { useListasStore } from "~/stores/listas";
const listasStore = useListasStore();
const systemStore = useSystemStore();
const { width, height } = useWindowSize();
onMounted(async () => {
await listasStore.getData();
console.log(listasStore.listas);
});
</script>

View File

@@ -16,12 +16,14 @@
<v-file-input
accept="image/apng, image/avif, image/gif, image/jpeg, image/png, image/svg+xml, image/webp"
label="File input"
label="Subir imagen"
@input="handleFileInput"
type="file"
v-model="imagen"
click:clear="limpiar_input"
clearable
/><v-btn
color="primary"
color="secundary"
:disabled="files.length <= 0 || !validateFileType()"
@click="uploadImage"
>
@@ -34,7 +36,9 @@
<h3>Username:</h3>
<p>{{ data.username }}</p>
<h3>Nombre:</h3>
<p>{{ data.name }} {{ data.last_name }}</p>
<p>{{ data.name }}</p>
<h3>Apellidos:</h3>
<p>{{ data.last_name }}</p>
<h3>Correo:</h3>
<p>{{ data.email }}</p>
<h3>Listas:</h3>
@@ -81,7 +85,9 @@ const uploadImage = async () => {
if (response) {
//imagenVista.value = response.image;
files.value.pop();
imagen.value = null;
if (imagen.value != null) {
imagen.value = null;
}
await getSession();
} else {
console.error(response);
@@ -89,21 +95,23 @@ const uploadImage = async () => {
};
const validateFileType = () => {
var fileName = imagen.value.name;
var idxDot = fileName.lastIndexOf(".") + 1;
var extFile = fileName.substr(idxDot, fileName.length).toLowerCase();
if (
extFile == "jpg" ||
extFile == "jpeg" ||
extFile == "png" ||
extFile == "gif"
) {
return true;
} else {
alert("Only jpg, jpeg, png and gif files are allowed!");
files.value.pop();
imagen.value = null;
return false;
if (imagen.value != null) {
var fileName = imagen.value.name;
var idxDot = fileName.lastIndexOf(".") + 1;
var extFile = fileName.substr(idxDot, fileName.length).toLowerCase();
if (
extFile == "jpg" ||
extFile == "jpeg" ||
extFile == "png" ||
extFile == "gif"
) {
return true;
} else {
alert("Only jpg, jpeg, png and gif files are allowed!");
files.value.pop();
imagen.value = null;
return false;
}
}
};
@@ -115,6 +123,11 @@ const openImageDialog = () => {
// TODO Implement image dialog logic
};
const limpiar_input = () => {
imagen.value = null;
files.value.pop();
};
onMounted(async () => {
await listasStore.getData();
});

View File

@@ -1,7 +1,149 @@
import { createVuetify } from "vuetify";
import * as components from "vuetify/components";
import * as directives from "vuetify/directives";
const lightTheme = {
dark: false,
colors: {
background: "#F8FAF7",
surface: "#FFFFFF",
primary: "#88D8B0",
"primary-darken-1": "#6BC49A",
secondary: "#C8E6C9",
"secondary-darken-1": "#A5D6A7",
accent: "#D4EDDA",
error: "#FF9A8B",
info: "#B3E0FF",
success: "#A5D6A7",
warning: "#FFE0B2",
"on-primary": "#2E3A32",
"on-surface": "#2E3A32",
},
variables: {
"border-color": "#E0E0E0",
"border-opacity": 0.12,
},
};
const darkTheme = {
dark: true,
colors: {
background: "#121F17",
surface: "#1E2922",
primary: "#4A7856",
"primary-darken-1": "#3A6A46",
secondary: "#3D5A45",
"secondary-darken-1": "#2D4A35",
accent: "#5D8C6C",
error: "#D32F2F",
info: "#2196F3",
success: "#4CAF50",
warning: "#FFA000",
"on-primary": "#E8F5E9",
"on-surface": "#E0E0E0",
},
variables: {
"border-color": "#37474F",
"border-opacity": 0.24,
},
};
const minimalLightTheme = {
dark: false,
colors: {
background: "#FFFFFF",
surface: "#F5F5F5",
primary: "#7C90DB", // Azul suave
"primary-darken-1": "#6A7EC9",
secondary: "#E0E0E0", // Gris neutro
"secondary-darken-1": "#C7C7C7",
accent: "#A5C4D4", // Azul pastel
error: "#FF6B6B", // Rojo coral suave
info: "#88C9F2",
success: "#77DD77", // Verde menta
warning: "#FFD166", // Amarillo pastel
"on-primary": "#2D3748", // Gris oscuro
"on-surface": "#2D3748",
},
variables: {
"border-color": "#E2E8F0",
"border-opacity": 0.16,
},
};
const minimalDarkTheme = {
dark: true,
colors: {
background: "#1E2B3A",
surface: "#2C3E50",
primary: "#8A9EFF", // Azul lavanda (inalterado)
"primary-darken-1": "#778CEB",
secondary: "#4A5568", // Gris azulado (reemplaza grises neutros)
"secondary-darken-1": "#3C4758",
accent: "#A7BED3", // Azul polvo (inalterado)
error: "#FF8A8A",
info: "#90CAF9",
success: "#81C784",
warning: "#FFD54F",
"on-primary": "#F7FAFC", // Blanco casi puro (mejor contraste)
"on-surface": "#E2E8F0", // Gris claro azulado
},
variables: {
"border-color": "#4A5568", // Coordinado con secondary
"border-opacity": 0.3,
},
};
const softDarkTheme = {
dark: true,
colors: {
background: "#2C3E50", // Gris-azul medio (como cielo nocturno)
surface: "#34495E", // 15% más claro que el fondo
primary: "#7F9CF5", // Azul lavanda más claro
"primary-darken-1": "#6B8BEA",
secondary: "#4B5563", // Gris neutro cálido
"secondary-darken-1": "#3E4A5A",
accent: "#94A3B8", // Gris-azul claro (para detalles)
error: "#FCA5A5", // Rojo suave
info: "#93C5FD", // Azul cielo
success: "#86EFAC", // Verde menta claro
warning: "#FCD34D", // Amarillo miel
"on-primary": "#F8FAFC", // Blanco niebla
"on-surface": "#E5E7EB", // Gris muy claro
},
variables: {
"border-color": "#475569", // Borde sutil
"border-opacity": 0.25,
},
};
export default defineNuxtPlugin((nuxtApp) => {
const vuetify = createVuetify({ components });
const vuetify = createVuetify({
components,
directives,
theme: {
defaultTheme: "light",
themes: {
light: minimalLightTheme,
dark: softDarkTheme,
},
variations: {
colors: ["primary", "secondary"],
lighten: 2,
darken: 2,
},
},
defaults: {
VBtn: {
color: "primary",
variant: "flat",
},
VCard: {
elevation: 1,
rounded: "lg",
},
},
});
nuxtApp.vueApp.use(vuetify);
});

45
package-lock.json generated
View File

@@ -10,6 +10,7 @@
"@mdi/font": "^7.4.47",
"@pinia/nuxt": "^0.11.2",
"@sidebase/nuxt-auth": "^1.0.1",
"@vueuse/core": "^13.8.0",
"nuxt": "^4.0.1",
"pinia": "^3.0.3",
"vue": "^3.5.18",
@@ -3874,6 +3875,12 @@
"integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==",
"license": "MIT"
},
"node_modules/@types/web-bluetooth": {
"version": "0.0.21",
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz",
"integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==",
"license": "MIT"
},
"node_modules/@types/yauzl": {
"version": "2.10.3",
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
@@ -4298,6 +4305,44 @@
"integrity": "sha512-cZy8Dq+uuIXbxCZpuLd2GJdeSO/lIzIspC2WtkqIpje5QyFbvLaI5wZtdUjLHjGZrlVX6GilejatWwVYYRc8tA==",
"license": "MIT"
},
"node_modules/@vueuse/core": {
"version": "13.8.0",
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-13.8.0.tgz",
"integrity": "sha512-rmBcgpEpxY0ZmyQQR94q1qkUcHREiLxQwNyWrtjMDipD0WTH/JBcAt0gdcn2PsH0SA76ec291cHFngmyaBhlxA==",
"license": "MIT",
"dependencies": {
"@types/web-bluetooth": "^0.0.21",
"@vueuse/metadata": "13.8.0",
"@vueuse/shared": "13.8.0"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"vue": "^3.5.0"
}
},
"node_modules/@vueuse/metadata": {
"version": "13.8.0",
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-13.8.0.tgz",
"integrity": "sha512-BYMp3Gp1kBUPv7AfQnJYP96mkX7g7cKdTIgwv/Jgd+pfQhz678naoZOAcknRtPLP4cFblDDW7rF4e3KFa+PfIA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@vueuse/shared": {
"version": "13.8.0",
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-13.8.0.tgz",
"integrity": "sha512-x4nfM0ykW+RmNJ4/1IzZsuLuWWrNTxlTWUiehTGI54wnOxIgI9EDdu/O5S77ac6hvQ3hk2KpOVFHaM0M796Kbw==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"vue": "^3.5.0"
}
},
"node_modules/@whatwg-node/disposablestack": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/@whatwg-node/disposablestack/-/disposablestack-0.0.6.tgz",

View File

@@ -13,6 +13,7 @@
"@mdi/font": "^7.4.47",
"@pinia/nuxt": "^0.11.2",
"@sidebase/nuxt-auth": "^1.0.1",
"@vueuse/core": "^13.8.0",
"nuxt": "^4.0.1",
"pinia": "^3.0.3",
"vue": "^3.5.18",