Compare commits

...

32 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
909d49a3e2 Add active state styling to navigation menu items 2025-08-14 12:59:16 +02:00
c6790ed645 Upgrade @sidebase/nuxt-auth to v1.0.1 2025-08-14 12:59:02 +02:00
78c4e2b46b Add logout button and display header on loading state 2025-08-14 09:11:58 +02:00
3b2198598e Fix profile image handling and data flow
The commit improves image handling by removing the separate imagenVista
ref, using data.image directly, and properly refreshing session data
after image upload. Also adds v-if guard for data availability.
2025-08-14 09:11:45 +02:00
29aef78804 Implement user profile image upload functionality
This commit improves image handling in the profile view by: - Adding
proper image validation - Implementing FormData for image upload -
Displaying uploaded image preview - Restricting allowed file types -
Adding error handling
2025-08-13 15:13:46 +02:00
3a163721f2 Add image upload functionality to profile page 2025-08-13 12:32:45 +02:00
54dbc4465d Add image upload functionality to profile page
The changes add file upload capabilities to the user profile page,
including: - File input control for selecting images - Basic image
upload handling infrastructure - Better layout with two-column design -
More robust date display

The subject line alone captures the key functional change.
2025-08-13 10:57:32 +02:00
523620be06 Fix navbar auth status and avatar size
The changes protect nav elements with auth checks and increase the
profile avatar size for better visibility.
2025-08-13 08:52:01 +02:00
2ecaa348da Navbar - change routes and add page name create profile page 2025-08-12 17:04:00 +02:00
3e55e82fb7 Replace Chuck Norris API with lists endpoint
The commit replaces the Chuck Norris joke functionality with a new lists
feature that fetches data from a local API endpoint using auth tokens.
2025-08-11 19:04:24 +02:00
bb08a62e5d Add redirect from protected pages to login 2025-08-11 18:44:42 +02:00
129da829e7 Add logout functionality and display user email in nav bar 2025-08-11 18:32:52 +02:00
13 changed files with 551 additions and 91 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

@@ -4,7 +4,7 @@
app
color="secondary"
height="30"
v-if="status === 'authenticated'"
v-if="status !== 'unauthenticated'"
>
<v-container class="text-caption text-center">
© {{ new Date().getFullYear() }} SharedLists - All Rights Reserved

View File

@@ -1,65 +1,95 @@
<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 === 'authenticated'"
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</span>
<span class="ms-1 text-info"
>{{ data?.email ? data?.email : "" }} -
{{
capitalize($route.name) == "Index"
? "Listas"
: capitalize($route.name)
}}</span
>
</v-app-bar-title>
<!-- Spacer to push the following elements to the right -->
<v-spacer />
<!-- Menu icons on the right side of the toolbar -->
<template v-slot:append>
<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="https://randomuser.me/api/portraits/men/78.jpg"
title="John Leider"
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>
<v-divider></v-divider>
<v-list density="compact" nav>
<v-list density="compact" nav active-class="bg-active">
<v-list-item
:active="$route.name === 'index'"
prepend-icon="mdi-home"
title="Home"
value="home"
@click="navigateTo('/')"
></v-list-item>
<v-list-item
prepend-icon="mdi-login"
title="Login"
value="login"
@click="navigateTo('/login')"
></v-list-item>
<v-list-item
prepend-icon="mdi-account-plus-outline"
title="Register"
value="register"
@click="navigateTo('/register')"
></v-list-item>
<v-list-item
prepend-icon="mdi-lock-reset"
title="Forgot"
value="forgot"
@click="navigateTo('/forgot-password')"
:active="$route.name === 'profile'"
prepend-icon="mdi-account"
title="Profile"
value="profile"
@click="navigateTo('/profile')"
></v-list-item>
</v-list>
</v-navigation-drawer>
</template>
<script setup>
import { ref } from "vue";
const { status } = useAuth();
// Drawer state to open/close the navigation drawer
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();
const logout = async () => {
await systemStore.deleteCookies();
await signOut({ callbackUrl: "/login" });
};
</script>
<style></style>
<style>
.bg-active {
background-color: grey;
color: white !important;
}
</style>

View File

@@ -1,12 +1,16 @@
import { fa } from "vuetify/locale";
// file: ~/middleware/authentication.global.ts
export default defineNuxtRouteMiddleware(async (to, next) => {
export default defineNuxtRouteMiddleware(async (to, from, next) => {
const { status } = useAuth();
const auth = status.value === "authenticated" ? true : false;
const isProtected = (await to.meta.auth) === true ? true : false;
if (!auth && isProtected) {
// User is not authenticated and page is protected
if (from.meta.auth) {
//viene de una pagina protegida
return navigateTo("/login");
} //viene de una pagina no protegida
return abortNavigation();
} else if (auth && !isProtected) {
// User is authenticated and page is not protected

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,25 +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"
>
<img
:src="norrisStore.response.icon_url"
alt="chuck norris"
size="64"
/>
<div class="mt-3">My Application's Home Page</div>
<div>
{{ norrisStore.response }}
{{ data }}
{{ status }}
{{ refreshToken }}
</div>
<div>
<v-btn color="primary" @click="refresh">Refresca token</v-btn>
</div>
<div>
<v-btn color="warning" @click="logout">Logout</v-btn>
</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>
@@ -27,19 +60,18 @@
definePageMeta({
auth: true,
});
import { useWindowSize } from "@vueuse/core";
const { data } = useAuth();
import { ref, onMounted } from "vue";
import { useChuckStore } from "~/stores/chuck";
import { useSystemStore } from "~/stores/system";
const systemStore = useSystemStore();
const norrisStore = useChuckStore();
const { data, status, refresh, refreshToken, signOut } = useAuth();
import { useListasStore } from "~/stores/listas";
const logout = async () => {
await systemStore.deleteCookies();
await signOut({ callbackUrl: "/login" });
};
const listasStore = useListasStore();
const systemStore = useSystemStore();
const { width, height } = useWindowSize();
onMounted(async () => {
await norrisStore.getData();
await listasStore.getData();
console.log(listasStore.listas);
});
</script>

134
app/pages/profile.vue Normal file
View File

@@ -0,0 +1,134 @@
<template>
<v-container
v-if="data"
class="fill-height d-flex align-center justify-center text-center"
>
<div class="full-height flex-column mr-5">
<v-avatar class="ma-10" color="primary" size="170">
<v-img
:width="200"
aspect-ratio="16/9"
cover
:src="data.image"
:alt="data.name"
></v-img>
</v-avatar>
<v-file-input
accept="image/apng, image/avif, image/gif, image/jpeg, image/png, image/svg+xml, image/webp"
label="Subir imagen"
@input="handleFileInput"
type="file"
v-model="imagen"
click:clear="limpiar_input"
clearable
/><v-btn
color="secundary"
:disabled="files.length <= 0 || !validateFileType()"
@click="uploadImage"
>
Button
</v-btn>
</div>
<div class="full-height flex-column">
<h3>Último login:</h3>
<p>{{ data.last_login ? data.last_login : "Nunca" }}</p>
<h3>Username:</h3>
<p>{{ data.username }}</p>
<h3>Nombre:</h3>
<p>{{ data.name }}</p>
<h3>Apellidos:</h3>
<p>{{ data.last_name }}</p>
<h3>Correo:</h3>
<p>{{ data.email }}</p>
<h3>Listas:</h3>
<p>
{{
listasStore.listas.length ? listasStore.listas.length : "0"
}}
</p>
</div>
</v-container>
</template>
<script setup>
definePageMeta({
auth: true,
});
const { data, token, getSession } = useAuth();
import { ref, onMounted } from "vue";
import { useSystemStore } from "~/stores/system";
import { useListasStore } from "~/stores/listas";
const listasStore = useListasStore();
const systemStore = useSystemStore();
const { handleFileInput, files } = useFileStorage();
let imagen = ref([]);
const uploadImage = async () => {
const url = "http://" + systemStore.url_backend + "/auth/users/me/";
let formData = new FormData();
formData.append("username", data.value.username);
formData.append("email", data.value.email);
formData.append("name", data.value.name);
formData.append("last_name", data.value.last_name);
formData.append("image", imagen.value ? imagen.value : null);
const response = await $fetch(url, {
headers: {
Authorization: `${token.value}`,
},
method: "PUT",
body: formData,
});
if (response) {
//imagenVista.value = response.image;
files.value.pop();
if (imagen.value != null) {
imagen.value = null;
}
await getSession();
} else {
console.error(response);
}
};
const validateFileType = () => {
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;
}
}
};
const validateTamanyo = () => {
//TODO validar tamaño
};
const openImageDialog = () => {
// TODO Implement image dialog logic
};
const limpiar_input = () => {
imagen.value = null;
files.value.pop();
};
onMounted(async () => {
await listasStore.getData();
});
</script>

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);
});

View File

@@ -1,14 +0,0 @@
import { defineStore } from "pinia";
export const useChuckStore = defineStore("chuckNorris", {
state: () => ({
response: "",
}),
actions: {
async getData() {
const response = await fetch("https://api.chucknorris.io/jokes/random");
const data = await response.json();
this.response = data;
},
},
});

20
app/stores/listas.js Normal file
View File

@@ -0,0 +1,20 @@
import { defineStore } from "pinia";
const { token } = useAuth();
export const useListasStore = defineStore("listas", {
state: () => ({
listas: [],
}),
actions: {
async getData() {
const response = await fetch("http://127.0.0.1:8000/lista/", {
method: "GET",
headers: {
Authorization: `${token.value}`,
},
});
const data = await response.json();
this.listas = data;
},
},
});

View File

@@ -3,7 +3,10 @@ export default defineNuxtConfig({
compatibilityDate: "2025-07-15",
ssr: false,
devtools: { enabled: false },
modules: ["@pinia/nuxt", "@sidebase/nuxt-auth"],
modules: ["@pinia/nuxt", "@sidebase/nuxt-auth", "nuxt-file-storage"],
fileStorage: {
mount: "/home/clonbg/Programar/git/curso_django/listas_rest/media/media",
},
runtimeConfig: {
baseURL: "http://localhost:3000",
},

99
package-lock.json generated
View File

@@ -10,11 +10,15 @@
"@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",
"vue-router": "^4.5.1",
"vuetify": "^3.9.3"
},
"devDependencies": {
"nuxt-file-storage": "^0.3.0"
}
},
"node_modules/@ampproject/remapping": {
@@ -3871,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",
@@ -4295,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",
@@ -8564,6 +8612,51 @@
}
}
},
"node_modules/nuxt-file-storage": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/nuxt-file-storage/-/nuxt-file-storage-0.3.0.tgz",
"integrity": "sha512-5Ey04J0vnNhdeopqIempv2dM9VKvNuCj68qokyJKEno9Dy1MS2lUWNew1djxr2s+5pMm6TLLXzfqku0yLcRtvg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@nuxt/kit": "^3.15.4",
"defu": "^6.1.4"
}
},
"node_modules/nuxt-file-storage/node_modules/@nuxt/kit": {
"version": "3.18.1",
"resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-3.18.1.tgz",
"integrity": "sha512-z6w1Fzv27CIKFlhct05rndkJSfoslplWH5fJ9dtusEvpYScLXp5cATWIbWkte9e9zFSmQTgDQJjNs3geQHE7og==",
"dev": true,
"license": "MIT",
"dependencies": {
"c12": "^3.2.0",
"consola": "^3.4.2",
"defu": "^6.1.4",
"destr": "^2.0.5",
"errx": "^0.1.0",
"exsolve": "^1.0.7",
"ignore": "^7.0.5",
"jiti": "^2.5.1",
"klona": "^2.0.6",
"knitwork": "^1.2.0",
"mlly": "^1.7.4",
"ohash": "^2.0.11",
"pathe": "^2.0.3",
"pkg-types": "^2.2.0",
"scule": "^1.3.0",
"semver": "^7.7.2",
"std-env": "^3.9.0",
"tinyglobby": "^0.2.14",
"ufo": "^1.6.1",
"unctx": "^2.4.1",
"unimport": "^5.2.0",
"untyped": "^2.0.0"
},
"engines": {
"node": ">=18.12.0"
}
},
"node_modules/nypm": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.1.tgz",
@@ -10993,9 +11086,9 @@
}
},
"node_modules/tmp": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz",
"integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==",
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz",
"integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==",
"license": "MIT",
"engines": {
"node": ">=14.14"

View File

@@ -13,10 +13,14 @@
"@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",
"vue-router": "^4.5.1",
"vuetify": "^3.9.3"
},
"devDependencies": {
"nuxt-file-storage": "^0.3.0"
}
}