Files
social-media/frontend/src/views/profile/ProfilePage.vue

583 lines
19 KiB
Vue

<script setup>
import {ref, markRaw} from 'vue';
import {useCreatorProfileStore} from '@/stores/creatorProfileStore.js';
import {useUserProfileStore} from "@/stores/userProfileStore.js";
import SocialsDialog from './creators/SocialsDialog.vue';
import AliasDialog from "@/views/profile/account/AliasDialog.vue";
import FullnameDialog from "@/views/profile/account/FullnameDialog.vue";
import EmailDialog from "@/views/profile/account/EmailDialog.vue";
import ChangeStripeIdDialog from '@/views/profile/creators/ChangeStripeIdDialog.vue';
import ChangeNameDialog from '@/views/profile/creators/ChangeNameDialog.vue';
import ChangeSlugDialog from '@/views/profile/creators/ChangeSlugDialog.vue';
import ChangeTitleDialog from '@/views/profile/creators/ChangeTitleDialog.vue';
import Youtube from "@/views/svg/Youtube.vue";
import Web from "@/views/svg/Web.vue";
import Reddit from "@/views/svg/Reddit.vue";
import X from "@/views/svg/X.vue";
import Linkedin from "@/views/svg/Linkedin.vue";
import Tiktok from "@/views/svg/Tiktok.vue";
import Instagram from "@/views/svg/Instagram.vue";
import Facebook from "@/views/svg/Facebook.vue";
import {useI18n} from 'vue-i18n';
import QRCodeVue from 'qrcode.vue';
const {t} = useI18n();
const userProfileStore = useUserProfileStore()
const creatorProfileStore = useCreatorProfileStore();
const baseURL = window.location.origin;
// ### Fullname
const dialogEditFullnameShown = ref(false)
function openEditFullname() {
dialogEditFullnameShown.value = true
}
function handleCloseEditFullname() {
dialogEditFullnameShown.value = false
}
function handleSaveEditFullname(firstname, lastname) {
userProfileStore.changeFullname(firstname, lastname)
dialogEditFullnameShown.value = false
}
// ### Alias
const dialogEditAliasShown = ref(false)
function openEditAlias() {
dialogEditAliasShown.value = true
}
function handleCloseEditAlias() {
dialogEditAliasShown.value = false
}
function handleSaveEditAlias(alias) {
userProfileStore.changeAlias(alias)
dialogEditAliasShown.value = false
}
// ### Email
const dialogEditEmailShown = ref(false)
function openEditEmail() {
dialogEditEmailShown.value = true
}
function handleCloseEditEmail() {
dialogEditEmailShown.value = false
}
function handleSaveEditEmail(firstname, lastname) {
userProfileStore.changeEmail(firstname, lastname)
dialogEditEmailShown.value = false
}
const dialogShown = ref(false);
const currentComponent = ref('');
const restoreDialogShown = ref(false);
const deleteDialogShown = ref(false);
const componentsMap = {
EmailDialog: markRaw(EmailDialog),
SocialsDialog: markRaw(SocialsDialog),
ChangeSlugDialog: markRaw(ChangeSlugDialog),
ChangeNameDialog: markRaw(ChangeNameDialog),
ChangeTitleDialog: markRaw(ChangeTitleDialog),
ChangeStripeIdDialog: markRaw(ChangeStripeIdDialog),
};
function requestCancel() {
currentComponent.value = null;
dialogShown.value = false;
}
const openDialog = (component) => {
currentComponent.value = componentsMap[component];
dialogShown.value = true;
};
const closeDialog = () => {
currentComponent.value = null;
dialogShown.value = false;
};
function handleRestore() {
creatorProfileStore.restoreCreatorPage();
restoreDialogShown.value = false;
}
function handleDelete() {
creatorProfileStore.removeCreatorPage();
deleteDialogShown.value = false;
}
function downloadQRCode() {
try {
// Get the SVG element
const svgElement = document.querySelector('.qr-code svg') ||
document.querySelector('.qr-container svg') ||
document.querySelector('svg[class*="qr"]');
if (!svgElement) {
console.error('QR code SVG element not found');
return;
}
// Create a Blob from the SVG
const svgData = new XMLSerializer().serializeToString(svgElement);
const svgBlob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
const svgUrl = URL.createObjectURL(svgBlob);
// Create an image element
const img = new Image();
img.onload = () => {
// Set padding
const padding = 20; // 20 pixels padding on each side
// Create canvas with padding
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Set canvas size to include padding
canvas.width = img.width + (padding * 2);
canvas.height = img.height + (padding * 2);
// Fill white background for entire canvas (including padding)
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw the image with padding offset
ctx.drawImage(img, padding, padding);
// Convert to PNG
const pngUrl = canvas.toDataURL('image/png');
// Create download link
const downloadLink = document.createElement('a');
downloadLink.href = pngUrl;
downloadLink.download = `hutopy-qr-${creatorProfileStore.creator.slug}.png`;
// Trigger download
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
// Cleanup
URL.revokeObjectURL(svgUrl);
};
img.onerror = (error) => {
console.error('Error loading SVG:', error);
};
// Load the SVG into the image
img.src = svgUrl;
} catch (error) {
console.error('Error in downloadQRCode:', error);
}
}
</script>
<template>
<div class="min-h-screen w-full">
<div class="flex flex-col items-center gap-4 m-4">
<div class="card">
<div class="card-title">
{{ t('personalInfo') }}
</div>
<div class="content">
<button
class="action"
@click="openEditFullname">
<span class="label">{{ t('fullName') }}</span>
<span class="value">{{ userProfileStore.fullname }}</span>
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
</button>
<button
class="action"
@click="openEditAlias">
<span class="label">{{ t('alias') }}</span>
<span class="value">{{ userProfileStore.user.alias }}</span>
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
</button>
</div>
</div>
<div class="card">
<div class="card-title">
{{ t('contactInfo') }}
</div>
<div class="content">
<button class="action" @click="openDialog('EmailDialog')">
<span class="label">{{ t('email') }}</span>
<span class="value">{{ userProfileStore.user.email }}</span>
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
</button>
</div>
</div>
<template v-if="creatorProfileStore.creator !== undefined">
<div class="card">
<div class="card-title">
{{ t('creatorInfo') }}
</div>
<div class="content">
<button class="action" @click="openDialog('ChangeSlugDialog')">
<span class="label">{{ t('handle') }}</span>
<span class="value">@{{ creatorProfileStore.creator.slug }}</span>
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
</button>
<!-- NAME -->
<button class="action" @click="openDialog('ChangeNameDialog')">
<span class="label">{{ t('name') }}</span>
<span class="value">{{ creatorProfileStore.creator.name }}</span>
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
</button>
<!-- TITLE -->
<button class="action" @click="openDialog('ChangeTitleDialog')">
<span class="label">{{ t('title') }}</span>
<span class="value">{{ creatorProfileStore.creator.title }}</span>
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
</button>
<!-- STRIPE -->
<button class="action" @click="openDialog('ChangeStripeIdDialog')">
<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>
</div>
</div>
<div class="card">
<div class="card-title">
{{ t('socialNetworks') }}
</div>
<div class="content">
<button class="action" @click="openDialog('SocialsDialog')">
<span class="label">
<facebook class="social-icon"></facebook>
</span>
<span class="value">{{ creatorProfileStore.creator.socials?.facebookUrl }}</span>
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
</button>
<button class="action" @click="openDialog('SocialsDialog')">
<span class="label">
<instagram class="social-icon"></instagram>
</span>
<span class="value">{{ creatorProfileStore.creator.socials?.instagramUrl }}</span>
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
</button>
<button class="action" @click="openDialog('SocialsDialog')">
<span class="label">
<x class="social-icon"></x>
</span>
<span class="value">{{ creatorProfileStore.creator.socials?.xUrl }}</span>
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
</button>
<button class="action" @click="openDialog('SocialsDialog')">
<span class="label">
<linkedin class="social-icon"></linkedin>
</span>
<span class="value">{{ creatorProfileStore.creator.socials?.linkedInUrl }}</span>
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
</button>
<button class="action" @click="openDialog('SocialsDialog')">
<span class="label">
<tiktok class="social-icon"></tiktok>
</span>
<span class="value">{{ creatorProfileStore.creator.socials?.tikTokUrl }}</span>
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
</button>
<button class="action" @click="openDialog('SocialsDialog')">
<span class="label">
<youtube class="social-icon"></youtube>
</span>
<span class="value">{{ creatorProfileStore.creator.socials?.youtubeUrl }}</span>
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
</button>
<button class="action" @click="openDialog('SocialsDialog')">
<span class="label">
<reddit class="social-icon"></reddit>
</span>
<span class="value">{{ creatorProfileStore.creator.socials?.redditUrl }}</span>
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
</button>
<button class="action" @click="openDialog('SocialsDialog')">
<span class="label">
<web class="social-icon"></web>
</span>
<span class="value">{{ creatorProfileStore.creator.socials?.websiteUrl }}</span>
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
</button>
</div>
</div>
<div class="card">
<div class="card-title">
{{ t('qrCode') }}
</div>
<div class="content">
<div class="qr-container">
<p class="qr-text">{{ t('qrCodeDescription') }}</p>
<QRCodeVue
v-if="creatorProfileStore.creator"
:value="baseURL + '/@' + creatorProfileStore.creator.slug"
:size="200"
level="H"
render-as="svg"
class="qr-code"
/>
<button
v-if="creatorProfileStore.creator"
class="download-button"
@click="downloadQRCode"
>
<v-icon>mdi-download</v-icon>
{{ t('downloadQRCode') }}
</button>
</div>
</div>
</div>
<div class="card">
<div class="card-title">
{{ t('dangerZone') }}
</div>
<div class="content">
<span class="p-2">
{{ t('dangerZoneWarning') }}
</span>
<div class="p-2">
<button v-if="!creatorProfileStore.creator.isDeleted"
class="primary danger-action"
@click="deleteDialogShown = true">
{{ t('deleteCreatorPage') }}
</button>
<button v-else
class="primary safe-action"
@click="restoreDialogShown = true">
{{ t('restoreCreatorPage') }}
</button>
</div>
</div>
</div>
</template>
</div>
</div>
<v-dialog v-model="dialogEditFullnameShown">
<FullnameDialog
:firstname="userProfileStore.user.firstname"
:lastname="userProfileStore.user.lastname"
@close="handleCloseEditFullname"
@save="handleSaveEditFullname"
/>
</v-dialog>
<v-dialog v-model="dialogEditAliasShown">
<alias-dialog
:alias="userProfileStore.user.alias"
@close="handleCloseEditAlias"
@save="handleSaveEditAlias"
></alias-dialog>
</v-dialog>
<v-dialog v-model="dialogShown">
<component
:is="currentComponent"
:creator="creatorProfileStore.creator"
:email="userProfileStore.user.email"
@closeRequested="closeDialog"
></component>
</v-dialog>
<v-dialog v-model="restoreDialogShown">
<div class="card dialog">
<div class="card-title">{{ t('restoreCreatorPage') }}</div>
<div class="card-content">
<p>{{ t('restoreWarning') }}</p>
<div class="card-actions">
<button class="secondary" @click="restoreDialogShown = false">
{{ t('cancel') }}
</button>
<button class="primary" @click="handleRestore">
{{ t('accept') }}
</button>
</div>
</div>
</div>
</v-dialog>
<v-dialog v-model="deleteDialogShown">
<div class="card dialog">
<div class="card-title">{{ t('deleteCreatorPage') }}</div>
<div class="card-content">
<p>{{ t('deleteWarning') }}</p>
<div class="card-actions">
<button class="secondary" @click="deleteDialogShown = false">
{{ t('cancel') }}
</button>
<button class="primary danger-action" @click="handleDelete">
{{ t('accept') }}
</button>
</div>
</div>
</div>
</v-dialog>
</template>
<style scoped>
.card {
@apply rounded-lg p-4 w-full;
}
.card-title {
@apply text-hOnBackground text-lg font-bold;
}
.content {
@apply flex flex-col gap-2;
}
.action {
@apply flex flex-row items-center w-full p-3 rounded-lg;
@apply hover:bg-hSurface;
@apply transition-colors duration-500;
}
.label {
@apply text-hOnBackground w-[200px] text-left;
@apply flex items-center justify-start;
}
.value {
@apply text-hOnBackground flex-1 text-center;
@apply flex items-center justify-center;
}
.chevron {
@apply text-hOnBackground w-[40px] text-right;
@apply flex items-center justify-end;
}
.social-icon {
@apply fill-current w-6 h-6;
@apply text-hOnBackground;
@apply mr-2;
}
.danger-action {
@apply bg-red-800 hover:bg-red-700 active:bg-red-600;
}
.safe-action {
@apply bg-green-800 hover:bg-green-700 active:bg-green-600;
}
.qr-container {
@apply flex flex-col items-center gap-4 p-4;
}
.qr-code {
@apply bg-white p-4 rounded-lg;
}
.qr-text {
@apply text-hOnBackground text-center;
}
.download-button {
@apply flex items-center gap-2 px-4 py-2 rounded-lg;
@apply bg-hutopyPrimary text-hOnPrimary;
@apply hover:bg-hutopySecondary;
@apply transition-colors duration-300;
}
</style>
<i18n>
{
"en": {
"personalInfo": "Personal Information",
"fullName": "Full Name",
"alias": "Alias",
"contactInfo": "Contact Information",
"email": "Email",
"creatorInfo": "Creator Information",
"dangerZone": "Danger Zone",
"dangerZoneWarning": "The actions below can have significant impacts on your creator page. Please proceed with caution.",
"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.",
"deleteCreatorPage": "Delete Creator Page",
"restoreCreatorPage": "Restore Creator Page",
"stripeAccountId": "Stripe Account ID",
"socialNetworks": "Social Networks",
"handle": "Creator Handle",
"qrCode": "QR Code",
"qrCodeDescription": "Print this QR code to share your Hutopy with the world! Perfect for business cards, social media, and promotional materials.",
"downloadQRCode": "Download QR Code"
},
"fr": {
"personalInfo": "Informations Personnelles",
"fullName": "Nom Complet",
"alias": "Alias",
"contactInfo": "Informations de Contact",
"email": "Email",
"creatorInfo": "Informations du Créateur",
"dangerZone": "Zone de Danger",
"dangerZoneWarning": "Les actions ci-dessous peuvent avoir des impacts significatifs sur votre page de créateur. Veuillez procéder avec précaution.",
"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.",
"deleteCreatorPage": "Supprimer la Page Créateur",
"restoreCreatorPage": "Restaurer la Page Créateur",
"stripeAccountId": "ID de Compte Stripe",
"socialNetworks": "Réseaux Sociaux",
"handle": "Identifiant du créateur",
"qrCode": "Code QR",
"qrCodeDescription": "Imprimez ce code QR pour partager votre Hutopy avec le monde ! Parfait pour les cartes de visite, les réseaux sociaux et les supports promotionnels.",
"downloadQRCode": "Télécharger le Code QR"
},
"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",
"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"
}
}
</i18n>