Add 'frontend/' from commit 'c070c0315d66a44154ab7d9f9ea6c211a15f4dba'
git-subtree-dir: frontend git-subtree-mainline:205a3bd14bgit-subtree-split:c070c0315d
This commit is contained in:
147
frontend/src/views/profile/ProfilePage.vue
Normal file
147
frontend/src/views/profile/ProfilePage.vue
Normal file
@@ -0,0 +1,147 @@
|
||||
<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>
|
||||
|
||||
<!-- 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' });
|
||||
};
|
||||
</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>
|
||||
299
frontend/src/views/profile/account/AccountPage.vue
Normal file
299
frontend/src/views/profile/account/AccountPage.vue
Normal file
@@ -0,0 +1,299 @@
|
||||
<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 class="w-full">
|
||||
<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="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="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>
|
||||
|
||||
<!-- Phone & email -->
|
||||
<v-card 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="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>-->
|
||||
|
||||
<!-- <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>
|
||||
|
||||
<!-- Modal -->
|
||||
<v-dialog v-model="dialogEditPortraitShown" max-width="600px">
|
||||
<portrait-dialog
|
||||
:portrait-url="userProfileStore.user.portraitUrl"
|
||||
@close="handleCloseEditPortrait"
|
||||
@save="handleSaveEditPortrait"
|
||||
></portrait-dialog>
|
||||
</v-dialog>
|
||||
|
||||
<v-dialog v-model="dialogEditFullnameShown" max-width="600px">
|
||||
<fullname-dialog
|
||||
:firstname="userProfileStore.user.firstname"
|
||||
:lastname="userProfileStore.user.lastname"
|
||||
@close="handleCloseEditFullname"
|
||||
@save="handleSaveEditFullname"
|
||||
></fullname-dialog>
|
||||
</v-dialog>
|
||||
|
||||
<v-dialog v-model="dialogEditAliasShown" max-width="600px">
|
||||
<alias-dialog
|
||||
:alias="userProfileStore.user.alias"
|
||||
@close="handleCloseEditAlias"
|
||||
@save="handleSaveEditAlias"
|
||||
></alias-dialog>
|
||||
</v-dialog>
|
||||
|
||||
<v-dialog v-model="dialogEditBirthdayShown" max-width="600px">
|
||||
<birthday-dialog
|
||||
:birth-date="userProfileStore.user.birthDate"
|
||||
@close="handleCloseEditBirthday"
|
||||
@save="handleSaveEditBirthday"
|
||||
></birthday-dialog>
|
||||
</v-dialog>
|
||||
|
||||
<v-dialog v-model="dialogEditPhoneShown" max-width="600px">
|
||||
<phone-dialog
|
||||
:phone="userProfileStore.user.phoneNumber"
|
||||
@close="handleCloseEditPhone"
|
||||
@save="handleSaveEditPhone"
|
||||
></phone-dialog>
|
||||
</v-dialog>
|
||||
|
||||
<v-dialog v-model="dialogEditEmailShown" max-width="600px">
|
||||
<email-dialog
|
||||
:email="userProfileStore.user.email"
|
||||
@close="handleCloseEditEmail"
|
||||
@save="handleSaveEditEmail"
|
||||
></email-dialog>
|
||||
</v-dialog>
|
||||
|
||||
<v-dialog v-model="dialogEditAddressShown" max-width="600px">
|
||||
<address-dialog
|
||||
:address="userProfileStore.user.address"
|
||||
@close="handleCloseEditAddress"
|
||||
@save="handleSaveEditAddress"
|
||||
></address-dialog>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref} from 'vue';
|
||||
import AddressDialog from './AddressDialog.vue';
|
||||
import EmailDialog from "./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";
|
||||
import FullnameDialog from "@/views/profile/account/FullnameDialog.vue";
|
||||
import PortraitDialog from "@/views/profile/account/PortraitDialog.vue";
|
||||
import {useUserProfileStore} from "@/stores/userProfileStore.js";
|
||||
|
||||
const userProfileStore = useUserProfileStore()
|
||||
|
||||
|
||||
// ### Portrait
|
||||
|
||||
const dialogEditPortraitShown = ref(false)
|
||||
|
||||
function openEditPortrait() {
|
||||
dialogEditPortraitShown.value = true
|
||||
}
|
||||
|
||||
function handleCloseEditPortrait() {
|
||||
dialogEditPortraitShown.value = false
|
||||
}
|
||||
|
||||
function handleSaveEditPortrait(portraitData) {
|
||||
userProfileStore.changePortrait(portraitData)
|
||||
dialogEditPortraitShown.value = false
|
||||
}
|
||||
|
||||
// ### 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
|
||||
}
|
||||
|
||||
// ### Birthday
|
||||
|
||||
const dialogEditBirthdayShown = ref(false)
|
||||
|
||||
function openEditBirthday() {
|
||||
dialogEditBirthdayShown.value = true
|
||||
}
|
||||
|
||||
function handleCloseEditBirthday() {
|
||||
dialogEditBirthdayShown.value = false
|
||||
}
|
||||
|
||||
function handleSaveEditBirthday(birthday) {
|
||||
userProfileStore.changeBirthday(birthday)
|
||||
dialogEditBirthdayShown.value = false
|
||||
}
|
||||
|
||||
// ### Phone
|
||||
|
||||
const dialogEditPhoneShown = ref(false)
|
||||
|
||||
function openEditPhone() {
|
||||
dialogEditPhoneShown.value = true
|
||||
}
|
||||
|
||||
function handleCloseEditPhone() {
|
||||
dialogEditPhoneShown.value = false
|
||||
}
|
||||
|
||||
function handleSaveEditPhone(phone) {
|
||||
userProfileStore.changePhone(phone)
|
||||
dialogEditPhoneShown.value = false
|
||||
}
|
||||
|
||||
// ### Email
|
||||
|
||||
const dialogEditEmailShown = ref(false)
|
||||
|
||||
function openEditEmail() {
|
||||
dialogEditEmailShown.value = true
|
||||
}
|
||||
|
||||
function handleCloseEditEmail() {
|
||||
dialogEditEmailShown.value = false
|
||||
}
|
||||
|
||||
function handleSaveEditEmail(email) {
|
||||
userProfileStore.changeEmail(email)
|
||||
dialogEditEmailShown.value = false
|
||||
}
|
||||
|
||||
// ### ADDRESS
|
||||
const dialogEditAddressShown = ref(false)
|
||||
|
||||
function openEditAddress() {
|
||||
dialogEditAddressShown.value = true
|
||||
}
|
||||
|
||||
function handleCloseEditAddress() {
|
||||
dialogEditAddressShown.value = false
|
||||
}
|
||||
|
||||
function handleSaveEditAddress(address) {
|
||||
userProfileStore.changeAddress(address)
|
||||
dialogEditAddressShown.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.editableValue {
|
||||
@apply py-2 px-4 border-gray-400 shadow flex items-center transition duration-200 ease-in-out w-full;
|
||||
@apply hover:bg-[#A6147D] hover:text-white hover:opacity-90;
|
||||
}
|
||||
|
||||
.label {
|
||||
@apply p-2 min-w-40 text-left;
|
||||
}
|
||||
|
||||
.value {
|
||||
@apply flex-auto pr-6 text-left;
|
||||
}
|
||||
</style>
|
||||
36
frontend/src/views/profile/account/AddressDialog.vue
Normal file
36
frontend/src/views/profile/account/AddressDialog.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
Adresse
|
||||
</v-card-title>
|
||||
|
||||
<div class="m-4">
|
||||
<v-text-field
|
||||
variant="outlined"
|
||||
v-model="address"
|
||||
label="Votre adresse"
|
||||
></v-text-field>
|
||||
</div>
|
||||
|
||||
<v-card-actions>
|
||||
<v-btn variant="plain" @click="requestClose">
|
||||
Annuler
|
||||
</v-btn>
|
||||
<v-btn color="#A6147D" @click="requestSave">
|
||||
Enregistrer
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref} from 'vue';
|
||||
|
||||
const props = defineProps(['address'])
|
||||
const emit = defineEmits(['close', 'save'])
|
||||
|
||||
const address = ref(props.address);
|
||||
|
||||
const requestClose = () => emit('close')
|
||||
const requestSave = () => emit('save', address.value)
|
||||
</script>
|
||||
38
frontend/src/views/profile/account/AliasDialog.vue
Normal file
38
frontend/src/views/profile/account/AliasDialog.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
{{ $t('personnalinformation.alias') }}
|
||||
</v-card-title>
|
||||
|
||||
<div class="m-4">
|
||||
<v-text-field
|
||||
variant="outlined"
|
||||
v-model="alias"
|
||||
:label="$t('personnalinformation.alias')"
|
||||
></v-text-field>
|
||||
</div>
|
||||
|
||||
<v-card-actions>
|
||||
<v-btn variant="plain" @click="requestClose">
|
||||
Annuler
|
||||
</v-btn>
|
||||
<v-btn color="#A6147D" @click="requestSave">
|
||||
Enregistrer
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref} from 'vue';
|
||||
|
||||
const props = defineProps(['alias'])
|
||||
const emit = defineEmits(['close', 'save'])
|
||||
|
||||
const alias = ref(props.alias)
|
||||
|
||||
const requestClose = () => emit('close')
|
||||
const requestSave = () => emit('save', alias.value)
|
||||
</script>
|
||||
|
||||
|
||||
36
frontend/src/views/profile/account/BirthdayDialog.vue
Normal file
36
frontend/src/views/profile/account/BirthdayDialog.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
Date de naissance
|
||||
</v-card-title>
|
||||
|
||||
<div class="m-4">
|
||||
<v-text-field
|
||||
variant="outlined"
|
||||
v-model="birthDate"
|
||||
label="AAAA-MM-JJ"
|
||||
></v-text-field>
|
||||
</div>
|
||||
|
||||
<v-card-actions>
|
||||
<v-btn variant="plain" @click="requestClose">
|
||||
Annuler
|
||||
</v-btn>
|
||||
<v-btn color="#A6147D" @click="requestSave">
|
||||
Enregistrer
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref} from 'vue';
|
||||
|
||||
const props = defineProps(['birthDate'])
|
||||
const emit = defineEmits(['close', 'save'])
|
||||
|
||||
const birthDate = ref(props.birthDate)
|
||||
|
||||
const requestClose = () => emit('close')
|
||||
const requestSave = () => emit('save', birthDate.value)
|
||||
</script>
|
||||
38
frontend/src/views/profile/account/EmailDialog.vue
Normal file
38
frontend/src/views/profile/account/EmailDialog.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
Courriel
|
||||
</v-card-title>
|
||||
|
||||
<div class="m-4">
|
||||
<v-text-field
|
||||
variant="outlined"
|
||||
v-model="email"
|
||||
label="Votre courriel"
|
||||
></v-text-field>
|
||||
</div>
|
||||
|
||||
<v-card-actions>
|
||||
<v-btn variant="plain" @click="requestClose">
|
||||
Annuler
|
||||
</v-btn>
|
||||
<v-btn color="#A6147D" @click="requestSave">
|
||||
Enregistrer
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref} from 'vue';
|
||||
|
||||
const props = defineProps(['email'])
|
||||
const emit = defineEmits(['close', 'save'])
|
||||
|
||||
const email = ref(props.email)
|
||||
|
||||
const requestClose = () => emit('close')
|
||||
const requestSave = () => emit('save', email.value)
|
||||
</script>
|
||||
|
||||
|
||||
45
frontend/src/views/profile/account/FullnameDialog.vue
Normal file
45
frontend/src/views/profile/account/FullnameDialog.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
{{ $t('personnalinformation.fullname') }}
|
||||
</v-card-title>
|
||||
|
||||
<div class="m-4">
|
||||
<v-text-field
|
||||
variant="outlined"
|
||||
v-model="firstname"
|
||||
:label="$t('personnalinformation.firstname')"
|
||||
></v-text-field>
|
||||
|
||||
<v-text-field
|
||||
variant="outlined"
|
||||
v-model="lastname"
|
||||
:label="$t('personnalinformation.lastname')"
|
||||
></v-text-field>
|
||||
</div>
|
||||
|
||||
<v-card-actions>
|
||||
<v-btn variant="plain" @click="requestClose">
|
||||
Annuler
|
||||
</v-btn>
|
||||
<v-btn color="#A6147D" @click="requestSave">
|
||||
Enregistrer
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref} from 'vue';
|
||||
|
||||
const props = defineProps(['firstname', 'lastname'])
|
||||
const emit = defineEmits(['close', 'save'])
|
||||
|
||||
const firstname = ref(props.firstname)
|
||||
const lastname = ref(props.lastname)
|
||||
|
||||
const requestClose = () => emit('close')
|
||||
const requestSave = () => emit('save', firstname.value, lastname.value)
|
||||
</script>
|
||||
|
||||
|
||||
37
frontend/src/views/profile/account/PhoneDialog.vue
Normal file
37
frontend/src/views/profile/account/PhoneDialog.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
Numéro de téléphone
|
||||
</v-card-title>
|
||||
|
||||
<div class="m-4">
|
||||
<v-text-field
|
||||
variant="outlined"
|
||||
v-model="phone"
|
||||
label="Votre numéro de téléphone"
|
||||
></v-text-field>
|
||||
</div>
|
||||
|
||||
<v-card-actions>
|
||||
<v-btn variant="plain" @click="requestClose">
|
||||
Annuler
|
||||
</v-btn>
|
||||
<v-btn color="#A6147D" @click="requestSave">
|
||||
Enregistrer
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref} from 'vue';
|
||||
|
||||
const props = defineProps(['phone'])
|
||||
const emit = defineEmits(['close', 'save'])
|
||||
|
||||
const phone = ref(props.phone)
|
||||
|
||||
const requestClose = () => emit('close')
|
||||
const requestSave = () => emit('save', phone.value)
|
||||
</script>
|
||||
58
frontend/src/views/profile/account/PortraitDialog.vue
Normal file
58
frontend/src/views/profile/account/PortraitDialog.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
Portrait
|
||||
</v-card-title>
|
||||
|
||||
<div class="m-4">
|
||||
<img
|
||||
:src="portraitData"
|
||||
class="mb-5 w-full transition duration-200 ease-in-out transform"
|
||||
alt="Aperçu de la bannière"
|
||||
/>
|
||||
|
||||
<v-file-input
|
||||
@change="onSelectedFileChanged"
|
||||
v-model="selectedFile"
|
||||
variant="outlined"
|
||||
accept="image/*"
|
||||
label="Votre bannière"
|
||||
></v-file-input>
|
||||
</div>
|
||||
|
||||
<v-card-actions>
|
||||
<v-btn variant="plain" @click="requestClose">
|
||||
Annuler
|
||||
</v-btn>
|
||||
<v-btn color="#A6147D" @click="requestSave">
|
||||
Enregistrer
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup>
|
||||
import {ref} from 'vue';
|
||||
|
||||
const props = defineProps(['portraitUrl'])
|
||||
const emit = defineEmits(['close', 'save'])
|
||||
|
||||
const portraitData = ref(props.portraitUrl)
|
||||
|
||||
const selectedFile = ref({})
|
||||
const onSelectedFileChanged = () => {
|
||||
if (selectedFile.value) {
|
||||
const reader = new FileReader()
|
||||
reader.onload = (event) => {
|
||||
portraitData.value = event.target.result
|
||||
}
|
||||
reader.readAsDataURL(selectedFile.value)
|
||||
} else {
|
||||
portraitData.value = null
|
||||
}
|
||||
}
|
||||
|
||||
const requestClose = () => emit('close')
|
||||
const requestSave = () => emit('save', selectedFile.value)
|
||||
</script>
|
||||
75
frontend/src/views/profile/creators/BannerPicker.vue
Normal file
75
frontend/src/views/profile/creators/BannerPicker.vue
Normal file
@@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<h2 class="text-2xl font-semibold mb-4 flex justify-center">
|
||||
Bannière
|
||||
</h2>
|
||||
|
||||
<img
|
||||
:src="fileUrl || fallbackUrl"
|
||||
class="mb-5 w-full transition duration-200 ease-in-out transform"
|
||||
alt="Aperçu de la bannière"
|
||||
>
|
||||
|
||||
<v-file-input
|
||||
v-model="selectedFile"
|
||||
variant="outlined"
|
||||
accept="image/*"
|
||||
label="Votre bannière"
|
||||
@change="onFileSelected"
|
||||
></v-file-input>
|
||||
|
||||
<div class="flex justify-end space-x-4">
|
||||
<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'
|
||||
|
||||
const props = defineProps({
|
||||
creator: {
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const emits = defineEmits(['closeRequested'])
|
||||
|
||||
const selectedFile = ref({})
|
||||
const fileUrl = ref(props.creator.images.banner)
|
||||
const fallbackUrl = '/images/hutopymedia/banners/hutopyul.png'
|
||||
|
||||
const onFileSelected = () => {
|
||||
if (selectedFile.value) {
|
||||
const reader = new FileReader()
|
||||
reader.onload = (event) => {
|
||||
fileUrl.value = event.target.result
|
||||
}
|
||||
reader.readAsDataURL(selectedFile.value)
|
||||
} else {
|
||||
fileUrl.value = null
|
||||
}
|
||||
}
|
||||
|
||||
const client = useClient()
|
||||
const publish = async () => {
|
||||
try {
|
||||
const formData = new FormData()
|
||||
formData.append('file', selectedFile.value)
|
||||
|
||||
await client.post(
|
||||
`/api/creators/${props.creator.id}/banner`,
|
||||
formData
|
||||
)
|
||||
|
||||
props.creator.images.banner = fileUrl
|
||||
emits('closeRequested')
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
const cancel = () => {
|
||||
emits('closeRequested')
|
||||
}
|
||||
</script>
|
||||
59
frontend/src/views/profile/creators/ChangeStripeID.vue
Normal file
59
frontend/src/views/profile/creators/ChangeStripeID.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<script setup>
|
||||
import { useClient } from '@/plugins/api.js';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
creator: {
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emits = defineEmits(['closeRequested']);
|
||||
|
||||
const stripeId = ref(props.creator.stripeId);
|
||||
|
||||
const client = useClient();
|
||||
|
||||
const save = async () => {
|
||||
try {
|
||||
await client.post(`/api/membership/stripe-account`, {
|
||||
stripeAccountId: stripeId.value,
|
||||
});
|
||||
|
||||
props.creator.stripeId = stripeId.value;
|
||||
emits('closeRequested');
|
||||
} catch (error) {
|
||||
console.error('Error saving stripe id:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const cancel = () => {
|
||||
emits('closeRequested');
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="pb-5 text-2xl">Modifier le id Stripe</div>
|
||||
<div class="flex flex-col space-y-4">
|
||||
<v-text-field
|
||||
variant="outlined"
|
||||
v-model="stripeId"
|
||||
label="Stripe Id"
|
||||
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" @click="save">Enregistrer</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
.space-y-4 > * + * {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
</style>
|
||||
62
frontend/src/views/profile/creators/ChangeTitle.vue
Normal file
62
frontend/src/views/profile/creators/ChangeTitle.vue
Normal file
@@ -0,0 +1,62 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useClient } from '@/plugins/api.js';
|
||||
|
||||
const props = defineProps({
|
||||
creator: {
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const emits = defineEmits(['closeRequested']);
|
||||
|
||||
const title = ref(props.creator.title);
|
||||
|
||||
const client = useClient();
|
||||
|
||||
const save = async () => {
|
||||
try {
|
||||
await client.post(
|
||||
`/api/creators/${props.creator.id}/title`,
|
||||
{
|
||||
title: title.value
|
||||
}
|
||||
);
|
||||
|
||||
props.creator.title = title.value;
|
||||
emits('closeRequested');
|
||||
} catch (error) {
|
||||
console.error('Error saving title:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const cancel = () => {
|
||||
emits('closeRequested');
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="pb-5 text-2xl">Modifier le Titre</div>
|
||||
<div class="flex flex-col space-y-4">
|
||||
<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" @click="save">Enregistrer</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
.space-y-4 > * + * {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
</style>
|
||||
250
frontend/src/views/profile/creators/ColorsPicker.vue
Normal file
250
frontend/src/views/profile/creators/ColorsPicker.vue
Normal file
@@ -0,0 +1,250 @@
|
||||
<script setup>
|
||||
import {ref, computed} from 'vue';
|
||||
import {useClient} from "@/plugins/api.js";
|
||||
|
||||
const props = defineProps({
|
||||
creator: {
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const emits = defineEmits(['closeRequested']);
|
||||
|
||||
const primaryColor = ref(props.creator.colors.primary)
|
||||
const secondaryColor = ref(props.creator.colors.secondary)
|
||||
const backgroundColor = ref(props.creator.colors.background)
|
||||
const errorColor = ref(props.creator.colors.error)
|
||||
const surfaceColor = ref(props.creator.colors.surface)
|
||||
const onPrimaryColor = ref(props.creator.colors.onPrimary)
|
||||
const onSecondaryColor = ref(props.creator.colors.onSecondary)
|
||||
const onBackgroundColor = ref(props.creator.colors.onBackground)
|
||||
const onErrorColor = ref(props.creator.colors.onError)
|
||||
const onSurfaceColor = ref(props.creator.colors.onSurface)
|
||||
|
||||
const selectedColorName = ref('primary');
|
||||
const selectedBackgroundColor = computed({
|
||||
get() {
|
||||
switch (selectedColorName.value) {
|
||||
case 'primary':
|
||||
return primaryColor.value
|
||||
case 'secondary':
|
||||
return secondaryColor.value
|
||||
case 'background':
|
||||
return backgroundColor.value
|
||||
case 'error':
|
||||
return errorColor.value
|
||||
case 'surface':
|
||||
return surfaceColor.value
|
||||
default:
|
||||
return '#e42aad';
|
||||
}
|
||||
},
|
||||
set(value) {
|
||||
switch (selectedColorName.value) {
|
||||
case 'primary':
|
||||
primaryColor.value = value
|
||||
break
|
||||
case 'secondary':
|
||||
secondaryColor.value = value
|
||||
break
|
||||
case 'background':
|
||||
backgroundColor.value = value
|
||||
break
|
||||
case 'error':
|
||||
errorColor.value = value
|
||||
break
|
||||
case 'surface':
|
||||
surfaceColor.value = value
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
const selectedTextColor = computed({
|
||||
get() {
|
||||
switch (selectedColorName.value) {
|
||||
case 'primary':
|
||||
return onPrimaryColor.value
|
||||
case 'secondary':
|
||||
return onSecondaryColor.value
|
||||
case 'background':
|
||||
return onBackgroundColor.value
|
||||
case 'error':
|
||||
return onErrorColor.value
|
||||
case 'surface':
|
||||
return onSurfaceColor.value
|
||||
default:
|
||||
return '#e42aad';
|
||||
}
|
||||
},
|
||||
set(value) {
|
||||
switch (selectedColorName.value) {
|
||||
case 'primary':
|
||||
onPrimaryColor.value = value
|
||||
break
|
||||
case 'secondary':
|
||||
onSecondaryColor.value = value
|
||||
break
|
||||
case 'background':
|
||||
onBackgroundColor.value = value
|
||||
break
|
||||
case 'error':
|
||||
onErrorColor.value = value
|
||||
break
|
||||
case 'surface':
|
||||
onSurfaceColor.value = value
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const selectColor = (colorName) => {
|
||||
selectedColorName.value = colorName;
|
||||
};
|
||||
|
||||
const client = useClient();
|
||||
const save = async () => {
|
||||
try {
|
||||
await client.post(
|
||||
`/api/creators/${props.creator.id}/colors`,
|
||||
{
|
||||
'primary': primaryColor.value,
|
||||
'secondary': secondaryColor.value,
|
||||
'background': backgroundColor.value,
|
||||
'error': errorColor.value,
|
||||
'surface': surfaceColor.value,
|
||||
'onPrimary': onPrimaryColor.value,
|
||||
'onSecondary': onSecondaryColor.value,
|
||||
'onBackground': onBackgroundColor.value,
|
||||
'onError': onErrorColor.value,
|
||||
'onSurface': onSurfaceColor.value,
|
||||
});
|
||||
|
||||
props.creator.colors.primary = primaryColor.value
|
||||
props.creator.colors.secondary = secondaryColor.value
|
||||
props.creator.colors.background = backgroundColor.value
|
||||
props.creator.colors.error = errorColor.value
|
||||
props.creator.colors.surface = surfaceColor.value
|
||||
props.creator.colors.onPrimary = onPrimaryColor.value
|
||||
props.creator.colors.onSecondary = onSecondaryColor.value
|
||||
props.creator.colors.onBackground = onBackgroundColor.value
|
||||
props.creator.colors.onError = onErrorColor.value
|
||||
props.creator.colors.onSurface = onSurfaceColor.value
|
||||
|
||||
emits('closeRequested');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const cancel = () => {
|
||||
emits('closeRequested');
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h2 class="text-2xl font-semibold mb-4 flex justify-center">
|
||||
Palette de Couleurs
|
||||
</h2>
|
||||
|
||||
<div class="flex flex-column gap-6 justify-center items-start mt-5">
|
||||
|
||||
<div class="grid grid-cols-5 grid-rows-1 gap-4">
|
||||
<div
|
||||
class="color-square"
|
||||
:class="{ selected: selectedColorName === 'primary' }"
|
||||
:style="{ backgroundColor: primaryColor }"
|
||||
@click="selectColor('primary')"
|
||||
>
|
||||
<span class="color-name"
|
||||
:style="{ color: onPrimaryColor }">
|
||||
Primary
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="color-square"
|
||||
:class="{ selected: selectedColorName === 'secondary' }"
|
||||
:style="{ backgroundColor: secondaryColor }"
|
||||
@click="selectColor('secondary')"
|
||||
>
|
||||
<span class="color-name"
|
||||
:style="{ color: onSecondaryColor }">
|
||||
Secondary
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="color-square"
|
||||
:class="{ selected: selectedColorName === 'surface' }"
|
||||
:style="{ backgroundColor: surfaceColor }"
|
||||
@click="selectColor('surface')"
|
||||
>
|
||||
<span class="color-name"
|
||||
:style="{ color: onSurfaceColor }">
|
||||
Surface
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="color-square"
|
||||
:class="{ selected: selectedColorName === 'background' }"
|
||||
:style="{ backgroundColor: backgroundColor }"
|
||||
@click="selectColor('background')"
|
||||
>
|
||||
<span class="color-name"
|
||||
:style="{ color: onBackgroundColor }">
|
||||
Background
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="color-square"
|
||||
:class="{ selected: selectedColorName === 'error' }"
|
||||
:style="{ backgroundColor: errorColor }"
|
||||
@click="selectColor('error')"
|
||||
>
|
||||
<span class="color-name"
|
||||
:style="{ color: onErrorColor }"
|
||||
>
|
||||
Error
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex row justify-center space-x-12 mx-auto">
|
||||
<div class="flex flex-col items-center">
|
||||
<span class="mb-2 font-weight-bold">Text</span>
|
||||
<v-color-picker v-model="selectedTextColor"></v-color-picker>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<span class="mb-2 font-weight-bold">Background</span>
|
||||
<v-color-picker v-model="selectedBackgroundColor"></v-color-picker>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end space-x-4 mt-4">
|
||||
<v-btn color="black" variant="text" @click="cancel">Annuler</v-btn>
|
||||
<v-btn color="#A6147D" @click="save">Enregistrer</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.color-square {
|
||||
@apply w-[150px] h-[150px];
|
||||
@apply flex rounded-md cursor-pointer relative;
|
||||
@apply items-center justify-center;
|
||||
@apply font-bold text-2xl;
|
||||
}
|
||||
|
||||
.color-square.selected {
|
||||
@apply border-4 border-solid border-[crimson];
|
||||
}
|
||||
|
||||
.color-name {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
84
frontend/src/views/profile/creators/CreateCreator.vue
Normal file
84
frontend/src/views/profile/creators/CreateCreator.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<script setup>
|
||||
import {ref} 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";
|
||||
|
||||
const creatorName = ref('');
|
||||
const errorMessage = ref('');
|
||||
const isLoading = ref(false);
|
||||
|
||||
const router = useRouter();
|
||||
const creatorProfileStore = useCreatorProfileStore();
|
||||
const userProfileStore = useUserProfileStore();
|
||||
|
||||
async function createAccount() {
|
||||
const client = useClient();
|
||||
try {
|
||||
errorMessage.value = '';
|
||||
isLoading.value = true;
|
||||
const normalizedCreatorName = creatorName.value.toLowerCase();
|
||||
await client.post('/api/creators', {
|
||||
creatorId: userProfileStore.user.id,
|
||||
name: normalizedCreatorName,
|
||||
});
|
||||
await creatorProfileStore.fetchCurrentCreatorProfile();
|
||||
await router.push(`/@${normalizedCreatorName}`);
|
||||
} catch (error) {
|
||||
if (error?.response?.data?.errors) {
|
||||
errorMessage.value = error.response.data.errors[0]?.['reason'] || 'An unexpected error occurred.';
|
||||
} else {
|
||||
errorMessage.value = error?.response?.data?.message || error.message || 'An unexpected error occurred.';
|
||||
}
|
||||
} finally {
|
||||
isLoading.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>
|
||||
<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">
|
||||
|
||||
<v-btn
|
||||
:disabled="isLoading"
|
||||
variant="outlined"
|
||||
@click="createAccount"
|
||||
:style="{ borderColor: 'rgb(159, 76, 173)', color: 'rgb(159, 76, 173)' }"
|
||||
>
|
||||
Créer
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.create-creator-card {
|
||||
@apply text-center max-w-[1000px] mx-auto p-10 bg-white shadow-2xl rounded mt-16 ;
|
||||
}
|
||||
|
||||
</style>
|
||||
312
frontend/src/views/profile/creators/CreatorPage.vue
Normal file
312
frontend/src/views/profile/creators/CreatorPage.vue
Normal file
@@ -0,0 +1,312 @@
|
||||
<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>
|
||||
78
frontend/src/views/profile/creators/LogoPicker.vue
Normal file
78
frontend/src/views/profile/creators/LogoPicker.vue
Normal file
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<h2 class="text-2xl font-semibold mb-4 flex justify-center">
|
||||
Logo
|
||||
</h2>
|
||||
|
||||
<div class="flex justify-center mb-5">
|
||||
<img
|
||||
:src="fileUrl || fallbackUrl"
|
||||
class="w-full transition duration-200 ease-in-out transform max-w-[400px]"
|
||||
alt="Aperçu du logo"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<v-file-input
|
||||
v-model="selectedFile"
|
||||
variant="outlined"
|
||||
accept="image/*"
|
||||
label="Votre logo"
|
||||
@change="onFileSelected"
|
||||
></v-file-input>
|
||||
|
||||
<div class="flex justify-end space-x-4">
|
||||
<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'
|
||||
|
||||
const props = defineProps({
|
||||
creator: {
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const emits = defineEmits(['closeRequested'])
|
||||
|
||||
const selectedFile = ref("")
|
||||
const fileUrl = ref(props.creator.images.logo)
|
||||
const fallbackUrl = '/images/usersmedia/HutopyProfile/profilepictures/profileHutopyProfile01.png' // Chemin de votre image de secours
|
||||
|
||||
const onFileSelected = () => {
|
||||
if (selectedFile.value) {
|
||||
const reader = new FileReader()
|
||||
reader.onload = (event) => {
|
||||
fileUrl.value = event.target.result
|
||||
}
|
||||
reader.readAsDataURL(selectedFile.value)
|
||||
} else {
|
||||
fileUrl.value = null
|
||||
}
|
||||
}
|
||||
|
||||
const client = useClient()
|
||||
const publish = async () => {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('file', selectedFile.value)
|
||||
|
||||
await client.post(
|
||||
`/api/creators/${props.creator.id}/logo`,
|
||||
formData)
|
||||
|
||||
props.creator.images.logo = fileUrl.value;
|
||||
emits('closeRequested');
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
const cancel = () => {
|
||||
emits('closeRequested')
|
||||
}
|
||||
</script>
|
||||
|
||||
161
frontend/src/views/profile/creators/Socials.vue
Normal file
161
frontend/src/views/profile/creators/Socials.vue
Normal file
@@ -0,0 +1,161 @@
|
||||
<script setup>
|
||||
import XIcon from '@/assets/icons/x.svg'
|
||||
import {ref} from 'vue'
|
||||
import {useClient} from "@/plugins/api.js";
|
||||
|
||||
const props = defineProps({
|
||||
creator: {
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const emits = defineEmits(['closeRequested'])
|
||||
|
||||
const facebookUrl = ref(props.creator.socials.facebookUrl)
|
||||
const instagramUrl = ref(props.creator.socials.instagramUrl)
|
||||
const linkedInUrl = ref(props.creator.socials.linkedInUrl)
|
||||
const redditUrl = ref(props.creator.socials.redditUrl)
|
||||
const tikTokUrl = ref(props.creator.socials.tikTokUrl)
|
||||
const websiteUrl = ref(props.creator.socials.websiteUrl)
|
||||
const xUrl = ref(props.creator.socials.xUrl)
|
||||
const youtubeUrl = ref(props.creator.socials.youtubeUrl)
|
||||
|
||||
const client = useClient()
|
||||
const save = async () => {
|
||||
try {
|
||||
await client.post(
|
||||
`/api/creators/${props.creator.id}/socials`,
|
||||
{
|
||||
"facebookUrl": facebookUrl.value || null,
|
||||
"instagramUrl": instagramUrl.value || null,
|
||||
"linkedInUrl": linkedInUrl.value || null,
|
||||
"redditUrl": redditUrl.value || null,
|
||||
"tikTokUrl": tikTokUrl.value || null,
|
||||
"websiteUrl": websiteUrl.value || null,
|
||||
"xUrl": xUrl.value || null,
|
||||
"youtubeUrl": youtubeUrl.value || null,
|
||||
})
|
||||
|
||||
props.creator.socials.facebookUrl = facebookUrl
|
||||
props.creator.socials.instagramUrl = instagramUrl
|
||||
props.creator.socials.linkedInUrl = linkedInUrl
|
||||
props.creator.socials.redditUrl = redditUrl
|
||||
props.creator.socials.tikTokUrl = tikTokUrl
|
||||
props.creator.socials.websiteUrl = websiteUrl
|
||||
props.creator.socials.xUrl = xUrl
|
||||
props.creator.socials.youtubeUrl = youtubeUrl
|
||||
|
||||
emits('closeRequested')
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
const cancel = () => {
|
||||
emits('closeRequested')
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.icon {
|
||||
|
||||
width: 28px;
|
||||
height: 30px;
|
||||
fill: #000000;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div class="pb-5 text-2xl">Reseaux Sociaux</div>
|
||||
<div class="flex flex-row align-center">
|
||||
<v-icon class="mb-5 mr-2">mdi-facebook</v-icon>
|
||||
<v-text-field
|
||||
variant="outlined"
|
||||
v-model="facebookUrl"
|
||||
label="Lien Facebook"
|
||||
outlined
|
||||
></v-text-field>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="flex flex-row align-center">
|
||||
<v-icon class="mb-5 mr-2">mdi-instagram</v-icon>
|
||||
<v-text-field
|
||||
variant="outlined"
|
||||
v-model="instagramUrl"
|
||||
label="Lien Instagram"
|
||||
outlined
|
||||
></v-text-field>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row align-center">
|
||||
<v-icon class="mb-5 mr-2">mdi-linkedin</v-icon>
|
||||
<v-text-field
|
||||
variant="outlined"
|
||||
v-model="linkedInUrl"
|
||||
label="Lien LinkedIn"
|
||||
outlined
|
||||
></v-text-field>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row align-center">
|
||||
<v-icon class="mb-5 mr-2">mdi-reddit</v-icon>
|
||||
<v-text-field
|
||||
variant="outlined"
|
||||
v-model="redditUrl"
|
||||
label="Lien Reddit"
|
||||
outlined
|
||||
></v-text-field>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row align-center">
|
||||
<div class="w-6 h-6 mb-5 mr-2">
|
||||
<XIcon></XIcon>
|
||||
</div>
|
||||
<v-text-field
|
||||
variant="outlined"
|
||||
v-model="tikTokUrl"
|
||||
label="Lien TikTok"
|
||||
outlined
|
||||
></v-text-field>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row align-center">
|
||||
<v-icon class="mb-5 mr-2">mdi-web</v-icon>
|
||||
<v-text-field
|
||||
variant="outlined"
|
||||
v-model="websiteUrl"
|
||||
label="Lien site web"
|
||||
outlined
|
||||
></v-text-field>
|
||||
</div>
|
||||
<div class="flex flex-row align-center">
|
||||
|
||||
<div class="w-6 h-6 mb-5 mr-2">
|
||||
<XIcon class="icon"></XIcon>
|
||||
</div>
|
||||
|
||||
<v-text-field
|
||||
variant="outlined"
|
||||
v-model="xUrl"
|
||||
label="Lien X"
|
||||
outlined
|
||||
></v-text-field>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row align-center">
|
||||
<v-icon class="mb-5 mr-2">mdi-youtube</v-icon>
|
||||
<v-text-field
|
||||
variant="outlined"
|
||||
v-model="youtubeUrl"
|
||||
label="Lien Youtube"
|
||||
outlined
|
||||
></v-text-field>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end space-x-4">
|
||||
<v-btn color="black" variant="text" @click="cancel">Annuler</v-btn>
|
||||
<v-btn color="#A6147D" @click="save">Enregistrer</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user