954 lines
36 KiB
Vue
954 lines
36 KiB
Vue
<script setup>
|
||
import { computed, markRaw, onMounted, ref } from 'vue';
|
||
import { useRoute, useRouter } from 'vue-router';
|
||
import { useCreatorProfileStore } from '@/stores/creatorProfileStore.js';
|
||
import { useUserProfileStore } from '@/stores/userProfileStore.js';
|
||
import { useClient } from '@/plugins/api.js';
|
||
import SocialsDialog from './creators/SocialsDialog.vue';
|
||
import AliasDialog from '@/views/profile/account/AliasDialog.vue';
|
||
import FullnameDialog from '@/views/profile/account/FullnameDialog.vue';
|
||
import EmailDialog from '@/views/profile/account/EmailDialog.vue';
|
||
import ChangePasswordDialog from '@/views/profile/account/ChangePasswordDialog.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 ChangePhoneDialog from '@/views/profile/creators/ChangePhoneDialog.vue';
|
||
import ChangeEmailDialog from '@/views/profile/creators/ChangeEmailDialog.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';
|
||
import { mdiCheck, mdiChevronRight, mdiContentCopy, mdiCreditCard, mdiCreditCardOff, mdiDownload } from '@mdi/js';
|
||
import { useToast } from 'vue-toastification';
|
||
import hutopyLogo from '@/assets/hutopy-icon-white-circle.png';
|
||
|
||
const { t } = useI18n();
|
||
const userProfileStore = useUserProfileStore();
|
||
const creatorProfileStore = useCreatorProfileStore();
|
||
const baseURL = window.location.origin;
|
||
const client = useClient();
|
||
|
||
const route = useRoute();
|
||
const router = useRouter();
|
||
const toast = useToast();
|
||
|
||
// Copy URL state
|
||
const copySuccess = ref(false);
|
||
const copyButtonRef = ref(null);
|
||
|
||
// Computed properties for Stripe status
|
||
const stripeReady = computed(() => {
|
||
return stripeStatus.value === 'fully_configured';
|
||
});
|
||
|
||
const stripeStatus = computed(() => {
|
||
console.log('stripeStatus');
|
||
const creator = creatorProfileStore.creator;
|
||
|
||
if (!creator.isStripeAccountPresent) {
|
||
return 'not_configured';
|
||
}
|
||
|
||
if (!creator.isStripeDetailsSubmitted) {
|
||
return 'needs_more_info';
|
||
}
|
||
|
||
if (!creator.isStripeChargesEnabled || !creator.isStripePayoutReady) {
|
||
return 'pending_verification';
|
||
}
|
||
|
||
return 'fully_configured';
|
||
});
|
||
|
||
const stripeStatusText = computed(() => {
|
||
switch (stripeStatus.value) {
|
||
case 'not_configured':
|
||
return t('notConfigured');
|
||
case 'needs_more_info':
|
||
return t('needsMoreInfo');
|
||
case 'pending_verification':
|
||
return t('pendingVerification');
|
||
default:
|
||
return t('configured');
|
||
}
|
||
});
|
||
|
||
const stripeButtonText = computed(() => {
|
||
switch (stripeStatus.value) {
|
||
case 'not_configured':
|
||
return t('configureStripe');
|
||
case 'needs_more_info':
|
||
case 'pending_verification':
|
||
return t('continueStripeSetup');
|
||
case 'fully_configured':
|
||
return t('removeStripe');
|
||
default:
|
||
return t('removeStripe');
|
||
}
|
||
});
|
||
|
||
const imageSettings = ref({
|
||
src: hutopyLogo,
|
||
x: undefined,
|
||
y: undefined,
|
||
width: 64,
|
||
height: 64,
|
||
excavate: false,
|
||
});
|
||
|
||
async function checkStripeAccountStatus() {
|
||
try {
|
||
const response = await client.post('/api/stripe/check-status');
|
||
if (response.data && response.data.stripeAccount) {
|
||
creatorProfileStore.creator.isStripeAccountPresent = response.data.isStripeAccountPresent;
|
||
creatorProfileStore.creator.isStripeDetailsSubmitted = response.data.isStripeDetailsSubmitted;
|
||
creatorProfileStore.creator.isStripePayoutReady = response.data.isStripePayoutReady;
|
||
creatorProfileStore.creator.isStripeChargesEnabled = response.data.isStripeChargesEnabled;
|
||
toast.success('Your Stripe account is connected and ready for payouts.');
|
||
} else {
|
||
toast.success('Your Stripe account is connected.');
|
||
}
|
||
} catch (error) {
|
||
toast.error('We couldn’t verify your Stripe connection. Please try again.');
|
||
}
|
||
}
|
||
|
||
onMounted(async () => {
|
||
const { stripe } = route.query;
|
||
|
||
if (stripe === 'complete') {
|
||
await checkStripeAccountStatus();
|
||
}
|
||
|
||
if (stripe === 'refresh') {
|
||
toast.warning('You didn’t finish connecting your Stripe account. Please try again.');
|
||
}
|
||
|
||
if (stripe) {
|
||
await router.replace({ query: { ...route.query, stripe: undefined } });
|
||
}
|
||
});
|
||
|
||
async function copyCreatorUrl() {
|
||
try {
|
||
const url = `${baseURL}/@${creatorProfileStore.creator.slug}`;
|
||
await navigator.clipboard.writeText(url);
|
||
copySuccess.value = true;
|
||
setTimeout(() => {
|
||
copySuccess.value = false;
|
||
}, 2000);
|
||
} catch (err) {
|
||
console.error('Failed to copy:', err);
|
||
}
|
||
}
|
||
|
||
// ### 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;
|
||
}
|
||
|
||
const dialogShown = ref(false);
|
||
const currentComponent = ref('');
|
||
const restoreDialogShown = ref(false);
|
||
const deleteDialogShown = ref(false);
|
||
|
||
const componentsMap = {
|
||
EmailDialog: markRaw(EmailDialog),
|
||
ChangePasswordDialog: markRaw(ChangePasswordDialog),
|
||
SocialsDialog: markRaw(SocialsDialog),
|
||
ChangeSlugDialog: markRaw(ChangeSlugDialog),
|
||
ChangeNameDialog: markRaw(ChangeNameDialog),
|
||
ChangeTitleDialog: markRaw(ChangeTitleDialog),
|
||
ChangeStripeIdDialog: markRaw(ChangeStripeIdDialog),
|
||
ChangePhoneDialog: markRaw(ChangePhoneDialog),
|
||
ChangeEmailDialog: markRaw(ChangeEmailDialog),
|
||
};
|
||
|
||
const stripeButtonBusy = ref(false);
|
||
|
||
async function connectStripe() {
|
||
try {
|
||
stripeButtonBusy.value = true;
|
||
const res = await client.post('/api/stripe/connect');
|
||
window.location.href = res.data.url;
|
||
} catch (error) {
|
||
toast.error('We couldn’t connect your Stripe account. Please try again.');
|
||
stripeButtonBusy.value = false;
|
||
}
|
||
}
|
||
|
||
async function removeStripe() {
|
||
try {
|
||
stripeButtonBusy.value = true;
|
||
await client.delete('/api/stripe');
|
||
creatorProfileStore.creator.isStripeAccountPresent = false;
|
||
creatorProfileStore.creator.isStripeDetailsSubmitted = false;
|
||
creatorProfileStore.creator.isStripePayoutReady = false;
|
||
creatorProfileStore.creator.isStripeChargesEnabled = false;
|
||
} catch (error) {
|
||
toast.error('We couldn’t connect your Stripe account. Please try again.');
|
||
} finally {
|
||
stripeButtonBusy.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 more specifically
|
||
const qrContainer = document.querySelector('.qr-code');
|
||
const canvasElement = qrContainer?.querySelector('canvas');
|
||
|
||
if (!canvasElement) {
|
||
console.error('QR code canvas element not found');
|
||
return;
|
||
}
|
||
|
||
const padding = 20;
|
||
const newCanvas = document.createElement('canvas');
|
||
const ctx = newCanvas.getContext('2d');
|
||
|
||
// Set canvas size to include padding
|
||
newCanvas.width = canvasElement.width + padding * 2;
|
||
newCanvas.height = canvasElement.height + padding * 2;
|
||
|
||
// Fill white background
|
||
ctx.fillStyle = 'white';
|
||
ctx.fillRect(0, 0, newCanvas.width, newCanvas.height);
|
||
|
||
// Draw the original QR code canvas with padding
|
||
ctx.drawImage(canvasElement, padding, padding);
|
||
|
||
// Convert to PNG and download
|
||
const pngUrl = newCanvas.toDataURL('image/png');
|
||
const downloadLink = document.createElement('a');
|
||
downloadLink.href = pngUrl;
|
||
downloadLink.download = `hutopy-qr-${creatorProfileStore.creator.slug}.png`;
|
||
|
||
document.body.appendChild(downloadLink);
|
||
downloadLink.click();
|
||
document.body.removeChild(downloadLink);
|
||
} catch (error) {
|
||
console.error('Error in downloadQRCode:', error);
|
||
}
|
||
}
|
||
|
||
async function deconfigureStripe() {
|
||
try {
|
||
await client.post(`/api/membership/stripe-account`, {
|
||
stripeAccountId: '',
|
||
});
|
||
await creatorProfileStore.fetchCreatorProfile();
|
||
} catch (error) {
|
||
console.error('Error deconfiguring stripe:', error);
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<div class="min-h-screen w-full">
|
||
<div class="m-4 flex flex-col items-center gap-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 :icon="mdiChevronRight" />
|
||
</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 :icon="mdiChevronRight" />
|
||
</span>
|
||
</button>
|
||
</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 :icon="mdiChevronRight" />
|
||
</span>
|
||
</button>
|
||
</div>
|
||
|
||
<div class="content">
|
||
<button
|
||
class="action"
|
||
@click="openDialog('ChangePasswordDialog')"
|
||
>
|
||
<span class="label">{{ t('changePassword') }}</span>
|
||
<span class="value">••••••••</span>
|
||
<span class="chevron">
|
||
<v-icon :icon="mdiChevronRight" />
|
||
</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<template v-if="creatorProfileStore.hasCreator">
|
||
<div class="card">
|
||
<div class="card-title">
|
||
{{ t('creatorInfo') }}
|
||
</div>
|
||
<div class="content">
|
||
<div
|
||
class="action"
|
||
@click="openDialog('ChangeSlugDialog')"
|
||
>
|
||
<span class="label">{{ t('handle') }}</span>
|
||
<span class="value">{{ baseURL }}/@{{ creatorProfileStore.creator.slug }}</span>
|
||
<button
|
||
ref="copyButtonRef"
|
||
:class="{ success: copySuccess }"
|
||
class="copy-button"
|
||
@click.stop="copyCreatorUrl"
|
||
>
|
||
<v-icon :icon="copySuccess ? mdiCheck : mdiContentCopy" />
|
||
</button>
|
||
<span class="chevron">
|
||
<v-icon :icon="mdiChevronRight" />
|
||
</span>
|
||
</div>
|
||
|
||
<!-- NAME -->
|
||
<button
|
||
class="action"
|
||
@click="openDialog('ChangeNameDialog')"
|
||
>
|
||
<span class="label">{{ t('name') }}</span>
|
||
<span class="value">{{ creatorProfileStore.creator.name }}</span>
|
||
<span class="chevron">
|
||
<v-icon :icon="mdiChevronRight" />
|
||
</span>
|
||
</button>
|
||
|
||
<!-- TITLE -->
|
||
<button
|
||
class="action"
|
||
@click="openDialog('ChangeTitleDialog')"
|
||
>
|
||
<span class="label">{{ t('title') }}</span>
|
||
<span class="value">{{ creatorProfileStore.creator.title }}</span>
|
||
<span class="chevron">
|
||
<v-icon :icon="mdiChevronRight" />
|
||
</span>
|
||
</button>
|
||
|
||
<!-- PHONE NUMBER -->
|
||
<button
|
||
class="action"
|
||
@click="openDialog('ChangePhoneDialog')"
|
||
>
|
||
<span class="label">{{ t('phoneNumber') }}</span>
|
||
<span
|
||
:class="{ 'not-set': !creatorProfileStore.creator.presentation?.phoneNumber }"
|
||
class="value"
|
||
>
|
||
{{ creatorProfileStore.creator.presentation?.phoneNumber || t('notSet') }}
|
||
</span>
|
||
<span class="chevron">
|
||
<v-icon :icon="mdiChevronRight" />
|
||
</span>
|
||
</button>
|
||
|
||
<!-- EMAIL -->
|
||
<button
|
||
class="action"
|
||
@click="openDialog('ChangeEmailDialog')"
|
||
>
|
||
<span class="label">{{ t('email') }}</span>
|
||
<span
|
||
:class="{ 'not-set': !creatorProfileStore.creator.presentation?.email }"
|
||
class="value"
|
||
>
|
||
{{ creatorProfileStore.creator.presentation?.email || t('notSet') }}
|
||
</span>
|
||
<span class="chevron">
|
||
<v-icon :icon="mdiChevronRight" />
|
||
</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">
|
||
{{ t('payment-information') }}
|
||
</div>
|
||
|
||
<div class="content">
|
||
<div class="stripe-status">
|
||
<span class="label">{{ t('stripeStatus') }}</span>
|
||
<span
|
||
:class="stripeStatus"
|
||
class="value"
|
||
>
|
||
{{ stripeStatusText }}
|
||
</span>
|
||
<div class="stripe-actions">
|
||
<button
|
||
:class="stripeReady ? 'deconfigure-stripe-button' : 'configure-stripe-button'"
|
||
:disabled="stripeButtonBusy"
|
||
@click="() => (stripeReady ? removeStripe() : connectStripe())"
|
||
>
|
||
<v-icon
|
||
v-if="!stripeButtonBusy"
|
||
:icon="stripeReady ? mdiCreditCardOff : mdiCreditCard"
|
||
/>
|
||
<v-progress-circular
|
||
v-else
|
||
class="mr-2"
|
||
color="text-hTextOnPrimary"
|
||
indeterminate
|
||
size="20"
|
||
width="2"
|
||
/>
|
||
{{ stripeButtonText }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</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 :icon="mdiChevronRight" />
|
||
</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 :icon="mdiChevronRight" />
|
||
</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 :icon="mdiChevronRight" />
|
||
</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 :icon="mdiChevronRight" />
|
||
</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 :icon="mdiChevronRight" />
|
||
</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 :icon="mdiChevronRight" />
|
||
</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 :icon="mdiChevronRight" />
|
||
</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 :icon="mdiChevronRight" />
|
||
</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>
|
||
<div class="qr-code">
|
||
<QRCodeVue
|
||
v-if="creatorProfileStore.creator"
|
||
:image-settings="imageSettings"
|
||
:margin="0"
|
||
:size="200"
|
||
:value="baseURL + '/@' + creatorProfileStore.creator.slug"
|
||
foreground="#6B0065"
|
||
level="H"
|
||
render-as="canvas"
|
||
/>
|
||
</div>
|
||
|
||
<button
|
||
v-if="creatorProfileStore.creator"
|
||
class="download-button"
|
||
@click="downloadQRCode"
|
||
>
|
||
<v-icon :icon="mdiDownload" />
|
||
{{ 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 m-2 w-auto flex justify-center">
|
||
<div class="w-1/3">
|
||
<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>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
|
||
<v-dialog
|
||
v-model="dialogEditFullnameShown"
|
||
persistent
|
||
>
|
||
<FullnameDialog
|
||
:firstname="userProfileStore.user.firstname"
|
||
:lastname="userProfileStore.user.lastname"
|
||
@close="handleCloseEditFullname"
|
||
@save="handleSaveEditFullname"
|
||
/>
|
||
</v-dialog>
|
||
<v-dialog
|
||
v-model="dialogEditAliasShown"
|
||
persistent
|
||
>
|
||
<alias-dialog
|
||
:alias="userProfileStore.user.alias"
|
||
@close="handleCloseEditAlias"
|
||
@save="handleSaveEditAlias"
|
||
></alias-dialog>
|
||
</v-dialog>
|
||
<v-dialog
|
||
v-model="dialogShown"
|
||
persistent
|
||
>
|
||
<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;
|
||
}
|
||
|
||
.copy-button {
|
||
@apply ml-2 p-1 rounded-full;
|
||
@apply transition-all duration-300;
|
||
@apply relative overflow-hidden;
|
||
@apply opacity-60;
|
||
}
|
||
|
||
.copy-button:hover {
|
||
@apply opacity-100;
|
||
@apply bg-hSurface;
|
||
}
|
||
|
||
.copy-button::after {
|
||
content: '';
|
||
@apply absolute inset-0 bg-white/20;
|
||
@apply scale-0 rounded-full;
|
||
@apply transition-transform duration-300;
|
||
}
|
||
|
||
.copy-button:active::after {
|
||
@apply scale-150;
|
||
@apply opacity-0;
|
||
}
|
||
|
||
.copy-button.success {
|
||
@apply bg-green-500/20;
|
||
@apply opacity-100;
|
||
}
|
||
|
||
.value {
|
||
@apply text-hOnBackground flex-1 text-center;
|
||
@apply flex items-center justify-center;
|
||
}
|
||
|
||
.value.not-set {
|
||
@apply text-gray-400;
|
||
}
|
||
|
||
.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-2xl;
|
||
}
|
||
|
||
.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;
|
||
}
|
||
|
||
.stripe-status {
|
||
@apply flex flex-row items-center w-full p-3 rounded-lg;
|
||
@apply bg-hSurface;
|
||
@apply cursor-default;
|
||
@apply transition-colors duration-300;
|
||
}
|
||
|
||
.stripe-status .value {
|
||
@apply text-hOnBackground flex-1 text-center;
|
||
@apply flex items-center justify-center;
|
||
}
|
||
|
||
.stripe-status .value.fully_configured {
|
||
@apply text-green-500;
|
||
}
|
||
|
||
.stripe-status .value.configured {
|
||
@apply text-green-500;
|
||
}
|
||
|
||
.stripe-status .value.pending_verification {
|
||
@apply text-yellow-500;
|
||
}
|
||
|
||
.stripe-status .value.needs_more_info {
|
||
@apply text-orange-500;
|
||
}
|
||
|
||
.stripe-actions {
|
||
@apply flex items-center gap-2 ml-4;
|
||
}
|
||
|
||
.configure-stripe-button {
|
||
@apply flex items-center justify-center gap-2 px-4 py-2 rounded-lg;
|
||
@apply bg-hutopyPrimary text-hOnPrimary;
|
||
@apply hover:bg-hutopySecondary;
|
||
@apply transition-colors duration-300;
|
||
}
|
||
|
||
.deconfigure-stripe-button {
|
||
@apply flex items-center justify-center gap-2 px-4 py-2 rounded-lg;
|
||
@apply bg-red-600 text-white;
|
||
@apply hover:bg-red-700;
|
||
@apply transition-colors duration-300;
|
||
}
|
||
</style>
|
||
|
||
<i18n>
|
||
{
|
||
"en": {
|
||
"personalInfo": "Personal Information",
|
||
"fullName": "Full Name",
|
||
"alias": "Alias",
|
||
"email": "Email",
|
||
"changePassword": "Update Password",
|
||
"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",
|
||
"payment-information": "Payment Information",
|
||
"stripeStatus": "Stripe Status",
|
||
"configured": "Configured",
|
||
"notConfigured": "Not Configured",
|
||
"needsMoreInfo": "Requires More Information",
|
||
"pendingVerification": "Pending Verification",
|
||
"continueStripeSetup": "Continue Stripe Setup",
|
||
"reviewStripe": "Review Stripe",
|
||
"notSet": "Not Set",
|
||
"configureStripe": "Connect Stripe",
|
||
"phoneNumber": "Phone Number",
|
||
"title": "Title",
|
||
"removeStripe": "Remove Stripe"
|
||
},
|
||
"fr": {
|
||
"personalInfo": "Informations Personnelles",
|
||
"fullName": "Nom Complet",
|
||
"alias": "Alias",
|
||
"email": "Email",
|
||
"changePassword": "Modifier le mot de passe",
|
||
"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",
|
||
"payment-information": "Informations de Paiement",
|
||
"stripeStatus": "État de Stripe",
|
||
"configured": "Configuré",
|
||
"notConfigured": "Non Configuré",
|
||
"needsMoreInfo": "Demande plus d'informations",
|
||
"pendingVerification": "Vérification en Cours",
|
||
"continueStripeSetup": "Continuer Configuration Stripe",
|
||
"reviewStripe": "Reviser Stripe",
|
||
"notSet": "Non Défini",
|
||
"configureStripe": "Connecter Stripe",
|
||
"phoneNumber": "Numéro de téléphone",
|
||
"title": "Titre",
|
||
"removeStripe": "Retirer Stripe"
|
||
}
|
||
}
|
||
</i18n>
|