chore(pack): optimize dependencies, reduce bundle size
This commit is contained in:
@@ -16,10 +16,11 @@
|
||||
</template>
|
||||
|
||||
<script async setup>
|
||||
import { mdiFileAccountOutline } from '@mdi/js';
|
||||
import SideBar from "@/views/main/SideBar.vue";
|
||||
import {useLanguageStore} from "@/stores/languageStore.js";
|
||||
import {watch} from "vue";
|
||||
import {useI18n} from "vue-i18n";
|
||||
import { useLanguageStore } from "@/stores/languageStore.js";
|
||||
import { watch } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
// Watch for language changes and update i18n locale
|
||||
const languageStore = useLanguageStore();
|
||||
|
||||
@@ -1,48 +1,56 @@
|
||||
import {createApp} from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from '@/router/router.js'
|
||||
import {createPinia} from 'pinia'
|
||||
import '@mdi/font/css/materialdesignicons.css'
|
||||
import 'vuetify/styles'
|
||||
import {createVuetify} from 'vuetify'
|
||||
import * as components from 'vuetify/components'
|
||||
import * as directives from 'vuetify/directives'
|
||||
import vueGoogleOauth from 'vue3-google-login'
|
||||
import {useAuthStore} from "@/stores/authStore.js";
|
||||
import {useUserProfileStore} from "@/stores/userProfileStore.js";
|
||||
import {useCreatorProfileStore} from "@/stores/creatorProfileStore.js";
|
||||
import './assets/main.css'
|
||||
import { createApp } from 'vue';
|
||||
import App from './App.vue';
|
||||
import router from '@/router/router.js';
|
||||
import { createPinia } from 'pinia';
|
||||
import 'vuetify/styles';
|
||||
import { createVuetify } from 'vuetify';
|
||||
import { aliases, mdi } from 'vuetify/iconsets/mdi-svg';
|
||||
import { VDialog, VApp, VBtn, VProgressLinear, VProgressCircular, VIcon, VTextField, VSnackbar, VForm, VTextarea } from 'vuetify/components';
|
||||
import { } from 'vuetify/directives';
|
||||
import vueGoogleOauth from 'vue3-google-login';
|
||||
import { useAuthStore } from "@/stores/authStore.js";
|
||||
import { useUserProfileStore } from "@/stores/userProfileStore.js";
|
||||
import { useCreatorProfileStore } from "@/stores/creatorProfileStore.js";
|
||||
import './assets/main.css';
|
||||
|
||||
const vuetify = createVuetify({
|
||||
components,
|
||||
directives
|
||||
components: {
|
||||
VDialog, VApp, VBtn, VProgressLinear, VProgressCircular, VIcon, VTextField, VSnackbar, VForm, VTextarea
|
||||
},
|
||||
directives: {
|
||||
},
|
||||
icons: {
|
||||
defaultSet: 'mdi',
|
||||
aliases,
|
||||
sets: { mdi }
|
||||
}
|
||||
});
|
||||
|
||||
import {createI18n} from 'vue-i18n'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import en from '@/locales/en.json'
|
||||
import fr from '@/locales/fr.json'
|
||||
import es from '@/locales/es.json'
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
fallbackLocale: 'en',
|
||||
messages: {
|
||||
en: en,
|
||||
fr: fr,
|
||||
es: es
|
||||
}
|
||||
legacy: false,
|
||||
fallbackLocale: 'en',
|
||||
messages: {
|
||||
en: en,
|
||||
fr: fr,
|
||||
es: es
|
||||
}
|
||||
})
|
||||
|
||||
const pinia = createPinia();
|
||||
|
||||
const app = createApp(App)
|
||||
.use(pinia)
|
||||
.use(vuetify)
|
||||
.use(router)
|
||||
.use(i18n)
|
||||
.use(vueGoogleOauth, {
|
||||
clientId: import.meta.env.VITE_GOOGLE_CLIENT_ID,
|
||||
});
|
||||
.use(pinia)
|
||||
.use(vuetify)
|
||||
.use(router)
|
||||
.use(i18n)
|
||||
.use(vueGoogleOauth, {
|
||||
clientId: import.meta.env.VITE_GOOGLE_CLIENT_ID,
|
||||
});
|
||||
|
||||
useAuthStore();
|
||||
useUserProfileStore();
|
||||
|
||||
@@ -1,24 +1,27 @@
|
||||
import { useAuthStore } from '@/stores/authStore.js';
|
||||
import PaymentFailed from '@/views/PaymentFailed.vue';
|
||||
import { createRouter, createWebHistory } from 'vue-router';
|
||||
|
||||
import CreatorHome from '@/views/creators/CreatorHome.vue';
|
||||
import CreatorLayout from '@/views/creators/CreatorLayout.vue';
|
||||
import About from '@/views/documentation/About.vue';
|
||||
import ContentPolicy from '@/views/documentation/ContentPolicy.vue';
|
||||
import CreatorGuide from '@/views/documentation/CreatorGuide.vue';
|
||||
import DocumentationLayout from '@/views/documentation/DocumentationLayout.vue';
|
||||
import FAQ from '@/views/documentation/FAQ.vue';
|
||||
import HelpAndContact from '@/views/documentation/HelpAndContact.vue';
|
||||
import Pricing from '@/views/documentation/Pricing.vue';
|
||||
import TermsAndConditions from '@/views/documentation/TermsAndConditions.vue';
|
||||
import ProfilePage from '@/views/profile/ProfilePage.vue';
|
||||
import { createRouter, createWebHistory } from 'vue-router';
|
||||
import LoginView from '../views/LoginView.vue';
|
||||
import PaymentCompleted from '../views/PaymentCompleted.vue';
|
||||
import Landing from '../views/main/Landing.vue';
|
||||
import CreateCreator from "@/views/creators/CreateCreator.vue";
|
||||
import RegisterView from "@/views/RegisterView.vue";
|
||||
import ForgotPasswordView from "@/views/ForgotPasswordView.vue";
|
||||
import ResetPasswordView from "@/views/ResetPasswordView.vue";
|
||||
const LoginView = () => import('@/views/LoginView.vue');
|
||||
|
||||
const About = () => import('@/views/documentation/About.vue');
|
||||
const ContentPolicy = () => import('@/views/documentation/ContentPolicy.vue');
|
||||
const CreatorGuide = () => import('@/views/documentation/CreatorGuide.vue');
|
||||
const DocumentationLayout = () => import('@/views/documentation/DocumentationLayout.vue');
|
||||
const FAQ = () => import('@/views/documentation/FAQ.vue');
|
||||
const HelpAndContact = () => import('@/views/documentation/HelpAndContact.vue');
|
||||
const Pricing = () => import('@/views/documentation/Pricing.vue');
|
||||
const TermsAndConditions = () => import('@/views/documentation/TermsAndConditions.vue');
|
||||
const ProfilePage = () => import('@/views/profile/ProfilePage.vue');
|
||||
const PaymentCompleted = () => import('@/views/PaymentCompleted.vue');
|
||||
const PaymentFailed = () => import('@/views/PaymentFailed.vue');
|
||||
const Landing = () => import('@/views/main/Landing.vue');
|
||||
|
||||
const CreateCreator = () => import('@/views/creators/CreateCreator.vue');
|
||||
const RegisterView = () => import('@/views/RegisterView.vue');
|
||||
const ForgotPasswordView = () => import('@/views/ForgotPasswordView.vue');
|
||||
const ResetPasswordView = () => import('@/views/ResetPasswordView.vue');
|
||||
|
||||
const routes = [
|
||||
{
|
||||
@@ -143,7 +146,7 @@ router.beforeEach((to, from, next) => {
|
||||
|
||||
if (to.matched.some((record) => record.meta.requiresAuth)) {
|
||||
if (!authStore.isAuthenticated) {
|
||||
next({
|
||||
next({
|
||||
name: 'login',
|
||||
query: { returnUrl: to.fullPath }
|
||||
});
|
||||
@@ -158,4 +161,4 @@ router.beforeEach((to, from, next) => {
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
export default router;
|
||||
|
||||
@@ -1,52 +1,36 @@
|
||||
<template>
|
||||
<div class="flex min-h-full justify-center items-center w-full p-4">
|
||||
<div class="flex min-h-full w-full items-center justify-center p-4">
|
||||
|
||||
<div class="flex flex-col gap-10 w-full max-w-[512px]">
|
||||
<h1 class="text-2xl font-bold login-text text-center">
|
||||
<div class="flex w-full max-w-[512px] flex-col gap-10">
|
||||
<h1 class="login-text text-center text-2xl font-bold">
|
||||
{{ t('title') }}
|
||||
</h1>
|
||||
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<google-login :callback="googleCallback"
|
||||
popup-type="TOKEN">
|
||||
<google-login :callback="googleCallback" popup-type="TOKEN">
|
||||
<button class="secondary">
|
||||
<v-icon class="mr-2">mdi-google</v-icon>
|
||||
<v-icon class="mr-2" :icon="mdiGoogle" />
|
||||
{{ t('continueWithGoogle') }}
|
||||
</button>
|
||||
</google-login>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center my-4">
|
||||
<div class="flex-grow h-[1px] bg-gray-200"></div>
|
||||
<span class="px-3 text-gray-300 uppercase text-sm font-semibold">{{ t('orContinueWith') }}</span>
|
||||
<div class="flex-grow h-[1px] bg-gray-200"></div>
|
||||
<div class="my-4 flex items-center">
|
||||
<div class="h-px grow bg-gray-200"></div>
|
||||
<span class="px-3 text-sm font-semibold uppercase text-gray-300">{{ t('orContinueWith') }}</span>
|
||||
<div class="h-px grow bg-gray-200"></div>
|
||||
</div>
|
||||
|
||||
<!-- Add email/password form -->
|
||||
<v-form @submit.prevent="handleLocalLogin">
|
||||
<div class="flex flex-col gap-4">
|
||||
<v-text-field
|
||||
v-model="email"
|
||||
:label="t('email')"
|
||||
type="email"
|
||||
required
|
||||
></v-text-field>
|
||||
<v-text-field v-model="email" :label="t('email')" type="email" required></v-text-field>
|
||||
|
||||
<v-text-field
|
||||
v-model="password"
|
||||
:label="t('password')"
|
||||
:type="showPassword ? 'text' : 'password'"
|
||||
required
|
||||
>
|
||||
<v-text-field v-model="password" :label="t('password')" :type="showPassword ? 'text' : 'password'" required>
|
||||
<template v-slot:append-inner>
|
||||
<v-icon
|
||||
@click="showPassword = !showPassword"
|
||||
class="visibility-toggle"
|
||||
size="small"
|
||||
>
|
||||
{{ showPassword ? 'mdi-eye-off' : 'mdi-eye' }}
|
||||
</v-icon>
|
||||
<v-icon @click="showPassword = !showPassword" class="visibility-toggle" size="small"
|
||||
:icon="showPassword ? mdiEyeOff : mdiEye" />
|
||||
</template>
|
||||
</v-text-field>
|
||||
|
||||
@@ -55,12 +39,12 @@
|
||||
</v-btn>
|
||||
|
||||
<div class="text-center">
|
||||
<a @click="forgotPassword" class="text-sm text-blue-500 cursor-pointer">
|
||||
<a @click="forgotPassword" class="cursor-pointer text-sm text-blue-500">
|
||||
{{ t('forgotPassword') }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="text-center mt-4">
|
||||
<div class="mt-4 text-center">
|
||||
{{ t('noAccount') }}
|
||||
<router-link to="/register" class="text-blue-500">
|
||||
{{ t('register') }}
|
||||
@@ -79,13 +63,14 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref} from 'vue';
|
||||
import {GoogleLogin} from "vue3-google-login";
|
||||
import {useAuthStore} from '@/stores/authStore.js';
|
||||
import {useI18n} from 'vue-i18n';
|
||||
import {useRouter} from 'vue-router';
|
||||
import { ref } from 'vue';
|
||||
import { GoogleLogin } from "vue3-google-login";
|
||||
import { useAuthStore } from '@/stores/authStore.js';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { mdiGoogle, mdiEye, mdiEyeOff } from '@mdi/js';
|
||||
|
||||
const {t} = useI18n();
|
||||
const { t } = useI18n();
|
||||
const router = useRouter();
|
||||
const authStore = useAuthStore();
|
||||
|
||||
@@ -146,7 +131,8 @@ function forgotPassword() {
|
||||
/* Dark mode support if needed */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.custom-divider {
|
||||
background-color: rgb(75, 85, 99); /* Equivalent to gray-600 */
|
||||
background-color: rgb(75, 85, 99);
|
||||
/* Equivalent to gray-600 */
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -193,4 +179,4 @@ function forgotPassword() {
|
||||
"continueWithGoogle": "Continuar con Google"
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
</i18n>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<!-- Navigation Link at the top -->
|
||||
<div class="navigation-link">
|
||||
<button class="link-button" @click="goBack()">
|
||||
<v-icon>mdi-arrow-left</v-icon>
|
||||
<v-icon :icon="mdiArrowLeft" />
|
||||
{{ t('returnToCreator') }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -14,9 +14,7 @@
|
||||
</h1>
|
||||
|
||||
<p>
|
||||
<v-icon size="120" color="success">
|
||||
mdi-check-circle
|
||||
</v-icon>
|
||||
<v-icon size="120" color="success" :icon="mdiCheckCircle" />
|
||||
</p>
|
||||
|
||||
<p>
|
||||
@@ -40,9 +38,10 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {useRouter, useRoute} from 'vue-router';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useBrandingStore } from '@/stores/brandingStore.js';
|
||||
import { mdiArrowLeft, mdiCheckCircle } from '@mdi/js';
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
@@ -114,7 +113,7 @@ function goBack() {
|
||||
content: '';
|
||||
background: linear-gradient(135deg, rgba(64, 64, 64, 1) 0%, rgba(64, 64, 64, 0) 20%, rgba(64, 64, 64, 0.5) 100%);
|
||||
mask: linear-gradient(#fff 0 0) content-box,
|
||||
linear-gradient(#fff 0 0);
|
||||
linear-gradient(#fff 0 0);
|
||||
mask-composite: exclude;
|
||||
pointer-events: none;
|
||||
}
|
||||
@@ -125,7 +124,7 @@ function goBack() {
|
||||
}
|
||||
|
||||
.link-button {
|
||||
@apply flex items-center gap-2;
|
||||
@apply flex items-center gap-2;
|
||||
@apply text-hutopyPrimary hover:text-hutopySecondary;
|
||||
@apply transition-colors;
|
||||
@apply duration-300;
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
<!-- Navigation Link at the top -->
|
||||
<div class="navigation-link">
|
||||
<button class="link-button" @click="goBack()">
|
||||
<v-icon>mdi-arrow-left</v-icon>
|
||||
<v-icon :icon="mdiArrowLeft" />
|
||||
{{ t('returnToCreator') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<h1>{{ t('title') }}</h1>
|
||||
<p>{{ t('message') }}</p>
|
||||
</div>
|
||||
@@ -18,12 +18,11 @@
|
||||
<script setup>
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useBrandingStore } from '@/stores/brandingStore.js';
|
||||
import { mdiArrowLeft } from '@mdi/js';
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
const brandingStore = useBrandingStore();
|
||||
|
||||
function goBack() {
|
||||
const creatorName = route.params.creator?.split('/')[0] || '';
|
||||
@@ -82,7 +81,7 @@ function goBack() {
|
||||
@apply p-[1px];
|
||||
background: linear-gradient(135deg, rgba(64, 64, 64, 1) 0%, rgba(64, 64, 64, 0) 20%, rgba(64, 64, 64, 0.5) 100%);
|
||||
mask: linear-gradient(#fff 0 0) content-box,
|
||||
linear-gradient(#fff 0 0);
|
||||
linear-gradient(#fff 0 0);
|
||||
mask-composite: exclude;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@@ -1,60 +1,31 @@
|
||||
<template>
|
||||
<div class="flex min-h-full justify-center items-center p-20 w-full">
|
||||
<div class="flex min-h-full w-full items-center justify-center p-20">
|
||||
<div class="card justify-items-center">
|
||||
<img :alt="t('alt')"
|
||||
src="/images/hutopymedia/loginpage/hutopylogin.svg"/>
|
||||
<img :alt="t('alt')" src="/images/hutopymedia/loginpage/hutopylogin.svg" />
|
||||
<div class="flex flex-col gap-10">
|
||||
<h1 class="text-2xl font-bold login-text text-center ">
|
||||
<h1 class="login-text text-center text-2xl font-bold ">
|
||||
{{ t('title') }}
|
||||
</h1>
|
||||
|
||||
<v-form @submit.prevent="handleRegister">
|
||||
<div class="flex flex-col gap-4">
|
||||
<v-text-field
|
||||
v-model="name"
|
||||
:label="t('name')"
|
||||
required
|
||||
></v-text-field>
|
||||
<v-text-field v-model="name" :label="t('name')" required></v-text-field>
|
||||
|
||||
<v-text-field
|
||||
v-model="email"
|
||||
:label="t('email')"
|
||||
type="email"
|
||||
required
|
||||
></v-text-field>
|
||||
<v-text-field v-model="email" :label="t('email')" type="email" required></v-text-field>
|
||||
|
||||
<v-text-field
|
||||
v-model="password"
|
||||
:label="t('password')"
|
||||
:type="showPassword ? 'text' : 'password'"
|
||||
required
|
||||
:hint="t('passwordRequirements')"
|
||||
>
|
||||
<v-text-field v-model="password" :label="t('password')" :type="showPassword ? 'text' : 'password'" required
|
||||
:hint="t('passwordRequirements')">
|
||||
<template v-slot:append-inner>
|
||||
<v-icon
|
||||
@click="showPassword = !showPassword"
|
||||
class="visibility-toggle"
|
||||
size="small"
|
||||
>
|
||||
{{ showPassword ? 'mdi-eye-off' : 'mdi-eye' }}
|
||||
</v-icon>
|
||||
<v-icon @click="showPassword = !showPassword" class="visibility-toggle" size="small"
|
||||
:icon="showPassword ? mdiEyeOff : mdiEye" />
|
||||
</template>
|
||||
</v-text-field>
|
||||
|
||||
<v-text-field
|
||||
v-model="confirmPassword"
|
||||
:label="t('confirmPassword')"
|
||||
:type="showConfirmPassword ? 'text' : 'password'"
|
||||
required
|
||||
>
|
||||
<v-text-field v-model="confirmPassword" :label="t('confirmPassword')"
|
||||
:type="showConfirmPassword ? 'text' : 'password'" required>
|
||||
<template v-slot:append-inner>
|
||||
<v-icon
|
||||
@click="showConfirmPassword = !showConfirmPassword"
|
||||
class="visibility-toggle"
|
||||
size="small"
|
||||
>
|
||||
{{ showConfirmPassword ? 'mdi-eye-off' : 'mdi-eye' }}
|
||||
</v-icon>
|
||||
<v-icon @click="showConfirmPassword = !showConfirmPassword" class="visibility-toggle" size="small"
|
||||
:icon="showConfirmPassword ? mdiEyeOff : mdiEye" />
|
||||
</template>
|
||||
</v-text-field>
|
||||
|
||||
@@ -62,7 +33,7 @@
|
||||
{{ t('register') }}
|
||||
</v-btn>
|
||||
|
||||
<div class="text-center mt-4">
|
||||
<div class="mt-4 text-center">
|
||||
{{ t('alreadyHaveAccount') }}
|
||||
<router-link to="/login" class="text-blue-500">
|
||||
{{ t('signIn') }}
|
||||
@@ -85,6 +56,7 @@ import { useClient } from '@/plugins/api.js';
|
||||
import { useAuthStore } from '@/stores/authStore.js';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { mdiEye, mdiEyeOff } from '@mdi/js';
|
||||
|
||||
const { t } = useI18n();
|
||||
const router = useRouter();
|
||||
@@ -192,4 +164,4 @@ async function handleRegister() {
|
||||
"registrationFailed": "El registro falló. Por favor, inténtelo de nuevo."
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
</i18n>
|
||||
|
||||
@@ -1,50 +1,32 @@
|
||||
<template>
|
||||
<div class="flex min-h-full justify-center items-center w-full p-4">
|
||||
<div class="flex flex-col gap-10 w-full max-w-[512px]">
|
||||
<h1 class="text-2xl font-bold text-center">
|
||||
<div class="flex min-h-full w-full items-center justify-center p-4">
|
||||
<div class="flex w-full max-w-[512px] flex-col gap-10">
|
||||
<h1 class="text-center text-2xl font-bold">
|
||||
{{ t('title') }}
|
||||
</h1>
|
||||
|
||||
|
||||
<form @submit.prevent="handleResetPassword" class="card">
|
||||
<div class="card-content">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="form-field">
|
||||
<label for="password" class="form-label">{{ t('newPassword') }}</label>
|
||||
<div class="relative">
|
||||
<input
|
||||
id="password"
|
||||
v-model="password"
|
||||
:type="showPassword ? 'text' : 'password'"
|
||||
class="form-input"
|
||||
required
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
@click="showPassword = !showPassword"
|
||||
class="password-toggle"
|
||||
>
|
||||
<v-icon size="small">{{ showPassword ? 'mdi-eye-off' : 'mdi-eye' }}</v-icon>
|
||||
<input id="password" v-model="password" :type="showPassword ? 'text' : 'password'" class="form-input"
|
||||
required />
|
||||
<button type="button" @click="showPassword = !showPassword" class="password-toggle">
|
||||
<v-icon size="small" :icon="showPassword ? mdiEyeOff : mdiEye" />
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-sm text-gray-500 mt-1">{{ t('passwordRequirements') }}</p>
|
||||
<p class="mt-1 text-sm text-gray-500">{{ t('passwordRequirements') }}</p>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label for="confirmPassword" class="form-label">{{ t('confirmPassword') }}</label>
|
||||
<div class="relative">
|
||||
<input
|
||||
id="confirmPassword"
|
||||
v-model="confirmPassword"
|
||||
:type="showConfirmPassword ? 'text' : 'password'"
|
||||
class="form-input"
|
||||
required
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
@click="showConfirmPassword = !showConfirmPassword"
|
||||
class="password-toggle"
|
||||
>
|
||||
<v-icon size="small">{{ showConfirmPassword ? 'mdi-eye-off' : 'mdi-eye' }}</v-icon>
|
||||
<input id="confirmPassword" v-model="confirmPassword" :type="showConfirmPassword ? 'text' : 'password'"
|
||||
class="form-input" required />
|
||||
<button type="button" @click="showConfirmPassword = !showConfirmPassword" class="password-toggle">
|
||||
<v-icon size="small" :icon="showConfirmPassword ? mdiEyeOff : mdiEye" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -53,18 +35,14 @@
|
||||
{{ errorMessage }}
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="primary w-full"
|
||||
:disabled="isLoading"
|
||||
>
|
||||
<button type="submit" class="primary w-full" :disabled="isLoading">
|
||||
<span v-if="isLoading" class="loading-spinner mr-2"></span>
|
||||
{{ t('resetPassword') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
<!-- Success message -->
|
||||
<div v-if="success" class="success-message">
|
||||
{{ t('passwordResetSuccess') }}
|
||||
@@ -83,7 +61,7 @@ import { ref, onMounted } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import { useClient } from '@/plugins/api.js';
|
||||
import { VIcon } from 'vuetify/components';
|
||||
import { mdiEye, mdiEyeOff } from '@mdi/js';
|
||||
|
||||
const { t } = useI18n();
|
||||
const router = useRouter();
|
||||
@@ -104,7 +82,7 @@ onMounted(() => {
|
||||
// Get email and token from URL query parameters
|
||||
email.value = route.query.email || '';
|
||||
token.value = route.query.token || '';
|
||||
|
||||
|
||||
// Validate that we have both email and token
|
||||
if (!email.value || !token.value) {
|
||||
errorMessage.value = t('invalidResetLink');
|
||||
@@ -114,27 +92,27 @@ onMounted(() => {
|
||||
async function handleResetPassword() {
|
||||
// Reset error message
|
||||
errorMessage.value = '';
|
||||
|
||||
|
||||
// Validate passwords match
|
||||
if (password.value !== confirmPassword.value) {
|
||||
errorMessage.value = t('passwordsDoNotMatch');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Validate password length
|
||||
if (password.value.length < 8) {
|
||||
errorMessage.value = t('passwordTooShort');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Validate that we have email and token
|
||||
if (!email.value || !token.value) {
|
||||
errorMessage.value = t('invalidResetLink');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
isLoading.value = true;
|
||||
|
||||
|
||||
try {
|
||||
// Call password reset API
|
||||
await clientApi.post('api/users/reset-password', {
|
||||
@@ -142,14 +120,14 @@ async function handleResetPassword() {
|
||||
token: token.value,
|
||||
newPassword: password.value
|
||||
});
|
||||
|
||||
|
||||
// Show success message
|
||||
success.value = true;
|
||||
|
||||
|
||||
// Clear form fields
|
||||
password.value = '';
|
||||
confirmPassword.value = '';
|
||||
|
||||
|
||||
// Redirect to login after a delay
|
||||
setTimeout(() => {
|
||||
router.push('/login');
|
||||
@@ -181,14 +159,11 @@ async function handleResetPassword() {
|
||||
}
|
||||
|
||||
.form-input {
|
||||
@apply bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg
|
||||
focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5
|
||||
dark:bg-gray-700 dark:border-gray-600 dark:text-white;
|
||||
@apply bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:text-white;
|
||||
}
|
||||
|
||||
.primary {
|
||||
@apply bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg text-sm px-5 py-2.5
|
||||
focus:outline-none focus:ring-4 focus:ring-blue-300 disabled:opacity-50 disabled:cursor-not-allowed;
|
||||
@apply bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg text-sm px-5 py-2.5 focus:outline-none focus:ring-4 focus:ring-blue-300 disabled:opacity-50 disabled:cursor-not-allowed;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<button v-if="!isEditMode" :title="t('edit')"
|
||||
class="flex size-12 items-center justify-center rounded-full bg-hutopyPrimary shadow-lg"
|
||||
@click="toggleEditMode()">
|
||||
<v-icon large>mdi-pencil</v-icon>
|
||||
<v-icon :icon="mdiPencil" large />
|
||||
</button>
|
||||
|
||||
<!-- Save button -->
|
||||
@@ -20,14 +20,14 @@
|
||||
<v-progress-circular indeterminate size="20" width="2" color="white" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<v-icon>mdi-check</v-icon>
|
||||
<v-icon :icon="mdiCheck" />
|
||||
</template>
|
||||
</button>
|
||||
|
||||
<!-- Cancel button -->
|
||||
<button v-if="isEditMode" :title="t('cancel')"
|
||||
class="flex size-12 items-center justify-center rounded-full bg-red-500 shadow-lg" @click="cancelEdit">
|
||||
<v-icon large>mdi-close</v-icon>
|
||||
<v-icon :icon="mdiClose" large />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -112,6 +112,7 @@ import { buildEmbedUrl, isValidYouTubeUrlOrId, extractVideoId } from '@/utils/yo
|
||||
import AlbumEditor from "@/views/creators/AlbumEditor.vue";
|
||||
import AlbumView from "@/views/creators/AlbumView.vue";
|
||||
import AlbumViewer from './AlbumViewer.vue';
|
||||
import { mdiPencil, mdiCheck, mdiClose } from '@mdi/js';
|
||||
|
||||
const { t } = useI18n();
|
||||
const creatorProfileStore = useCreatorProfileStore();
|
||||
|
||||
@@ -1,50 +1,37 @@
|
||||
<template>
|
||||
<div class="relative">
|
||||
<!-- Banner Container with mouse events -->
|
||||
<div
|
||||
class="relative rounded-b-2xl overflow-y-auto"
|
||||
@mouseenter="showTint = isCurrentCreator"
|
||||
@mouseleave="showTint = false"
|
||||
@click="isCurrentCreator && openBannerEditor()"
|
||||
>
|
||||
<img
|
||||
class="w-full aspect-[4/1] banner object-cover"
|
||||
:src="brandingStore.value?.bannerUrl ?? '/images/placeholders/banner.png'"
|
||||
:alt="t('alt')"
|
||||
>
|
||||
<div class="relative overflow-y-auto rounded-b-2xl" @mouseenter="showTint = isCurrentCreator"
|
||||
@mouseleave="showTint = false" @click="isCurrentCreator && openBannerEditor()">
|
||||
<img class="banner aspect-[4/1] w-full object-cover"
|
||||
:src="brandingStore.value?.bannerUrl ?? '/images/placeholders/banner.png'" :alt="t('alt')">
|
||||
<!-- Tint Effect -->
|
||||
<div
|
||||
v-if="showTint"
|
||||
class="absolute inset-0 bg-black/25 cursor-pointer"
|
||||
>
|
||||
<div v-if="showTint" class="absolute inset-0 cursor-pointer bg-black/25">
|
||||
<!-- Top-right Icon -->
|
||||
<div
|
||||
class="absolute top-4 right-4 w-12 h-12 bg-hutopyPrimary rounded-full flex items-center justify-center shadow-lg"
|
||||
>
|
||||
<v-icon large>mdi-pencil</v-icon>
|
||||
class="absolute right-4 top-4 flex size-12 items-center justify-center rounded-full bg-hutopyPrimary shadow-lg">
|
||||
<v-icon large :icon="mdiPencil" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<v-dialog v-model="isDialogOpen" max-width="800px">
|
||||
<BannerEditor
|
||||
:creator="brandingStore.value"
|
||||
@closeRequested="() => isDialogOpen = false"
|
||||
/>
|
||||
<BannerEditor :creator="brandingStore.value" @closeRequested="() => isDialogOpen = false" />
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import BannerEditor from "@/views/creators/BannerEditor.vue";
|
||||
import {computed, ref} from "vue";
|
||||
import {useBrandingStore} from "@/stores/brandingStore.js";
|
||||
import {useAuthStore} from "@/stores/authStore.js";
|
||||
import {useI18n} from 'vue-i18n';
|
||||
import { computed, ref } from "vue";
|
||||
import { useBrandingStore } from "@/stores/brandingStore.js";
|
||||
import { useAuthStore } from "@/stores/authStore.js";
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { mdiPencil } from '@mdi/js';
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const brandingStore = useBrandingStore();
|
||||
const {t} = useI18n();
|
||||
const { t } = useI18n();
|
||||
|
||||
// State
|
||||
const showTint = ref(false);
|
||||
@@ -60,8 +47,7 @@ const isCurrentCreator = computed(() => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
||||
<i18n>
|
||||
{
|
||||
@@ -75,4 +61,4 @@ const isCurrentCreator = computed(() => {
|
||||
"alt": "Banner del creador"
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
</i18n>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
<!-- Upload prompt -->
|
||||
<div class="drop-zone-content">
|
||||
<v-icon size="large">mdi-plus</v-icon>
|
||||
<v-icon size="large" :icon="mdiPlus" />
|
||||
<span class="mt-2 text-sm">{{ t('dropzoneText') }}</span>
|
||||
</div>
|
||||
|
||||
@@ -37,17 +37,17 @@
|
||||
<!-- Left arrow -->
|
||||
<button @click.stop="moveImage(index, 'up')" @touchstart.stop="moveImage(index, 'up')"
|
||||
class="action-btn left-btn" :disabled="index === 0" :title="t('moveLeft')">
|
||||
<v-icon>mdi-arrow-left</v-icon>
|
||||
<v-icon :icon="mdiArrowLeft" />
|
||||
</button>
|
||||
<!-- Right arrow -->
|
||||
<button @click.stop="moveImage(index, 'down')" @touchstart.stop="moveImage(index, 'down')"
|
||||
class="action-btn right-btn" :disabled="index === localImages.length - 1" :title="t('moveRight')">
|
||||
<v-icon>mdi-arrow-right</v-icon>
|
||||
<v-icon :icon="mdiArrowRight" />
|
||||
</button>
|
||||
<!-- Delete button -->
|
||||
<button @click.stop="deleteImage(index)" touchstart.stop="deleteImage(index)" class="action-btn delete-btn"
|
||||
:title="t('delete')">
|
||||
<v-icon>mdi-delete</v-icon>
|
||||
<v-icon :icon="mdiDelete" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
@@ -62,6 +62,7 @@ import { ref, onMounted } from "vue";
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { v7 } from 'uuid';
|
||||
import draggable from 'vuedraggable';
|
||||
import { mdiArrowLeft, mdiArrowRight, mdiDelete, mdiPlus } from '@mdi/js';
|
||||
|
||||
const props = defineProps({
|
||||
images: {
|
||||
|
||||
@@ -1,46 +1,25 @@
|
||||
<template>
|
||||
<v-dialog
|
||||
v-model="dialog"
|
||||
fullscreen
|
||||
:scrim="true"
|
||||
transition="dialog-bottom-transition"
|
||||
@click:outside="closeViewer"
|
||||
>
|
||||
<v-dialog v-model="dialog" fullscreen :scrim="true" transition="dialog-bottom-transition"
|
||||
@click:outside="closeViewer">
|
||||
<div class="album-viewer" @click.self="closeViewer">
|
||||
<!-- Main image container -->
|
||||
<div class="image-container">
|
||||
<img
|
||||
:src="currentImage"
|
||||
:alt="t('viewer.imageAlt', { index: currentIndex + 1 })"
|
||||
class="main-image"
|
||||
/>
|
||||
<img :src="currentImage" :alt="t('viewer.imageAlt', { index: currentIndex + 1 })" class="main-image" />
|
||||
|
||||
<!-- Navigation buttons -->
|
||||
<button
|
||||
class="nav-btn left-btn"
|
||||
@click.stop="previousImage"
|
||||
:disabled="currentIndex === 0"
|
||||
:title="t('viewer.previous')"
|
||||
>
|
||||
<v-icon size="large" color="white">mdi-chevron-left</v-icon>
|
||||
<button class="nav-btn left-btn" @click.stop="previousImage" :disabled="currentIndex === 0"
|
||||
:title="t('viewer.previous')">
|
||||
<v-icon size="large" color="white" :icon="mdiChevronLeft" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="nav-btn right-btn"
|
||||
@click.stop="nextImage"
|
||||
:disabled="currentIndex === images.length - 1"
|
||||
:title="t('viewer.next')"
|
||||
>
|
||||
<v-icon size="large" color="white">mdi-chevron-right</v-icon>
|
||||
<button class="nav-btn right-btn" @click.stop="nextImage" :disabled="currentIndex === images.length - 1"
|
||||
:title="t('viewer.next')">
|
||||
<v-icon size="large" color="white" :icon="mdiChevronRight" />
|
||||
</button>
|
||||
|
||||
<!-- Close button -->
|
||||
<button
|
||||
class="close-btn"
|
||||
@click.stop="closeViewer"
|
||||
:title="t('viewer.close')"
|
||||
>
|
||||
<v-icon size="large" color="white">mdi-close</v-icon>
|
||||
<button class="close-btn" @click.stop="closeViewer" :title="t('viewer.close')">
|
||||
<v-icon size="large" color="white" :icon="mdiClose" />
|
||||
</button>
|
||||
|
||||
<!-- Image counter -->
|
||||
@@ -55,6 +34,7 @@
|
||||
<script setup>
|
||||
import { ref, watch, computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { mdiChevronLeft, mdiChevronRight, mdiClose } from '@mdi/js';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
|
||||
@@ -1,31 +1,18 @@
|
||||
<template>
|
||||
<div class="relative"
|
||||
@mouseenter="showTint = isCurrentCreator"
|
||||
@mouseleave="showTint = false"
|
||||
@click="isCurrentCreator && openBannerEditor()"
|
||||
>
|
||||
<div class="relative" @mouseenter="showTint = isCurrentCreator" @mouseleave="showTint = false"
|
||||
@click="isCurrentCreator && openBannerEditor()">
|
||||
|
||||
<div class="rounded-full border-4 border-hPrimary w-[110px] h-[110px]">
|
||||
<img
|
||||
:src="brandingStore.value?.portraitUrl ?? '/images/placeholders/profile.png'"
|
||||
:alt="t('logoAlt')"
|
||||
width="110px"
|
||||
height="110px"
|
||||
class="rounded-full"
|
||||
/>
|
||||
<div class="size-[110px] rounded-full border-4 border-hPrimary">
|
||||
<img :src="brandingStore.value?.portraitUrl ?? '/images/placeholders/profile.png'" :alt="t('logoAlt')"
|
||||
width="110px" height="110px" class="rounded-full" />
|
||||
</div>
|
||||
|
||||
<!-- Tint Effect -->
|
||||
<div
|
||||
v-if="showTint"
|
||||
class="absolute rounded-full inset-0 bg-black/25 cursor-pointer"
|
||||
:title="t('editLogo')"
|
||||
>
|
||||
<div v-if="showTint" class="absolute inset-0 cursor-pointer rounded-full bg-black/25" :title="t('editLogo')">
|
||||
<!-- Top-right Icon -->
|
||||
<div
|
||||
class="absolute top-0 right-0 w-12 h-12 bg-hutopyPrimary rounded-full flex items-center justify-center shadow-lg"
|
||||
>
|
||||
<v-icon large>mdi-pencil</v-icon>
|
||||
class="absolute right-0 top-0 flex size-12 items-center justify-center rounded-full bg-hutopyPrimary shadow-lg">
|
||||
<v-icon large :icon="mdiPencil" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -33,21 +20,20 @@
|
||||
|
||||
<v-dialog v-model="isDialogOpen" max-width="800px">
|
||||
<template #default="{ close }">
|
||||
<creator-logo-editor
|
||||
:creator="brandingStore?.value"
|
||||
@closeRequested="() => isDialogOpen = false"
|
||||
></creator-logo-editor>
|
||||
<creator-logo-editor :creator="brandingStore?.value"
|
||||
@closeRequested="() => isDialogOpen = false"></creator-logo-editor>
|
||||
</template>
|
||||
</v-dialog>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {useAuthStore} from "@/stores/authStore.js";
|
||||
import {useBrandingStore} from "@/stores/brandingStore.js";
|
||||
import { useAuthStore } from "@/stores/authStore.js";
|
||||
import { useBrandingStore } from "@/stores/brandingStore.js";
|
||||
import CreatorLogoEditor from "@/views/creators/CreatorLogoEditor.vue";
|
||||
import {computed, ref} from "vue";
|
||||
import { computed, ref } from "vue";
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { mdiPencil } from '@mdi/js';
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const brandingStore = useBrandingStore();
|
||||
@@ -72,7 +58,6 @@ const isCurrentCreator = computed(() => {
|
||||
.logo-image {
|
||||
@apply border-4 border-solid border-hTertiary;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<i18n>
|
||||
@@ -90,4 +75,4 @@ const isCurrentCreator = computed(() => {
|
||||
"editLogo": "Editar logo"
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
</i18n>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<script setup>
|
||||
import {ref, onMounted, onUnmounted, computed} from "vue";
|
||||
import {v7} from "uuid";
|
||||
import {useClient} from "@/plugins/api.js";
|
||||
import {useI18n} from 'vue-i18n';
|
||||
import { ref, onMounted, onUnmounted, computed } from "vue";
|
||||
import { v7 } from "uuid";
|
||||
import { useClient } from "@/plugins/api.js";
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import config from '@/config';
|
||||
import { mdiCheckCircle, mdiCloseCircle } from '@mdi/js';
|
||||
|
||||
const props = defineProps({
|
||||
name: {
|
||||
@@ -24,7 +25,7 @@ const emits = defineEmits([
|
||||
]);
|
||||
|
||||
const name = ref(props.name);
|
||||
const {t} = useI18n();
|
||||
const { t } = useI18n();
|
||||
|
||||
const isOperationPending = ref(false);
|
||||
const reservationState = ref(null);
|
||||
@@ -129,9 +130,9 @@ const checkNameAvailability = async (nameToCheck) => {
|
||||
currentController = controller;
|
||||
|
||||
await client.post(
|
||||
`/api/creators/@${encodeURIComponent(nameToCheck)}/reserve`,
|
||||
{reservationId: reservationId.value},
|
||||
{signal: controller.signal}
|
||||
`/api/creators/@${encodeURIComponent(nameToCheck)}/reserve`,
|
||||
{ reservationId: reservationId.value },
|
||||
{ signal: controller.signal }
|
||||
);
|
||||
|
||||
// Only process the response if this is still the current request
|
||||
@@ -162,35 +163,23 @@ onUnmounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-text-field
|
||||
variant="outlined"
|
||||
:label="t('creator.name.label')"
|
||||
v-model="name"
|
||||
@input="handleInput"
|
||||
:error-messages="validationError"
|
||||
>
|
||||
<v-text-field variant="outlined" :label="t('creator.name.label')" v-model="name" @input="handleInput"
|
||||
:error-messages="validationError">
|
||||
<template #prepend-inner>
|
||||
<span class="text-gray-400 font-sans">{{ baseUrl }}</span>
|
||||
<span class="font-sans text-gray-400">{{ baseUrl }}</span>
|
||||
</template>
|
||||
|
||||
<template #append-inner>
|
||||
<v-progress-circular
|
||||
v-if="reservationState === 'loading'"
|
||||
indeterminate
|
||||
size="24"
|
||||
width="3"
|
||||
color="grey"
|
||||
></v-progress-circular>
|
||||
<v-progress-circular v-if="reservationState === 'loading'" indeterminate size="24" width="3"
|
||||
color="grey"></v-progress-circular>
|
||||
|
||||
<v-icon v-else-if="reservationState === 'reserved'" color="green">mdi-check-circle</v-icon>
|
||||
<v-icon v-else-if="reservationState === 'unavailable'" color="red">mdi-close-circle</v-icon>
|
||||
<v-icon v-else-if="reservationState === 'reserved'" color="green" :icon="mdiCheckCircle" />
|
||||
<v-icon v-else-if="reservationState === 'unavailable'" color="red" :icon="mdiCloseCircle" />
|
||||
</template>
|
||||
</v-text-field>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
||||
<i18n>
|
||||
{
|
||||
@@ -228,4 +217,4 @@ onUnmounted(() => {
|
||||
}
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
</i18n>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<script setup>
|
||||
import {useI18n} from "vue-i18n";
|
||||
import {useAuthStore} from "@/stores/authStore.js";
|
||||
import {useCreatorProfileStore} from "@/stores/creatorProfileStore.js";
|
||||
import {useUserProfileStore} from "@/stores/userProfileStore.js";
|
||||
import {useLanguageStore} from "@/stores/languageStore.js";
|
||||
import {useRouter, useRoute} from 'vue-router';
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useAuthStore } from "@/stores/authStore.js";
|
||||
import { useCreatorProfileStore } from "@/stores/creatorProfileStore.js";
|
||||
import { useUserProfileStore } from "@/stores/userProfileStore.js";
|
||||
import { useLanguageStore } from "@/stores/languageStore.js";
|
||||
import { useRoute } from 'vue-router';
|
||||
import { mdiFileAccountOutline, mdiAccount, mdiLogin, mdiTranslateVariant, mdiLogout } from '@mdi/js';
|
||||
|
||||
const {locale, t} = useI18n();
|
||||
const { locale, t } = useI18n();
|
||||
const languageStore = useLanguageStore();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
const userProfileStore = useUserProfileStore();
|
||||
@@ -36,20 +36,14 @@ function handleLogout() {
|
||||
|
||||
<div class="side-logo">
|
||||
<router-link to="/@hutopy">
|
||||
<img src="/images/hutopy-logo.png"
|
||||
alt="hutopy logo"
|
||||
height="50">
|
||||
<img src="/images/hutopy-logo.png" alt="hutopy logo" height="50">
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<div class="side-menu">
|
||||
|
||||
<div v-if="authStore.isAuthenticated"
|
||||
class="side-menu-portrait">
|
||||
<img :src="userProfileStore.portraitUrl"
|
||||
alt="Profile Image"
|
||||
referrerpolicy="no-referrer"
|
||||
class="rounded-full">
|
||||
<div v-if="authStore.isAuthenticated" class="side-menu-portrait">
|
||||
<img :src="userProfileStore.portraitUrl" alt="Profile Image" referrerpolicy="no-referrer" class="rounded-full">
|
||||
<span class="profile-label">{{ userProfileStore.alias }}</span>
|
||||
</div>
|
||||
|
||||
@@ -58,13 +52,13 @@ function handleLogout() {
|
||||
<template v-if="authStore.isAuthenticated">
|
||||
<router-link v-if="creatorProfileStore.hasCreator" :to="`/@${creatorProfileStore.creator.slug}`">
|
||||
<button class="menu-item-action">
|
||||
<i class="mdi mdi-file-account-outline"></i>
|
||||
<v-icon :icon="mdiFileAccountOutline" />
|
||||
<span class="label">{{ t('sidebar.myPage') }}</span>
|
||||
</button>
|
||||
</router-link>
|
||||
<router-link v-else to="/create-creator">
|
||||
<button class="menu-item-action">
|
||||
<i class="mdi mdi-file-account-outline"></i>
|
||||
<v-icon :icon="mdiFileAccountOutline" />
|
||||
<span class="label">{{ t('sidebar.myPage') }}</span>
|
||||
</button>
|
||||
</router-link>
|
||||
@@ -73,31 +67,29 @@ function handleLogout() {
|
||||
<template v-if="authStore.isAuthenticated">
|
||||
<router-link to="/profile">
|
||||
<button class="menu-item-action">
|
||||
<i class="mdi mdi-account"></i>
|
||||
<v-icon :icon="mdiAccount" />
|
||||
<span class="label">{{ t('sidebar.myProfile') }}</span>
|
||||
</button>
|
||||
</router-link>
|
||||
</template>
|
||||
|
||||
<button class="menu-item-action"
|
||||
@click="toggleLanguage">
|
||||
<i class="mdi mdi-translate-variant"></i>
|
||||
<button class="menu-item-action" @click="toggleLanguage">
|
||||
<v-icon :icon="mdiTranslateVariant" />
|
||||
<span class="label">{{ locale }}</span>
|
||||
</button>
|
||||
|
||||
<template v-if="!authStore.isAuthenticated">
|
||||
<router-link to="/login">
|
||||
<button class="menu-item-action">
|
||||
<i class="mdi mdi-login"></i>
|
||||
<v-icon :icon="mdiLogin" />
|
||||
<span class="label">{{ t('sidebar.signIn') }}</span>
|
||||
</button>
|
||||
|
||||
</router-link>
|
||||
</template>
|
||||
<div v-else>
|
||||
<button class="menu-item-action"
|
||||
@click="handleLogout">
|
||||
<i class="mdi mdi-logout"></i>
|
||||
<button class="menu-item-action" @click="handleLogout">
|
||||
<v-icon :icon="mdiLogout" />
|
||||
<span class="label">{{ t('sidebar.signOut') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -108,7 +100,6 @@ function handleLogout() {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.side-container {
|
||||
@apply bg-hSurface text-hOnSurface;
|
||||
@apply lg:fixed lg:max-h-screen;
|
||||
@@ -168,7 +159,6 @@ function handleLogout() {
|
||||
@apply text-xl;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<i18n>
|
||||
@@ -198,4 +188,4 @@ function handleLogout() {
|
||||
}
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
</i18n>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<script setup>
|
||||
import {ref, markRaw} from 'vue';
|
||||
import {useCreatorProfileStore} from '@/stores/creatorProfileStore.js';
|
||||
import {useUserProfileStore} from "@/stores/userProfileStore.js";
|
||||
import {useClient} from '@/plugins/api.js';
|
||||
import {useRouter} from 'vue-router';
|
||||
import { ref, markRaw } from 'vue';
|
||||
import { useCreatorProfileStore } from '@/stores/creatorProfileStore.js';
|
||||
import { useUserProfileStore } from "@/stores/userProfileStore.js";
|
||||
import { useClient } from '@/plugins/api.js';
|
||||
import SocialsDialog from './creators/SocialsDialog.vue';
|
||||
import AliasDialog from "@/views/profile/account/AliasDialog.vue";
|
||||
import FullnameDialog from "@/views/profile/account/FullnameDialog.vue";
|
||||
@@ -23,11 +22,11 @@ import Linkedin from "@/views/svg/Linkedin.vue";
|
||||
import Tiktok from "@/views/svg/Tiktok.vue";
|
||||
import Instagram from "@/views/svg/Instagram.vue";
|
||||
import Facebook from "@/views/svg/Facebook.vue";
|
||||
import {useI18n} from 'vue-i18n';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import QRCodeVue from 'qrcode.vue';
|
||||
import { mdiChevronRight, mdiCreditCard, mdiCreditCardOff, mdiDownload, mdiCheck, mdiContentCopy } from '@mdi/js';
|
||||
|
||||
const {t} = useI18n();
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
const userProfileStore = useUserProfileStore()
|
||||
const creatorProfileStore = useCreatorProfileStore();
|
||||
const baseURL = window.location.origin;
|
||||
@@ -143,10 +142,10 @@ function handleDelete() {
|
||||
function downloadQRCode() {
|
||||
try {
|
||||
// Get the SVG element
|
||||
const svgElement = document.querySelector('.qr-code svg') ||
|
||||
document.querySelector('.qr-container svg') ||
|
||||
document.querySelector('svg[class*="qr"]');
|
||||
|
||||
const svgElement = document.querySelector('.qr-code svg') ||
|
||||
document.querySelector('.qr-container svg') ||
|
||||
document.querySelector('svg[class*="qr"]');
|
||||
|
||||
if (!svgElement) {
|
||||
console.error('QR code SVG element not found');
|
||||
return;
|
||||
@@ -162,43 +161,43 @@ function downloadQRCode() {
|
||||
img.onload = () => {
|
||||
// Set padding
|
||||
const padding = 20; // 20 pixels padding on each side
|
||||
|
||||
|
||||
// Create canvas with padding
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
|
||||
// Set canvas size to include padding
|
||||
canvas.width = img.width + (padding * 2);
|
||||
canvas.height = img.height + (padding * 2);
|
||||
|
||||
|
||||
// Fill white background for entire canvas (including padding)
|
||||
ctx.fillStyle = 'white';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
|
||||
// Draw the image with padding offset
|
||||
ctx.drawImage(img, padding, padding);
|
||||
|
||||
|
||||
// Convert to PNG
|
||||
const pngUrl = canvas.toDataURL('image/png');
|
||||
|
||||
|
||||
// Create download link
|
||||
const downloadLink = document.createElement('a');
|
||||
downloadLink.href = pngUrl;
|
||||
downloadLink.download = `hutopy-qr-${creatorProfileStore.creator.slug}.png`;
|
||||
|
||||
|
||||
// Trigger download
|
||||
document.body.appendChild(downloadLink);
|
||||
downloadLink.click();
|
||||
document.body.removeChild(downloadLink);
|
||||
|
||||
|
||||
// Cleanup
|
||||
URL.revokeObjectURL(svgUrl);
|
||||
};
|
||||
|
||||
|
||||
img.onerror = (error) => {
|
||||
console.error('Error loading SVG:', error);
|
||||
};
|
||||
|
||||
|
||||
// Load the SVG into the image
|
||||
img.src = svgUrl;
|
||||
} catch (error) {
|
||||
@@ -221,7 +220,7 @@ async function deconfigureStripe() {
|
||||
<template>
|
||||
|
||||
<div class="min-h-screen w-full">
|
||||
<div class="flex flex-col items-center gap-4 m-4">
|
||||
<div class="m-4 flex flex-col items-center gap-4">
|
||||
|
||||
<div class="card">
|
||||
|
||||
@@ -230,20 +229,20 @@ async function deconfigureStripe() {
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<button
|
||||
class="action"
|
||||
@click="openEditFullname">
|
||||
<button class="action" @click="openEditFullname">
|
||||
<span class="label">{{ t('fullName') }}</span>
|
||||
<span class="value">{{ userProfileStore.fullname }}</span>
|
||||
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
|
||||
<span class="chevron">
|
||||
<v-icon :icon="mdiChevronRight" />
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="action"
|
||||
@click="openEditAlias">
|
||||
<button class="action" @click="openEditAlias">
|
||||
<span class="label">{{ t('alias') }}</span>
|
||||
<span class="value">{{ userProfileStore.user.alias }}</span>
|
||||
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
|
||||
<span class="chevron">
|
||||
<v-icon :icon="mdiChevronRight" />
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -251,7 +250,9 @@ async function deconfigureStripe() {
|
||||
<button class="action" @click="openDialog('EmailDialog')">
|
||||
<span class="label">{{ t('email') }}</span>
|
||||
<span class="value">{{ userProfileStore.user.email }}</span>
|
||||
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
|
||||
<span class="chevron">
|
||||
<v-icon :icon="mdiChevronRight" />
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -259,10 +260,12 @@ async function deconfigureStripe() {
|
||||
<button class="action" @click="openDialog('ChangePasswordDialog')">
|
||||
<span class="label">{{ t('changePassword') }}</span>
|
||||
<span class="value">••••••••</span>
|
||||
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
|
||||
<span class="chevron">
|
||||
<v-icon :icon="mdiChevronRight" />
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<template v-if="creatorProfileStore.hasCreator">
|
||||
@@ -275,29 +278,31 @@ async function deconfigureStripe() {
|
||||
<div class="action" @click="openDialog('ChangeSlugDialog')">
|
||||
<span class="label">{{ t('handle') }}</span>
|
||||
<span class="value">{{ baseURL }}/@{{ creatorProfileStore.creator.slug }}</span>
|
||||
<button
|
||||
ref="copyButtonRef"
|
||||
class="copy-button"
|
||||
@click.stop="copyCreatorUrl"
|
||||
:class="{ 'success': copySuccess }"
|
||||
>
|
||||
<v-icon>{{ copySuccess ? 'mdi-check' : 'mdi-content-copy' }}</v-icon>
|
||||
<button ref="copyButtonRef" class="copy-button" @click.stop="copyCreatorUrl"
|
||||
:class="{ 'success': copySuccess }">
|
||||
<v-icon :icon="copySuccess ? mdiCheck : mdiContentCopy" />
|
||||
</button>
|
||||
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
|
||||
<span class="chevron">
|
||||
<v-icon :icon="mdiChevronRight" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- NAME -->
|
||||
<button class="action" @click="openDialog('ChangeNameDialog')">
|
||||
<span class="label">{{ t('name') }}</span>
|
||||
<span class="value">{{ creatorProfileStore.creator.name }}</span>
|
||||
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
|
||||
<span class="chevron">
|
||||
<v-icon :icon="mdiChevronRight" />
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<!-- TITLE -->
|
||||
<button class="action" @click="openDialog('ChangeTitleDialog')">
|
||||
<span class="label">{{ t('title') }}</span>
|
||||
<span class="value">{{ creatorProfileStore.creator.title }}</span>
|
||||
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
|
||||
<span class="chevron">
|
||||
<v-icon :icon="mdiChevronRight" />
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<!-- PHONE NUMBER -->
|
||||
@@ -306,7 +311,9 @@ async function deconfigureStripe() {
|
||||
<span class="value" :class="{ 'not-set': !creatorProfileStore.creator.presentation?.phoneNumber }">
|
||||
{{ creatorProfileStore.creator.presentation?.phoneNumber || t('notSet') }}
|
||||
</span>
|
||||
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
|
||||
<span class="chevron">
|
||||
<v-icon :icon="mdiChevronRight" />
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<!-- EMAIL -->
|
||||
@@ -315,9 +322,10 @@ async function deconfigureStripe() {
|
||||
<span class="value" :class="{ 'not-set': !creatorProfileStore.creator.presentation?.email }">
|
||||
{{ creatorProfileStore.creator.presentation?.email || t('notSet') }}
|
||||
</span>
|
||||
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
|
||||
<span class="chevron">
|
||||
<v-icon :icon="mdiChevronRight" />
|
||||
</span>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -335,13 +343,12 @@ async function deconfigureStripe() {
|
||||
</span>
|
||||
<div class="stripe-actions">
|
||||
<button class="configure-stripe-button" @click="openDialog('ChangeStripeIdDialog')">
|
||||
<v-icon>mdi-credit-card</v-icon>
|
||||
<v-icon :icon="mdiCreditCard" />
|
||||
{{ t('configureStripe') }}
|
||||
</button>
|
||||
<button v-if="creatorProfileStore.creator.acceptDonation"
|
||||
class="deconfigure-stripe-button"
|
||||
@click="deconfigureStripe">
|
||||
<v-icon>mdi-credit-card-off</v-icon>
|
||||
<button v-if="creatorProfileStore.creator.acceptDonation" class="deconfigure-stripe-button"
|
||||
@click="deconfigureStripe">
|
||||
<v-icon :icon="mdiCreditCardOff"/>
|
||||
{{ t('deconfigureStripe') }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -361,7 +368,9 @@ async function deconfigureStripe() {
|
||||
<facebook class="social-icon"></facebook>
|
||||
</span>
|
||||
<span class="value">{{ creatorProfileStore.creator.socials?.facebookUrl }}</span>
|
||||
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
|
||||
<span class="chevron">
|
||||
<v-icon :icon="mdiChevronRight" />
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button class="action" @click="openDialog('SocialsDialog')">
|
||||
@@ -369,7 +378,9 @@ async function deconfigureStripe() {
|
||||
<instagram class="social-icon"></instagram>
|
||||
</span>
|
||||
<span class="value">{{ creatorProfileStore.creator.socials?.instagramUrl }}</span>
|
||||
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
|
||||
<span class="chevron">
|
||||
<v-icon :icon="mdiChevronRight" />
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button class="action" @click="openDialog('SocialsDialog')">
|
||||
@@ -377,7 +388,9 @@ async function deconfigureStripe() {
|
||||
<x class="social-icon"></x>
|
||||
</span>
|
||||
<span class="value">{{ creatorProfileStore.creator.socials?.xUrl }}</span>
|
||||
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
|
||||
<span class="chevron">
|
||||
<v-icon :icon="mdiChevronRight" />
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button class="action" @click="openDialog('SocialsDialog')">
|
||||
@@ -385,7 +398,9 @@ async function deconfigureStripe() {
|
||||
<linkedin class="social-icon"></linkedin>
|
||||
</span>
|
||||
<span class="value">{{ creatorProfileStore.creator.socials?.linkedInUrl }}</span>
|
||||
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
|
||||
<span class="chevron">
|
||||
<v-icon :icon="mdiChevronRight" />
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button class="action" @click="openDialog('SocialsDialog')">
|
||||
@@ -393,7 +408,9 @@ async function deconfigureStripe() {
|
||||
<tiktok class="social-icon"></tiktok>
|
||||
</span>
|
||||
<span class="value">{{ creatorProfileStore.creator.socials?.tikTokUrl }}</span>
|
||||
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
|
||||
<span class="chevron">
|
||||
<v-icon :icon="mdiChevronRight" />
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button class="action" @click="openDialog('SocialsDialog')">
|
||||
@@ -401,7 +418,9 @@ async function deconfigureStripe() {
|
||||
<youtube class="social-icon"></youtube>
|
||||
</span>
|
||||
<span class="value">{{ creatorProfileStore.creator.socials?.youtubeUrl }}</span>
|
||||
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
|
||||
<span class="chevron">
|
||||
<v-icon :icon="mdiChevronRight" />
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button class="action" @click="openDialog('SocialsDialog')">
|
||||
@@ -409,7 +428,9 @@ async function deconfigureStripe() {
|
||||
<reddit class="social-icon"></reddit>
|
||||
</span>
|
||||
<span class="value">{{ creatorProfileStore.creator.socials?.redditUrl }}</span>
|
||||
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
|
||||
<span class="chevron">
|
||||
<v-icon :icon="mdiChevronRight" />
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button class="action" @click="openDialog('SocialsDialog')">
|
||||
@@ -417,7 +438,9 @@ async function deconfigureStripe() {
|
||||
<web class="social-icon"></web>
|
||||
</span>
|
||||
<span class="value">{{ creatorProfileStore.creator.socials?.websiteUrl }}</span>
|
||||
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
|
||||
<span class="chevron">
|
||||
<v-icon :icon="mdiChevronRight" />
|
||||
</span>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
@@ -430,20 +453,10 @@ async function deconfigureStripe() {
|
||||
<div class="content">
|
||||
<div class="qr-container">
|
||||
<p class="qr-text">{{ t('qrCodeDescription') }}</p>
|
||||
<QRCodeVue
|
||||
v-if="creatorProfileStore.creator"
|
||||
:value="baseURL + '/@' + creatorProfileStore.creator.slug"
|
||||
:size="200"
|
||||
level="H"
|
||||
render-as="svg"
|
||||
class="qr-code"
|
||||
/>
|
||||
<button
|
||||
v-if="creatorProfileStore.creator"
|
||||
class="download-button"
|
||||
@click="downloadQRCode"
|
||||
>
|
||||
<v-icon>mdi-download</v-icon>
|
||||
<QRCodeVue v-if="creatorProfileStore.creator" :value="baseURL + '/@' + creatorProfileStore.creator.slug"
|
||||
:size="200" level="H" render-as="svg" class="qr-code" />
|
||||
<button v-if="creatorProfileStore.creator" class="download-button" @click="downloadQRCode">
|
||||
<v-icon :icon="mdiDownload" />
|
||||
{{ t('downloadQRCode') }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -459,14 +472,11 @@ async function deconfigureStripe() {
|
||||
{{ t('dangerZoneWarning') }}
|
||||
</span>
|
||||
<div class="p-2">
|
||||
<button v-if="!creatorProfileStore.creator.isDeleted"
|
||||
class="primary danger-action"
|
||||
@click="deleteDialogShown = true">
|
||||
<button v-if="!creatorProfileStore.creator.isDeleted" class="primary danger-action"
|
||||
@click="deleteDialogShown = true">
|
||||
{{ t('deleteCreatorPage') }}
|
||||
</button>
|
||||
<button v-else
|
||||
class="primary safe-action"
|
||||
@click="restoreDialogShown = true">
|
||||
<button v-else class="primary safe-action" @click="restoreDialogShown = true">
|
||||
{{ t('restoreCreatorPage') }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -478,27 +488,16 @@ async function deconfigureStripe() {
|
||||
</div>
|
||||
|
||||
<v-dialog v-model="dialogEditFullnameShown">
|
||||
<FullnameDialog
|
||||
:firstname="userProfileStore.user.firstname"
|
||||
:lastname="userProfileStore.user.lastname"
|
||||
@close="handleCloseEditFullname"
|
||||
@save="handleSaveEditFullname"
|
||||
/>
|
||||
<FullnameDialog :firstname="userProfileStore.user.firstname" :lastname="userProfileStore.user.lastname"
|
||||
@close="handleCloseEditFullname" @save="handleSaveEditFullname" />
|
||||
</v-dialog>
|
||||
<v-dialog v-model="dialogEditAliasShown">
|
||||
<alias-dialog
|
||||
:alias="userProfileStore.user.alias"
|
||||
@close="handleCloseEditAlias"
|
||||
@save="handleSaveEditAlias"
|
||||
></alias-dialog>
|
||||
<alias-dialog :alias="userProfileStore.user.alias" @close="handleCloseEditAlias"
|
||||
@save="handleSaveEditAlias"></alias-dialog>
|
||||
</v-dialog>
|
||||
<v-dialog v-model="dialogShown">
|
||||
<component
|
||||
:is="currentComponent"
|
||||
:creator="creatorProfileStore.creator"
|
||||
:email="userProfileStore.user.email"
|
||||
@closeRequested="closeDialog"
|
||||
></component>
|
||||
<component :is="currentComponent" :creator="creatorProfileStore.creator" :email="userProfileStore.user.email"
|
||||
@closeRequested="closeDialog"></component>
|
||||
</v-dialog>
|
||||
<v-dialog v-model="restoreDialogShown">
|
||||
<div class="card dialog">
|
||||
@@ -760,4 +759,4 @@ async function deconfigureStripe() {
|
||||
"deconfigureStripe": "Eliminar Stripe"
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
</i18n>
|
||||
|
||||
@@ -1,47 +1,26 @@
|
||||
<template>
|
||||
<div class="card dialog">
|
||||
|
||||
|
||||
<div class="card-title">
|
||||
{{ t('changePassword') }}
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<p class="description mb-4">{{ t('passwordDescription') }}</p>
|
||||
|
||||
<v-text-field
|
||||
v-model="newPassword"
|
||||
:label="t('newPassword')"
|
||||
:type="showNewPassword ? 'text' : 'password'"
|
||||
variant="outlined"
|
||||
required
|
||||
:hint="t('passwordRequirements')"
|
||||
>
|
||||
|
||||
<v-text-field v-model="newPassword" :label="t('newPassword')" :type="showNewPassword ? 'text' : 'password'"
|
||||
variant="outlined" required :hint="t('passwordRequirements')">
|
||||
<template v-slot:append-inner>
|
||||
<v-icon
|
||||
@click="showNewPassword = !showNewPassword"
|
||||
class="visibility-toggle"
|
||||
size="small"
|
||||
>
|
||||
{{ showNewPassword ? 'mdi-eye-off' : 'mdi-eye' }}
|
||||
</v-icon>
|
||||
<v-icon @click="showNewPassword = !showNewPassword" class="visibility-toggle" size="small"
|
||||
:icon="showNewPassword ? mdiEyeOff : mdiEye" />
|
||||
</template>
|
||||
</v-text-field>
|
||||
|
||||
<v-text-field
|
||||
v-model="confirmPassword"
|
||||
:label="t('confirmPassword')"
|
||||
:type="showConfirmPassword ? 'text' : 'password'"
|
||||
variant="outlined"
|
||||
required
|
||||
>
|
||||
|
||||
<v-text-field v-model="confirmPassword" :label="t('confirmPassword')"
|
||||
:type="showConfirmPassword ? 'text' : 'password'" variant="outlined" required>
|
||||
<template v-slot:append-inner>
|
||||
<v-icon
|
||||
@click="showConfirmPassword = !showConfirmPassword"
|
||||
class="visibility-toggle"
|
||||
size="small"
|
||||
>
|
||||
{{ showConfirmPassword ? 'mdi-eye-off' : 'mdi-eye' }}
|
||||
</v-icon>
|
||||
<v-icon @click="showConfirmPassword = !showConfirmPassword" class="visibility-toggle" size="small"
|
||||
:icon="showNewPassword ? mdiEyeOff : mdiEye" />
|
||||
</template>
|
||||
</v-text-field>
|
||||
|
||||
@@ -58,7 +37,7 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -81,7 +60,7 @@ const showConfirmPassword = ref(false);
|
||||
async function handleChangePassword() {
|
||||
// Clear previous error
|
||||
errorMessage.value = '';
|
||||
|
||||
|
||||
// Validate passwords match
|
||||
if (newPassword.value !== confirmPassword.value) {
|
||||
errorMessage.value = t('passwordsDoNotMatch');
|
||||
@@ -100,10 +79,10 @@ async function handleChangePassword() {
|
||||
// Pass empty string for current password since we're already authenticated
|
||||
// This will use the set-password endpoint for OAuth users
|
||||
await authStore.changePassword(newPassword.value);
|
||||
|
||||
|
||||
// Success - close dialog
|
||||
emit('closeRequested');
|
||||
|
||||
|
||||
// You could also emit a success event if needed
|
||||
// emit('success');
|
||||
} catch (error) {
|
||||
@@ -117,7 +96,6 @@ async function handleChangePassword() {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.dialog {
|
||||
@apply max-w-md mx-auto;
|
||||
}
|
||||
@@ -138,7 +116,6 @@ async function handleChangePassword() {
|
||||
:deep(.v-field__append-inner) {
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<i18n>
|
||||
@@ -181,4 +158,4 @@ async function handleChangePassword() {
|
||||
}
|
||||
}
|
||||
|
||||
</i18n>
|
||||
</i18n>
|
||||
|
||||
Reference in New Issue
Block a user