Is hutopy finally trilangual

This commit is contained in:
2025-04-19 01:56:59 -04:00
parent af71c5e952
commit b1681252cc
140 changed files with 5441 additions and 1085 deletions

View File

@@ -9,6 +9,7 @@
"preview": "vite preview"
},
"dependencies": {
"@intlify/unplugin-vue-i18n": "^6.0.5",
"@mdi/font": "^7.4.47",
"@stripe/stripe-js": "^3.0.10",
"@tinymce/tinymce-vue": "^6.0.1",
@@ -22,12 +23,13 @@
"uuid": "^10.0.0",
"vue": "^3.4.15",
"vue-advanced-cropper": "^2.8.9",
"vue-i18n": "^9.14.0",
"vue-i18n": "^10.0.7",
"vue-router": "^4.2.5",
"vue3-google-login": "^2.0.26",
"vuetify": "^3.5.6"
},
"devDependencies": {
"@types/webpack-env": "^1.18.8",
"@vitejs/plugin-vue": "^5.0.3",
"autoprefixer": "^10.4.17",
"eslint": "^8.57.0",

4253
frontend/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -17,6 +17,21 @@
<script async setup>
import SideBar from "@/views/main/SideBar.vue";
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();
const { locale } = useI18n();
// Watch for changes to the language store
watch(() => languageStore.locale, (newLocale) => {
if (newLocale) {
locale.value = newLocale;
}
}, { immediate: true });
</script>
<style scoped>

View File

@@ -1,18 +0,0 @@
import { createI18n } from 'vue-i18n';
import { useSessionStorage } from '@vueuse/core';
// Get the stored locale or default to 'fr'
const storedLocale = useSessionStorage('user-locale', 'fr');
const i18n = createI18n({
legacy: false,
locale: storedLocale.value,
fallbackLocale: 'en',
messages: {
en: {},
fr: {},
es: {}
}
});
export default i18n;

View File

@@ -0,0 +1,53 @@
{
// Common actions
"save": "Save",
"cancel": "Cancel",
"edit": "Edit",
"delete": "Delete",
"create": "Create",
"apply": "Apply",
"preview": "Preview",
"label": "Label",
"confirm": "Confirm",
"close": "Close",
// Common status
"loading": "Loading...",
"error": "Error",
"success": "Success",
// Common messages
"changesSaved": "Changes saved successfully",
"errorOccurred": "An error occurred",
// Common fields
"name": "Name",
"email": "Email",
"password": "Password",
"description": "Description",
"title": "Title",
"image": "Image",
"file": "File",
// Common validation
"required": "This field is required",
"invalidEmail": "Invalid email address",
"invalidPassword": "Invalid password",
// Social media
"facebook": "Facebook",
"instagram": "Instagram",
"linkedin": "LinkedIn",
"reddit": "Reddit",
"tiktok": "TikTok",
"x": "X (Twitter)",
"youtube": "YouTube",
"website": "Website",
// Errors
"errors": {
"unexpected": "An unexpected error occurred",
"imageLoad": "Error loading image",
"imageUpload": "Error uploading image"
}
}

View File

@@ -0,0 +1,40 @@
{
"save": "Guardar",
"cancel": "Cancelar",
"edit": "Editar",
"delete": "Eliminar",
"create": "Crear",
"apply": "Aplicar",
"preview": "Vista previa",
"label": "Etiqueta",
"confirm": "Confirmar",
"close": "Cerrar",
"loading": "Cargando...",
"error": "Error",
"success": "Éxito",
"changesSaved": "Cambios guardados con éxito",
"errorOccurred": "Ha ocurrido un error",
"name": "Nombre",
"email": "Correo electrónico",
"password": "Contraseña",
"description": "Descripción",
"title": "Título",
"image": "Imagen",
"file": "Archivo",
"required": "Este campo es obligatorio",
"invalidEmail": "Correo electrónico inválido",
"invalidPassword": "Contraseña inválida",
"facebook": "Facebook",
"instagram": "Instagram",
"linkedin": "LinkedIn",
"reddit": "Reddit",
"tiktok": "TikTok",
"x": "X (Twitter)",
"youtube": "YouTube",
"website": "Sitio web",
"errors": {
"unexpected": "Ha ocurrido un error inesperado",
"imageLoad": "Error al cargar la imagen",
"imageUpload": "Error al subir la imagen"
}
}

View File

@@ -0,0 +1,40 @@
{
"save": "Enregistrer",
"cancel": "Annuler",
"edit": "Modifier",
"delete": "Supprimer",
"create": "Créer",
"apply": "Appliquer",
"preview": "Aperçu",
"label": "Étiquette",
"confirm": "Confirmer",
"close": "Fermer",
"loading": "Chargement...",
"error": "Erreur",
"success": "Succès",
"changesSaved": "Modifications enregistrées avec succès",
"errorOccurred": "Une erreur est survenue",
"name": "Nom",
"email": "Email",
"password": "Mot de passe",
"description": "Description",
"title": "Titre",
"image": "Image",
"file": "Fichier",
"required": "Ce champ est requis",
"invalidEmail": "Adresse email invalide",
"invalidPassword": "Mot de passe invalide",
"facebook": "Facebook",
"instagram": "Instagram",
"linkedin": "LinkedIn",
"reddit": "Reddit",
"tiktok": "TikTok",
"x": "X (Twitter)",
"youtube": "YouTube",
"website": "Site web",
"errors": {
"unexpected": "Une erreur inattendue s'est produite",
"imageLoad": "Erreur lors du chargement de l'image",
"imageUpload": "Erreur lors du téléchargement de l'image"
}
}

View File

@@ -1,6 +1,6 @@
import {createApp} from 'vue'
import App from './App.vue'
import router from './router/router.js'
import router from '@/router/router.js'
import {createPinia} from 'pinia'
import '@mdi/font/css/materialdesignicons.css'
import 'vuetify/styles'
@@ -9,7 +9,6 @@ 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 i18n from './i18n.js';
import {useUserProfileStore} from "@/stores/userProfileStore.js";
import {useCreatorProfileStore} from "@/stores/creatorProfileStore.js";
import './assets/main.css'
@@ -19,21 +18,34 @@ const vuetify = createVuetify({
directives
});
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
}
})
const pinia = createPinia();
const app = createApp(App)
.use(createPinia())
.use(pinia)
.use(vuetify)
.use(router)
.use(i18n)
.use(vueGoogleOauth, {
clientId: import.meta.env.VITE_GOOGLE_CLIENT_ID,
})
.use(i18n)
});
// Make $t globally available
app.config.globalProperties.$t = i18n.global.t;
// this force the creation and initialization of the stores
useAuthStore()
useUserProfileStore()
useCreatorProfileStore()
useAuthStore();
useUserProfileStore();
useCreatorProfileStore();
app.mount('#app');

View File

@@ -1,23 +0,0 @@
export default class UserTransactionsModel
{
amount = "";
currency = "";
tipMessage = "";
created = "";
static createFromApiResult(apiResult){
const userTransactionModel = Object.assign(new UserTransactionsModel(), apiResult)
const date = new Date(userTransactionModel.created);
const options = {
year: 'numeric',
month: 'long',
day: 'numeric',
timeZone: 'America/Montreal'
};
userTransactionModel.created = new Intl.DateTimeFormat('fr-CA', options).format(date);
return userTransactionModel;
}
}

View File

@@ -5,16 +5,21 @@ import { useI18n } from 'vue-i18n'
export const useLanguageStore = defineStore(
'language',
() => {
const { locale } = useI18n()
// Initialize with the stored value or default to 'fr'
const storedLocale = useSessionStorage('user-locale', 'fr')
// Get i18n instance
const { locale } = useI18n()
// Set the initial locale from storage
locale.value = storedLocale.value
if (locale && storedLocale.value) {
locale.value = storedLocale.value
}
function setLocale(newLocale) {
locale.value = newLocale
if (locale) {
locale.value = newLocale
}
storedLocale.value = newLocale
}

View File

@@ -1,15 +0,0 @@
{
"save": "Save",
"cancel": "Cancel",
"edit": "Edit",
"delete": "Delete",
"create": "Create",
"apply": "Apply",
"preview": "Preview",
"label": "Label",
"errors": {
"unexpected": "An unexpected error occurred",
"imageLoad": "Error loading image",
"imageUpload": "Error uploading image"
}
}

View File

@@ -1,15 +0,0 @@
{
"save": "Guardar",
"cancel": "Cancelar",
"edit": "Editar",
"delete": "Eliminar",
"create": "Crear",
"apply": "Aplicar",
"preview": "Vista previa",
"label": "Etiqueta",
"errors": {
"unexpected": "Se produjo un error inesperado",
"imageLoad": "Error al cargar la imagen",
"imageUpload": "Error al subir la imagen"
}
}

View File

@@ -1,15 +0,0 @@
{
"save": "Enregistrer",
"cancel": "Annuler",
"edit": "Modifier",
"delete": "Supprimer",
"create": "Créer",
"apply": "Appliquer",
"preview": "Aperçu",
"label": "Étiquette",
"errors": {
"unexpected": "Une erreur inattendue s'est produite",
"imageLoad": "Erreur lors du chargement de l'image",
"imageUpload": "Erreur lors du téléchargement de l'image"
}
}

View File

@@ -1,95 +0,0 @@
import { ref, computed, watch } from 'vue'
import { useI18n } from 'vue-i18n'
// Helper function to get component path from stack trace
function getComponentPath() {
const stack = new Error().stack
const stackLines = stack.split('\n')
// Find the first line that contains a .vue file
const vueFileLine = stackLines.find(line => line.includes('.vue'))
if (!vueFileLine) return null
// Extract the file path
const match = vueFileLine.match(/\/src\/(.*?\.vue)/)
if (!match) return null
// Return the full path without the .vue extension
return match[1].replace(/\.vue$/, '')
}
// Helper function to get nested value from an object using dot notation
function getNestedValue(obj, path) {
return path.split('.').reduce((current, key) => {
return current && current[key] !== undefined ? current[key] : null
}, obj)
}
export function useTranslations() {
const { locale } = useI18n()
const componentPath = ref(getComponentPath())
const componentTranslations = ref({})
const commonTranslations = ref({})
// Load translations when locale changes
const loadTranslations = async () => {
// Load component translations
if (componentPath.value) {
try {
// The translation files are in the same directory as the Vue components
const importPath = `../${componentPath.value}.${locale.value}.json`
const translations = await import(importPath)
componentTranslations.value = translations.default
} catch (e) {
try {
const importPath = `../${componentPath.value}.en.json`
const translations = await import(importPath)
componentTranslations.value = translations.default
} catch (e) {
componentTranslations.value = {}
}
}
}
// Load common translations
try {
const importPath = `./common.${locale.value}.json`
const translations = await import(importPath)
commonTranslations.value = translations.default
} catch (e) {
try {
const importPath = './common.en.json'
const translations = await import(importPath)
commonTranslations.value = translations.default
} catch (e) {
commonTranslations.value = {}
}
}
}
// Load translations immediately
loadTranslations()
// Watch for locale changes
watch(locale, () => {
loadTranslations()
})
// Return synchronous translation function
return (key) => {
// Try to get the translation from component translations first
const componentValue = getNestedValue(componentTranslations.value, key)
if (componentValue !== null) {
return componentValue
}
// Then try common translations
const commonValue = getNestedValue(commonTranslations.value, key)
if (commonValue !== null) {
return commonValue
}
// If not found, return the key with MISSING prefix
return `MISSING: ${key}`
}
}

View File

@@ -1,4 +0,0 @@
{
"title": "Login",
"alt": "hutopy login"
}

View File

@@ -1,4 +0,0 @@
{
"title": "Iniciar sesión",
"alt": "iniciar sesión hutopy"
}

View File

@@ -1,4 +0,0 @@
{
"title": "Connexion",
"alt": "connexion hutopy"
}

View File

@@ -2,9 +2,9 @@
import {ref} from 'vue';
import {GoogleLogin} from "vue3-google-login";
import {useAuthStore} from '@/stores/authStore.js';
import {useTranslations} from '@/translations/translations';
import {useI18n} from 'vue-i18n';
const t = useTranslations();
const {t} = useI18n();
const authStore = useAuthStore();
@@ -44,3 +44,20 @@ async function googleCallback(token) {
<style scoped>
</style>
<i18n>
{
"en": {
"title": "Sign in to Hutopy",
"alt": "Hutopy Login"
},
"fr": {
"title": "Connectez-vous à Hutopy",
"alt": "Connexion Hutopy"
},
"es": {
"title": "Iniciar sesión en Hutopy",
"alt": "Inicio de sesión Hutopy"
}
}
</i18n>

View File

@@ -1,7 +0,0 @@
{
"title": "Payment Successful!",
"message": "Your payment has been processed successfully.",
"usernameDefault": "the creator",
"receipt": "A receipt has been sent to your email.",
"continue": "Continue to"
}

View File

@@ -1,7 +0,0 @@
{
"title": "¡Pago exitoso!",
"message": "Su pago ha sido procesado con éxito.",
"usernameDefault": "el creador",
"receipt": "Se ha enviado un recibo a su correo electrónico.",
"continue": "Continuar a"
}

View File

@@ -1,7 +0,0 @@
{
"title": "Paiement réussi !",
"message": "Votre paiement a été traité avec succès.",
"usernameDefault": "le créateur",
"receipt": "Un reçu a été envoyé à votre adresse e-mail.",
"continue": "Continuer vers"
}

View File

@@ -45,11 +45,11 @@
<script setup>
import {useRouter, useRoute} from 'vue-router';
import {useTranslations} from '@/translations/translations';
import { useI18n } from 'vue-i18n';
const router = useRouter();
const route = useRoute();
const t = useTranslations();
const { t } = useI18n();
function goBack() {
const returnUrl = route.query.returnUrl;
@@ -62,6 +62,32 @@ function goBack() {
</script>
<i18n>
{
"en": {
"title": "Payment Successful!",
"message": "Your payment has been processed successfully.",
"usernameDefault": "the creator",
"receipt": "A receipt has been sent to your email.",
"continue": "Continue to"
},
"fr": {
"title": "Paiement réussi !",
"message": "Votre paiement a été traité avec succès.",
"usernameDefault": "le créateur",
"receipt": "Un reçu a été envoyé à votre email.",
"continue": "Continuer vers"
},
"es": {
"title": "¡Pago exitoso!",
"message": "Su pago ha sido procesado con éxito.",
"usernameDefault": "el creador",
"receipt": "Se ha enviado un recibo a su correo electrónico.",
"continue": "Continuar a"
}
}
</i18n>
<style scoped>
.container {
@apply min-h-screen;

View File

@@ -1,5 +0,0 @@
{
"title": "Payment Failed",
"message": "We couldn't process your payment. Please try again or contact support if the problem persists.",
"continue": "Return to"
}

View File

@@ -1,5 +0,0 @@
{
"title": "Pago fallido",
"message": "No pudimos procesar su pago. Por favor, inténtelo de nuevo o contacte con soporte si el problema persiste.",
"continue": "Volver a"
}

View File

@@ -1,5 +0,0 @@
{
"title": "Échec du paiement",
"message": "Nous n'avons pas pu traiter votre paiement. Veuillez réessayer ou contacter le support si le problème persiste.",
"continue": "Retourner à"
}

View File

@@ -5,7 +5,7 @@
<p>{{ t('message') }}</p>
<div class="card-actions">
<button class="action-button" @click="goBack()">
{{ t('continue') }}
{{ t('retry') }}
</button>
</div>
</div>
@@ -14,11 +14,11 @@
<script setup>
import { useRouter, useRoute } from 'vue-router';
import { useTranslations } from '@/translations/translations';
import { useI18n } from 'vue-i18n';
const router = useRouter();
const route = useRoute();
const t = useTranslations();
const { t } = useI18n();
function goBack() {
const returnUrl = route.query.returnUrl;
@@ -30,6 +30,26 @@ function goBack() {
}
</script>
<i18n>
{
"en": {
"title": "Payment Failed",
"message": "We couldn't process your payment.",
"retry": "Try Again"
},
"fr": {
"title": "Échec du paiement",
"message": "Nous n'avons pas pu traiter votre paiement.",
"retry": "Réessayer"
},
"es": {
"title": "Pago fallido",
"message": "No pudimos procesar su pago.",
"retry": "Intentar de nuevo"
}
}
</i18n>
<style scoped>
.container {
@apply min-h-screen;

View File

@@ -1,5 +0,0 @@
{
"message": "Message (optional)",
"amount": "Amount ($)",
"send": "Send"
}

View File

@@ -1,5 +0,0 @@
{
"message": "Mensaje (opcional)",
"amount": "Cantidad ($)",
"send": "Enviar"
}

View File

@@ -1,5 +0,0 @@
{
"message": "Message (facultatif)",
"amount": "Montant ($)",
"send": "Envoyez"
}

View File

@@ -1,94 +0,0 @@
<template>
<v-container>
<v-row>
<v-text-field :label="t('message')" v-model="tipMessage"
style="border-radius: 10px; margin-top: 10px; margin-bottom: 10px; color: #a30e79; background-color: #f4f4f4">
</v-text-field>
</v-row>
<v-row>
<v-text-field :label="t('amount')" v-model="price"
style="border-radius: 10px; margin-bottom: 10px; color: #a30e79; background-color: #f4f4f4">
</v-text-field>
</v-row>
<v-row justify="center">
<v-btn @click="goPay()"
style="margin-bottom: 10px; width: 200px; background-color: #6b0065; color: white; font-weight: bold;">
<v-icon left style="margin-right: 10px;">
mdi-gift
</v-icon>
{{ t('send') }}
</v-btn>
</v-row>
<v-dialog v-model="isPaymentDialogActive" max-width="720" persistent>
<template v-slot:default>
<v-card>
<div id="checkout">
<!-- Checkout will insert the payment form here -->
</div>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn block class="ma-auto" style="width: 200px;" :text="t('cancel')" @click="closeDialog()"></v-btn>
</v-card-actions>
</v-card>
</template>
</v-dialog>
</v-container>
</template>
<script setup>
import { useClient } from '@/plugins/api.js';
import { loadStripe } from '@stripe/stripe-js';
import { onMounted, ref } from "vue";
import { useTranslations } from '@/translations/translations';
const props = defineProps(['creatorId'])
const t = useTranslations();
let stripe = null;
const client = useClient();
const price = ref(0);
const tipMessage = ref("");
const isPaymentDialogActive = ref(false);
var checkout;
onMounted(async () => {
stripe = await loadStripe(import.meta.env.VITE_STRIPE_API_KEY);
})
const fetchClientSecret = async () => {
const clientSecret = await createCheckoutSession();
return clientSecret;
};
async function createCheckoutSession() {
let clientSecret = await client.post('/api/Stripe', {
amount: (price.value * 100),
tipMessage: tipMessage.value,
creatorId: props.creatorId
});
let secret = clientSecret["data"];
return secret;
}
function closeDialog() {
isPaymentDialogActive.value = false;
checkout.destroy();
}
async function goPay() {
isPaymentDialogActive.value = true;
checkout = await stripe.initEmbeddedCheckout({
fetchClientSecret,
});
await checkout.mount('#checkout');
}
</script>

View File

@@ -1,3 +0,0 @@
{
"alt": "Creator banner"
}

View File

@@ -1,3 +0,0 @@
{
"alt": "Banner del creador"
}

View File

@@ -1,3 +0,0 @@
{
"alt": "Bannière du créateur"
}

View File

@@ -1,5 +1,4 @@
<template>
<div class="relative">
<!-- Banner Container with mouse events -->
<div
@@ -35,7 +34,6 @@
></banner-editor>
</template>
</v-dialog>
</template>
<script setup>
@@ -43,11 +41,11 @@ 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 { useTranslations } from '@/translations/translations';
import { useI18n } from 'vue-i18n';
const authStore = useAuthStore();
const brandingStore = useBrandingStore();
const t = useTranslations();
const { t } = useI18n();
// State
const showTint = ref(false);
@@ -61,12 +59,24 @@ const openBannerEditor = () => {
const isCurrentCreator = computed(() => {
return authStore.userId === brandingStore.value.id;
});
</script>
<style scoped>
.banner {
@apply border-b border-solid border-hTertiary;
}
</style>
</style>
<i18n>
{
"en": {
"alt": "Creator banner"
},
"fr": {
"alt": "Bannière du créateur"
},
"es": {
"alt": "Banner del creador"
}
}
</i18n>

View File

@@ -1,12 +0,0 @@
{
"social": {
"facebook": "Facebook",
"instagram": "Instagram",
"linkedin": "LinkedIn",
"reddit": "Reddit",
"tiktok": "TikTok",
"x": "X (Twitter)",
"youtube": "YouTube",
"website": "Website"
}
}

View File

@@ -1,12 +0,0 @@
{
"social": {
"facebook": "Facebook",
"instagram": "Instagram",
"linkedin": "LinkedIn",
"reddit": "Reddit",
"tiktok": "TikTok",
"x": "X (Twitter)",
"youtube": "YouTube",
"website": "Sitio Web"
}
}

View File

@@ -1,12 +0,0 @@
{
"social": {
"facebook": "Facebook",
"instagram": "Instagram",
"linkedin": "LinkedIn",
"reddit": "Reddit",
"tiktok": "TikTok",
"x": "X (Twitter)",
"youtube": "YouTube",
"website": "Site Web"
}
}

View File

@@ -12,11 +12,11 @@ import Tiktok from "@/views/svg/Tiktok.vue";
import Reddit from "@/views/svg/Reddit.vue";
import Youtube from "@/views/svg/Youtube.vue";
import Web from "@/views/svg/Web.vue";
import { useTranslations } from '@/translations/translations'
import { useI18n } from 'vue-i18n'
const brandingStore = useBrandingStore();
const baseURL = window.location.origin;
const t = useTranslations();
const { t } = useI18n();
// Gèrer le breakpoint du block information.
// Définir un point de rupture pour "moyen" (correspondant à md: de Tailwind)
@@ -89,56 +89,56 @@ onUnmounted(() => {
<a v-if="brandingStore.value?.socials?.facebookUrl"
:href="brandingStore.value?.socials?.facebookUrl"
target="_blank"
:title="t('social.facebook')">
:title="t('facebook')">
<facebook class="social-icon"></facebook>
</a>
<a v-if="brandingStore.value?.socials?.instagramUrl"
:href="brandingStore.value?.socials?.instagramUrl"
target="_blank"
:title="t('social.instagram')">
:title="t('instagram')">
<instagram class="social-icon"></instagram>
</a>
<a v-if="brandingStore.value?.socials?.linkedInUrl"
:href="brandingStore.value?.socials?.linkedInUrl"
target="_blank"
:title="t('social.linkedin')">
:title="t('linkedin')">
<linkedin class="social-icon"></linkedin>
</a>
<a v-if="brandingStore.value?.socials?.redditUrl"
:href="brandingStore.value?.socials?.redditUrl"
target="_blank"
:title="t('social.reddit')">
:title="t('reddit')">
<reddit class="social-icon"></reddit>
</a>
<a v-if="brandingStore.value?.socials?.tikTokUrl"
:href="brandingStore.value?.socials?.tikTokUrl"
target="_blank"
:title="t('social.tiktok')">
:title="t('tiktok')">
<tiktok class="social-icon"></tiktok>
</a>
<a v-if="brandingStore.value?.socials?.xUrl"
:href="brandingStore.value?.socials?.xUrl"
target="_blank"
:title="t('social.x')">
:title="t('x')">
<x class="social-icon"></x>
</a>
<a v-if="brandingStore.value?.socials?.youtubeUrl"
:href="brandingStore.value?.socials?.youtubeUrl"
target="_blank"
:title="t('social.youtube')">
:title="t('youtube')">
<youtube class="social-icon"></youtube>
</a>
<a v-if="brandingStore.value?.socials?.websiteUrl"
:href="brandingStore.value?.socials?.websiteUrl"
target="_blank"
:title="t('social.website')">
:title="t('website')">
<web class="social-icon"></web>
</a>
@@ -166,4 +166,39 @@ onUnmounted(() => {
@apply flex flex-col gap-y-4;
}
}
</style>
</style>
<i18n>
{
"en": {
"facebook": "Facebook",
"instagram": "Instagram",
"linkedin": "LinkedIn",
"reddit": "Reddit",
"tiktok": "TikTok",
"x": "X (Twitter)",
"youtube": "YouTube",
"website": "Website"
},
"fr": {
"facebook": "Facebook",
"instagram": "Instagram",
"linkedin": "LinkedIn",
"reddit": "Reddit",
"tiktok": "TikTok",
"x": "X (Twitter)",
"youtube": "YouTube",
"website": "Site web"
},
"es": {
"facebook": "Facebook",
"instagram": "Instagram",
"linkedin": "LinkedIn",
"reddit": "Reddit",
"tiktok": "TikTok",
"x": "X (Twitter)",
"youtube": "YouTube",
"website": "Sitio web"
}
}
</i18n>

View File

@@ -1,7 +0,0 @@
{
"title": "Choose your Banner",
"description": "The banner must have a 3:1 ratio. Target dimensions are 960 x 320.",
"chooseImage": "Choose an image...",
"clickToEdit": "Click to edit",
"preview": "Banner preview"
}

View File

@@ -1,7 +0,0 @@
{
"title": "Elige tu Banner",
"description": "El banner debe tener una relación de 3:1. Las dimensiones objetivo son 960 x 320.",
"chooseImage": "Elegir una imagen...",
"clickToEdit": "Clic para editar",
"preview": "Vista previa del banner"
}

View File

@@ -1,7 +0,0 @@
{
"title": "Choisissez votre Bannière",
"description": "La bannière doit avoir un ratio de 3:1. Les dimensions cibles sont 960 x 320.",
"chooseImage": "Choisir une image...",
"clickToEdit": "Cliquez pour modifier",
"preview": "Aperçu de la bannière"
}

View File

@@ -72,7 +72,7 @@ import {ref} from 'vue'
import {useClient} from '@/plugins/api.js'
import { Cropper } from 'vue-advanced-cropper'
import 'vue-advanced-cropper/dist/style.css'
import { useTranslations } from '@/translations/translations'
import { useI18n } from 'vue-i18n'
const props = defineProps({
creator: {
@@ -94,7 +94,7 @@ const TARGET_WIDTH = 960
const TARGET_HEIGHT = 320
// Get translations for this component
const t = useTranslations()
const { t } = useI18n()
const triggerFileInput = () => {
fileInput.value.click()
@@ -276,3 +276,20 @@ const cancel = () => {
@apply max-h-full;
}
</style>
<i18n>
{
"en": {
"chooseImage": "Choose an image",
"clickToEdit": "Click to edit"
},
"fr": {
"chooseImage": "Choisir une image",
"clickToEdit": "Cliquez pour modifier"
},
"es": {
"chooseImage": "Elegir una imagen",
"clickToEdit": "Haga clic para editar"
}
}
</i18n>

View File

@@ -1,3 +0,0 @@
{
"title": "Create your Hutopy"
}

View File

@@ -1,3 +0,0 @@
{
"title": "Crea tu Hutopy"
}

View File

@@ -1,3 +0,0 @@
{
"title": "Créez votre Hutopy"
}

View File

@@ -5,7 +5,7 @@ import {useCreatorProfileStore} from "@/stores/creatorProfileStore.js";
import {useClient} from "@/plugins/api.js";
import {useRouter, useRoute} from "vue-router";
import NameEditor from "@/views/creators/NameEditor.vue";
import { useTranslations } from '@/translations/translations'
import { useI18n } from 'vue-i18n'
const creatorName = ref('');
const creatorNameReservationId = ref(undefined);
@@ -18,7 +18,7 @@ const router = useRouter();
const route = useRoute();
const creatorProfileStore = useCreatorProfileStore();
const userProfileStore = useUserProfileStore();
const t = useTranslations();
const { t } = useI18n();
function handleCreatorNameReservationIdChanged($event) {
creatorNameReservationId.value = $event
@@ -112,4 +112,33 @@ async function createAccount() {
@apply flex items-center justify-center;
}
</style>
</style>
<i18n>
{
"en": {
"title": "Create your Hutopy",
"cancel": "Cancel",
"create": "Create Creator Page",
"errors": {
"unexpected": "An unexpected error occurred"
}
},
"fr": {
"title": "Créez votre Hutopy",
"cancel": "Annuler",
"create": "Créer la page créateur",
"errors": {
"unexpected": "Une erreur inattendue s'est produite"
}
},
"es": {
"title": "Crea tu Hutopy",
"cancel": "Cancelar",
"create": "Crear página de creador",
"errors": {
"unexpected": "Se produjo un error inesperado"
}
}
}
</i18n>

View File

@@ -1,19 +0,0 @@
{
"sections": {
"about": {
"title": "About Us",
"description": "Description",
"mainImage": "Main image"
},
"support": {
"title": "Why Support Us",
"description": "Description",
"subtitle": "Subtitle"
}
},
"fields": {
"phoneNumber": "Phone Number",
"email": "Email Address",
"videoUrl": "Video URL"
}
}

View File

@@ -1,19 +0,0 @@
{
"sections": {
"about": {
"title": "Quiénes Somos",
"description": "Descripción",
"mainImage": "Imagen principal"
},
"support": {
"title": "Por Qué Apoyarnos",
"description": "Descripción",
"subtitle": "Subtítulo"
}
},
"fields": {
"phoneNumber": "Número de Teléfono",
"email": "Dirección de Correo",
"videoUrl": "URL del Video"
}
}

View File

@@ -1,19 +0,0 @@
{
"sections": {
"about": {
"title": "Qui sommes-nous",
"description": "Description",
"mainImage": "Image principale"
},
"support": {
"title": "Pourquoi nous supporter",
"description": "Description",
"subtitle": "Sous-titre"
}
},
"fields": {
"phoneNumber": "Numéro de Téléphone",
"email": "Adresse Email",
"videoUrl": "URL Vidéo"
}
}

View File

@@ -9,7 +9,7 @@
class="primary"
@click="isEditMode ? saveChanges() : toggleEditMode()"
>
{{ isEditMode ? t('save') : t('edit') }}
{{ isEditMode ? t('common.save') : t('common.edit') }}
</button>
<button
@@ -17,14 +17,14 @@
class="secondary"
@click="cancelEdit"
>
{{ t('cancel') }}
{{ t('common.cancel') }}
</button>
</div>
<!-- MainPage -->
<div class="flex flex-col mt-4">
<h1 class="flex justify-start text-2xl font-bold text-center mb-4">{{ t('sections.about.title') }}</h1>
<h1 class="flex justify-start text-2xl font-bold text-center mb-4">{{ t('creator.sections.about.title') }}</h1>
<div>
<!-- Main image Bloc D'information-->
@@ -35,7 +35,7 @@
{{ mainImageText }}
</p>
</div>
<v-textarea v-if="isEditMode" v-model="editableMainImageText" class="w-full p-2 py-6 " :label="t('sections.about.description')"
<v-textarea v-if="isEditMode" v-model="editableMainImageText" class="w-full p-2 py-6 " :label="t('creator.sections.about.description')"
variant="outlined"></v-textarea>
<div class="flex flex-row items-center space-x-4">
@@ -44,26 +44,26 @@
<img
v-if="mainImageUrl"
:src="mainImageUrl"
:alt="t('sections.about.mainImage')"
:alt="t('creator.sections.about.mainImage')"
class="max-w-full h-auto cursor-pointer"/>
</div>
<div v-if="isEditMode" class="relative flex justify-center">
<label>
<input class="hidden" type="file" @change="updateImage('mainImageUrl', $event)"/>
<img :src="mainImageUrl || fallbackImage"
:alt="t('sections.about.mainImage')"
:alt="t('creator.sections.about.mainImage')"
class=" max-w-full h-auto cursor-pointer max-h-96"/>
</label>
<button v-if="isEditMode"
class="absolute top-10 right-2 px-2 py-1 bg-red-500 text-white hover:bg-red-600"
@click="deleteImage('mainImageUrl')">
{{ t('delete') }}
{{ t('common.delete') }}
</button>
</div>
<div class="w-1/2 flex flex-col justify-center">
<h2 v-if="videoSubtitleMain" class="text-xl font-semibold text-center">
{{ t('sections.support.title') }}
{{ t('creator.sections.support.title') }}
</h2>
<div v-if="!isEditMode">
@@ -76,7 +76,7 @@
<v-textarea
v-model="editableMainVideoText"
class="p-2 rounded-md mt-4"
:label="t('sections.support.description')"
:label="t('creator.sections.support.description')"
rows="10"
variant="outlined"
></v-textarea>
@@ -92,7 +92,7 @@
<v-text-field
v-model="editableVideoSubtitle"
class="w-full p-2"
:label="t('sections.support.subtitle')"
:label="t('creator.sections.support.subtitle')"
variant="outlined"
></v-text-field>
</div>
@@ -100,7 +100,7 @@
<v-textarea v-if="isEditMode"
v-model="editableVideoText"
class="w-full p-2"
:label="t('sections.support.description')"
:label="t('creator.sections.support.description')"
variant="outlined"
></v-textarea>
</div>
@@ -123,7 +123,7 @@
<v-text-field
v-model="editableVideoUrlMain"
class="w-full p-2 rounded-md"
:label="t('fields.videoUrl')"
:label="t('creator.fields.videoUrl')"
type="text"
variant="outlined"
/>
@@ -141,28 +141,28 @@
<!-- Première image -->
<div v-if="image1Url" class="relative w-full sm:flex-1 ">
<img :src="image1Url"
alt="Image 1"
:alt="t('creator.sections.about.image1')"
class="rounded-md max-w-full h-auto cursor-pointer"/>
</div>
<!-- Deuxième image -->
<div v-if="image2Url" class="relative w-full sm:flex-1 ">
<img :src="image2Url"
alt="Image 2"
:alt="t('creator.sections.about.image2')"
class="rounded-md max-w-full h-auto cursor-pointer"/>
</div>
<!-- Troisième image -->
<div v-if="image3Url" class="relative w-full sm:flex-1 ">
<img :src="image3Url"
alt="Image 3"
:alt="t('creator.sections.about.image3')"
class="rounded-md max-w-full h-auto cursor-pointer"/>
</div>
<!-- Quatrième image -->
<div v-if="image4Url" class="relative w-full sm:flex-1 ">
<img :src="image4Url"
alt="Image 4"
:alt="t('creator.sections.about.image4')"
class="rounded-md max-w-full h-auto cursor-pointer"/>
</div>
</div>
@@ -173,19 +173,19 @@
<div v-if="isEditMode" class="rounded-2xl">
<!--images-->
<div class=" text-2xl pa-2">Images</div>
<div class=" text-2xl pa-2">{{ t('creator.sections.about.images') }}</div>
<div class="pa-2 grid grid-cols-1 gap-4 md:grid-cols-4">
<!-- Première image -->
<div class="relative">
<label>
<input class="hidden" type="file" @change="updateImage('image1Url', $event)"/>
<img :src="image1Url || fallbackImage"
alt="Image 1"
:alt="t('creator.sections.about.image1')"
class="rounded-md max-w-full h-auto cursor-pointer"/>
</label>
<button class="absolute top-2 right-2 px-2 py-1 bg-red-500 text-white hover:bg-red-600"
@click="deleteImage('image1Url')">
X
{{ t('common.delete') }}
</button>
</div>
@@ -194,12 +194,12 @@
<label>
<input class="hidden" type="file" @change="updateImage('image2Url', $event)"/>
<img :src="image2Url || fallbackImage"
alt="Image 2"
:alt="t('creator.sections.about.image2')"
class="rounded-md max-w-full h-auto cursor-pointer"/>
</label>
<button class="absolute top-2 right-2 px-2 py-1 bg-red-500 text-white hover:bg-red-600"
@click="deleteImage('image2Url')">
X
{{ t('common.delete') }}
</button>
</div>
@@ -208,12 +208,12 @@
<label>
<input class="hidden" type="file" @change="updateImage('image3Url', $event)"/>
<img :src="image3Url || fallbackImage"
alt="Image 3"
:alt="t('creator.sections.about.image3')"
class="rounded-md max-w-full h-auto cursor-pointer"/>
</label>
<button class="absolute top-2 right-2 px-2 py-1 bg-red-500 text-white hover:bg-red-600"
@click="deleteImage('image3Url')">
X
{{ t('common.delete') }}
</button>
</div>
@@ -222,18 +222,18 @@
<label>
<input class="hidden" type="file" @change="updateImage('image4Url', $event)"/>
<img :src="image4Url || fallbackImage"
alt="Image 4"
:alt="t('creator.sections.about.image4')"
class="rounded-md max-w-full h-auto cursor-pointer"/>
</label>
<button class="absolute top-2 right-2 px-2 py-1 bg-red-500 text-white hover:bg-red-600"
@click="deleteImage('image4Url')">
X
{{ t('common.delete') }}
</button>
</div>
</div>
<!-- Description-->
<div class="text-2xl pa-2"> Description</div>
<div class="text-2xl pa-2">{{ t('creator.sections.about.description') }}</div>
</div>
<!--Edit-->
@@ -241,14 +241,14 @@
<v-text-field
v-model="editablePhoneNumber"
class="w-full p-2"
:label="t('fields.phoneNumber')"
:label="t('creator.fields.phoneNumber')"
variant="outlined"
></v-text-field>
<v-text-field
v-model="editableEmail"
class="w-full p-2"
:label="t('fields.email')"
:label="t('creator.fields.email')"
variant="outlined"
></v-text-field>
</div>
@@ -284,12 +284,12 @@ import {useClient} from "@/plugins/api.js";
import {useBrandingStore} from "@/stores/brandingStore.js";
import {useCreatorProfileStore} from "@/stores/creatorProfileStore.js";
import {useAuthStore} from "@/stores/authStore.js";
import {useTranslations} from '@/translations/translations'
import {useI18n} from 'vue-i18n';
const { t } = useI18n();
const authStore = useAuthStore();
const creatorProfileStore = useCreatorProfileStore();
const brandingStore = useBrandingStore();
const authStore = useAuthStore();
const t = useTranslations();
const client = useClient();
const isLoading = ref(true);
@@ -525,4 +525,105 @@ function cancelEdit() {
border: 0;
border-radius: 0.5rem; /* Pour les bords arrondis */
}
</style>
</style>
<i18n>
{
"en": {
"common": {
"save": "Save",
"edit": "Edit",
"cancel": "Cancel",
"delete": "Delete"
},
"creator": {
"sections": {
"about": {
"title": "About",
"description": "Description",
"mainImage": "Main image",
"image1": "Image 1",
"image2": "Image 2",
"image3": "Image 3",
"image4": "Image 4",
"images": "Images"
},
"support": {
"title": "Support",
"description": "Description",
"subtitle": "Subtitle"
}
},
"fields": {
"videoUrl": "Video URL",
"phoneNumber": "Phone Number",
"email": "Email"
}
}
},
"fr": {
"common": {
"save": "Enregistrer",
"edit": "Modifier",
"cancel": "Annuler",
"delete": "Supprimer"
},
"creator": {
"sections": {
"about": {
"title": "À propos",
"description": "Description",
"mainImage": "Image principale",
"image1": "Image 1",
"image2": "Image 2",
"image3": "Image 3",
"image4": "Image 4",
"images": "Images"
},
"support": {
"title": "Support",
"description": "Description",
"subtitle": "Sous-titre"
}
},
"fields": {
"videoUrl": "URL de la vidéo",
"phoneNumber": "Numéro de téléphone",
"email": "Email"
}
}
},
"es": {
"common": {
"save": "Guardar",
"edit": "Editar",
"cancel": "Cancelar",
"delete": "Eliminar"
},
"creator": {
"sections": {
"about": {
"title": "Acerca de",
"description": "Descripción",
"mainImage": "Imagen principal",
"image1": "Imagen 1",
"image2": "Imagen 2",
"image3": "Imagen 3",
"image4": "Imagen 4",
"images": "Imágenes"
},
"support": {
"title": "Soporte",
"description": "Descripción",
"subtitle": "Subtítulo"
}
},
"fields": {
"videoUrl": "URL del video",
"phoneNumber": "Número de teléfono",
"email": "Correo electrónico"
}
}
}
}
</i18n>

View File

@@ -1,5 +0,0 @@
{
"deletion": {
"pending": "This Creator page is pending deletion. You can revert the action in your profile."
}
}

View File

@@ -1,5 +0,0 @@
{
"deletion": {
"pending": "Esta página de creador está pendiente de eliminación. Puede revertir la acción en su perfil."
}
}

View File

@@ -1,5 +0,0 @@
{
"deletion": {
"pending": "Cette page de créateur est en attente de suppression. Vous pouvez annuler l'action dans votre profil."
}
}

View File

@@ -3,11 +3,11 @@ import {useBrandingStore} from "@/stores/brandingStore.js";
import {onMounted} from "vue";
import Banner from "@/views/creators/Banner.vue";
import Footer from "@/views/main/Footer.vue";
import { useTranslations } from '@/translations/translations'
import { useI18n } from 'vue-i18n';
const brandingStore = useBrandingStore();
const creatorName = window.location.pathname.split('/@').pop();
const t = useTranslations();
const { t } = useI18n();
onMounted(async () => {
await brandingStore.updateBrand(creatorName);
@@ -25,7 +25,7 @@ onMounted(async () => {
<div v-else>
<div v-if="brandingStore.value.isDeleted"
class="bg-red-500 p-2 m-4 text-center font-semibold">
{{ t('deletion.pending') }}
{{ t('creator.layout.deletion.pending') }}
</div>
<banner></banner>
<router-view></router-view>
@@ -35,3 +35,35 @@ onMounted(async () => {
</div>
</template>
<i18n>
{
"en": {
"creator": {
"layout": {
"deletion": {
"pending": "This creator page is pending deletion"
}
}
}
},
"fr": {
"creator": {
"layout": {
"deletion": {
"pending": "Cette page créateur est en attente de suppression"
}
}
}
},
"es": {
"creator": {
"layout": {
"deletion": {
"pending": "Esta página de creador está pendiente de eliminación"
}
}
}
}
}
</i18n>

View File

@@ -1,4 +0,0 @@
{
"alt": "Profile Picture",
"edit": "Edit Profile Picture"
}

View File

@@ -1,4 +0,0 @@
{
"alt": "Foto de perfil",
"edit": "Editar foto de perfil"
}

View File

@@ -1,4 +0,0 @@
{
"alt": "Photo de profil",
"edit": "Modifier la photo de profil"
}

View File

@@ -8,14 +8,14 @@
<img
class="shadow-2xl object-cover rounded-full border-solid border-hSecondary border-102 w-[200px] h-[200px] logo-image"
:src="brandingStore.value.images?.logo ?? '/images/placeholders/profile.png'"
:alt="t('alt')"
:alt="t('logoAlt')"
/>
<!-- Tint Effect -->
<div
v-if="showTint"
class="absolute rounded-full inset-0 bg-black/25 cursor-pointer"
:title="t('edit')"
:title="t('editLogo')"
>
<!-- Top-right Icon -->
<div
@@ -43,11 +43,11 @@ 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 { useTranslations } from '@/translations/translations'
import { useI18n } from 'vue-i18n'
const authStore = useAuthStore();
const brandingStore = useBrandingStore();
const t = useTranslations();
const { t } = useI18n();
// State
const showTint = ref(false);
@@ -69,4 +69,21 @@ const isCurrentCreator = computed(() => {
@apply border-b-4 border-t-4 border-solid border-hTertiary;
}
</style>
</style>
<i18n>
{
"en": {
"logoAlt": "Creator logo",
"editLogo": "Edit logo"
},
"fr": {
"logoAlt": "Logo du créateur",
"editLogo": "Modifier le logo"
},
"es": {
"logoAlt": "Logo del creador",
"editLogo": "Editar logo"
}
}
</i18n>

View File

@@ -1,7 +0,0 @@
{
"title": "Choose your Logo",
"description": "The logo must be square. Recommended dimensions are 200 x 200 pixels.",
"chooseImage": "Choose an image...",
"clickToEdit": "Click to edit",
"preview": "Logo preview"
}

View File

@@ -1,7 +0,0 @@
{
"title": "Elige tu Logo",
"description": "El logo debe ser cuadrado. Las dimensiones recomendadas son 200 x 200 píxeles.",
"chooseImage": "Elegir una imagen...",
"clickToEdit": "Clic para editar",
"preview": "Vista previa del logo"
}

View File

@@ -1,7 +0,0 @@
{
"title": "Choisissez votre Logo",
"description": "Le logo doit être carré. Les dimensions recommandées sont 200 x 200 pixels.",
"chooseImage": "Choisir une image...",
"clickToEdit": "Cliquez pour modifier",
"preview": "Aperçu du logo"
}

View File

@@ -1,12 +1,12 @@
<template>
<div class="card">
<div class="card-title">
{{ t('title') }}
{{ t('logoTitle') }}
</div>
<div class="card-content">
<p class="card-text">
{{ t('description') }}
{{ t('logoDescription') }}
</p>
<div class="file-input-container">
@@ -75,7 +75,7 @@ import {ref} from 'vue'
import {useClient} from '@/plugins/api.js'
import { Cropper, CircleStencil } from 'vue-advanced-cropper'
import 'vue-advanced-cropper/dist/style.css'
import { useTranslations } from '@/translations/translations'
import { useI18n } from 'vue-i18n';
const props = defineProps({
creator: {
@@ -96,8 +96,7 @@ const cropper = ref(null)
const TARGET_WIDTH = 200
const TARGET_HEIGHT = 200
// Get translations for this component
const t = useTranslations()
const { t } = useI18n();
const triggerFileInput = () => {
fileInput.value.click()
@@ -292,3 +291,26 @@ const cancel = () => {
}
</style>
<i18n>
{
"en": {
"logoTitle": "Edit Logo",
"logoDescription": "Choose a logo image for your creator page. The image will be cropped to a circle.",
"chooseImage": "Choose Image",
"clickToEdit": "Click to edit"
},
"fr": {
"logoTitle": "Modifier le logo",
"logoDescription": "Choisissez une image de logo pour votre page de créateur. L'image sera recadrée en cercle.",
"chooseImage": "Choisir une image",
"clickToEdit": "Cliquez pour modifier"
},
"es": {
"logoTitle": "Editar logo",
"logoDescription": "Elige una imagen de logo para tu página de creador. La imagen se recortará en círculo.",
"chooseImage": "Elegir imagen",
"clickToEdit": "Haz clic para editar"
}
}
</i18n>

View File

@@ -1,10 +0,0 @@
{
"isupport": "I Support",
"amount": "Amount",
"message": "Message",
"send": "Send",
"cancel": "Cancel",
"errors": {
"payment": "An error occurred during payment processing"
}
}

View File

@@ -1,10 +0,0 @@
{
"isupport": "Apoyo",
"amount": "Cantidad",
"message": "Mensaje",
"send": "Enviar",
"cancel": "Cancelar",
"errors": {
"payment": "Ocurrió un error durante el procesamiento del pago"
}
}

View File

@@ -1,10 +0,0 @@
{
"isupport": "Je Soutiens",
"amount": "Montant",
"message": "Message",
"send": "Envoyer",
"cancel": "Annuler",
"errors": {
"payment": "Une erreur s'est produite lors du traitement du paiement"
}
}

View File

@@ -3,13 +3,13 @@
class="secondary donation-action"
@click="openDonationDialog()"
>
{{ t('isupport') }}
{{ t('creator.donation.isupport') }}
</button>
<v-dialog v-model="donationModal">
<div class="card">
<div class="card-title">
{{ t('isupport') }}
{{ t('creator.donation.isupport') }}
</div>
<div class="card-content">
@@ -20,7 +20,7 @@
placeholder="0"
:min="0"
class="p-2"
:label="t('amount')"
:label="t('creator.donation.amount')"
density="comfortable"
variant="outlined"
hide-details
@@ -32,7 +32,7 @@
<v-textarea
v-model="tipMessage"
:label="t('message')"
:label="t('creator.donation.message')"
class="p-2"
density="comfortable"
variant="outlined"
@@ -45,12 +45,12 @@
<button class="secondary"
@click="closeDonationDialog()">
{{ t('cancel') }}
{{ t('common.cancel') }}
</button>
<button class="primary"
@click="goPay()">
{{ t('send') }}
{{ t('creator.donation.send') }}
</button>
</div>
@@ -71,7 +71,7 @@
class="ma-auto"
style="width: 200px"
@click="closeDialog()"
>{{ t('cancel') }}
>{{ t('common.cancel') }}
</v-btn>
</v-card-actions>
</v-card>
@@ -84,10 +84,10 @@ import {useClient} from '@/plugins/api.js';
import {useBrandingStore} from '@/stores/brandingStore.js';
import {loadStripe} from '@stripe/stripe-js';
import {onMounted, ref} from 'vue';
import { useTranslations } from '@/translations/translations'
import { useI18n } from 'vue-i18n';
const brandingStore = useBrandingStore();
const t = useTranslations();
const { t } = useI18n();
const props = defineProps({
creatorId: {default: 'missing-creator-id', required: true},
@@ -136,7 +136,7 @@ async function createCheckoutSession() {
return clientSecret.data;
} catch (error) {
console.error(error);
errorMessage.value = t('errors.payment');
errorMessage.value = t('creator.donation.errors.payment');
}
}
@@ -185,3 +185,56 @@ function preventNonNumeric(event) {
padding: 5px;
}
</style>
<i18n>
{
"en": {
"common": {
"cancel": "Cancel"
},
"creator": {
"donation": {
"isupport": "I Support",
"amount": "Amount ($)",
"message": "Message (optional)",
"send": "Send",
"errors": {
"payment": "An error occurred during payment processing"
}
}
}
},
"fr": {
"common": {
"cancel": "Annuler"
},
"creator": {
"donation": {
"isupport": "Je Soutiens",
"amount": "Montant ($)",
"message": "Message (optionnel)",
"send": "Envoyer",
"errors": {
"payment": "Une erreur s'est produite lors du traitement du paiement"
}
}
}
},
"es": {
"common": {
"cancel": "Cancelar"
},
"creator": {
"donation": {
"isupport": "Apoyo",
"amount": "Cantidad ($)",
"message": "Mensaje (opcional)",
"send": "Enviar",
"errors": {
"payment": "Ocurrió un error durante el procesamiento del pago"
}
}
}
}
}
</i18n>

View File

@@ -1,3 +0,0 @@
{
"label": "Page name"
}

View File

@@ -1,3 +0,0 @@
{
"label": "Nombre de la página"
}

View File

@@ -1,4 +0,0 @@
{
"label": "Nom de la page"
}
}

View File

@@ -2,7 +2,7 @@
import {ref, onMounted, onUnmounted} from "vue";
import {v7} from "uuid";
import {useClient} from "@/plugins/api.js";
import { useTranslations } from '@/translations/translations'
import { useI18n } from 'vue-i18n';
const props = defineProps({
name: {
@@ -19,7 +19,7 @@ const emits = defineEmits([
]);
const name = ref(props.name);
const t = useTranslations();
const { t } = useI18n();
const isOperationPending = ref(false);
const reservationState = ref(null);
@@ -112,7 +112,7 @@ onUnmounted(() => {
<template>
<v-text-field
variant="outlined"
:label="t('label')"
:label="t('creator.name.label')"
v-model="name"
outlined
@input="handleInput"
@@ -134,4 +134,30 @@ onUnmounted(() => {
<style scoped>
</style>
</style>
<i18n>
{
"en": {
"creator": {
"name": {
"label": "Page name"
}
}
},
"fr": {
"creator": {
"name": {
"label": "Nom de la page"
}
}
},
"es": {
"creator": {
"name": {
"label": "Nombre de la página"
}
}
}
}
</i18n>

View File

@@ -1,3 +0,0 @@
{
"verified": "Verified Account"
}

View File

@@ -1,3 +0,0 @@
{
"verified": "Cuenta Verificada"
}

View File

@@ -1,3 +0,0 @@
{
"verified": "Compte Vérifié"
}

View File

@@ -21,8 +21,22 @@
<script setup>
import IconAccountVerified from "@/components/icons/IconAccountVerified.vue";
import {useBrandingStore} from "@/stores/brandingStore.js";
import { useTranslations } from '@/translations/translations'
import { useI18n } from 'vue-i18n';
const brandingStore = useBrandingStore();
const t = useTranslations();
</script>
const { t } = useI18n();
</script>
<i18n>
{
"en": {
"verified": "Verified Account"
},
"fr": {
"verified": "Compte vérifié"
},
"es": {
"verified": "Cuenta verificada"
}
}
</i18n>

View File

@@ -1,10 +0,0 @@
{
"helpandcontact": "Help & Contact",
"faq": "FAQ",
"creatorguide": "Creator Guide",
"termsandconditions": "Terms & Conditions",
"contentpolicy": "Content Policy",
"about": "About",
"pricing": "Pricing",
"allRightsReserved": "All Rights Reserved"
}

View File

@@ -1,10 +0,0 @@
{
"helpandcontact": "Ayuda y Contacto",
"faq": "FAQ",
"creatorguide": "Guía del Creador",
"termsandconditions": "Términos y Condiciones",
"contentpolicy": "Política de Contenido",
"about": "Acerca de",
"pricing": "Precios",
"allRightsReserved": "Todos los Derechos Reservados"
}

View File

@@ -1,10 +0,0 @@
{
"helpandcontact": "Aide & Contact",
"faq": "FAQ",
"creatorguide": "Guide du Créateur",
"termsandconditions": "Conditions Générales",
"contentpolicy": "Politique de Contenu",
"about": "À Propos",
"pricing": "Tarifs",
"allRightsReserved": "Tous Droits Réservés"
}

View File

@@ -2,9 +2,9 @@
import Instagram from "@/views/svg/Instagram.vue";
import Facebook from "@/views/svg/Facebook.vue";
import X from "@/views/svg/X.vue";
import { useTranslations } from "@/translations/translations";
import { useI18n } from 'vue-i18n';
const t = useTranslations();
const { t } = useI18n();
</script>
<template>
@@ -26,36 +26,36 @@ const t = useTranslations();
<div class="footer-links">
<router-link to="/documents/helpandcontact"
class="link">
{{ t('helpandcontact') }}
{{ t('footer.helpandcontact') }}
</router-link>
<router-link to="/documents/faq"
class="link">
{{ t('faq') }}
{{ t('footer.faq') }}
</router-link>
<router-link to="/documents/guideforcreators"
class="link">
{{ t('creatorguide') }}
{{ t('footer.creatorguide') }}
</router-link>
<router-link to="/documents/termsandconditions"
class="link">
{{ t('termsandconditions') }}
{{ t('footer.termsandconditions') }}
</router-link>
<router-link to="/documents/contentpolicy"
class="link">
{{ t('contentpolicy') }}
{{ t('footer.contentpolicy') }}
</router-link>
<router-link to="/documents/about"
class="link">
{{ t('about') }}
{{ t('footer.about') }}
</router-link>
<router-link to="/documents/pricing"
class="link">
{{ t('pricing') }}
{{ t('footer.pricing') }}
</router-link>
</div>
<div class="footer-copyright">
Hutopy &copy;{{ new Date().getFullYear() }} - {{ t('allRightsReserved') }}
Hutopy &copy;{{ new Date().getFullYear() }} - {{ t('footer.allRightsReserved') }}
</div>
</footer>
@@ -90,4 +90,45 @@ const t = useTranslations();
@apply hover:text-gray-400;
}
</style>
</style>
<i18n>
{
"en": {
"footer": {
"helpandcontact": "Help & Contact",
"faq": "FAQ",
"creatorguide": "Creator Guide",
"termsandconditions": "Terms & Conditions",
"contentpolicy": "Content Policy",
"about": "About",
"pricing": "Pricing",
"allRightsReserved": "All Rights Reserved"
}
},
"fr": {
"footer": {
"helpandcontact": "Aide & Contact",
"faq": "FAQ",
"creatorguide": "Guide du Créateur",
"termsandconditions": "Conditions Générales",
"contentpolicy": "Politique de Contenu",
"about": "À Propos",
"pricing": "Tarifs",
"allRightsReserved": "Tous Droits Réservés"
}
},
"es": {
"footer": {
"helpandcontact": "Ayuda y Contacto",
"faq": "Preguntas Frecuentes",
"creatorguide": "Guía del Creador",
"termsandconditions": "Términos y Condiciones",
"contentpolicy": "Política de Contenido",
"about": "Acerca de",
"pricing": "Precios",
"allRightsReserved": "Todos los Derechos Reservados"
}
}
}
</i18n>

View File

@@ -1,5 +1,8 @@
<script setup>
import Footer from "@/views/main/Footer.vue";
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
</script>
<template>
@@ -17,13 +20,13 @@ import Footer from "@/views/main/Footer.vue";
<v-btn
class="text-white w-full sm:w-auto inscription-btn-header"
to="/login">
Inscription
{{ t('inscription') }}
</v-btn>
<v-btn
class="w-full sm:w-auto inscription-btn-header-outlined"
to="/create-creator"
variant="outlined">
Créer ma page
{{ t('createPage') }}
</v-btn>
</div>
@@ -32,10 +35,10 @@ import Footer from "@/views/main/Footer.vue";
<div class="support-container flex flex-col items-center space-y-4 md:flex-row md:space-y-0 md:space-x-6">
<div class="support-text text-justify md:text-left">
<span class="text-white"> Ici, vous <span class="highlight">SOUTENEZ</span> </span><br>
<span class="text-white"> les <span class="highlight2">CRÉATEURS</span>, </span><br>
<span class="text-white"> les <span class="highlight2">PROJETS</span> </span><br>
<span class="text-white"> que vous <span class="highlight">AIMEZ</span> </span>
<span class="text-white"> {{ t('support') }} </span><br>
<span class="text-white"> {{ t('creators') }} </span><br>
<span class="text-white"> {{ t('projects') }} </span><br>
<span class="text-white"> {{ t('love') }} </span>
</div>
<img alt="YourHutopy" class="w-48 h-48 md:w-48 md:h-48 object-contain"
src="/images/hutopymedia/banners/heart.png">
@@ -45,34 +48,32 @@ import Footer from "@/views/main/Footer.vue";
<div class="flex flex-col lg:flex-row justify-center items-center lg:space-x-14 space-y-6 lg:space-y-0 pa-1">
<div class="bg-hSurface p-4 max-w-md text-center rounded-3xl space-y-8 shadow-xl h-[520px]">
<div class="text-xl mb-2 box-text">Je soutiens</div>
<div class="text-xl mb-2 box-text">{{ t('supportText') }}</div>
<img
alt="YourHutopy"
class="max-h-56 mx-auto"
src="/images/hutopymedia/homepage/hands.png"
>
<div class="text-md text-justify px-6 ">
Soutenez financièrement vos créateurs préférés et participez directement à leur succès. Chaque contribution
est un geste fort pour encourager leur talent et leur passion.
{{ t('supportDescription') }}
</div>
<!-- <v-btn>Soutenir</v-btn> -->
</div>
<div class="bg-hSurface p-4 max-w-md text-center rounded-3xl space-y-8 shadow-xl h-[520px]">
<div class="text-xl mb-2 box-text">Je crée</div>
<div class="text-xl mb-2 box-text">{{ t('create') }}</div>
<img
alt="YourHutopy"
class="max-h-56 mx-auto"
src="/images/hutopymedia/homepage/brain.png"
>
<div class="text-md text-justify px-6">
Offrez à votre communauté lopportunité de vous soutenir dans vos projets et vos passions. Transformez vos
idées en réalité grâce à ceux qui croient en vous.
{{ t('creatorDescription') }}
</div>
<v-btn
class="inscription-btn"
to="/login"
>
Inscription
{{ t('signup') }}
</v-btn>
</div>
</div>
@@ -85,20 +86,19 @@ import Footer from "@/views/main/Footer.vue";
<div class="space-y-6">
<img alt="YourHutopy" class="w-full mb-6" src="/images/hutopymedia/homepage/votrehutopy.png">
<div class="space-y-4">
<p class="text-lg leading-relaxed text-justify sm:mx-5 md:mx-1 homepagetext">Hutopy, c'est quoi ?</p>
<p class="text-lg leading-relaxed text-justify sm:mx-5 md:mx-1 homepagetext">{{ t('whatIsHutopy') }}</p>
<p class="text-lg leading-relaxed text-justify sm:mx-5 md:mx-1 homepagetext">
Hutopy est une plateforme québécoise conçue pour mettre en lumière et soutenir financièrement les créateurs, les projets et les organismes dici. Que vous soyez humoriste, acteur, cuisinier, écrivain, musicien, artisan, entrepreneur social ou organisme communautaire, Hutopy vous offre un espace 100 % gratuit et accessible pour partager votre mission, bâtir votre communauté et recevoir un soutien financier direct.
{{ t('hutopyDescription') }}
</p>
<p class="text-lg leading-relaxed text-justify sm:mx-5 md:mx-1 homepagetext">
Simple, accessible et humain, Hutopy valorise le talent et le soutien, car chaque geste, aussi petit
soit-il, peut faire une grande différence dans laccomplissement de sa propre utopie.
{{ t('hutopyValues') }}
</p>
<div class="flex justify-center">
<v-btn
class="text-white mt-12 flex items-center justify-center round create-btn"
to="/create-creator"
>
Créer ma page
{{ t('createPage') }}
</v-btn>
</div>
</div>
@@ -241,4 +241,57 @@ body {
font-family: "Roboto", sans-serif;
}
</style>
</style>
<i18n>
{
"en": {
"inscription": "Sign Up",
"createPage": "Create Page",
"support": "Support",
"creators": "Creators",
"projects": "Projects",
"love": "Love",
"supportText": "Support",
"supportDescription": "Support your favorite creators and help them grow. Your contributions make a real difference in their creative journey.",
"create": "Create",
"creatorDescription": "Create your own page and start your creative journey. Share your passion with the world and build your community.",
"signup": "Sign Up",
"whatIsHutopy": "What is Hutopy?",
"hutopyDescription": "Hutopy is a platform that connects creators with their audience. We provide tools and features to help creators monetize their content and build their community.",
"hutopyValues": "Our values are centered around creativity, community, and support. We believe in empowering creators to pursue their passions and build sustainable careers."
},
"fr": {
"inscription": "S'inscrire",
"createPage": "Créer une Page",
"support": "Soutenir",
"creators": "Créateurs",
"projects": "Projets",
"love": "Passion",
"supportText": "Soutenir",
"supportDescription": "Soutenez vos créateurs préférés et aidez-les à grandir. Vos contributions font une réelle différence dans leur parcours créatif.",
"create": "Créer",
"creatorDescription": "Créez votre propre page et commencez votre parcours créatif. Partagez votre passion avec le monde et construisez votre communauté.",
"signup": "S'inscrire",
"whatIsHutopy": "Qu'est-ce que Hutopy ?",
"hutopyDescription": "Hutopy est une plateforme qui connecte les créateurs avec leur audience. Nous fournissons des outils et des fonctionnalités pour aider les créateurs à monétiser leur contenu et à construire leur communauté.",
"hutopyValues": "Nos valeurs sont centrées sur la créativité, la communauté et le soutien. Nous croyons en l'autonomisation des créateurs pour poursuivre leurs passions et construire des carrières durables."
},
"es": {
"inscription": "Registrarse",
"createPage": "Crear Página",
"support": "Apoyar",
"creators": "Creadores",
"projects": "Proyectos",
"love": "Pasión",
"supportText": "Apoyar",
"supportDescription": "Apoya a tus creadores favoritos y ayúdales a crecer. Tus contribuciones hacen una diferencia real en su viaje creativo.",
"create": "Crear",
"creatorDescription": "Crea tu propia página y comienza tu viaje creativo. Comparte tu pasión con el mundo y construye tu comunidad.",
"signup": "Registrarse",
"whatIsHutopy": "¿Qué es Hutopy?",
"hutopyDescription": "Hutopy es una plataforma que conecta a los creadores con su audiencia. Proporcionamos herramientas y funciones para ayudar a los creadores a monetizar su contenido y construir su comunidad.",
"hutopyValues": "Nuestros valores se centran en la creatividad, la comunidad y el apoyo. Creemos en empoderar a los creadores para perseguir sus pasiones y construir carreras sostenibles."
}
}
</i18n>

View File

@@ -1,7 +0,0 @@
{
"myPage": "my page",
"myProfile": "my profile",
"reduce": "collapse",
"signIn": "login",
"signOut": "sign out"
}

View File

@@ -1,7 +0,0 @@
{
"myPage": "mi página",
"myProfile": "mi perfil",
"reduce": "reducir",
"signIn": "iniciar sesión",
"signOut": "cerrar sesión"
}

View File

@@ -1,7 +0,0 @@
{
"myPage": "ma page",
"myProfile": "mon profil",
"reduce": "réduire",
"signIn": "connexion",
"signOut": "se déconnecter"
}

View File

@@ -3,11 +3,9 @@ import {useI18n} from "vue-i18n";
import {useAuthStore} from "@/stores/authStore.js";
import {useCreatorProfileStore} from "@/stores/creatorProfileStore.js";
import {useUserProfileStore} from "@/stores/userProfileStore.js";
import { useTranslations } from '@/translations/translations'
import {useLanguageStore} from "@/stores/languageStore.js";
const {locale} = useI18n();
const t = useTranslations();
const {locale, t} = useI18n();
const languageStore = useLanguageStore();
const userProfileStore = useUserProfileStore();
@@ -62,13 +60,13 @@ function toggleLanguage() {
<router-link v-if="creatorProfileStore.hasCreator" :to="`/@${creatorProfileStore.creator.slug}`">
<button class="menu-item-action">
<i class="mdi mdi-file-account-outline"></i>
<span class="label">{{ t('myPage') }}</span>
<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>
<span class="label">{{ t('myPage') }}</span>
<span class="label">{{ t('sidebar.myPage') }}</span>
</button>
</router-link>
</template>
@@ -77,7 +75,7 @@ function toggleLanguage() {
<router-link to="/profile">
<button class="menu-item-action">
<i class="mdi mdi-account"></i>
<span class="label">{{ t('myProfile') }}</span>
<span class="label">{{ t('sidebar.myProfile') }}</span>
</button>
</router-link>
</template>
@@ -92,7 +90,7 @@ function toggleLanguage() {
<router-link to="/login">
<button class="menu-item-action">
<i class="mdi mdi-login"></i>
<span class="label">{{ t('signIn') }}</span>
<span class="label">{{ t('sidebar.signIn') }}</span>
</button>
</router-link>
</template>
@@ -100,7 +98,7 @@ function toggleLanguage() {
<button class="menu-item-action"
@click="authStore.logout">
<i class="mdi mdi-logout"></i>
<span class="label">{{ t('signOut') }}</span>
<span class="label">{{ t('sidebar.signOut') }}</span>
</button>
</div>
@@ -161,4 +159,33 @@ function toggleLanguage() {
@apply w-10 h-10 justify-center lg:w-full lg:h-auto lg:justify-normal;
}
</style>
</style>
<i18n>
{
"en": {
"sidebar": {
"myPage": "My Page",
"myProfile": "My Profile",
"signIn": "Sign In",
"signOut": "Sign Out"
}
},
"fr": {
"sidebar": {
"myPage": "Ma Page",
"myProfile": "Mon Profil",
"signIn": "Se Connecter",
"signOut": "Se Déconnecter"
}
},
"es": {
"sidebar": {
"myPage": "Mi Página",
"myProfile": "Mi Perfil",
"signIn": "Iniciar Sesión",
"signOut": "Cerrar Sesión"
}
}
}
</i18n>

View File

@@ -1,23 +0,0 @@
{
"personal": {
"informations": "Personal Information",
"fullname": "Full Name",
"alias": "Alias",
"contact": "Contact Details",
"email": "Email"
},
"creator": {
"informations": "Creator Information",
"name": "Name",
"slug": "URL",
"title": "Title",
"stripeAccountId": "Stripe Account ID",
"socialnetwork": "Social Networks"
},
"danger": {
"title": "Danger Zone",
"warning": "WARNING: This will delete your creator page and suspend all tips and donations.",
"delete": "DELETE PAGE",
"restore": "RESTORE PAGE"
}
}

View File

@@ -1,23 +0,0 @@
{
"personal": {
"informations": "Información personal",
"fullname": "Nombre completo",
"alias": "Alias",
"contact": "Detalles de contacto",
"email": "Correo electrónico"
},
"creator": {
"informations": "Información del creador",
"name": "Nombre",
"slug": "URL",
"title": "Título",
"stripeAccountId": "ID de cuenta Stripe",
"socialnetwork": "Redes sociales"
},
"danger": {
"title": "Zona de peligro",
"warning": "PRECAUCIÓN: Esto eliminará tu página de creador y suspenderá todos los propinas y donaciones.",
"delete": "ELIMINAR PÁGINA",
"restore": "RESTAURAR PÁGINA"
}
}

View File

@@ -1,23 +0,0 @@
{
"personal": {
"informations": "Informations personnelles",
"fullname": "Nom complet",
"alias": "Pseudonyme",
"contact": "Coordonnées",
"email": "Email"
},
"creator": {
"informations": "Informations du créateur",
"name": "Nom",
"slug": "URL",
"title": "Titre",
"stripeAccountId": "ID du compte Stripe",
"socialnetwork": "Réseaux sociaux"
},
"danger": {
"title": "Zone de danger",
"warning": "ATTENTION : Cela supprimera votre page de créateur et suspendra tous les pourboires et dons.",
"delete": "SUPPRIMER LA PAGE",
"restore": "RESTAURER LA PAGE"
}
}

View File

@@ -18,9 +18,9 @@ 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 { useTranslations } from "@/translations/translations";
import { useI18n } from 'vue-i18n';
const t = useTranslations();
const { t } = useI18n();
const userProfileStore = useUserProfileStore()
// ### Fullname
@@ -134,14 +134,14 @@ const closeDialog = () => {
<div class="card">
<div class="card-title">
{{ t('personal.informations') }}
{{ t('personalInfo') }}
</div>
<div class="content">
<button
class="action"
@click="openEditFullname">
<span class="label">{{ t('personal.fullname') }}</span>
<span class="label">{{ t('fullName') }}</span>
<span class="value">{{ userProfileStore.fullname }}</span>
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
</button>
@@ -149,7 +149,7 @@ const closeDialog = () => {
<button
class="action"
@click="openEditAlias">
<span class="label">{{ t('personal.alias') }}</span>
<span class="label">{{ t('alias') }}</span>
<span class="value">{{ userProfileStore.user.alias }}</span>
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
</button>
@@ -160,12 +160,12 @@ const closeDialog = () => {
<div class="card">
<div class="card-title">
{{ t('personal.contact') }}
{{ t('contactInfo') }}
</div>
<div class="content">
<button class="action" @click="openDialog('EmailDialog')">
<span class="label">{{ t('personal.email') }}</span>
<span class="label">{{ t('email') }}</span>
<span class="value">{{ userProfileStore.user.email }}</span>
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
</button>
@@ -176,33 +176,33 @@ const closeDialog = () => {
<template v-if="creatorProfileStore.creator !== undefined">
<div class="card">
<div class="card-title">
{{ t('creator.informations') }}
{{ t('creatorInfo') }}
</div>
<div class="content">
<!-- NAME -->
<button class="action" @click="openDialog('ChangeNameDialog')">
<span class="label">{{ t('creator.name') }}</span>
<span class="label">{{ t('name') }}</span>
<span class="value">{{ creatorProfileStore.creator.name }}</span>
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
</button>
<button class="action" @click="openDialog('ChangeSlugDialog')">
<span class="label">{{ t('creator.slug') }}</span>
<span class="label">{{ t('username') }}</span>
<span class="value">@{{ creatorProfileStore.creator.slug }}</span>
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
</button>
<!-- TITLE -->
<button class="action" @click="openDialog('ChangeTitleDialog')">
<span class="label">{{ t('creator.title') }}</span>
<span class="label">{{ t('title') }}</span>
<span class="value">{{ creatorProfileStore.creator.title }}</span>
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
</button>
<!-- STRIPE -->
<button class="action" @click="openDialog('ChangeStripeIdDialog')">
<span class="label">{{ t('creator.stripeAccountId') }}</span>
<span class="label">{{ t('stripeAccountId') }}</span>
<span class="value">{{ creatorProfileStore.creator.stripeId }}</span>
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
</button>
@@ -212,7 +212,7 @@ const closeDialog = () => {
<div class="card">
<div class="card-title">
{{ t('creator.socialnetwork') }}
{{ t('socialNetworks') }}
</div>
<div>
@@ -248,7 +248,7 @@ const closeDialog = () => {
<span class="value">{{ creatorProfileStore.creator.socials?.linkedInUrl }}</span>
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
</button>
<button class="action" @click="openDialog('SocialsDialog')">
<span class="label">
<tiktok class="social-icon"></tiktok>
@@ -285,20 +285,20 @@ const closeDialog = () => {
</div>
<div class="card">
<div class="card-title">{{ t('danger.title') }}</div>
<div class="card-title">{{ t('dangerZone') }}</div>
<div class="content">
<p>
{{ t('danger.warning') }}
{{ t('deleteWarning') }}
</p>
<button v-if="!creatorProfileStore.creator.isDeleted"
class="danger-action"
@click="creatorProfileStore.removeCreatorPage()">
{{ t('danger.delete') }}
{{ t('deleteCreatorPage') }}
</button>
<button v-else
class="safe-action"
@click="creatorProfileStore.restoreCreatorPage()">
{{ t('danger.restore') }}
{{ t('restoreCreatorPage') }}
</button>
</div>
</div>
@@ -309,7 +309,6 @@ const closeDialog = () => {
</template>
<style scoped>
.card {
@apply bg-hBackground rounded-lg p-4 w-full max-w-2xl;
}
@@ -355,4 +354,42 @@ const closeDialog = () => {
@apply mt-4;
@apply bg-green-800 hover:bg-green-700 active:bg-green-600;
}
</style>
</style>
<i18n>
{
"en": {
"personalInfo": "Personal Information",
"fullName": "Full Name",
"alias": "Alias",
"contactInfo": "Contact Information",
"email": "Email",
"creatorInfo": "Creator Information",
"dangerZone": "Danger Zone",
"deleteWarning": "Are you sure you want to delete your creator page? This action cannot be undone.",
"restoreWarning": "Are you sure you want to restore your creator page? This will make your page visible again."
},
"fr": {
"personalInfo": "Informations Personnelles",
"fullName": "Nom Complet",
"alias": "Alias",
"contactInfo": "Informations de Contact",
"email": "Email",
"creatorInfo": "Informations du Créateur",
"dangerZone": "Zone de Danger",
"deleteWarning": "Êtes-vous sûr de vouloir supprimer votre page de créateur ? Cette action est irréversible.",
"restoreWarning": "Êtes-vous sûr de vouloir restaurer votre page de créateur ? Cela rendra votre page à nouveau visible."
},
"es": {
"personalInfo": "Información Personal",
"fullName": "Nombre Completo",
"alias": "Alias",
"contactInfo": "Información de Contacto",
"email": "Correo Electrónico",
"creatorInfo": "Información del Creador",
"dangerZone": "Zona de Peligro",
"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."
}
}
</i18n>

View File

@@ -1,4 +0,0 @@
{
"title": "Address",
"label": "Your address"
}

View File

@@ -1,4 +0,0 @@
{
"title": "Dirección",
"label": "Tu dirección"
}

View File

@@ -1,4 +0,0 @@
{
"title": "Adresse",
"label": "Votre adresse"
}

View File

@@ -25,9 +25,9 @@
<script setup>
import {ref} from 'vue';
import { useTranslations } from "@/translations/translations";
import { useI18n } from 'vue-i18n';
const t = useTranslations();
const { t } = useI18n();
const props = defineProps(['address'])
const emit = defineEmits(['close', 'save'])
@@ -36,3 +36,20 @@ const address = ref(props.address);
const requestClose = () => emit('close')
const requestSave = () => emit('save', address.value)
</script>
<i18n>
{
"en": {
"title": "Address",
"label": "Your address"
},
"fr": {
"title": "Adresse",
"label": "Votre adresse"
},
"es": {
"title": "Dirección",
"label": "Tu dirección"
}
}
</i18n>

View File

@@ -1,4 +0,0 @@
{
"title": "Alias",
"label": "Your alias"
}

View File

@@ -1,4 +0,0 @@
{
"title": "Alias",
"label": "Tu alias"
}

View File

@@ -1,4 +0,0 @@
{
"title": "Pseudonyme",
"label": "Votre pseudonyme"
}

View File

@@ -34,9 +34,9 @@
<script setup>
import {ref} from 'vue';
import { useTranslations } from "@/translations/translations";
import { useI18n } from 'vue-i18n';
const t = useTranslations();
const { t } = useI18n();
const props = defineProps(['alias'])
const emit = defineEmits(['close', 'save'])
@@ -46,4 +46,21 @@ 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"
}
}
</i18n>

View File

@@ -1,4 +0,0 @@
{
"title": "Birthday",
"label": "Your birthday"
}

View File

@@ -1,4 +0,0 @@
{
"title": "Fecha de nacimiento",
"label": "Tu fecha de nacimiento"
}

View File

@@ -1,4 +0,0 @@
{
"title": "Date de naissance",
"label": "Votre date de naissance"
}

View File

@@ -25,9 +25,9 @@
<script setup>
import {ref} from 'vue';
import { useTranslations } from "@/translations/translations";
import {useI18n} from 'vue-i18n';
const t = useTranslations();
const {t} = useI18n();
const props = defineProps(['birthDate'])
const emit = defineEmits(['close', 'save'])
@@ -36,3 +36,20 @@ const birthDate = ref(props.birthDate)
const requestClose = () => emit('close')
const requestSave = () => emit('save', birthDate.value)
</script>
<i18n>
{
"en": {
"title": "Birthday",
"label": "Your birthday"
},
"fr": {
"title": "Date de naissance",
"label": "Votre date de naissance"
},
"es": {
"title": "Fecha de nacimiento",
"label": "Tu fecha de nacimiento"
}
}
</i18n>

View File

@@ -1,4 +0,0 @@
{
"title": "Email",
"label": "Your email"
}

Some files were not shown because too many files have changed in this diff Show More