feat(i18n): deprecate Spanish language support in the frontend
This commit is contained in:
@@ -948,39 +948,6 @@
|
||||
"phoneNumber": "Numéro de téléphone",
|
||||
"title": "Titre",
|
||||
"removeStripe": "Retirer Stripe"
|
||||
},
|
||||
"es": {
|
||||
"personalInfo": "Información Personal",
|
||||
"fullName": "Nombre Completo",
|
||||
"alias": "Alias",
|
||||
"email": "Correo Electrónico",
|
||||
"changePassword": "Actualizar contraseña",
|
||||
"creatorInfo": "Información del Creador",
|
||||
"dangerZone": "Zona de Peligro",
|
||||
"dangerZoneWarning": "Las acciones a continuación pueden tener impactos significativos en tu página de creador. Por favor, procede con precaución.",
|
||||
"deleteWarning": "¿Estás seguro de que quieres eliminar tu página de creador? Esta acción no se puede deshacer.",
|
||||
"restoreWarning": "¿Estás seguro de que quieres restaurar tu página de creador? Esto hará que tu página sea visible nuevamente.",
|
||||
"deleteCreatorPage": "Eliminar Página de Creador",
|
||||
"restoreCreatorPage": "Restaurar Página de Creador",
|
||||
"stripeAccountId": "ID de Cuenta Stripe",
|
||||
"socialNetworks": "Redes Sociales",
|
||||
"handle": "Identificador del creador",
|
||||
"qrCode": "Código QR",
|
||||
"qrCodeDescription": "¡Imprime este código QR para compartir tu Hutopy con el mundo! Perfecto para tarjetas de presentación, redes sociales y materiales promocionales.",
|
||||
"downloadQRCode": "Descargar Código QR",
|
||||
"payment-information": "Información de Pago",
|
||||
"stripeStatus": "Estado de Stripe",
|
||||
"configured": "Configurado",
|
||||
"notConfigured": "No Configurado",
|
||||
"needsMoreInfo": "Requiere Más Información",
|
||||
"pendingVerification": "Verificación Pendiente",
|
||||
"continueStripeSetup": "Continuar Configuración de Stripe",
|
||||
"reviewStripe": "Revisar Stripe",
|
||||
"notSet": "No Establecido",
|
||||
"configureStripe": "Connectar Stripe",
|
||||
"phoneNumber": "Número de teléfono",
|
||||
"title": "Título",
|
||||
"removeStripe": "Eliminar Stripe"
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
|
||||
@@ -1,66 +1,58 @@
|
||||
<template>
|
||||
<div class="card dialog">
|
||||
<div class="card-title">
|
||||
{{ t('title') }}
|
||||
</div>
|
||||
|
||||
<div class="card dialog">
|
||||
<div class="card-content">
|
||||
<v-text-field
|
||||
v-model="alias"
|
||||
:label="t('label')"
|
||||
variant="outlined"
|
||||
></v-text-field>
|
||||
</div>
|
||||
|
||||
<div class="card-title">
|
||||
{{ t('title') }}
|
||||
<div class="card-actions">
|
||||
<button
|
||||
class="secondary"
|
||||
@click="requestClose"
|
||||
>
|
||||
{{ t('cancel') }}
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="primary"
|
||||
@click="requestSave"
|
||||
>
|
||||
{{ t('save') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<v-text-field
|
||||
variant="outlined"
|
||||
v-model="alias"
|
||||
:label="t('label')"
|
||||
></v-text-field>
|
||||
</div>
|
||||
|
||||
<div class="card-actions">
|
||||
|
||||
<button class="secondary"
|
||||
@click="requestClose">
|
||||
{{ t('cancel') }}
|
||||
</button>
|
||||
|
||||
<button class="primary"
|
||||
@click="requestSave">
|
||||
{{ t('save') }}
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref} from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps(['alias'])
|
||||
const emit = defineEmits(['close', 'save'])
|
||||
const { t } = useI18n();
|
||||
const props = defineProps(['alias']);
|
||||
const emit = defineEmits(['close', 'save']);
|
||||
|
||||
const alias = ref(props.alias)
|
||||
const alias = ref(props.alias);
|
||||
|
||||
const requestClose = () => emit('close')
|
||||
const requestSave = () => emit('save', alias.value)
|
||||
const requestClose = () => emit('close');
|
||||
const requestSave = () => emit('save', alias.value);
|
||||
</script>
|
||||
|
||||
<i18n>
|
||||
{
|
||||
"en": {
|
||||
"title": "Alias",
|
||||
"label": "Your alias"
|
||||
},
|
||||
"fr": {
|
||||
"title": "Alias",
|
||||
"label": "Votre alias"
|
||||
},
|
||||
"es": {
|
||||
"title": "Alias",
|
||||
"label": "Tu alias"
|
||||
}
|
||||
"en": {
|
||||
"title": "Alias",
|
||||
"label": "Your alias"
|
||||
},
|
||||
"fr": {
|
||||
"title": "Alias",
|
||||
"label": "Votre alias"
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
|
||||
|
||||
|
||||
@@ -1,162 +1,177 @@
|
||||
<template>
|
||||
<div class="card dialog">
|
||||
<div class="card dialog">
|
||||
<div class="card-title">
|
||||
{{ t('changePassword') }}
|
||||
</div>
|
||||
|
||||
<div class="card-title">
|
||||
{{ t('changePassword') }}
|
||||
<div class="card-content">
|
||||
<p class="description mb-4">{{ t('passwordDescription') }}</p>
|
||||
|
||||
<v-text-field
|
||||
v-model="newPassword"
|
||||
:hint="t('passwordRequirements')"
|
||||
:label="t('newPassword')"
|
||||
:type="showNewPassword ? 'text' : 'password'"
|
||||
required
|
||||
variant="outlined"
|
||||
>
|
||||
<template v-slot:append-inner>
|
||||
<v-icon
|
||||
:icon="showNewPassword ? mdiEyeOff : mdiEye"
|
||||
class="visibility-toggle"
|
||||
size="small"
|
||||
@click="showNewPassword = !showNewPassword"
|
||||
/>
|
||||
</template>
|
||||
</v-text-field>
|
||||
|
||||
<v-text-field
|
||||
v-model="confirmPassword"
|
||||
:label="t('confirmPassword')"
|
||||
:type="showConfirmPassword ? 'text' : 'password'"
|
||||
required
|
||||
variant="outlined"
|
||||
>
|
||||
<template v-slot:append-inner>
|
||||
<v-icon
|
||||
:icon="showNewPassword ? mdiEyeOff : mdiEye"
|
||||
class="visibility-toggle"
|
||||
size="small"
|
||||
@click="showConfirmPassword = !showConfirmPassword"
|
||||
/>
|
||||
</template>
|
||||
</v-text-field>
|
||||
|
||||
<div
|
||||
v-if="errorMessage"
|
||||
class="error-message mb-4"
|
||||
>
|
||||
{{ errorMessage }}
|
||||
</div>
|
||||
|
||||
<div class="card-actions">
|
||||
<button
|
||||
class="secondary"
|
||||
@click="$emit('closeRequested')"
|
||||
>
|
||||
{{ t('cancel') }}
|
||||
</button>
|
||||
<button
|
||||
:disabled="isLoading"
|
||||
class="primary"
|
||||
@click="handleChangePassword"
|
||||
>
|
||||
{{ t('save') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</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')">
|
||||
<template v-slot:append-inner>
|
||||
<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>
|
||||
<template v-slot:append-inner>
|
||||
<v-icon @click="showConfirmPassword = !showConfirmPassword" class="visibility-toggle" size="small"
|
||||
:icon="showNewPassword ? mdiEyeOff : mdiEye" />
|
||||
</template>
|
||||
</v-text-field>
|
||||
|
||||
<div v-if="errorMessage" class="error-message mb-4">
|
||||
{{ errorMessage }}
|
||||
</div>
|
||||
|
||||
<div class="card-actions">
|
||||
<button class="secondary" @click="$emit('closeRequested')">
|
||||
{{ t('cancel') }}
|
||||
</button>
|
||||
<button class="primary" @click="handleChangePassword" :disabled="isLoading">
|
||||
{{ t('save') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useAuthStore } from '@/stores/authStore.js';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { mdiEye, mdiEyeOff } from '@mdi/js';
|
||||
import { ref } from 'vue';
|
||||
import { useAuthStore } from '@/stores/authStore.js';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { mdiEye, mdiEyeOff } from '@mdi/js';
|
||||
|
||||
const { t } = useI18n();
|
||||
const authStore = useAuthStore();
|
||||
const emit = defineEmits(['closeRequested']);
|
||||
const { t } = useI18n();
|
||||
const authStore = useAuthStore();
|
||||
const emit = defineEmits(['closeRequested']);
|
||||
|
||||
const newPassword = ref('');
|
||||
const confirmPassword = ref('');
|
||||
const isLoading = ref(false);
|
||||
const errorMessage = ref('');
|
||||
const showNewPassword = ref(false);
|
||||
const showConfirmPassword = ref(false);
|
||||
const newPassword = ref('');
|
||||
const confirmPassword = ref('');
|
||||
const isLoading = ref(false);
|
||||
const errorMessage = ref('');
|
||||
const showNewPassword = ref(false);
|
||||
const showConfirmPassword = ref(false);
|
||||
|
||||
async function handleChangePassword() {
|
||||
// Clear previous error
|
||||
errorMessage.value = '';
|
||||
async function handleChangePassword() {
|
||||
// Clear previous error
|
||||
errorMessage.value = '';
|
||||
|
||||
// Validate passwords match
|
||||
if (newPassword.value !== confirmPassword.value) {
|
||||
errorMessage.value = t('passwordsDoNotMatch');
|
||||
return;
|
||||
}
|
||||
// Validate passwords match
|
||||
if (newPassword.value !== confirmPassword.value) {
|
||||
errorMessage.value = t('passwordsDoNotMatch');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate password length
|
||||
if (newPassword.value.length < 8) {
|
||||
errorMessage.value = t('passwordTooShort');
|
||||
return;
|
||||
}
|
||||
// Validate password length
|
||||
if (newPassword.value.length < 8) {
|
||||
errorMessage.value = t('passwordTooShort');
|
||||
return;
|
||||
}
|
||||
|
||||
isLoading.value = true;
|
||||
isLoading.value = true;
|
||||
|
||||
try {
|
||||
// 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);
|
||||
try {
|
||||
// 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');
|
||||
// Success - close dialog
|
||||
emit('closeRequested');
|
||||
|
||||
// You could also emit a success event if needed
|
||||
// emit('success');
|
||||
} catch (error) {
|
||||
console.error('Failed to change password:', error);
|
||||
// Use error message from response if available, or the error message itself, or fallback
|
||||
errorMessage.value = error.response?.data || error.message || t('passwordUpdateFailed');
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
// You could also emit a success event if needed
|
||||
// emit('success');
|
||||
} catch (error) {
|
||||
console.error('Failed to change password:', error);
|
||||
// Use error message from response if available, or the error message itself, or fallback
|
||||
errorMessage.value = error.response?.data || error.message || t('passwordUpdateFailed');
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dialog {
|
||||
@apply max-w-md mx-auto;
|
||||
}
|
||||
.dialog {
|
||||
@apply max-w-md mx-auto;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
@apply text-red-500 text-sm mt-2;
|
||||
}
|
||||
.error-message {
|
||||
@apply text-red-500 text-sm mt-2;
|
||||
}
|
||||
|
||||
.visibility-toggle {
|
||||
@apply cursor-pointer;
|
||||
@apply transition-opacity duration-300;
|
||||
@apply opacity-60 hover:opacity-100;
|
||||
@apply absolute right-2 top-1/2 transform -translate-y-1/2;
|
||||
@apply z-10;
|
||||
}
|
||||
.visibility-toggle {
|
||||
@apply cursor-pointer;
|
||||
@apply transition-opacity duration-300;
|
||||
@apply opacity-60 hover:opacity-100;
|
||||
@apply absolute right-2 top-1/2 transform -translate-y-1/2;
|
||||
@apply z-10;
|
||||
}
|
||||
|
||||
/* Override Vuetify's default padding to accommodate our icon */
|
||||
:deep(.v-field__append-inner) {
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
/* Override Vuetify's default padding to accommodate our icon */
|
||||
:deep(.v-field__append-inner) {
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<i18n>
|
||||
{
|
||||
"en": {
|
||||
"changePassword": "Update Password",
|
||||
"newPassword": "New Password",
|
||||
"confirmPassword": "Confirm New Password",
|
||||
"passwordRequirements": "Password must be at least 8 characters",
|
||||
"passwordDescription": "Updating your password allows you to log in directly with your email and password.",
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"passwordsDoNotMatch": "New passwords do not match",
|
||||
"passwordTooShort": "Password must be at least 8 characters long",
|
||||
"passwordUpdateFailed": "Failed to update password. Please try again."
|
||||
},
|
||||
"fr": {
|
||||
"changePassword": "Modifier le mot de passe",
|
||||
"newPassword": "Nouveau mot de passe",
|
||||
"confirmPassword": "Confirmer le nouveau mot de passe",
|
||||
"passwordRequirements": "Le mot de passe doit comporter au moins 8 caractères",
|
||||
"passwordDescription": "La modification de votre mot de passe vous permet de vous connecter directement avec votre email et mot de passe.",
|
||||
"save": "Enregistrer",
|
||||
"cancel": "Annuler",
|
||||
"passwordsDoNotMatch": "Les nouveaux mots de passe ne correspondent pas",
|
||||
"passwordTooShort": "Le mot de passe doit comporter au moins 8 caractères",
|
||||
"passwordUpdateFailed": "Échec de la mise à jour du mot de passe. Veuillez réessayer."
|
||||
},
|
||||
"es": {
|
||||
"changePassword": "Actualizar contraseña",
|
||||
"newPassword": "Nueva contraseña",
|
||||
"confirmPassword": "Confirmar nueva contraseña",
|
||||
"passwordRequirements": "La contraseña debe tener al menos 8 caracteres",
|
||||
"passwordDescription": "La actualización de su contraseña le permite iniciar sesión directamente con su correo electrónico y contraseña.",
|
||||
"save": "Guardar",
|
||||
"cancel": "Cancelar",
|
||||
"passwordsDoNotMatch": "Las nuevas contraseñas no coinciden",
|
||||
"passwordTooShort": "La contraseña debe tener al menos 8 caracteres",
|
||||
"passwordUpdateFailed": "Error al actualizar la contraseña. Por favor, inténtelo de nuevo."
|
||||
}
|
||||
"en": {
|
||||
"changePassword": "Update Password",
|
||||
"newPassword": "New Password",
|
||||
"confirmPassword": "Confirm New Password",
|
||||
"passwordRequirements": "Password must be at least 8 characters",
|
||||
"passwordDescription": "Updating your password allows you to log in directly with your email and password.",
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"passwordsDoNotMatch": "New passwords do not match",
|
||||
"passwordTooShort": "Password must be at least 8 characters long",
|
||||
"passwordUpdateFailed": "Failed to update password. Please try again."
|
||||
},
|
||||
"fr": {
|
||||
"changePassword": "Modifier le mot de passe",
|
||||
"newPassword": "Nouveau mot de passe",
|
||||
"confirmPassword": "Confirmer le nouveau mot de passe",
|
||||
"passwordRequirements": "Le mot de passe doit comporter au moins 8 caractères",
|
||||
"passwordDescription": "La modification de votre mot de passe vous permet de vous connecter directement avec votre email et mot de passe.",
|
||||
"save": "Enregistrer",
|
||||
"cancel": "Annuler",
|
||||
"passwordsDoNotMatch": "Les nouveaux mots de passe ne correspondent pas",
|
||||
"passwordTooShort": "Le mot de passe doit comporter au moins 8 caractères",
|
||||
"passwordUpdateFailed": "Échec de la mise à jour du mot de passe. Veuillez réessayer."
|
||||
}
|
||||
}
|
||||
|
||||
</i18n>
|
||||
|
||||
@@ -1,82 +1,78 @@
|
||||
<template>
|
||||
<div class="card dialog">
|
||||
<div class="card-title">
|
||||
{{ t('title') }}
|
||||
</div>
|
||||
<div class="card dialog">
|
||||
<div class="card-title">
|
||||
{{ t('title') }}
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<v-text-field
|
||||
v-model="email"
|
||||
:label="t('label')"
|
||||
variant="outlined"
|
||||
></v-text-field>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<v-text-field
|
||||
v-model="email"
|
||||
:label="t('label')"
|
||||
variant="outlined"
|
||||
></v-text-field>
|
||||
</div>
|
||||
|
||||
<div class="card-actions">
|
||||
<button class="secondary"
|
||||
@click="cancel">
|
||||
{{ t('cancel') }}
|
||||
</button>
|
||||
<button class="primary"
|
||||
@click="save">
|
||||
{{ t('save') }}
|
||||
</button>
|
||||
<div class="card-actions">
|
||||
<button
|
||||
class="secondary"
|
||||
@click="cancel"
|
||||
>
|
||||
{{ t('cancel') }}
|
||||
</button>
|
||||
<button
|
||||
class="primary"
|
||||
@click="save"
|
||||
>
|
||||
{{ t('save') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref} from 'vue';
|
||||
import {useClient} from "@/plugins/api.js";
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { ref } from 'vue';
|
||||
import { useClient } from '@/plugins/api.js';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
email: {
|
||||
required: true,
|
||||
type: String
|
||||
}
|
||||
});
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
email: {
|
||||
required: true,
|
||||
type: String,
|
||||
},
|
||||
});
|
||||
|
||||
const emits = defineEmits(['closeRequested']);
|
||||
const emits = defineEmits(['closeRequested']);
|
||||
|
||||
const email = ref(props.email);
|
||||
const email = ref(props.email);
|
||||
|
||||
const client = useClient();
|
||||
const save = async () => {
|
||||
try {
|
||||
await client.post(
|
||||
`/api/users/email`,
|
||||
{
|
||||
email: email.value
|
||||
});
|
||||
const client = useClient();
|
||||
const save = async () => {
|
||||
try {
|
||||
await client.post(`/api/users/email`, {
|
||||
email: email.value,
|
||||
});
|
||||
|
||||
emits('closeRequested');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
emits('closeRequested');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const cancel = () => {
|
||||
emits('closeRequested');
|
||||
};
|
||||
const cancel = () => {
|
||||
emits('closeRequested');
|
||||
};
|
||||
</script>
|
||||
|
||||
<i18n>
|
||||
{
|
||||
"en": {
|
||||
"title": "Change your Email",
|
||||
"label": "Your email"
|
||||
},
|
||||
"fr": {
|
||||
"title": "Changez votre Courriel",
|
||||
"label": "Votre email"
|
||||
},
|
||||
"es": {
|
||||
"title": "Cambia tu correo electrónico",
|
||||
"label": "Tu correo electrónico"
|
||||
}
|
||||
"en": {
|
||||
"title": "Change your Email",
|
||||
"label": "Your email"
|
||||
},
|
||||
"fr": {
|
||||
"title": "Changez votre Courriel",
|
||||
"label": "Votre email"
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
|
||||
|
||||
|
||||
@@ -1,70 +1,69 @@
|
||||
<script setup>
|
||||
import {ref} from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps(['firstname', 'lastname'])
|
||||
const emit = defineEmits(['close', 'save'])
|
||||
const { t } = useI18n();
|
||||
const props = defineProps(['firstname', 'lastname']);
|
||||
const emit = defineEmits(['close', 'save']);
|
||||
|
||||
const firstname = ref(props.firstname)
|
||||
const lastname = ref(props.lastname)
|
||||
const firstname = ref(props.firstname);
|
||||
const lastname = ref(props.lastname);
|
||||
|
||||
const requestClose = () => emit('close')
|
||||
const requestSave = () => emit('save', firstname.value, lastname.value)
|
||||
const requestClose = () => emit('close');
|
||||
const requestSave = () => emit('save', firstname.value, lastname.value);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card dialog">
|
||||
<div class="card-title">
|
||||
{{ t('title') }}
|
||||
</div>
|
||||
<div class="card dialog">
|
||||
<div class="card-title">
|
||||
{{ t('title') }}
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<v-text-field
|
||||
variant="outlined"
|
||||
v-model="firstname"
|
||||
:label="t('firstname')"
|
||||
></v-text-field>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<v-text-field
|
||||
v-model="firstname"
|
||||
:label="t('firstname')"
|
||||
variant="outlined"
|
||||
></v-text-field>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<v-text-field
|
||||
variant="outlined"
|
||||
v-model="lastname"
|
||||
:label="t('lastname')"
|
||||
></v-text-field>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<v-text-field
|
||||
v-model="lastname"
|
||||
:label="t('lastname')"
|
||||
variant="outlined"
|
||||
></v-text-field>
|
||||
</div>
|
||||
|
||||
<div class="card-actions">
|
||||
<button class="secondary"
|
||||
@click="requestClose">
|
||||
{{ t('cancel') }}
|
||||
</button>
|
||||
<div class="card-actions">
|
||||
<button
|
||||
class="secondary"
|
||||
@click="requestClose"
|
||||
>
|
||||
{{ t('cancel') }}
|
||||
</button>
|
||||
|
||||
<button class="primary"
|
||||
@click="requestSave">
|
||||
{{ t('save') }}
|
||||
</button>
|
||||
<button
|
||||
class="primary"
|
||||
@click="requestSave"
|
||||
>
|
||||
{{ t('save') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<i18n>
|
||||
{
|
||||
"en": {
|
||||
"title": "Full Name",
|
||||
"firstname": "First Name",
|
||||
"lastname": "Last Name"
|
||||
},
|
||||
"fr": {
|
||||
"title": "Nom complet",
|
||||
"firstname": "Prénom",
|
||||
"lastname": "Nom"
|
||||
},
|
||||
"es": {
|
||||
"title": "Nombre completo",
|
||||
"firstname": "Nombre",
|
||||
"lastname": "Apellido"
|
||||
}
|
||||
"en": {
|
||||
"title": "Full Name",
|
||||
"firstname": "First Name",
|
||||
"lastname": "Last Name"
|
||||
},
|
||||
"fr": {
|
||||
"title": "Nom complet",
|
||||
"firstname": "Prénom",
|
||||
"lastname": "Nom"
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
@@ -1,174 +1,164 @@
|
||||
<template>
|
||||
<div class="card dialog">
|
||||
<div class="card-title">{{ t('changeEmail') }}</div>
|
||||
<div class="card-content">
|
||||
<v-text-field
|
||||
v-model="email"
|
||||
class="w-full p-2"
|
||||
:label="t('email')"
|
||||
type="email"
|
||||
variant="outlined"
|
||||
:error-messages="emailErrors"
|
||||
:rules="emailRules"
|
||||
validate-on="blur"
|
||||
/>
|
||||
|
||||
<v-alert
|
||||
v-if="!!errorMessage"
|
||||
outlined
|
||||
type="error"
|
||||
class="mt-4">
|
||||
{{ errorMessage }}
|
||||
</v-alert>
|
||||
<div class="card dialog">
|
||||
<div class="card-title">{{ t('changeEmail') }}</div>
|
||||
<div class="card-content">
|
||||
<v-text-field
|
||||
v-model="email"
|
||||
:error-messages="emailErrors"
|
||||
:label="t('email')"
|
||||
:rules="emailRules"
|
||||
class="w-full p-2"
|
||||
type="email"
|
||||
validate-on="blur"
|
||||
variant="outlined"
|
||||
/>
|
||||
|
||||
<div class="card-actions">
|
||||
<button class="secondary" @click="$emit('closeRequested')">
|
||||
{{ t('cancel') }}
|
||||
</button>
|
||||
<button class="primary" @click="saveEmail" :disabled="!canSave || isLoading">
|
||||
{{ t('save') }}
|
||||
</button>
|
||||
</div>
|
||||
<v-alert
|
||||
v-if="!!errorMessage"
|
||||
class="mt-4"
|
||||
outlined
|
||||
type="error"
|
||||
>
|
||||
{{ errorMessage }}
|
||||
</v-alert>
|
||||
|
||||
<div class="card-actions">
|
||||
<button
|
||||
class="secondary"
|
||||
@click="$emit('closeRequested')"
|
||||
>
|
||||
{{ t('cancel') }}
|
||||
</button>
|
||||
<button
|
||||
:disabled="!canSave || isLoading"
|
||||
class="primary"
|
||||
@click="saveEmail"
|
||||
>
|
||||
{{ t('save') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useClient } from '@/plugins/api.js';
|
||||
import { useCreatorProfileStore } from '@/stores/creatorProfileStore.js';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useClient } from '@/plugins/api.js';
|
||||
import { useCreatorProfileStore } from '@/stores/creatorProfileStore.js';
|
||||
|
||||
const { t } = useI18n();
|
||||
const client = useClient();
|
||||
const creatorProfileStore = useCreatorProfileStore();
|
||||
const { t } = useI18n();
|
||||
const client = useClient();
|
||||
const creatorProfileStore = useCreatorProfileStore();
|
||||
|
||||
const props = defineProps({
|
||||
creator: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
const props = defineProps({
|
||||
creator: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const email = ref(props.creator.presentation?.email || '');
|
||||
const isLoading = ref(false);
|
||||
const errorMessage = ref('');
|
||||
const email = ref(props.creator.presentation?.email || '');
|
||||
const isLoading = ref(false);
|
||||
const errorMessage = ref('');
|
||||
|
||||
// Email validation
|
||||
const isValidEmail = (email) => {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return emailRegex.test(email);
|
||||
};
|
||||
// Email validation
|
||||
const isValidEmail = email => {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return emailRegex.test(email);
|
||||
};
|
||||
|
||||
const emailRules = [
|
||||
v => !!v || t('validation.emailRequired'),
|
||||
v => isValidEmail(v) || t('validation.emailInvalid'),
|
||||
];
|
||||
const emailRules = [
|
||||
v => !!v || t('validation.emailRequired'),
|
||||
v => isValidEmail(v) || t('validation.emailInvalid'),
|
||||
];
|
||||
|
||||
const emailErrors = computed(() => {
|
||||
if (!email.value) {
|
||||
return [t('validation.emailRequired')];
|
||||
}
|
||||
if (!isValidEmail(email.value)) {
|
||||
return [t('validation.emailInvalid')];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
const emailErrors = computed(() => {
|
||||
if (!email.value) {
|
||||
return [t('validation.emailRequired')];
|
||||
}
|
||||
if (!isValidEmail(email.value)) {
|
||||
return [t('validation.emailInvalid')];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
const canSave = computed(() => {
|
||||
return email.value &&
|
||||
isValidEmail(email.value) &&
|
||||
email.value !== (props.creator.presentation?.email || '');
|
||||
});
|
||||
const canSave = computed(() => {
|
||||
return email.value && isValidEmail(email.value) && email.value !== (props.creator.presentation?.email || '');
|
||||
});
|
||||
|
||||
async function saveEmail() {
|
||||
if (!props.creator.id) {
|
||||
console.error("Creator ID is missing!");
|
||||
return;
|
||||
}
|
||||
async function saveEmail() {
|
||||
if (!props.creator.id) {
|
||||
console.error('Creator ID is missing!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!canSave.value) {
|
||||
return;
|
||||
}
|
||||
if (!canSave.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
isLoading.value = true;
|
||||
errorMessage.value = '';
|
||||
try {
|
||||
isLoading.value = true;
|
||||
errorMessage.value = '';
|
||||
|
||||
// Save email
|
||||
await client.post(
|
||||
`/api/creators/${props.creator.id}/email`,
|
||||
{
|
||||
email: email.value.trim()
|
||||
}
|
||||
);
|
||||
// Save email
|
||||
await client.post(`/api/creators/${props.creator.id}/email`, {
|
||||
email: email.value.trim(),
|
||||
});
|
||||
|
||||
// Refresh creator profile
|
||||
await creatorProfileStore.fetchCreatorProfile();
|
||||
// Refresh creator profile
|
||||
await creatorProfileStore.fetchCreatorProfile();
|
||||
|
||||
// Close dialog
|
||||
emit('closeRequested');
|
||||
} catch (error) {
|
||||
console.error("Error saving email:", error);
|
||||
if (error?.response?.data?.errors) {
|
||||
errorMessage.value = error.response.data.errors[0]?.['reason'] || t('errors.unexpected');
|
||||
} else {
|
||||
errorMessage.value = error?.response?.data?.message || error.message || t('errors.unexpected');
|
||||
// Close dialog
|
||||
emit('closeRequested');
|
||||
} catch (error) {
|
||||
console.error('Error saving email:', error);
|
||||
if (error?.response?.data?.errors) {
|
||||
errorMessage.value = error.response.data.errors[0]?.['reason'] || t('errors.unexpected');
|
||||
} else {
|
||||
errorMessage.value = error?.response?.data?.message || error.message || t('errors.unexpected');
|
||||
}
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
const emit = defineEmits(['closeRequested']);
|
||||
const emit = defineEmits(['closeRequested']);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dialog {
|
||||
@apply max-w-md mx-auto;
|
||||
}
|
||||
.dialog {
|
||||
@apply max-w-md mx-auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
<i18n>
|
||||
{
|
||||
"en": {
|
||||
"changeEmail": "Change Email",
|
||||
"email": "Email",
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"validation": {
|
||||
"emailRequired": "Email is required",
|
||||
"emailInvalid": "Please enter a valid email address"
|
||||
"en": {
|
||||
"changeEmail": "Change Email",
|
||||
"email": "Email",
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"validation": {
|
||||
"emailRequired": "Email is required",
|
||||
"emailInvalid": "Please enter a valid email address"
|
||||
},
|
||||
"errors": {
|
||||
"unexpected": "An unexpected error occurred"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"unexpected": "An unexpected error occurred"
|
||||
"fr": {
|
||||
"changeEmail": "Modifier l'email",
|
||||
"email": "Email",
|
||||
"save": "Enregistrer",
|
||||
"cancel": "Annuler",
|
||||
"validation": {
|
||||
"emailRequired": "L'email est requis",
|
||||
"emailInvalid": "Veuillez entrer une adresse email valide"
|
||||
},
|
||||
"errors": {
|
||||
"unexpected": "Une erreur inattendue s'est produite"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fr": {
|
||||
"changeEmail": "Modifier l'email",
|
||||
"email": "Email",
|
||||
"save": "Enregistrer",
|
||||
"cancel": "Annuler",
|
||||
"validation": {
|
||||
"emailRequired": "L'email est requis",
|
||||
"emailInvalid": "Veuillez entrer une adresse email valide"
|
||||
},
|
||||
"errors": {
|
||||
"unexpected": "Une erreur inattendue s'est produite"
|
||||
}
|
||||
},
|
||||
"es": {
|
||||
"changeEmail": "Cambiar correo electrónico",
|
||||
"email": "Correo electrónico",
|
||||
"save": "Guardar",
|
||||
"cancel": "Cancelar",
|
||||
"validation": {
|
||||
"emailRequired": "El correo electrónico es obligatorio",
|
||||
"emailInvalid": "Por favor ingrese una dirección de correo electrónico válida"
|
||||
},
|
||||
"errors": {
|
||||
"unexpected": "Se produjo un error inesperado"
|
||||
}
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
@@ -1,87 +1,82 @@
|
||||
<script setup>
|
||||
import {ref} from 'vue';
|
||||
import {useClient} from '@/plugins/api.js';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { ref } from 'vue';
|
||||
import { useClient } from '@/plugins/api.js';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
creator: {
|
||||
required: true
|
||||
}
|
||||
});
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
creator: {
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emits = defineEmits(['closeRequested']);
|
||||
const emits = defineEmits(['closeRequested']);
|
||||
|
||||
const name = ref(props.creator.name);
|
||||
const name = ref(props.creator.name);
|
||||
|
||||
const client = useClient();
|
||||
const client = useClient();
|
||||
|
||||
async function save() {
|
||||
try {
|
||||
await client.post(
|
||||
`/api/creators/${props.creator.id}/name`,
|
||||
{
|
||||
name: name.value
|
||||
async function save() {
|
||||
try {
|
||||
await client.post(`/api/creators/${props.creator.id}/name`, {
|
||||
name: name.value,
|
||||
});
|
||||
|
||||
props.creator.name = name.value;
|
||||
emits('closeRequested');
|
||||
} catch (error) {
|
||||
console.error('Error saving title:', error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
props.creator.name = name.value;
|
||||
emits('closeRequested');
|
||||
} catch (error) {
|
||||
console.error('Error saving title:', error);
|
||||
}
|
||||
}
|
||||
|
||||
const cancel = () => {
|
||||
emits('closeRequested');
|
||||
};
|
||||
const cancel = () => {
|
||||
emits('closeRequested');
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card dialog">
|
||||
<div class="card-title">
|
||||
{{ t('title') }}
|
||||
</div>
|
||||
<div class="card dialog">
|
||||
<div class="card-title">
|
||||
{{ t('title') }}
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<v-text-field
|
||||
v-model="name"
|
||||
:label="t('label')"
|
||||
outlined
|
||||
variant="outlined"
|
||||
></v-text-field>
|
||||
<div class="card-content">
|
||||
<v-text-field
|
||||
v-model="name"
|
||||
:label="t('label')"
|
||||
outlined
|
||||
variant="outlined"
|
||||
></v-text-field>
|
||||
|
||||
<div class="card-actions">
|
||||
<button class="secondary"
|
||||
@click="cancel">
|
||||
{{ t('cancel') }}
|
||||
</button>
|
||||
<button class="primary"
|
||||
@click="save">
|
||||
{{ t('save') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<button
|
||||
class="secondary"
|
||||
@click="cancel"
|
||||
>
|
||||
{{ t('cancel') }}
|
||||
</button>
|
||||
<button
|
||||
class="primary"
|
||||
@click="save"
|
||||
>
|
||||
{{ t('save') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
||||
<i18n>
|
||||
{
|
||||
"en": {
|
||||
"title": "Change Name",
|
||||
"label": "Your name"
|
||||
},
|
||||
"fr": {
|
||||
"title": "Modifier le nom",
|
||||
"label": "Votre nom"
|
||||
},
|
||||
"es": {
|
||||
"title": "Cambiar nombre",
|
||||
"label": "Tu nombre"
|
||||
}
|
||||
"en": {
|
||||
"title": "Change Name",
|
||||
"label": "Your name"
|
||||
},
|
||||
"fr": {
|
||||
"title": "Modifier le nom",
|
||||
"label": "Votre nom"
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
|
||||
@@ -1,246 +1,239 @@
|
||||
<template>
|
||||
<div class="card dialog">
|
||||
<div class="card-title">{{ t('changePhoneNumber') }}</div>
|
||||
<div class="card-content">
|
||||
<v-text-field
|
||||
v-model="displayPhoneNumber"
|
||||
class="w-full p-2"
|
||||
:label="t('phoneNumber')"
|
||||
type="tel"
|
||||
variant="outlined"
|
||||
:error-messages="phoneErrors"
|
||||
:rules="phoneRules"
|
||||
validate-on="blur"
|
||||
:placeholder="t('phonePlaceholder')"
|
||||
@input="handlePhoneInput"
|
||||
@keydown="handleKeydown"
|
||||
maxlength="14"
|
||||
/>
|
||||
|
||||
<v-alert
|
||||
v-if="!!errorMessage"
|
||||
outlined
|
||||
type="error"
|
||||
class="mt-4">
|
||||
{{ errorMessage }}
|
||||
</v-alert>
|
||||
<div class="card dialog">
|
||||
<div class="card-title">{{ t('changePhoneNumber') }}</div>
|
||||
<div class="card-content">
|
||||
<v-text-field
|
||||
v-model="displayPhoneNumber"
|
||||
:error-messages="phoneErrors"
|
||||
:label="t('phoneNumber')"
|
||||
:placeholder="t('phonePlaceholder')"
|
||||
:rules="phoneRules"
|
||||
class="w-full p-2"
|
||||
maxlength="14"
|
||||
type="tel"
|
||||
validate-on="blur"
|
||||
variant="outlined"
|
||||
@input="handlePhoneInput"
|
||||
@keydown="handleKeydown"
|
||||
/>
|
||||
|
||||
<div class="card-actions">
|
||||
<button class="secondary" @click="$emit('closeRequested')">
|
||||
{{ t('cancel') }}
|
||||
</button>
|
||||
<button class="primary" @click="savePhoneNumber" :disabled="!canSave || isLoading">
|
||||
{{ t('save') }}
|
||||
</button>
|
||||
</div>
|
||||
<v-alert
|
||||
v-if="!!errorMessage"
|
||||
class="mt-4"
|
||||
outlined
|
||||
type="error"
|
||||
>
|
||||
{{ errorMessage }}
|
||||
</v-alert>
|
||||
|
||||
<div class="card-actions">
|
||||
<button
|
||||
class="secondary"
|
||||
@click="$emit('closeRequested')"
|
||||
>
|
||||
{{ t('cancel') }}
|
||||
</button>
|
||||
<button
|
||||
:disabled="!canSave || isLoading"
|
||||
class="primary"
|
||||
@click="savePhoneNumber"
|
||||
>
|
||||
{{ t('save') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useClient } from '@/plugins/api.js';
|
||||
import { useCreatorProfileStore } from '@/stores/creatorProfileStore.js';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useClient } from '@/plugins/api.js';
|
||||
import { useCreatorProfileStore } from '@/stores/creatorProfileStore.js';
|
||||
|
||||
const { t } = useI18n();
|
||||
const client = useClient();
|
||||
const creatorProfileStore = useCreatorProfileStore();
|
||||
const { t } = useI18n();
|
||||
const client = useClient();
|
||||
const creatorProfileStore = useCreatorProfileStore();
|
||||
|
||||
const props = defineProps({
|
||||
creator: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
const props = defineProps({
|
||||
creator: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Format existing phone number to display format
|
||||
const formatPhoneForDisplay = (phone) => {
|
||||
if (!phone) return '';
|
||||
const digits = phone.replace(/\D/g, '');
|
||||
if (digits.length === 10) {
|
||||
return `(${digits.slice(0, 3)}) ${digits.slice(3, 6)}-${digits.slice(6)}`;
|
||||
}
|
||||
return phone;
|
||||
};
|
||||
// Format existing phone number to display format
|
||||
const formatPhoneForDisplay = phone => {
|
||||
if (!phone) return '';
|
||||
const digits = phone.replace(/\D/g, '');
|
||||
if (digits.length === 10) {
|
||||
return `(${digits.slice(0, 3)}) ${digits.slice(3, 6)}-${digits.slice(6)}`;
|
||||
}
|
||||
return phone;
|
||||
};
|
||||
|
||||
// Extract just the digits from formatted phone
|
||||
const extractDigits = (formattedPhone) => {
|
||||
return formattedPhone.replace(/\D/g, '');
|
||||
};
|
||||
// Extract just the digits from formatted phone
|
||||
const extractDigits = formattedPhone => {
|
||||
return formattedPhone.replace(/\D/g, '');
|
||||
};
|
||||
|
||||
const displayPhoneNumber = ref(formatPhoneForDisplay(props.creator.presentation?.phoneNumber || ''));
|
||||
const phoneDigits = ref(extractDigits(displayPhoneNumber.value));
|
||||
const isLoading = ref(false);
|
||||
const errorMessage = ref('');
|
||||
const displayPhoneNumber = ref(formatPhoneForDisplay(props.creator.presentation?.phoneNumber || ''));
|
||||
const phoneDigits = ref(extractDigits(displayPhoneNumber.value));
|
||||
const isLoading = ref(false);
|
||||
const errorMessage = ref('');
|
||||
|
||||
// Phone number formatting and validation
|
||||
const formatPhoneNumber = (digits) => {
|
||||
// Remove all non-digits
|
||||
const cleaned = digits.replace(/\D/g, '');
|
||||
|
||||
// Apply formatting based on length
|
||||
if (cleaned.length === 0) return '';
|
||||
if (cleaned.length <= 3) return `(${cleaned}`;
|
||||
if (cleaned.length <= 6) return `(${cleaned.slice(0, 3)}) ${cleaned.slice(3)}`;
|
||||
return `(${cleaned.slice(0, 3)}) ${cleaned.slice(3, 6)}-${cleaned.slice(6, 10)}`;
|
||||
};
|
||||
// Phone number formatting and validation
|
||||
const formatPhoneNumber = digits => {
|
||||
// Remove all non-digits
|
||||
const cleaned = digits.replace(/\D/g, '');
|
||||
|
||||
const handlePhoneInput = (event) => {
|
||||
const input = event.target.value;
|
||||
const digits = extractDigits(input);
|
||||
|
||||
// Limit to 10 digits
|
||||
if (digits.length > 10) return;
|
||||
|
||||
phoneDigits.value = digits;
|
||||
displayPhoneNumber.value = formatPhoneNumber(digits);
|
||||
};
|
||||
// Apply formatting based on length
|
||||
if (cleaned.length === 0) return '';
|
||||
if (cleaned.length <= 3) return `(${cleaned}`;
|
||||
if (cleaned.length <= 6) return `(${cleaned.slice(0, 3)}) ${cleaned.slice(3)}`;
|
||||
return `(${cleaned.slice(0, 3)}) ${cleaned.slice(3, 6)}-${cleaned.slice(6, 10)}`;
|
||||
};
|
||||
|
||||
const handleKeydown = (event) => {
|
||||
// Allow backspace, delete, tab, escape, enter
|
||||
if ([8, 9, 27, 13, 46].includes(event.keyCode)) return;
|
||||
|
||||
// Allow Ctrl+A, Ctrl+C, Ctrl+V, Ctrl+X
|
||||
if ((event.ctrlKey || event.metaKey) && [65, 67, 86, 88].includes(event.keyCode)) return;
|
||||
|
||||
// Allow arrow keys
|
||||
if (event.keyCode >= 35 && event.keyCode <= 40) return;
|
||||
|
||||
// Only allow numbers (0-9)
|
||||
if (event.keyCode < 48 || event.keyCode > 57) {
|
||||
event.preventDefault();
|
||||
}
|
||||
};
|
||||
const handlePhoneInput = event => {
|
||||
const input = event.target.value;
|
||||
const digits = extractDigits(input);
|
||||
|
||||
// Watch for changes to phoneDigits to update display
|
||||
watch(phoneDigits, (newDigits) => {
|
||||
displayPhoneNumber.value = formatPhoneNumber(newDigits);
|
||||
});
|
||||
// Limit to 10 digits
|
||||
if (digits.length > 10) return;
|
||||
|
||||
const isValidPhoneNumber = (digits) => {
|
||||
return digits.length === 10;
|
||||
};
|
||||
phoneDigits.value = digits;
|
||||
displayPhoneNumber.value = formatPhoneNumber(digits);
|
||||
};
|
||||
|
||||
const phoneRules = [
|
||||
v => {
|
||||
const digits = extractDigits(v);
|
||||
return digits.length > 0 || t('validation.phoneRequired');
|
||||
},
|
||||
v => {
|
||||
const digits = extractDigits(v);
|
||||
return isValidPhoneNumber(digits) || t('validation.phoneInvalid');
|
||||
},
|
||||
];
|
||||
const handleKeydown = event => {
|
||||
// Allow backspace, delete, tab, escape, enter
|
||||
if ([8, 9, 27, 13, 46].includes(event.keyCode)) return;
|
||||
|
||||
const phoneErrors = computed(() => {
|
||||
if (phoneDigits.value.length === 0) {
|
||||
return [t('validation.phoneRequired')];
|
||||
}
|
||||
if (!isValidPhoneNumber(phoneDigits.value)) {
|
||||
return [t('validation.phoneInvalid')];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
// Allow Ctrl+A, Ctrl+C, Ctrl+V, Ctrl+X
|
||||
if ((event.ctrlKey || event.metaKey) && [65, 67, 86, 88].includes(event.keyCode)) return;
|
||||
|
||||
const canSave = computed(() => {
|
||||
return phoneDigits.value.length === 10 &&
|
||||
phoneDigits.value !== extractDigits(props.creator.presentation?.phoneNumber || '');
|
||||
});
|
||||
// Allow arrow keys
|
||||
if (event.keyCode >= 35 && event.keyCode <= 40) return;
|
||||
|
||||
async function savePhoneNumber() {
|
||||
if (!props.creator.id) {
|
||||
console.error("Creator ID is missing!");
|
||||
return;
|
||||
}
|
||||
// Only allow numbers (0-9)
|
||||
if (event.keyCode < 48 || event.keyCode > 57) {
|
||||
event.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
if (!canSave.value) {
|
||||
return;
|
||||
}
|
||||
// Watch for changes to phoneDigits to update display
|
||||
watch(phoneDigits, newDigits => {
|
||||
displayPhoneNumber.value = formatPhoneNumber(newDigits);
|
||||
});
|
||||
|
||||
try {
|
||||
isLoading.value = true;
|
||||
errorMessage.value = '';
|
||||
const isValidPhoneNumber = digits => {
|
||||
return digits.length === 10;
|
||||
};
|
||||
|
||||
// Save the formatted phone number
|
||||
const formattedPhone = formatPhoneNumber(phoneDigits.value);
|
||||
|
||||
await client.post(
|
||||
`/api/creators/${props.creator.id}/phone`,
|
||||
{
|
||||
phoneNumber: formattedPhone
|
||||
}
|
||||
);
|
||||
const phoneRules = [
|
||||
v => {
|
||||
const digits = extractDigits(v);
|
||||
return digits.length > 0 || t('validation.phoneRequired');
|
||||
},
|
||||
v => {
|
||||
const digits = extractDigits(v);
|
||||
return isValidPhoneNumber(digits) || t('validation.phoneInvalid');
|
||||
},
|
||||
];
|
||||
|
||||
// Refresh creator profile
|
||||
await creatorProfileStore.fetchCreatorProfile();
|
||||
|
||||
// Close dialog
|
||||
emit('closeRequested');
|
||||
} catch (error) {
|
||||
console.error("Error saving phone number:", error);
|
||||
if (error?.response?.data?.errors) {
|
||||
errorMessage.value = error.response.data.errors[0]?.['reason'] || t('errors.unexpected');
|
||||
} else {
|
||||
errorMessage.value = error?.response?.data?.message || error.message || t('errors.unexpected');
|
||||
const phoneErrors = computed(() => {
|
||||
if (phoneDigits.value.length === 0) {
|
||||
return [t('validation.phoneRequired')];
|
||||
}
|
||||
if (!isValidPhoneNumber(phoneDigits.value)) {
|
||||
return [t('validation.phoneInvalid')];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
const canSave = computed(() => {
|
||||
return (
|
||||
phoneDigits.value.length === 10 &&
|
||||
phoneDigits.value !== extractDigits(props.creator.presentation?.phoneNumber || '')
|
||||
);
|
||||
});
|
||||
|
||||
async function savePhoneNumber() {
|
||||
if (!props.creator.id) {
|
||||
console.error('Creator ID is missing!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!canSave.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
isLoading.value = true;
|
||||
errorMessage.value = '';
|
||||
|
||||
// Save the formatted phone number
|
||||
const formattedPhone = formatPhoneNumber(phoneDigits.value);
|
||||
|
||||
await client.post(`/api/creators/${props.creator.id}/phone`, {
|
||||
phoneNumber: formattedPhone,
|
||||
});
|
||||
|
||||
// Refresh creator profile
|
||||
await creatorProfileStore.fetchCreatorProfile();
|
||||
|
||||
// Close dialog
|
||||
emit('closeRequested');
|
||||
} catch (error) {
|
||||
console.error('Error saving phone number:', error);
|
||||
if (error?.response?.data?.errors) {
|
||||
errorMessage.value = error.response.data.errors[0]?.['reason'] || t('errors.unexpected');
|
||||
} else {
|
||||
errorMessage.value = error?.response?.data?.message || error.message || t('errors.unexpected');
|
||||
}
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
const emit = defineEmits(['closeRequested']);
|
||||
const emit = defineEmits(['closeRequested']);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dialog {
|
||||
@apply max-w-md mx-auto;
|
||||
}
|
||||
.dialog {
|
||||
@apply max-w-md mx-auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
<i18n>
|
||||
{
|
||||
"en": {
|
||||
"changePhoneNumber": "Change Phone Number",
|
||||
"phoneNumber": "Phone Number",
|
||||
"phonePlaceholder": "(555) 123-4567",
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"validation": {
|
||||
"phoneRequired": "Phone number is required",
|
||||
"phoneInvalid": "Please enter a complete 10-digit phone number"
|
||||
"en": {
|
||||
"changePhoneNumber": "Change Phone Number",
|
||||
"phoneNumber": "Phone Number",
|
||||
"phonePlaceholder": "(555) 123-4567",
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"validation": {
|
||||
"phoneRequired": "Phone number is required",
|
||||
"phoneInvalid": "Please enter a complete 10-digit phone number"
|
||||
},
|
||||
"errors": {
|
||||
"unexpected": "An unexpected error occurred"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"unexpected": "An unexpected error occurred"
|
||||
"fr": {
|
||||
"changePhoneNumber": "Modifier le numéro de téléphone",
|
||||
"phoneNumber": "Numéro de téléphone",
|
||||
"phonePlaceholder": "(555) 123-4567",
|
||||
"save": "Enregistrer",
|
||||
"cancel": "Annuler",
|
||||
"validation": {
|
||||
"phoneRequired": "Le numéro de téléphone est requis",
|
||||
"phoneInvalid": "Veuillez entrer un numéro de téléphone complet à 10 chiffres"
|
||||
},
|
||||
"errors": {
|
||||
"unexpected": "Une erreur inattendue s'est produite"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fr": {
|
||||
"changePhoneNumber": "Modifier le numéro de téléphone",
|
||||
"phoneNumber": "Numéro de téléphone",
|
||||
"phonePlaceholder": "(555) 123-4567",
|
||||
"save": "Enregistrer",
|
||||
"cancel": "Annuler",
|
||||
"validation": {
|
||||
"phoneRequired": "Le numéro de téléphone est requis",
|
||||
"phoneInvalid": "Veuillez entrer un numéro de téléphone complet à 10 chiffres"
|
||||
},
|
||||
"errors": {
|
||||
"unexpected": "Une erreur inattendue s'est produite"
|
||||
}
|
||||
},
|
||||
"es": {
|
||||
"changePhoneNumber": "Cambiar número de teléfono",
|
||||
"phoneNumber": "Número de teléfono",
|
||||
"phonePlaceholder": "(555) 123-4567",
|
||||
"save": "Guardar",
|
||||
"cancel": "Cancelar",
|
||||
"validation": {
|
||||
"phoneRequired": "El número de teléfono es obligatorio",
|
||||
"phoneInvalid": "Por favor ingrese un número de teléfono completo de 10 dígitos"
|
||||
},
|
||||
"errors": {
|
||||
"unexpected": "Se produjo un error inesperado"
|
||||
}
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
@@ -1,119 +1,120 @@
|
||||
<script setup>
|
||||
import {computed, ref, watch} from 'vue';
|
||||
import {useCreatorProfileStore} from '@/stores/creatorProfileStore.js';
|
||||
import {useClient} from "@/plugins/api.js";
|
||||
import NameEditor from "@/views/creators/NameEditor.vue";
|
||||
import {useI18n} from 'vue-i18n';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useCreatorProfileStore } from '@/stores/creatorProfileStore.js';
|
||||
import { useClient } from '@/plugins/api.js';
|
||||
import NameEditor from '@/views/creators/NameEditor.vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const props = defineProps({
|
||||
creator: {
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['closeRequested']);
|
||||
|
||||
const creatorProfileStore = useCreatorProfileStore();
|
||||
const client = useClient();
|
||||
const {t} = useI18n();
|
||||
|
||||
const newSlug = ref(props.creator.slug);
|
||||
const slugReservationId = ref(undefined);
|
||||
const isOperationPending = ref(false);
|
||||
const errorMessage = ref('');
|
||||
const isCurrentHandle = ref(false);
|
||||
|
||||
// Watch for changes to the new slug to check if it's the same as the current one
|
||||
watch(newSlug, (newValue) => {
|
||||
isCurrentHandle.value = newValue === props.creator.slug;
|
||||
if (isCurrentHandle.value) {
|
||||
slugReservationId.value = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
const canSave = computed(() => slugReservationId.value !== undefined && !isCurrentHandle.value);
|
||||
|
||||
function handleSlugReservationIdChanged($event) {
|
||||
slugReservationId.value = $event;
|
||||
}
|
||||
|
||||
async function save() {
|
||||
try {
|
||||
isOperationPending.value = true;
|
||||
errorMessage.value = '';
|
||||
|
||||
await client.put(`/api/creators/${props.creator.id}/slug`, {
|
||||
slugReservationId: slugReservationId.value
|
||||
const props = defineProps({
|
||||
creator: {
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
await creatorProfileStore.fetchCreatorProfile();
|
||||
emit('closeRequested');
|
||||
} catch (error) {
|
||||
if (error?.response?.data?.errors) {
|
||||
errorMessage.value = error.response.data.errors[0]?.['reason'] || 'An unexpected error occurred.';
|
||||
} else {
|
||||
errorMessage.value = error?.response?.data?.message || error.message || 'An unexpected error occurred.';
|
||||
}
|
||||
} finally {
|
||||
isOperationPending.value = false;
|
||||
}
|
||||
}
|
||||
const emit = defineEmits(['closeRequested']);
|
||||
|
||||
const cancel = () => {
|
||||
emit('closeRequested');
|
||||
};
|
||||
const creatorProfileStore = useCreatorProfileStore();
|
||||
const client = useClient();
|
||||
const { t } = useI18n();
|
||||
|
||||
const newSlug = ref(props.creator.slug);
|
||||
const slugReservationId = ref(undefined);
|
||||
const isOperationPending = ref(false);
|
||||
const errorMessage = ref('');
|
||||
const isCurrentHandle = ref(false);
|
||||
|
||||
// Watch for changes to the new slug to check if it's the same as the current one
|
||||
watch(newSlug, newValue => {
|
||||
isCurrentHandle.value = newValue === props.creator.slug;
|
||||
if (isCurrentHandle.value) {
|
||||
slugReservationId.value = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
const canSave = computed(() => slugReservationId.value !== undefined && !isCurrentHandle.value);
|
||||
|
||||
function handleSlugReservationIdChanged($event) {
|
||||
slugReservationId.value = $event;
|
||||
}
|
||||
|
||||
async function save() {
|
||||
try {
|
||||
isOperationPending.value = true;
|
||||
errorMessage.value = '';
|
||||
|
||||
await client.put(`/api/creators/${props.creator.id}/slug`, {
|
||||
slugReservationId: slugReservationId.value,
|
||||
});
|
||||
|
||||
await creatorProfileStore.fetchCreatorProfile();
|
||||
emit('closeRequested');
|
||||
} catch (error) {
|
||||
if (error?.response?.data?.errors) {
|
||||
errorMessage.value = error.response.data.errors[0]?.['reason'] || 'An unexpected error occurred.';
|
||||
} else {
|
||||
errorMessage.value = error?.response?.data?.message || error.message || 'An unexpected error occurred.';
|
||||
}
|
||||
} finally {
|
||||
isOperationPending.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
const cancel = () => {
|
||||
emit('closeRequested');
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card dialog">
|
||||
<div class="card-title">
|
||||
{{ t('title') }}
|
||||
<div class="card dialog">
|
||||
<div class="card-title">
|
||||
{{ t('title') }}
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<name-editor
|
||||
v-model:name="newSlug"
|
||||
:creator-name-reservation-id="slugReservationId"
|
||||
:original-slug="creator.slug"
|
||||
@update:creator-name-reservation-id="handleSlugReservationIdChanged"
|
||||
></name-editor>
|
||||
|
||||
<v-alert
|
||||
v-if="!!errorMessage"
|
||||
class="mt-4"
|
||||
outlined
|
||||
type="error"
|
||||
>
|
||||
{{ errorMessage }}
|
||||
</v-alert>
|
||||
|
||||
<div class="card-actions">
|
||||
<button
|
||||
class="secondary"
|
||||
@click="cancel"
|
||||
>
|
||||
{{ t('cancel') }}
|
||||
</button>
|
||||
<button
|
||||
:disabled="!canSave || isOperationPending"
|
||||
class="primary"
|
||||
@click="save"
|
||||
>
|
||||
{{ t('save') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<name-editor
|
||||
v-model:name="newSlug"
|
||||
:creator-name-reservation-id="slugReservationId"
|
||||
@update:creator-name-reservation-id="handleSlugReservationIdChanged"
|
||||
:original-slug="creator.slug"
|
||||
></name-editor>
|
||||
|
||||
<v-alert
|
||||
v-if="!!errorMessage"
|
||||
outlined
|
||||
type="error"
|
||||
class="mt-4">
|
||||
{{ errorMessage }}
|
||||
</v-alert>
|
||||
|
||||
<div class="card-actions">
|
||||
<button class="secondary"
|
||||
@click="cancel">
|
||||
{{ t('cancel') }}
|
||||
</button>
|
||||
<button class="primary"
|
||||
@click="save"
|
||||
:disabled="!canSave || isOperationPending">
|
||||
{{ t('save') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
||||
<i18n>
|
||||
{
|
||||
"en": {
|
||||
"title": "Change Creator Handle"
|
||||
},
|
||||
"fr": {
|
||||
"title": "Modifier l'identifiant du créateur"
|
||||
},
|
||||
"es": {
|
||||
"title": "Cambiar identificador del creador"
|
||||
}
|
||||
"en": {
|
||||
"title": "Change Creator Handle"
|
||||
},
|
||||
"fr": {
|
||||
"title": "Modifier l'identifiant du créateur"
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
</i18n>
|
||||
@@ -1,86 +1,84 @@
|
||||
<script setup>
|
||||
import {useClient} from '@/plugins/api.js';
|
||||
import {ref} from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import {useCreatorProfileStore} from '@/stores/creatorProfileStore.js';
|
||||
import { useClient } from '@/plugins/api.js';
|
||||
import { ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useCreatorProfileStore } from '@/stores/creatorProfileStore.js';
|
||||
|
||||
const props = defineProps({
|
||||
creator: {
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emits = defineEmits(['closeRequested']);
|
||||
|
||||
const stripeId = ref('');
|
||||
const { t } = useI18n();
|
||||
const creatorProfileStore = useCreatorProfileStore();
|
||||
|
||||
const client = useClient();
|
||||
|
||||
const save = async () => {
|
||||
try {
|
||||
await client.post(`/api/membership/stripe-account`, {
|
||||
stripeAccountId: stripeId.value,
|
||||
const props = defineProps({
|
||||
creator: {
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
await creatorProfileStore.fetchCreatorProfile();
|
||||
emits('closeRequested');
|
||||
} catch (error) {
|
||||
console.error('Error saving stripe id:', error);
|
||||
}
|
||||
};
|
||||
const emits = defineEmits(['closeRequested']);
|
||||
|
||||
const cancel = () => {
|
||||
emits('closeRequested');
|
||||
};
|
||||
const stripeId = ref('');
|
||||
const { t } = useI18n();
|
||||
const creatorProfileStore = useCreatorProfileStore();
|
||||
|
||||
const client = useClient();
|
||||
|
||||
const save = async () => {
|
||||
try {
|
||||
await client.post(`/api/membership/stripe-account`, {
|
||||
stripeAccountId: stripeId.value,
|
||||
});
|
||||
|
||||
await creatorProfileStore.fetchCreatorProfile();
|
||||
emits('closeRequested');
|
||||
} catch (error) {
|
||||
console.error('Error saving stripe id:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const cancel = () => {
|
||||
emits('closeRequested');
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card dialog">
|
||||
<div class="card-title">
|
||||
{{ t('title') }}
|
||||
</div>
|
||||
<div class="card dialog">
|
||||
<div class="card-title">
|
||||
{{ t('title') }}
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<v-text-field
|
||||
v-model="stripeId"
|
||||
:label="t('label')"
|
||||
outlined
|
||||
variant="outlined"
|
||||
></v-text-field>
|
||||
<div class="card-content">
|
||||
<v-text-field
|
||||
v-model="stripeId"
|
||||
:label="t('label')"
|
||||
outlined
|
||||
variant="outlined"
|
||||
></v-text-field>
|
||||
|
||||
<div class="card-actions">
|
||||
<button class="secondary"
|
||||
@click="cancel">
|
||||
{{ t('cancel') }}
|
||||
</button>
|
||||
<button class="primary"
|
||||
@click="save">
|
||||
{{ t('save') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<button
|
||||
class="secondary"
|
||||
@click="cancel"
|
||||
>
|
||||
{{ t('cancel') }}
|
||||
</button>
|
||||
<button
|
||||
class="primary"
|
||||
@click="save"
|
||||
>
|
||||
{{ t('save') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
||||
<i18n>
|
||||
{
|
||||
"en": {
|
||||
"title": "Change Stripe ID",
|
||||
"label": "Your Stripe ID"
|
||||
},
|
||||
"fr": {
|
||||
"title": "Modifier l'ID Stripe",
|
||||
"label": "Votre ID Stripe"
|
||||
},
|
||||
"es": {
|
||||
"title": "Cambiar ID de Stripe",
|
||||
"label": "Tu ID de Stripe"
|
||||
}
|
||||
"en": {
|
||||
"title": "Change Stripe ID",
|
||||
"label": "Your Stripe ID"
|
||||
},
|
||||
"fr": {
|
||||
"title": "Modifier l'ID Stripe",
|
||||
"label": "Votre ID Stripe"
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
|
||||
@@ -1,88 +1,82 @@
|
||||
<script setup>
|
||||
import {ref} from 'vue';
|
||||
import {useClient} from '@/plugins/api.js';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { ref } from 'vue';
|
||||
import { useClient } from '@/plugins/api.js';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const props = defineProps({
|
||||
creator: {
|
||||
required: true
|
||||
}
|
||||
});
|
||||
const props = defineProps({
|
||||
creator: {
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emits = defineEmits(['closeRequested']);
|
||||
const emits = defineEmits(['closeRequested']);
|
||||
|
||||
const title = ref(props.creator.title);
|
||||
const { t } = useI18n();
|
||||
const title = ref(props.creator.title);
|
||||
const { t } = useI18n();
|
||||
|
||||
const client = useClient();
|
||||
const client = useClient();
|
||||
|
||||
async function save() {
|
||||
try {
|
||||
await client.post(
|
||||
`/api/creators/${props.creator.id}/title`,
|
||||
{
|
||||
title: title.value
|
||||
async function save() {
|
||||
try {
|
||||
await client.post(`/api/creators/${props.creator.id}/title`, {
|
||||
title: title.value,
|
||||
});
|
||||
|
||||
props.creator.title = title.value;
|
||||
emits('closeRequested');
|
||||
} catch (error) {
|
||||
console.error('Error saving title:', error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
props.creator.title = title.value;
|
||||
emits('closeRequested');
|
||||
} catch (error) {
|
||||
console.error('Error saving title:', error);
|
||||
}
|
||||
}
|
||||
|
||||
const cancel = () => {
|
||||
emits('closeRequested');
|
||||
};
|
||||
const cancel = () => {
|
||||
emits('closeRequested');
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card dialog">
|
||||
<div class="card dialog">
|
||||
<div class="card-title">
|
||||
{{ t('title') }}
|
||||
</div>
|
||||
|
||||
<div class="card-title">
|
||||
{{ t('title') }}
|
||||
<div class="card-content">
|
||||
<v-text-field
|
||||
v-model="title"
|
||||
:label="t('label')"
|
||||
outlined
|
||||
variant="outlined"
|
||||
></v-text-field>
|
||||
|
||||
<div class="card-actions">
|
||||
<button
|
||||
class="secondary"
|
||||
@click="cancel"
|
||||
>
|
||||
{{ t('cancel') }}
|
||||
</button>
|
||||
<button
|
||||
class="primary"
|
||||
@click="save"
|
||||
>
|
||||
{{ t('save') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<v-text-field
|
||||
v-model="title"
|
||||
:label="t('label')"
|
||||
outlined
|
||||
variant="outlined"
|
||||
></v-text-field>
|
||||
|
||||
<div class="card-actions">
|
||||
<button class="secondary"
|
||||
@click="cancel">
|
||||
{{ t('cancel') }}
|
||||
</button>
|
||||
<button class="primary"
|
||||
@click="save">
|
||||
{{ t('save') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
||||
<i18n>
|
||||
{
|
||||
"en": {
|
||||
"title": "Change Title",
|
||||
"label": "Your title"
|
||||
},
|
||||
"fr": {
|
||||
"title": "Modifier le titre",
|
||||
"label": "Votre titre"
|
||||
},
|
||||
"es": {
|
||||
"title": "Cambiar título",
|
||||
"label": "Tu título"
|
||||
}
|
||||
"en": {
|
||||
"title": "Change Title",
|
||||
"label": "Your title"
|
||||
},
|
||||
"fr": {
|
||||
"title": "Modifier le titre",
|
||||
"label": "Votre titre"
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
|
||||
@@ -1,210 +1,200 @@
|
||||
<script setup>
|
||||
import {ref} from 'vue'
|
||||
import {useClient} from "@/plugins/api.js";
|
||||
import X from "@/views/svg/X.vue";
|
||||
import Tiktok from "@/views/svg/Tiktok.vue";
|
||||
import Reddit from "@/views/svg/Reddit.vue";
|
||||
import Web from "@/views/svg/Web.vue";
|
||||
import Youtube from "@/views/svg/Youtube.vue";
|
||||
import Linkedin from "@/views/svg/Linkedin.vue";
|
||||
import Instagram from "@/views/svg/Instagram.vue";
|
||||
import Facebook from "@/views/svg/Facebook.vue";
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { ref } from 'vue';
|
||||
import { useClient } from '@/plugins/api.js';
|
||||
import X from '@/views/svg/X.vue';
|
||||
import Tiktok from '@/views/svg/Tiktok.vue';
|
||||
import Reddit from '@/views/svg/Reddit.vue';
|
||||
import Web from '@/views/svg/Web.vue';
|
||||
import Youtube from '@/views/svg/Youtube.vue';
|
||||
import Linkedin from '@/views/svg/Linkedin.vue';
|
||||
import Instagram from '@/views/svg/Instagram.vue';
|
||||
import Facebook from '@/views/svg/Facebook.vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
creator: {
|
||||
required: true
|
||||
}
|
||||
})
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
creator: {
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emits = defineEmits(['closeRequested'])
|
||||
const emits = defineEmits(['closeRequested']);
|
||||
|
||||
const facebookUrl = ref(props.creator.socials.facebookUrl)
|
||||
const instagramUrl = ref(props.creator.socials.instagramUrl)
|
||||
const linkedInUrl = ref(props.creator.socials.linkedInUrl)
|
||||
const redditUrl = ref(props.creator.socials.redditUrl)
|
||||
const tikTokUrl = ref(props.creator.socials.tikTokUrl)
|
||||
const websiteUrl = ref(props.creator.socials.websiteUrl)
|
||||
const xUrl = ref(props.creator.socials.xUrl)
|
||||
const youtubeUrl = ref(props.creator.socials.youtubeUrl)
|
||||
const facebookUrl = ref(props.creator.socials.facebookUrl);
|
||||
const instagramUrl = ref(props.creator.socials.instagramUrl);
|
||||
const linkedInUrl = ref(props.creator.socials.linkedInUrl);
|
||||
const redditUrl = ref(props.creator.socials.redditUrl);
|
||||
const tikTokUrl = ref(props.creator.socials.tikTokUrl);
|
||||
const websiteUrl = ref(props.creator.socials.websiteUrl);
|
||||
const xUrl = ref(props.creator.socials.xUrl);
|
||||
const youtubeUrl = ref(props.creator.socials.youtubeUrl);
|
||||
|
||||
const client = useClient()
|
||||
const save = async () => {
|
||||
try {
|
||||
await client.post(
|
||||
`/api/creators/${props.creator.id}/socials`,
|
||||
{
|
||||
"facebookUrl": facebookUrl.value || null,
|
||||
"instagramUrl": instagramUrl.value || null,
|
||||
"linkedInUrl": linkedInUrl.value || null,
|
||||
"redditUrl": redditUrl.value || null,
|
||||
"tikTokUrl": tikTokUrl.value || null,
|
||||
"websiteUrl": websiteUrl.value || null,
|
||||
"xUrl": xUrl.value || null,
|
||||
"youtubeUrl": youtubeUrl.value || null,
|
||||
})
|
||||
const client = useClient();
|
||||
const save = async () => {
|
||||
try {
|
||||
await client.post(`/api/creators/${props.creator.id}/socials`, {
|
||||
facebookUrl: facebookUrl.value || null,
|
||||
instagramUrl: instagramUrl.value || null,
|
||||
linkedInUrl: linkedInUrl.value || null,
|
||||
redditUrl: redditUrl.value || null,
|
||||
tikTokUrl: tikTokUrl.value || null,
|
||||
websiteUrl: websiteUrl.value || null,
|
||||
xUrl: xUrl.value || null,
|
||||
youtubeUrl: youtubeUrl.value || null,
|
||||
});
|
||||
|
||||
props.creator.socials.facebookUrl = facebookUrl
|
||||
props.creator.socials.instagramUrl = instagramUrl
|
||||
props.creator.socials.linkedInUrl = linkedInUrl
|
||||
props.creator.socials.redditUrl = redditUrl
|
||||
props.creator.socials.tikTokUrl = tikTokUrl
|
||||
props.creator.socials.websiteUrl = websiteUrl
|
||||
props.creator.socials.xUrl = xUrl
|
||||
props.creator.socials.youtubeUrl = youtubeUrl
|
||||
props.creator.socials.facebookUrl = facebookUrl;
|
||||
props.creator.socials.instagramUrl = instagramUrl;
|
||||
props.creator.socials.linkedInUrl = linkedInUrl;
|
||||
props.creator.socials.redditUrl = redditUrl;
|
||||
props.creator.socials.tikTokUrl = tikTokUrl;
|
||||
props.creator.socials.websiteUrl = websiteUrl;
|
||||
props.creator.socials.xUrl = xUrl;
|
||||
props.creator.socials.youtubeUrl = youtubeUrl;
|
||||
|
||||
emits('closeRequested')
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
const cancel = () => {
|
||||
emits('closeRequested')
|
||||
}
|
||||
emits('closeRequested');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const cancel = () => {
|
||||
emits('closeRequested');
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card dialog">
|
||||
<div class="card-title">
|
||||
{{ t('title') }}
|
||||
</div>
|
||||
|
||||
<div class="card dialog">
|
||||
<div class="card-content">
|
||||
<div class="editor-line">
|
||||
<facebook class="social-icon"></facebook>
|
||||
<input
|
||||
v-model="facebookUrl"
|
||||
:placeholder="t('facebook')"
|
||||
class="input-field"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="card-title">
|
||||
{{ t('title') }}
|
||||
<div class="editor-line">
|
||||
<instagram class="social-icon"></instagram>
|
||||
<input
|
||||
v-model="instagramUrl"
|
||||
:placeholder="t('instagram')"
|
||||
class="input-field"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="editor-line">
|
||||
<linkedin class="social-icon"></linkedin>
|
||||
<input
|
||||
v-model="linkedInUrl"
|
||||
:placeholder="t('linkedin')"
|
||||
class="input-field"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="editor-line">
|
||||
<reddit class="social-icon"></reddit>
|
||||
<input
|
||||
v-model="redditUrl"
|
||||
:placeholder="t('reddit')"
|
||||
class="input-field"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="editor-line">
|
||||
<tiktok class="social-icon"></tiktok>
|
||||
<input
|
||||
v-model="tikTokUrl"
|
||||
:placeholder="t('tiktok')"
|
||||
class="input-field"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="editor-line">
|
||||
<web class="social-icon"></web>
|
||||
<input
|
||||
v-model="websiteUrl"
|
||||
:placeholder="t('website')"
|
||||
class="input-field"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="editor-line">
|
||||
<x class="social-icon"></x>
|
||||
<input
|
||||
v-model="xUrl"
|
||||
:placeholder="t('x')"
|
||||
class="input-field"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="editor-line">
|
||||
<youtube class="social-icon"></youtube>
|
||||
<input
|
||||
v-model="youtubeUrl"
|
||||
:placeholder="t('youtube')"
|
||||
class="input-field"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-actions">
|
||||
<button
|
||||
class="secondary"
|
||||
@click="cancel"
|
||||
>
|
||||
{{ t('cancel') }}
|
||||
</button>
|
||||
<button
|
||||
class="primary"
|
||||
@click="save"
|
||||
>
|
||||
{{ t('save') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
|
||||
<div class="editor-line">
|
||||
<facebook class="social-icon"></facebook>
|
||||
<input
|
||||
v-model="facebookUrl"
|
||||
class="input-field"
|
||||
:placeholder="t('facebook')"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="editor-line">
|
||||
<instagram class="social-icon"></instagram>
|
||||
<input
|
||||
v-model="instagramUrl"
|
||||
class="input-field"
|
||||
:placeholder="t('instagram')"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="editor-line">
|
||||
<linkedin class="social-icon"></linkedin>
|
||||
<input
|
||||
v-model="linkedInUrl"
|
||||
class="input-field"
|
||||
:placeholder="t('linkedin')"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="editor-line">
|
||||
<reddit class="social-icon"></reddit>
|
||||
<input
|
||||
v-model="redditUrl"
|
||||
class="input-field"
|
||||
:placeholder="t('reddit')"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="editor-line">
|
||||
<tiktok class="social-icon"></tiktok>
|
||||
<input
|
||||
v-model="tikTokUrl"
|
||||
class="input-field"
|
||||
:placeholder="t('tiktok')"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="editor-line">
|
||||
<web class="social-icon"></web>
|
||||
<input
|
||||
v-model="websiteUrl"
|
||||
class="input-field"
|
||||
:placeholder="t('website')"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="editor-line">
|
||||
<x class="social-icon"></x>
|
||||
<input
|
||||
v-model="xUrl"
|
||||
class="input-field"
|
||||
:placeholder="t('x')"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="editor-line">
|
||||
<youtube class="social-icon"></youtube>
|
||||
<input
|
||||
v-model="youtubeUrl"
|
||||
class="input-field"
|
||||
:placeholder="t('youtube')"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="card-actions">
|
||||
<button class="secondary"
|
||||
@click="cancel">
|
||||
{{ t('cancel') }}
|
||||
</button>
|
||||
<button class="primary"
|
||||
@click="save">
|
||||
{{ t('save') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.editor-line {
|
||||
@apply flex flex-row gap-4;
|
||||
@apply items-center;
|
||||
}
|
||||
|
||||
.editor-line {
|
||||
@apply flex flex-row gap-4;
|
||||
@apply items-center;
|
||||
}
|
||||
|
||||
.social-icon {
|
||||
@apply w-8 h-8;
|
||||
}
|
||||
|
||||
.input-field {
|
||||
@apply w-full p-[10px];
|
||||
@apply rounded-sm;
|
||||
@apply transition duration-200;
|
||||
@apply ring-1 ring-[#6D6C70] focus:outline-none focus:ring-hutopySecondary;
|
||||
@apply hover:ring-hutopyPrimary;
|
||||
@apply placeholder:text-[#6D6C70]
|
||||
}
|
||||
.social-icon {
|
||||
@apply w-8 h-8;
|
||||
}
|
||||
|
||||
.input-field {
|
||||
@apply w-full p-[10px];
|
||||
@apply rounded-sm;
|
||||
@apply transition duration-200;
|
||||
@apply ring-1 ring-[#6D6C70] focus:outline-none focus:ring-hutopySecondary;
|
||||
@apply hover:ring-hutopyPrimary;
|
||||
@apply placeholder:text-[#6D6C70];
|
||||
}
|
||||
</style>
|
||||
|
||||
<i18n>
|
||||
{
|
||||
"en": {
|
||||
"title": "Social Media Links"
|
||||
},
|
||||
"fr": {
|
||||
"title": "Liens des réseaux sociaux"
|
||||
},
|
||||
"es": {
|
||||
"title": "Enlaces de redes sociales"
|
||||
}
|
||||
"en": {
|
||||
"title": "Social Media Links"
|
||||
},
|
||||
"fr": {
|
||||
"title": "Liens des réseaux sociaux"
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
|
||||
Reference in New Issue
Block a user