This commit is contained in:
2025-02-07 15:44:59 -05:00
parent 2b30479263
commit 009368ca8f
38 changed files with 1815 additions and 945 deletions

View File

@@ -25,7 +25,7 @@ import LoginView from '../views/LoginView.vue';
import PaymentCompleted from '../views/PaymentCompleted.vue';
import Home from '../views/main/Home.vue';
import Wallet from '../views/main/Wallet.vue';
import CreateCreator from "@/views/profile/creators/CreateCreator.vue";
import CreateCreator from "@/views/creators/CreateCreator.vue";
const routes = [
{

View File

@@ -24,6 +24,7 @@ export const useAuthStore = defineStore(
const refreshToken = useSessionStorage('auth-refreshToken', undefined)
const isAuthenticated = computed(() => !!accessToken.value)
const userId = computed(() => {
const claims = getClaimsFromToken(accessToken.value)
return claims.sub;
@@ -98,5 +99,4 @@ export const useAuthStore = defineStore(
}
return {accessToken, refreshToken, isAuthenticated, userId, login, loginWithGoogle, logout}
})
})

View File

@@ -41,8 +41,8 @@ export const useBrandingStore = defineStore(
if (newCreator !== undefined) {
value.value = await fetchCreatorData(newCreator)
currentBrand.value = newCreator
colors.value = value.value.colors
presentationInfos.value = value.value.presentationInfos
colors.value = value.value?.colors
presentationInfos.value = value.value?.presentationInfos
} else {
value.value = {}
currentBrand.value = undefined

View File

@@ -1,67 +1,70 @@
import { useClient } from '@/plugins/api.js';
import { useAuthStore } from '@/stores/authStore.js';
import { useSessionStorage } from '@vueuse/core';
import { defineStore } from 'pinia';
import { computed, watch } from 'vue';
import { useRouter } from 'vue-router';
import {useClient} from '@/plugins/api.js';
import {useAuthStore} from '@/stores/authStore.js';
import {useSessionStorage} from '@vueuse/core';
import {defineStore} from 'pinia';
import {computed, watch} from 'vue';
import {useRouter} from 'vue-router';
export const useCreatorProfileStore = defineStore('creator-profile', () => {
const router = useRouter();
const authStore = useAuthStore();
watch(
() => authStore.isAuthenticated,
async (newValue) => {
if (newValue) {
await fetchCurrentCreatorProfile();
if (value.value === undefined) {
await router.push('/');
} else {
await router.push(`/@${value.value.name}`);
}
} else {
value.value = undefined;
}
}
);
const value = useSessionStorage(
export const useCreatorProfileStore = defineStore(
'creator-profile',
undefined,
{ writeDefaults: false }
);
() => {
const router = useRouter();
const authStore = useAuthStore();
watch(
() => authStore.isAuthenticated,
async (newValue) => {
if (newValue) {
await fetchCurrentCreatorProfile();
if (value.value === undefined) {
await router.push('/');
} else {
await router.push(`/@${value.value.name}`);
}
} else {
value.value = undefined;
}
}
);
const hasCreator = computed(
() => value.value && Object.getOwnPropertyNames(value.value).length >= 1
);
const value = useSessionStorage(
'creator-profile',
{},
{writeDefaults: false}
);
const client = useClient();
const hasCreator = computed(
() => value.value && Object.getOwnPropertyNames(value.value).length >= 1
);
async function fetchCurrentCreatorProfile() {
try {
const creatorResponse = await client.get(`/api/creators/profile`);
value.value = creatorResponse.data;
// TODO: no cache-busting ???
} catch (error) {
value.value = undefined;
}
}
const client = useClient();
async function ConfigureStripeAccount() {
try {
await client.post(`/api/membership/stripe-account`);
return true;
} catch (error) {
return false;
}
}
async function fetchCurrentCreatorProfile() {
try {
const creatorResponse = await client.get(`/api/creators/profile`);
console.log('creatorProfile');
console.dir(creatorResponse.data)
value.value = creatorResponse.data;
console.dir(value.value);
// TODO: no cache-busting ???
} catch (error) {
console.log(`!!!`)
value.value = undefined;
}
}
return {
creator: value,
hasCreator,
fetchCurrentCreatorProfile,
ConfigureStripeAccount,
};
});
async function ConfigureStripeAccount() {
try {
await client.post(`/api/membership/stripe-account`);
return true;
} catch (error) {
return false;
}
}
return {
creator: value,
hasCreator,
fetchCurrentCreatorProfile,
ConfigureStripeAccount,
};
});

View File

@@ -0,0 +1,65 @@
<template>
<div class="relative">
<!-- Banner Container with mouse events -->
<div
class="relative"
@mouseenter="showTint = isCurrentCreator"
@mouseleave="showTint = false"
@click="isCurrentCreator && openBannerEditor()"
>
<img
class="w-full drop-shadow-[0_10px_6px_rgba(0,0,0,0.25)] h-60"
:src="brandingStore.value.images.banner ? brandingStore.value.images.banner : '/images/placeholders/banner.png'"
alt="Profile Banner"
>
<!-- Tint Effect -->
<div
v-if="showTint"
class="absolute inset-0 bg-black/25 cursor-pointer"
>
<!-- Top-right Icon -->
<div
class="absolute top-4 right-4 w-12 h-12 bg-white rounded-full flex items-center justify-center shadow-lg"
>
<v-icon large>mdi-pencil</v-icon>
</div>
</div>
</div>
</div>
<v-dialog v-model="isDialogOpen" max-width="800px">
<template #default="{ close }">
<div class="bg-white rounded-2xl p-4">
<banner-editor :creator="brandingStore.value"
@closeRequested="() => isDialogOpen = false"
></banner-editor>
</div>
</template>
</v-dialog>
</template>
<script setup>
import BannerEditor from "@/views/creators/BannerEditor.vue";
import {computed, ref} from "vue";
import {useBrandingStore} from "@/stores/brandingStore.js";
import {useAuthStore} from "@/stores/authStore.js";
const authStore = useAuthStore();
const brandingStore = useBrandingStore();
// State
const showTint = ref(false);
const isDialogOpen = ref(false);
// Methods
const openBannerEditor = () => {
isDialogOpen.value = true;
};
const isCurrentCreator = computed(() => {
return authStore.userId === brandingStore.value.id;
});
</script>

View File

@@ -0,0 +1,25 @@
<template>
<!-- PC -->
<div class="shadow-lg rounded-2xl mt-2">
<div class="relative z-20">
<div class="min-h-8 rounded-t-2xl shadow-lg"
:style="{ backgroundColor: branding.colors.primary }"
></div>
<actual-banner></actual-banner>
<banner-actions></banner-actions>
</div>
</div>
</template>
<script setup>
import {useBrandingStore} from "@/stores/brandingStore.js";
import ActualBanner from "@/views/creators/ActualBanner.vue";
import BannerActions from "@/views/creators/BannerActions.vue";
const branding = useBrandingStore();
</script>

View File

@@ -1,9 +1,10 @@
<script setup>
import { useClient } from '@/plugins/api.js';
import { useBrandingStore } from '@/stores/brandingStore.js';
import {useClient} from '@/plugins/api.js';
import {useBrandingStore} from '@/stores/brandingStore.js';
import DonationButtonBanner from '@/views/creators/DonationButtonBanner.vue';
import { onBeforeUnmount, onMounted, ref } from 'vue';
import IconAccountVerified from "@/components/icons/IconAccountVerified.vue";
import {onBeforeUnmount, onMounted, ref} from 'vue';
import CreatorLogo from "@/views/creators/CreatorLogo.vue";
import NameTitle from "@/views/creators/NameTitle.vue";
const brandingStore = useBrandingStore();
const isMobile = ref(false);
@@ -80,10 +81,10 @@ onMounted(async () => {
window.addEventListener('resize', updateIsMobile);
const observer = new IntersectionObserver(
([entry]) => {
isSticky.value = !entry.isIntersecting;
},
{ threshold: 0 }
([entry]) => {
isSticky.value = !entry.isIntersecting;
},
{threshold: 0}
);
if (mainContainer.value) {
@@ -109,117 +110,48 @@ onBeforeUnmount(() => {
<div class="flex flex-column w-full">
<!-- Container principal avec le profil -->
<div class="relative w-full shadow-xl rounded-2xl">
<div
ref="mainContainer"
class="rounded-b-2xl shadow-2xl"
:style="{
backgroundColor: brandingStore.colors.primary,
boxShadow: '0 5px 10px rgba(0, 0, 0, 0.3)',
}"
>
<div>
<div class="rounded-b-2xl shadow-2xl"
:style="{
backgroundColor: brandingStore.colors.primary,
boxShadow: '0 5px 10px rgba(0, 0, 0, 0.3)',
}">
<div class="flex flex-row p-2">
<!-- Profile et Info -->
<div>
<!-- Version PC -->
<div v-show="!isMobile" class="items-start">
<div>
<img
class="shadow-2xl rounded-full border-solid border-102 absolute z-20 max-w-[190px] ml-10 -mt-5"
:src="
brandingStore.value.images.logo
? brandingStore.value.images.logo
: '/images/placeholders/logo.png'
"
alt="Profile Picture"
:style="{
borderColor: brandingStore.colors.secondary,
height: '190px',
}"
/>
</div>
<div
class="ml-64 w-25 min-w-60 flex flex-row"
:style="{ color: brandingStore.colors.onPrimary }"
>
<div v-show="brandingStore.value.verified" class="text-blue m-4 align-content-center verifiedhook">
<icon-account-verified></icon-account-verified>
</div>
<div class="flex flex-col">
<span class="capitalize text-3xl titlepos">
{{ brandingStore.value.name }}
</span>
<span class="capitalize text-lg titlepos">
{{ brandingStore.value.title }}
</span>
</div>
</div>
</div>
<!-- Version Mobile -->
<div class="relative">
<div
:style="{
borderColor: brandingStore.colors.secondary,
height: '80px',
}"
>
<div
v-show="isMobile"
class="absolute -top-7 left-0 px-3 flex flex-row items-center z-30"
>
<div>
<img
class="shadow-2xl rounded-full border-solid z-20 max-w-[150px]"
:src="
brandingStore.value.images.logo
? brandingStore.value.images.logo
: '/images/placeholders/logo.png'
"
alt="Profile Picture"
:style="{ height: '135px' }"
/>
</div>
<div v-show="brandingStore.value.verified" class="text-blue m-4 align-content-center">
<icon-account-verified></icon-account-verified>
</div>
<div class="ml-3 text-white w-full flex flex-col items-start">
<p class="capitalize text-2xl">
{{ brandingStore.value.name }}
</p>
<p class="capitalize text-md">
{{ brandingStore.value.title }}
</p>
</div>
</div>
</div>
</div>
<creator-logo/>
</div>
<div class="flex items-center">
<name-title></name-title>
</div>
<!-- Actions - Follow et Register -->
<!-- <div class="flex flex-col items-center justify-center w-full">-->
<!-- <div class="flex flex-row space-x-1 justify-center mt-3 mb-2">-->
<!-- &lt;!&ndash;<subscribe-button></subscribe-button>&ndash;&gt;-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="flex flex-col items-center justify-center w-full">-->
<!-- <div class="flex flex-row space-x-1 justify-center mt-3 mb-2">-->
<!-- &lt;!&ndash;<subscribe-button></subscribe-button>&ndash;&gt;-->
<!-- </div>-->
<!-- </div>-->
</div>
<!-- Bouton Support -->
<div
v-show="brandingStore.value.acceptDonation"
class="z-20 shadow-2xl rounded-md text-white flex justify-center items-center z-50"
:class="{
v-show="brandingStore.value.acceptDonation"
class="z-20 shadow-2xl rounded-md text-white flex justify-center items-center z-50"
:class="{
'absolute bottom-6 right-8 w-64 h-28 ': !isMobile,
'fixed bottom-0 left-0 right-0 w-full h-16': isMobile,
}"
:style="{ backgroundColor: brandingStore.colors.secondary }"
:style="{ backgroundColor: brandingStore.colors.secondary }"
>
<donation-button-banner
v-if="creator"
:creator-id="creator.id"
:creator-name="creator.name"
:on-success-url="baseURL + '/paymentcompleted/' + creator.id"
:on-cancelled-url="baseURL + '/paymentfailed/' + creator.id"
v-if="creator"
:creator-id="creator.id"
:creator-name="creator.name"
:on-success-url="baseURL + '/paymentcompleted/' + creator.id"
:on-cancelled-url="baseURL + '/paymentfailed/' + creator.id"
></donation-button-banner>
</div>
</div>
@@ -227,8 +159,8 @@ onBeforeUnmount(() => {
<!-- Section pour les icônes de réseaux sociaux -->
<div
class="rounded-b-2xl -mt-3 h-12 px-36 flex flex-col items-center justify-center"
:style="{
class="rounded-b-2xl -mt-3 h-12 px-36 flex flex-col items-center justify-center"
:style="{
backgroundColor: brandingStore.colors.secondary,
boxShadow: '0 5px 20px rgba(0, 0, 0, 0.3)',
}"
@@ -236,20 +168,20 @@ onBeforeUnmount(() => {
<div class="flex justify-evenly mt-3 w-full">
<div class="flex flex-row space-x-6 justify-center">
<a
v-for="socialNetwork in GetSocialsUrls()"
:key="socialNetwork.url"
:href="socialNetwork.url"
target="_blank"
class="text-white text-md transform transition-transform duration-200 hover:scale-125 hover:text-blue-500"
v-for="socialNetwork in GetSocialsUrls()"
:key="socialNetwork.url"
:href="socialNetwork.url"
target="_blank"
class="text-white text-md transform transition-transform duration-200 hover:scale-125 hover:text-blue-500"
>
<v-icon v-if="socialNetwork.icon.includes('mdi')">
{{ socialNetwork.icon }}
</v-icon>
<img
v-else
:src="socialNetwork.icon"
class="w-6 h-6 mt-0.5"
:alt="socialNetwork.url"
v-else
:src="socialNetwork.icon"
class="w-6 h-6 mt-0.5"
:alt="socialNetwork.url"
/>
</a>
</div>
@@ -258,28 +190,3 @@ onBeforeUnmount(() => {
</div>
</template>
<style scoped>
.nav-button {
@apply rounded flex justify-center font-sans py-1 text-white tracking-widest p-4;
}
.nav-button:hover {
@apply bg-purple-800;
}
/* Transition CSS */
.transition-all {
transition: all 0.3s ease-in-out;
}
.titlepos {
position: relative;
top: 30px;
}
.verifiedhook{
position: relative;
top: 16px;
}
</style>

View File

@@ -1,5 +1,5 @@
<template>
<h2 class="text-2xl font-semibold mb-4 flex justify-center">
<h2 class="text-2xl font-semibold mb-4">
Bannière
</h2>
@@ -24,8 +24,8 @@
</template>
<script setup>
import { ref } from 'vue'
import { useClient } from '@/plugins/api.js'
import {ref} from 'vue'
import {useClient} from '@/plugins/api.js'
const props = defineProps({
creator: {
@@ -36,8 +36,8 @@ const props = defineProps({
const emits = defineEmits(['closeRequested'])
const selectedFile = ref({})
const fileUrl = ref(props.creator.images.banner)
const fallbackUrl = '/images/hutopymedia/banners/hutopyul.png'
const fileUrl = ref(props.creator?.images?.banner)
const fallbackUrl = '/images/hutopymedia/banners/hutopyul.png'
const onFileSelected = () => {
if (selectedFile.value) {

View File

@@ -1,27 +1,37 @@
<script setup>
import {ref} from 'vue'
import {computed, ref, watch} from 'vue'
import {useUserProfileStore} from "@/stores/userProfileStore.js";
import {useCreatorProfileStore} from "@/stores/creatorProfileStore.js";
import {useClient} from "@/plugins/api.js";
import {useRouter} from "vue-router";
import NameEditor from "@/views/creators/NameEditor.vue";
const creatorName = ref('');
const creatorNameReservationId = ref(undefined);
const canSave = computed(() => creatorNameReservationId.value !== undefined)
const isOperationPending = ref(false);
const errorMessage = ref('');
const isLoading = ref(false);
const router = useRouter();
const creatorProfileStore = useCreatorProfileStore();
const userProfileStore = useUserProfileStore();
function handleCreatorNameReservationIdChanged($event) {
console.log(`in handleCreatorNameReservationIdChanged: ${$event.value}`);
creatorNameReservationId.value = $event.value
}
async function createAccount() {
const client = useClient();
try {
isOperationPending.value = true;
const client = useClient();
errorMessage.value = '';
isLoading.value = true;
const normalizedCreatorName = creatorName.value.toLowerCase();
await client.post('/api/creators', {
creatorId: userProfileStore.user.id,
name: normalizedCreatorName,
slugReservationId: creatorNameReservationId.value,
});
await creatorProfileStore.fetchCurrentCreatorProfile();
await router.push(`/@${normalizedCreatorName}`);
@@ -32,38 +42,37 @@ async function createAccount() {
errorMessage.value = error?.response?.data?.message || error.message || 'An unexpected error occurred.';
}
} finally {
isLoading.value = false;
isOperationPending.value = false;
}
}
</script>
<template>
<div>
<div class="create-creator-card">
<div class="py-2 text-3xl font-bold">
<div class="text-center mb-10">Créez votre Hutopy.</div>
<div class="create-creator-card">
<div class="py-2 text-3xl font-bold text-center mb-10">
Créez votre Hutopy.
</div>
<div class="flex flex-column justify-end gap-2">
<v-alert
v-if="!!errorMessage"
dense
outlined
text
type="error"
>
{{ errorMessage }}
</v-alert>
<v-text-field
variant="outlined"
v-model="creatorName"
label="Nom de la page"
outlined
></v-text-field>
<div class="flex flex-row justify-end gap-2">
<name-editor
v-model:name="creatorName"
creator-name-reservation-id="creatorNameDirty"
@update:creator-name-reservation-id="handleCreatorNameReservationIdChanged($event)"
></name-editor>
<div class="flex flex-row justify-end gap-2">
<v-btn
:disabled="isLoading"
:disabled="!canSave"
variant="outlined"
@click="createAccount"
:style="{ borderColor: 'rgb(159, 76, 173)', color: 'rgb(159, 76, 173)' }"
@@ -71,9 +80,9 @@ async function createAccount() {
Créer
</v-btn>
</div>
</div>
</div>
</div>
</template>
<style scoped>

View File

@@ -1,94 +0,0 @@
<template>
<!-- PC -->
<div v-if="!isMobile">
<div class="shadow-lg rounded-2xl mt-2">
<div class="relative z-20">
<div class="min-h-8 rounded-t-2xl shadow-lg" :style="{ backgroundColor: branding.colors.primary }"></div>
<!-- Banner -->
<div class="relative">
<div>
<img
class="w-full drop-shadow-[0_10px_6px_rgba(0,0,0,0.25)]"
:src="branding.value.images.banner ? branding.value.images.banner : '/images/placeholders/banner.png'"
alt="Profile Banner"
style="max-height: 425px"
>
</div>
</div>
</div>
<banner-actions></banner-actions>
</div>
</div>
<!-- Mobile -->
<div v-if="isMobile">
<div class="shadow-lg rounded-2xl ">
<div class="relative z-20">
<div class="shadow-2xl flex items-center px-2 py-2"
:style="{ backgroundColor: branding.colors.primary, color: branding.colors.onPrimary }">
<router-link to="/@Hutopy">
<div class="flex items-center">
<HutopySvg></HutopySvg>
<div class="text-xl font-bold -ml-2 ">Hutopy</div>
</div>
</router-link>
<div class="flex-1"></div>
<router-link to="/login">
<button class="lg:hidden flex items-center justify-center mr-4">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
:stroke="branding.colors.onPrimary" class="w-8 h-8">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
</svg>
</button>
</router-link>
</div>
<!-- Banner -->
<div class="relative">
<div>
<img
class="w-full drop-shadow-[0_10px_6px_rgba(0,0,0,0.25)]"
:src="branding.value.images.banner ? branding.value.images.banner : '/images/placeholders/banner.png'"
alt="Profile Banner"
style="max-height: 425px"
>
</div>
</div>
</div>
<banner-actions></banner-actions>
</div>
</div>
</template>
<script setup>
import {ref, onMounted, onBeforeUnmount} from "vue";
import BannerActions from "@/views/creators/BannerActions.vue";
import {useBrandingStore} from "@/stores/brandingStore.js";
import HutopySvg from "@/views/svg/HutopySvg.vue";
const branding = useBrandingStore();
const isMobile = ref(false);
function updateIsMobile() {
isMobile.value = window.innerWidth <= 640;
}
onMounted(() => {
updateIsMobile();
window.addEventListener("resize", updateIsMobile);
});
onBeforeUnmount(() => {
window.removeEventListener("resize", updateIsMobile);
});
</script>
<style>
</style>

View File

@@ -5,7 +5,7 @@
<v-progress-linear indeterminate></v-progress-linear>
</div>
<div v-else>
<creator-banner></creator-banner>
<banner></banner>
</div>
<div class="py-8 flex-grow">
<router-view></router-view>
@@ -16,11 +16,11 @@
</div>
</div>
</template>
<script async setup>
import CreatorBanner from "@/views/creators/CreatorBanner.vue";
import Banner from "@/views/creators/Banner.vue";
import Footer from "@/views/main/Footer.vue";
import {useBrandingStore} from "@/stores/brandingStore.js";
const brandingStore = useBrandingStore()

View File

@@ -0,0 +1,70 @@
<template>
<div class="rounded-full relative bg-red"
@mouseenter="showTint = isCurrentCreator"
@mouseleave="showTint = false"
@click="isCurrentCreator && openBannerEditor()"
>
<img
class="shadow-2xl rounded-full border-solid border-102 max-w-[190px]"
:src="brandingStore.value.images.logo
? brandingStore.value.images.logo
: '/images/placeholders/logo.png'"
alt="Profile Picture"
:style="{
borderColor: brandingStore.colors.secondary,
height: '190px',
}"
/>
<!-- Tint Effect -->
<div
v-if="showTint"
class="absolute rounded-full inset-0 bg-black/25 cursor-pointer"
>
<!-- Top-right Icon -->
<div
class="absolute top-4 right-4 w-12 h-12 bg-white rounded-full flex items-center justify-center shadow-lg"
>
<v-icon large>mdi-pencil</v-icon>
</div>
</div>
</div>
<v-dialog v-model="isDialogOpen" max-width="800px">
<template #default="{ close }">
<div class="bg-white rounded-2xl p-4">
<creator-logo-editor
:creator="brandingStore?.value"
@closeRequested="() => isDialogOpen = false"
></creator-logo-editor>
</div>
</template>
</v-dialog>
</template>
<script setup>
import {useAuthStore} from "@/stores/authStore.js";
import {useBrandingStore} from "@/stores/brandingStore.js";
import CreatorLogoEditor from "@/views/creators/CreatorLogoEditor.vue";
import {computed, ref} from "vue";
const authStore = useAuthStore();
const brandingStore = useBrandingStore();
// State
const showTint = ref(false);
const isDialogOpen = ref(false);
// Methods
const openBannerEditor = () => {
isDialogOpen.value = true;
};
const isCurrentCreator = computed(() => {
return authStore.userId === brandingStore.value.id;
});
</script>

View File

@@ -23,12 +23,13 @@
<v-btn color="black" variant="text" @click="cancel">Annuler</v-btn>
<v-btn color="#A6147D" @click="publish">Enregistrer</v-btn>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useClient } from '@/plugins/api.js'
import {ref} from 'vue'
import {useClient} from '@/plugins/api.js'
const props = defineProps({
creator: {

View File

@@ -0,0 +1,89 @@
<script setup>
import {computed, ref} from "vue";
import {v7} from "uuid";
import {useClient} from "@/plugins/api.js";
const props = defineProps({
name: {
required: true
},
creatorNameReservationId: {
required: true
}
});
const emits = defineEmits([
'update:name',
'update:creatorNameReservationId'
]);
const name = ref(props.name);
const isReserved = computed(() => reservationState.value === 'reserved');
const isOperationPending = ref(false);
const reservationState = ref(null);
const reservationId = ref(null);
let timeout = null;
const handleInput = () => {
clearTimeout(timeout);
timeout = setTimeout(() =>
checkNameAvailability(),
200);
};
const client = useClient()
const checkNameAvailability = async () => {
if (!name.value || name.value.trim() === "") {
reservationState.value = null;
return;
}
try {
const id = v7();
isOperationPending.value = true;
reservationState.value = "loading";
await client.post(
`/api/creators/@${encodeURIComponent(name.value)}/reserve`,
{reservationId: id}
);
reservationState.value = "reserved";
reservationId.value = id;
} catch (error) {
reservationState.value = "unavailable"; // Handle API failure case
reservationId.value = undefined;
} finally {
emits('update:name', name);
emits('update:creatorNameReservationId', reservationId);
isOperationPending.value = false;
}
};
</script>
<template>
<v-text-field
variant="outlined"
label="Nom de la page"
v-model="name"
outlined
@input="handleInput"
>
<template #append-inner>
<v-progress-circular
v-if="reservationState === 'loading'"
indeterminate
size="24"
width="3"
color="grey"
></v-progress-circular>
<v-icon v-else-if="reservationState === 'reserved'" color="green">mdi-check-circle</v-icon>
<v-icon v-else-if="reservationState === 'unavailable'" color="red">mdi-close-circle</v-icon>
</template>
</v-text-field>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,79 @@
<template>
<div class="relative">
<div class="relative flex flex-row"
@mouseenter="showTint = isCurrentCreator"
@mouseleave="showTint = false"
@click="isCurrentCreator && openBannerEditor()"
>
<div v-show="brandingStore.value.verified"
class="text-blue m-4">
<icon-account-verified></icon-account-verified>
</div>
<div class="flex flex-col"
:style="{ color: brandingStore.colors.onPrimary }">
<span class="capitalize text-3xl">
{{ brandingStore.value.name }}
</span>
<span class="capitalize text-lg">
{{ brandingStore.value.title }}
</span>
</div>
<!-- Tint Effect -->
<div
v-if="showTint"
class="absolute inset-0 bg-black/25 cursor-pointer"
>
<!-- Top-right Icon -->
<div
class="absolute top-1 right-1 w-12 h-12 bg-white rounded-full flex items-center justify-center shadow-lg"
>
<v-icon large>mdi-pencil</v-icon>
</div>
</div>
</div>
</div>
<v-dialog v-model="isDialogOpen" max-width="800px">
<template #default="{ close }">
<div class="bg-white rounded-2xl p-4">
<name-title-editor
:creator="brandingStore?.value"
@closeRequested="() => isDialogOpen = false"
></name-title-editor>
</div>
</template>
</v-dialog>
</template>
<script setup>
import IconAccountVerified from "@/components/icons/IconAccountVerified.vue";
import {useBrandingStore} from "@/stores/brandingStore.js";
import {useAuthStore} from "@/stores/authStore.js";
import {computed, ref} from "vue";
import NameTitleEditor from "@/views/creators/NameTitleEditor.vue";
const authStore = useAuthStore();
const brandingStore = useBrandingStore();
// State
const showTint = ref(false);
const isDialogOpen = ref(false);
// Methods
const openBannerEditor = () => {
isDialogOpen.value = true;
};
const isCurrentCreator = computed(() => {
return authStore.userId === brandingStore.value.id;
});
</script>

View File

@@ -0,0 +1,76 @@
<script setup>
import {computed, ref} from "vue";
import {useClient} from "@/plugins/api.js";
import NameTitle from "@/views/creators/NameTitle.vue";
import NameEditor from "@/views/creators/NameEditor.vue";
const props = defineProps({
creator: {
required: true
}
});
const emits = defineEmits(['closeRequested'])
const name = ref(props.creator.name);
const title = ref(props.creator.title);
const canSave = computed(() => name != props.creator.name);
const client = useClient()
const save = async () => {
try {
await client.post(`/api/creators/${props.creator.id}/name`);
await client.post(`/api/creators/${props.creator.id}/title`);
props.creator.creator.name = name;
props.creator.title.name = title;
emits('closeRequested')
} catch (error) {
console.error(error)
}
}
function cancel() {
emits('closeRequested');
}
</script>
<template>
<div class="pb-5 text-2xl">
Modifier le Titre
</div>
<div class="flex flex-col space-y-4">
<name-editor
:name="name"
></name-editor>
<v-text-field
variant="outlined"
v-model="title"
label="Titre"
outlined
></v-text-field>
<div class="flex justify-end space-x-4">
<v-btn color="black"
variant="text"
@click="cancel">
Annuler
</v-btn>
<v-btn color="#A6147D"
:disabled="!canSave"
@click="save">
Enregistrer
</v-btn>
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -1,95 +1,89 @@
<template>
<div class="flex flex-col items-center w-[800px] gap-4">
<h1 class="uppercase pb-5 text-2xl">
<v-icon class="mr-2">mdi-information</v-icon>
{{ $t('personnalinformation.title') }}
</h1>
<v-card rounded="xl" class="w-full">
<v-card class="w-full">
<v-card-title>
{{ $t('personnalinformation.informations') }}
</v-card-title>
<v-card-title>
{{ $t('personnalinformation.informations') }}
</v-card-title>
<!-- <button-->
<!-- class="editableValue"-->
<!-- @click="openEditPortrait">-->
<!-- <span class="label">{{ $t('personnalinformation.profilepicture') }}</span>-->
<!-- <span class="value">Un portrait vous permet de personnaliser votre profil</span>-->
<!-- <span>-->
<!-- <img-->
<!-- :src="userProfileStore.user.portraitUrl"-->
<!-- alt="Profile Image"-->
<!-- class="rounded-full"-->
<!-- width="48px"-->
<!-- height="48px"/>-->
<!-- </span>-->
<!-- </button>-->
<!-- <button-->
<!-- class="editableValue"-->
<!-- @click="openEditPortrait">-->
<!-- <span class="label">{{ $t('personnalinformation.profilepicture') }}</span>-->
<!-- <span class="value">Un portrait vous permet de personnaliser votre profil</span>-->
<!-- <span>-->
<!-- <img-->
<!-- :src="userProfileStore.user.portraitUrl"-->
<!-- alt="Profile Image"-->
<!-- class="rounded-full"-->
<!-- width="48px"-->
<!-- height="48px"/>-->
<!-- </span>-->
<!-- </button>-->
<button
class="editableValue"
@click="openEditFullname">
<span class="label">{{ $t('personnalinformation.fullname') }}</span>
<span class="value">{{ userProfileStore.fullname }}</span>
<span><v-icon>mdi-chevron-right</v-icon></span>
</button>
<button
class="editableValue"
@click="openEditFullname">
<span class="label">{{ $t('personnalinformation.fullname') }}</span>
<span class="value">{{ userProfileStore.fullname }}</span>
<span><v-icon>mdi-chevron-right</v-icon></span>
</button>
<button
class="editableValue"
@click="openEditAlias">
<span class="label">{{ $t('personnalinformation.alias') }}</span>
<span class="value">{{ userProfileStore.user.alias }}</span>
<span><v-icon>mdi-chevron-right</v-icon></span>
</button>
<button
class="editableValue"
@click="openEditAlias">
<span class="label">{{ $t('personnalinformation.alias') }}</span>
<span class="value">{{ userProfileStore.user.alias }}</span>
<span><v-icon>mdi-chevron-right</v-icon></span>
</button>
<!-- <button-->
<!-- class="editableValue"-->
<!-- @click="openEditBirthday">-->
<!-- <span class="label">{{ $t('personnalinformation.dob') }}</span>-->
<!-- <span class="value">{{ userProfileStore.user.birthDate }}</span>-->
<!-- <span><v-icon>mdi-chevron-right</v-icon></span>-->
<!-- </button>-->
<!-- <button-->
<!-- class="editableValue"-->
<!-- @click="openEditBirthday">-->
<!-- <span class="label">{{ $t('personnalinformation.dob') }}</span>-->
<!-- <span class="value">{{ userProfileStore.user.birthDate }}</span>-->
<!-- <span><v-icon>mdi-chevron-right</v-icon></span>-->
<!-- </button>-->
</v-card>
</v-card>
<!-- Phone & email -->
<v-card class="w-full">
<v-card-title>
{{ $t('personnalinformation.contactdetails') }}
</v-card-title>
<!-- Phone & email -->
<v-card rounded="xl" class="w-full">
<v-card-title>
{{ $t('personnalinformation.contactdetails') }}
</v-card-title>
<button
class="editableValue"
@click="openEditEmail">
<span class="label">{{ $t('personnalinformation.email') }}</span>
<span class="value">{{ userProfileStore.user.email }}</span>
<span><v-icon>mdi-chevron-right</v-icon></span>
</button>
<button
class="editableValue"
@click="openEditEmail">
<span class="label">{{ $t('personnalinformation.email') }}</span>
<span class="value">{{ userProfileStore.user.email }}</span>
<span><v-icon>mdi-chevron-right</v-icon></span>
</button>
<!-- <button-->
<!-- class="editableValue"-->
<!-- @click="openEditPhone">-->
<!-- <span class="label">{{ $t('personnalinformation.phone') }}</span>-->
<!-- <span class="value">{{ userProfileStore.user.phoneNumber }}</span>-->
<!-- <span><v-icon>mdi-chevron-right</v-icon></span>-->
<!-- </button>-->
</v-card>
<!-- <button-->
<!-- class="editableValue"-->
<!-- @click="openEditPhone">-->
<!-- <span class="label">{{ $t('personnalinformation.phone') }}</span>-->
<!-- <span class="value">{{ userProfileStore.user.phoneNumber }}</span>-->
<!-- <span><v-icon>mdi-chevron-right</v-icon></span>-->
<!-- </button>-->
</v-card>
<!-- Address -->
<!-- <v-card class="w-full">-->
<!-- <v-card-title>-->
<!-- {{ $t('personnalinformation.addresses') }}-->
<!-- </v-card-title>-->
<!-- Address -->
<!-- <v-card class="w-full">-->
<!-- <v-card-title>-->
<!-- {{ $t('personnalinformation.addresses') }}-->
<!-- </v-card-title>-->
<!-- <button-->
<!-- class="editableValue"-->
<!-- @click="openEditAddress">-->
<!-- <span class="label">{{ $t('personnalinformation.home') }}</span>-->
<!-- <span class="value">{{ userProfileStore.user.address }}</span>-->
<!-- <span><v-icon>mdi-chevron-right</v-icon></span>-->
<!-- </button>-->
<!-- </v-card>-->
</div>
<!-- <button-->
<!-- class="editableValue"-->
<!-- @click="openEditAddress">-->
<!-- <span class="label">{{ $t('personnalinformation.home') }}</span>-->
<!-- <span class="value">{{ userProfileStore.user.address }}</span>-->
<!-- <span><v-icon>mdi-chevron-right</v-icon></span>-->
<!-- </button>-->
<!-- </v-card>-->
<!-- Modal -->
<v-dialog v-model="dialogEditPortraitShown" max-width="600px">
@@ -152,8 +146,8 @@
<script setup>
import {ref} from 'vue';
import AddressDialog from './AddressDialog.vue';
import EmailDialog from "./EmailDialog.vue";
import AddressDialog from './account/AddressDialog.vue';
import EmailDialog from "./account/EmailDialog.vue";
import PhoneDialog from "@/views/profile/account/PhoneDialog.vue";
import BirthdayDialog from "@/views/profile/account/BirthdayDialog.vue";
import AliasDialog from "@/views/profile/account/AliasDialog.vue";

View File

@@ -0,0 +1,283 @@
<script setup>
import XIcon from '@/assets/icons/x.svg';
import {useCreatorProfileStore} from '@/stores/creatorProfileStore.js';
import ChangeStripeID from '@/views/profile/creators/ChangeStripeID.vue';
import ChangeTitle from '@/views/profile/creators/ChangeTitle.vue';
import {computed, ref} from 'vue';
import ColorsPicker from './creators/ColorsPicker.vue';
import LogoPicker from '../creators/CreatorLogoEditor.vue';
import Socials from './creators/Socials.vue';
const creatorProfileStore = useCreatorProfileStore();
const dialog = ref(false);
const currentComponent = ref('');
const componentsMap = {
LogoPicker,
Socials,
ColorsPicker,
ChangeTitle,
ChangeStripeID,
};
function requestCancel() {
currentComponent.value = null;
dialog.value = false;
}
const openDialog = (component) => {
currentComponent.value = componentsMap[component];
dialog.value = true;
};
const closeDialog = () => {
currentComponent.value = null;
dialog.value = false;
};
</script>
<template>
<v-dialog v-model="dialog" max-width="800px">
<v-card
:style="{ borderRadius: '25px', border: '3px solid rgb(159, 76, 173)' }"
>
<v-card-text>
<component
:is="currentComponent"
:creator="creatorProfileStore.creator"
@closeRequested="closeDialog"
@requestAccept="requestAccept"
@requestCancel="requestCancel"
></component>
</v-card-text>
</v-card>
</v-dialog>
<!-- Lorsque l'utilisateur n'a pas de creator name-->
<v-card rounded="xl" class="w-full">>
<h1 class="uppercase">
{{ $t('creatorinfopage.informations') }}
</h1>
<!-- INFOS -->
<div class="flex flex-col w-full">
<button
class="HoverBtn active:bg-gray-300 py-2 px-4 border-gray-400 shadow flex items-center transition duration-200 ease-in-out w-full"
>
<span class="flex-none pa-2 min-w-32 text-left">
{{ $t('creatorinfopage.name') }}
</span>
<span class="flex-auto text-left pr-6 capitalize">
{{ creatorProfileStore.creator.name }}
</span>
<span class="flex-none">
<v-icon>mdi-chevron-right</v-icon>
</span>
</button>
</div>
<!-- TITLE -->
<div class="flex flex-col w-full">
<button
@click="openDialog('ChangeTitle')"
class="HoverBtn active:bg-gray-300 py-2 px-4 border-gray-400 shadow flex items-center transition duration-200 ease-in-out w-full"
>
<span class="flex-none pa-2 min-w-32 text-left">{{
$t('creatorinfopage.title')
}}</span>
<span class="flex-auto text-left pr-6 capitalize">{{
creatorProfileStore.creator.title
}}</span>
<span class="flex-none">
<v-icon>mdi-chevron-right</v-icon>
</span>
</button>
</div>
<!-- STRIPE -->
<div class="flex flex-col w-full">
<button
@click="openDialog('ChangeStripeID')"
class="HoverBtn active:bg-gray-300 py-2 px-4 border-gray-400 shadow flex items-center transition duration-200 ease-in-out w-full rounded-b"
>
<span class="flex-none pa-2 min-w-32 text-left"
>Stripe Account ID</span
>
<span class="flex-auto text-left pr-6">{{
creatorProfileStore.creator.stripeId
}}</span>
<span class="flex-none">
<v-icon>mdi-chevron-right</v-icon>
</span>
</button>
</div>
</v-card>
<v-card rounded="xl" class="w-full">
<div class="py-5 uppercase ml-4">
{{ $t('creatorinfopage.banner&profile') }}
</div>
<div class="flex flex-col w-full">
<button
@click="openDialog('ColorsPicker')"
class="HoverBtn active:bg-gray-300 py-2 px-4 border-gray-400 shadow flex items-center transition duration-200 ease-in-out w-full"
>
<span class="flex-auto text-left pr-6 capitalize">
Choisissez votre palette de couleurs.
</span>
<span class="flex-none">
<v-icon>mdi-chevron-right</v-icon>
</span>
</button>
</div>
</v-card>
<v-card rounded="xl" class="w-full">
<div class="uppercase">
{{ $t('creatorinfopage.socialnetwork') }}
</div>
<div class="flex flex-col w-full">
<button
@click="openDialog('Socials')"
class="HoverBtn active:bg-gray-300 py-2 px-4 border-gray-400 shadow flex items-center transition duration-200 ease-in-out w-full"
>
<span class="pa-2 min-w-32 text-left">
<v-icon>mdi-facebook</v-icon>
</span>
<span class="flex-auto text-left pr-6">
{{ creatorProfileStore.creator.socials?.facebookUrl }}
</span>
<span class="flex-none">
<v-icon>mdi-chevron-right</v-icon>
</span>
</button>
<button
@click="openDialog('Socials')"
class="HoverBtn active:bg-gray-300 py-2 px-4 border-gray-400 shadow flex items-center transition duration-200 ease-in-out w-full"
>
<span class="flex-none pa-2 min-w-32 text-left">
<v-icon>mdi-instagram</v-icon></span
>
<span class="flex-auto text-left pr-6">{{
creatorProfileStore.creator.socials?.instagramUrl
}}</span>
<span class="flex-none">
<v-icon>mdi-chevron-right</v-icon>
</span>
</button>
<button
@click="openDialog('Socials')"
class="HoverBtn active:bg-gray-300 py-2 px-4 border-gray-400 shadow flex items-center transition duration-200 ease-in-out w-full"
>
<span class="flex-none pa-2 w-9 h-9 text-left ml-0.5">
<XIcon></XIcon>
</span>
<span class="flex-auto text-left pr-6">{{
creatorProfileStore.creator.socials?.xUrl
}}</span>
<span class="flex-none">
<v-icon>mdi-chevron-right</v-icon>
</span>
</button>
<button
@click="openDialog('Socials')"
class="HoverBtn active:bg-gray-300 py-2 px-4 border-gray-400 shadow flex items-center transition duration-200 ease-in-out w-full"
>
<span class="pa-2 min-w-32 text-left"
><v-icon>mdi-linkedin</v-icon></span
>
<span class="flex-auto text-left pr-6">{{
creatorProfileStore.creator.socials?.linkedInUrl
}}</span>
<span class="flex-none">
<v-icon>mdi-chevron-right</v-icon>
</span>
</button>
<button
@click="openDialog('Socials')"
class="HoverBtn active:bg-gray-300 py-2 px-4 border-gray-400 shadow flex items-center transition duration-200 ease-in-out w-full"
>
<span class="flex-none pa-2 min-w-32 text-left">
<XIcon class="w-5 h-5"></XIcon>
</span>
<span class="flex-auto text-left pr-6">{{
creatorProfileStore.creator.socials?.tikTokUrl
}}</span>
<span class="flex-none">
<v-icon>mdi-chevron-right</v-icon>
</span>
</button>
<button
@click="openDialog('Socials')"
class="HoverBtn active:bg-gray-300 py-2 px-4 border-gray-400 shadow flex items-center transition duration-200 ease-in-out w-full"
>
<span class="pa-2 min-w-32 text-left"
><v-icon>mdi-youtube</v-icon></span
>
<span class="flex-auto text-left pr-6">{{
creatorProfileStore.creator.socials?.youtubeUrl
}}</span>
<span class="flex-none">
<v-icon>mdi-chevron-right</v-icon>
</span>
</button>
<button
@click="openDialog('Socials')"
class="HoverBtn active:bg-gray-300 py-2 px-4 border-gray-400 shadow flex items-center transition duration-200 ease-in-out w-full"
>
<span class="pa-2 min-w-32 text-left"
><v-icon>mdi-reddit</v-icon></span
>
<span class="flex-auto text-left pr-6">{{
creatorProfileStore.creator.socials?.redditUrl
}}</span>
<span class="flex-none">
<v-icon>mdi-chevron-right</v-icon>
</span>
</button>
<button
@click="openDialog('Socials')"
class="HoverBtn active:bg-gray-300 py-2 px-4 border-gray-400 shadow flex items-center transition duration-200 ease-in-out w-full rounded-b"
>
<span class="pa-2 min-w-32 text-left"
><v-icon>mdi-web</v-icon></span
>
<span class="flex-auto text-left pr-6">{{
creatorProfileStore.creator.socials?.websiteUrl
}}</span>
<span class="flex-none">
<v-icon>mdi-chevron-right</v-icon>
</span>
</button>
</div>
</v-card>
</template>
<style>
.HoverBtn:hover {
@apply bg-[#A6147D] text-white;
@apply hover:opacity-90;
}
.custom-border {
border: 3px solid;
}
</style>

View File

@@ -1,147 +1,16 @@
<template>
<!-- Mobile -->
<div v-if="isMobileView" class="flex flex-col items-center justify-center min-h-screen bg-gray-100 p-6 text-center">
<!-- Image -->
<img src="/images/usersmedia/HutopyProfile/profilepictures/profileHutopyProfile01.png" alt="Image" class="w-64 h-64 rounded-full mb-4 border" />
<!-- Message -->
<div class="text-lg text-gray-700 mt-8">
<p class="font-semibold mb-2">Pour vous connecter et modifier votre page, veuillez utiliser un appareil avec un écran plus large, comme un ordinateur.</p>
<p>Pour le moment, l'expérience sur téléphone n'est pas encore complétée.</p>
<p class="mt-4 font-bold">Désolé de l'inconvénient.</p>
</div>
<div class="bg-red flex flex-col gap-8 p-8">
<account-page></account-page>
<creator-page></creator-page>
</div>
<!-- PC -->
<div v-else>
<div class="flex flex-col md:flex-row bg-[#f4f4f4] h-full">
<!-- Left Menu -->
<div class=" z-20 w-full md:max-w-xs fixed md:sticky md:top-0 md:flex md:flex-col top-0">
<div class="sticky top-20 z-30">
<div class="flex flex-col items-center md:items-start md:pl-4 mt-16">
<h1 class="text-2xl py-4 font-bold text-center md:text-left">{{$t('profilemenu.manageyouraccount')}}</h1>
<div class="relative flex items-center md:mt-0 w-full">
<!-- Navigation buttons for small screens -->
<button @click="scrollLeftFunc"
class="rounded p-1 absolute left-2 z-10 md:hidden text-fuchsia-800 text-2xl ">
<v-icon>mdi-chevron-left</v-icon>
</button>
<div
ref="scrollContainer"
class="flex md:flex-col space-x-2 space-y-0 md:space-x-0 md:space-y-2 p-4 items-center md:items-start overflow-x-scroll md:overflow-x-visible mx-2 md:mx-0 custom-scroll min-w-[400px] px-1"
@mousedown="mouseDown"
@mouseleave="mouseLeave"
@mouseup="mouseUp"
@mousemove="mouseMove">
<v-btn variant="text" @click="currentComponent = 'CreatorPage'">
<v-icon class="mr-2">mdi-file-edit-outline</v-icon>
{{ $t('profilemenu.creator') }}
</v-btn>
<v-btn variant="text" @click="currentComponent = 'AccountPage'">
<v-icon class="mr-2">mdi-information</v-icon>
{{ $t('profilemenu.user') }}
</v-btn>
</div>
<button @click="scrollRightFunc"
class="rounded p-1 absolute right-2 z-10 md:hidden text-fuchsia-800 bg-[#f4f4f4] text-2xl">
<v-icon>mdi-chevron-right</v-icon>
</button>
</div>
</div>
</div>
</div>
<!-- Mid Content -->
<div class="flex flex-col flex-1 align-center py-12 p-3 mt-28 md:mt-0">
<template v-if="currentComponent === 'CreatorPage'">
<creator-page></creator-page>
</template>
<template v-else-if="currentComponent === 'AccountPage'">
<account-page></account-page>
</template>
</div>
</div>
</div>
</template>
<script setup>
import { ref, watch } from "vue";
import CreatorPage from "@/views/profile/creators/CreatorPage.vue";
import AccountPage from "@/views/profile/account/AccountPage.vue";
import { useRoute } from "vue-router";
import { useDisplay } from "vuetify";
const { smAndDown } = useDisplay();
const route = useRoute();
const startingComponent = route.query.target || 'CreatorPage';
const currentComponent = ref(startingComponent);
const isMobileView = ref(smAndDown.value);
watch(smAndDown, (newVal) => {
isMobileView.value = newVal;
});
// Gestion du slider (scroll sur petit écran)
const isDown = ref(false);
const startX = ref(0);
const scrollLeft = ref(0);
const mouseDown = (e) => {
const slider = document.querySelector('.custom-scroll');
isDown.value = true;
slider.classList.add('active');
startX.value = e.pageX - slider.offsetLeft;
scrollLeft.value = slider.scrollLeft;
};
const mouseLeave = () => {
isDown.value = false;
const slider = document.querySelector('.custom-scroll');
slider.classList.remove('active');
};
const mouseUp = () => {
isDown.value = false;
const slider = document.querySelector('.custom-scroll');
slider.classList.remove('active');
};
const mouseMove = (e) => {
if (!isDown.value) return;
e.preventDefault();
const slider = document.querySelector('.custom-scroll');
const x = e.pageX - slider.offsetLeft;
const walk = (x - startX.value) * 3; // scroll-fast
slider.scrollLeft = scrollLeft.value - walk;
};
const scrollLeftFunc = () => {
const container = document.querySelector('.custom-scroll');
container.scrollBy({ left: -100, behavior: 'smooth' });
};
const scrollRightFunc = () => {
const container = document.querySelector('.custom-scroll');
container.scrollBy({ left: 100, behavior: 'smooth' });
};
import CreatorPage from "@/views/profile/CreatorPage.vue";
import AccountPage from "@/views/profile/AccountPage.vue";
</script>
<style scoped>
.custom-scroll {
-ms-overflow-style: none; /* Internet Explorer 10+ */
scrollbar-width: none; /* Firefox */
}
.custom-scroll::-webkit-scrollbar {
display: none; /* Safari and Chrome */
}
</style>

View File

@@ -1,6 +1,6 @@
<script setup>
import { ref } from 'vue';
import { useClient } from '@/plugins/api.js';
import {ref} from 'vue';
import {useClient} from '@/plugins/api.js';
const props = defineProps({
creator: {
@@ -14,7 +14,7 @@ const title = ref(props.creator.title);
const client = useClient();
const save = async () => {
async function save() {
try {
await client.post(
`/api/creators/${props.creator.id}/title`,
@@ -28,7 +28,7 @@ const save = async () => {
} catch (error) {
console.error('Error saving title:', error);
}
};
}
const cancel = () => {
emits('closeRequested');
@@ -56,6 +56,7 @@ const cancel = () => {
.flex {
display: flex;
}
.space-y-4 > * + * {
margin-top: 1rem;
}

View File

@@ -1,312 +0,0 @@
<script setup>
import XIcon from '@/assets/icons/x.svg';
import { useCreatorProfileStore } from '@/stores/creatorProfileStore.js';
import ChangeStripeID from '@/views/profile/creators/ChangeStripeID.vue';
import ChangeTitle from '@/views/profile/creators/ChangeTitle.vue';
import { computed, ref } from 'vue';
import BannerPicker from './BannerPicker.vue';
import ColorsPicker from './ColorsPicker.vue';
import LogoPicker from './LogoPicker.vue';
import Socials from './Socials.vue';
const creatorProfileStore = useCreatorProfileStore();
console.log(creatorProfileStore.creator);
const imageBanner = computed(
() =>
creatorProfileStore.creator.images.banner ||
'/images/placeholders/banner.png'
);
const imageLogo = computed(
() =>
creatorProfileStore.creator.images.logo || '/images/placeholders/logo.png'
);
const dialog = ref(false);
const currentComponent = ref('');
const componentsMap = {
BannerPicker,
LogoPicker,
Socials,
ColorsPicker,
ChangeTitle,
ChangeStripeID,
};
function requestCancel() {
currentComponent.value = null;
dialog.value = false;
}
const openDialog = (component) => {
currentComponent.value = componentsMap[component];
dialog.value = true;
};
const closeDialog = () => {
currentComponent.value = null;
dialog.value = false;
};
</script>
<template>
<v-dialog v-model="dialog" max-width="800px">
<v-card
:style="{ borderRadius: '25px', border: '3px solid rgb(159, 76, 173)' }"
>
<v-card-text>
<component
:is="currentComponent"
:creator="creatorProfileStore.creator"
@closeRequested="closeDialog"
@requestAccept="requestAccept"
@requestCancel="requestCancel"
></component>
</v-card-text>
</v-card>
</v-dialog>
<!-- Lorsque l'utilisateur n'a pas de creator name-->
<div class="flex flex-col items-center w-full">
<h1 class="uppercase pb-5 text-2xl">
<v-icon class="mr-2">mdi-file-edit-outline</v-icon>
{{ $t('creatorinfopage.pageinformation') }}
</h1>
<div v-if="creatorProfileStore.hasCreator" class="w-full max-w-[800px]">
<div class="my-10 border rounded bg-white">
<div class="py-5 uppercase ml-4">
{{ $t('creatorinfopage.informations') }}
</div>
<div class="flex flex-col w-full">
<button
class="HoverBtn active:bg-gray-300 py-2 px-4 border-gray-400 shadow flex items-center transition duration-200 ease-in-out w-full"
>
<span class="flex-none pa-2 min-w-32 text-left">{{
$t('creatorinfopage.name')
}}</span>
<span class="flex-auto text-left pr-6 capitalize">{{
creatorProfileStore.creator.name
}}</span>
<span class="flex-none">
<v-icon>mdi-chevron-right</v-icon>
</span>
</button>
</div>
<div class="flex flex-col w-full">
<button
@click="openDialog('ChangeTitle')"
class="HoverBtn active:bg-gray-300 py-2 px-4 border-gray-400 shadow flex items-center transition duration-200 ease-in-out w-full"
>
<span class="flex-none pa-2 min-w-32 text-left">{{
$t('creatorinfopage.title')
}}</span>
<span class="flex-auto text-left pr-6 capitalize">{{
creatorProfileStore.creator.title
}}</span>
<span class="flex-none">
<v-icon>mdi-chevron-right</v-icon>
</span>
</button>
</div>
<div class="flex flex-col w-full">
<button
@click="openDialog('ChangeStripeID')"
class="HoverBtn active:bg-gray-300 py-2 px-4 border-gray-400 shadow flex items-center transition duration-200 ease-in-out w-full rounded-b"
>
<span class="flex-none pa-2 min-w-32 text-left"
>Stripe Account ID</span
>
<span class="flex-auto text-left pr-6">{{
creatorProfileStore.creator.stripeId
}}</span>
<span class="flex-none">
<v-icon>mdi-chevron-right</v-icon>
</span>
</button>
</div>
</div>
<div class="border rounded bg-white">
<div class="py-5 uppercase ml-4">
{{ $t('creatorinfopage.banner&profile') }}
</div>
<div class="flex flex-col w-full gap-4">
<button
@click="openDialog('ColorsPicker')"
class="HoverBtn active:bg-gray-300 py-2 px-4 border-gray-400 shadow flex items-center transition duration-200 ease-in-out w-full"
>
<span class="flex-auto text-left pr-6 capitalize">
Choisissez votre palette de couleurs.
</span>
<span class="flex-none">
<v-icon>mdi-chevron-right</v-icon>
</span>
</button>
<button>
<img
@click="openDialog('BannerPicker')"
:src="imageBanner"
class="w-full transition duration-200 ease-in-out transform hover:brightness-125"
alt="Tutorial Banner"
/>
</button>
<button class="flex justify-center my-5">
<img
@click="openDialog('LogoPicker')"
class="custom-border hover:brightness-125 active:bg-gray-600 shadow flex items-center transition duration-200 ease-in-out w-48 h-48 rounded-full"
:src="imageLogo"
alt="Profile Image"
/>
</button>
</div>
</div>
<div class="mt-10 border rounded bg-white">
<div class="py-5 uppercase ml-4">
{{ $t('creatorinfopage.socialnetwork') }}
</div>
<div class="flex flex-col w-full ">
<button
@click="openDialog('Socials')"
class="HoverBtn active:bg-gray-300 py-2 px-4 border-gray-400 shadow flex items-center transition duration-200 ease-in-out w-full"
>
<span class="pa-2 min-w-32 text-left"
><v-icon>mdi-facebook</v-icon></span
>
<span class="flex-auto text-left pr-6">{{
creatorProfileStore.creator.socials.facebookUrl
}}</span>
<span class="flex-none">
<v-icon>mdi-chevron-right</v-icon>
</span>
</button>
<button
@click="openDialog('Socials')"
class="HoverBtn active:bg-gray-300 py-2 px-4 border-gray-400 shadow flex items-center transition duration-200 ease-in-out w-full"
>
<span class="flex-none pa-2 min-w-32 text-left">
<v-icon>mdi-instagram</v-icon></span
>
<span class="flex-auto text-left pr-6">{{
creatorProfileStore.creator.socials.instagramUrl
}}</span>
<span class="flex-none">
<v-icon>mdi-chevron-right</v-icon>
</span>
</button>
<button
@click="openDialog('Socials')"
class="HoverBtn active:bg-gray-300 py-2 px-4 border-gray-400 shadow flex items-center transition duration-200 ease-in-out w-full"
>
<span class="flex-none pa-2 w-9 h-9 text-left ml-0.5">
<XIcon></XIcon>
</span>
<span class="flex-auto text-left pr-6">{{
creatorProfileStore.creator.socials.xUrl
}}</span>
<span class="flex-none">
<v-icon>mdi-chevron-right</v-icon>
</span>
</button>
<button
@click="openDialog('Socials')"
class="HoverBtn active:bg-gray-300 py-2 px-4 border-gray-400 shadow flex items-center transition duration-200 ease-in-out w-full"
>
<span class="pa-2 min-w-32 text-left"
><v-icon>mdi-linkedin</v-icon></span
>
<span class="flex-auto text-left pr-6">{{
creatorProfileStore.creator.socials.linkedInUrl
}}</span>
<span class="flex-none">
<v-icon>mdi-chevron-right</v-icon>
</span>
</button>
<button
@click="openDialog('Socials')"
class="HoverBtn active:bg-gray-300 py-2 px-4 border-gray-400 shadow flex items-center transition duration-200 ease-in-out w-full"
>
<span class="flex-none pa-2 min-w-32 text-left">
<XIcon class="w-5 h-5"></XIcon>
</span>
<span class="flex-auto text-left pr-6">{{
creatorProfileStore.creator.socials.tikTokUrl
}}</span>
<span class="flex-none">
<v-icon>mdi-chevron-right</v-icon>
</span>
</button>
<button
@click="openDialog('Socials')"
class="HoverBtn active:bg-gray-300 py-2 px-4 border-gray-400 shadow flex items-center transition duration-200 ease-in-out w-full"
>
<span class="pa-2 min-w-32 text-left"
><v-icon>mdi-youtube</v-icon></span
>
<span class="flex-auto text-left pr-6">{{
creatorProfileStore.creator.socials.youtubeUrl
}}</span>
<span class="flex-none">
<v-icon>mdi-chevron-right</v-icon>
</span>
</button>
<button
@click="openDialog('Socials')"
class="HoverBtn active:bg-gray-300 py-2 px-4 border-gray-400 shadow flex items-center transition duration-200 ease-in-out w-full"
>
<span class="pa-2 min-w-32 text-left"
><v-icon>mdi-reddit</v-icon></span
>
<span class="flex-auto text-left pr-6">{{
creatorProfileStore.creator.socials.redditUrl
}}</span>
<span class="flex-none">
<v-icon>mdi-chevron-right</v-icon>
</span>
</button>
<button
@click="openDialog('Socials')"
class="HoverBtn active:bg-gray-300 py-2 px-4 border-gray-400 shadow flex items-center transition duration-200 ease-in-out w-full rounded-b"
>
<span class="pa-2 min-w-32 text-left"
><v-icon>mdi-web</v-icon></span
>
<span class="flex-auto text-left pr-6">{{
creatorProfileStore.creator.socials.websiteUrl
}}</span>
<span class="flex-none">
<v-icon>mdi-chevron-right</v-icon>
</span>
</button>
</div>
</div>
</div>
</div>
</template>
<style>
.HoverBtn:hover {
@apply bg-[#A6147D] text-white;
@apply hover:opacity-90;
}
.custom-border {
border: 3px solid;
}
</style>