Files
social-media/frontend/src/views/creators/CreatorLogoEditor.vue

289 lines
6.2 KiB
Vue

<template>
<div class="card">
<div class="card-title">
Choisissez votre Logo
</div>
<div class="card-content">
<p class="card-text">
Le logo doit être carré. Les dimensions recommandées sont 200 x 200 pixels.
</p>
<div class="file-input-container">
<input
type="file"
ref="fileInput"
accept="image/*"
class="hidden"
@change="onFileSelected"
/>
<button
class="choose-file-button"
@click="triggerFileInput"
>
Choisir une image...
</button>
</div>
<div v-if="errorMessage" class="error-message">
{{ errorMessage }}
</div>
<div v-if="showCropper" class="cropper-wrapper">
<Cropper
ref="cropper"
:src="fileUrl"
:aspect-ratio="1"
:stencil-component="CircleStencil"
:stencil-props="{
aspectRatio: 1,
class: 'circle-stencil'
}"
/>
</div>
<div v-else class="image-preview-container" @click="startEditing">
<div class="circular-preview">
<img
:src="fileUrl || fallbackUrl"
alt="Aperçu du logo"
class="preview-image"
/>
<div class="edit-overlay">
<span class="edit-text">Cliquez pour modifier</span>
</div>
</div>
</div>
</div>
<div class="card-actions">
<button class="secondary"
@click="cancel">
Annuler
</button>
<button class="primary"
@click="showCropper ? applyCrop() : publish()"
:disabled="!selectedFile">
{{ showCropper ? 'Appliquer' : 'Enregistrer' }}
</button>
</div>
</div>
</template>
<script setup>
import {ref} from 'vue'
import {useClient} from '@/plugins/api.js'
import { Cropper, CircleStencil } from 'vue-advanced-cropper'
import 'vue-advanced-cropper/dist/style.css'
const props = defineProps({
creator: {
required: true
}
})
const emits = defineEmits(['closeRequested'])
const fileInput = ref(null)
const selectedFile = ref(null)
const fileUrl = ref(props.creator.images.logo)
const fallbackUrl = '/images/usersmedia/HutopyProfile/profilepictures/profileHutopyProfile01.png'
const errorMessage = ref('')
const showCropper = ref(false)
const cropper = ref(null)
const TARGET_WIDTH = 200
const TARGET_HEIGHT = 200
const triggerFileInput = () => {
fileInput.value.click()
}
const onFileSelected = (event) => {
const file = event.target.files[0]
if (file) {
selectedFile.value = file
const reader = new FileReader()
reader.onload = (e) => {
fileUrl.value = e.target.result
showCropper.value = true
}
reader.readAsDataURL(file)
} else {
selectedFile.value = null
fileUrl.value = null
showCropper.value = false
}
}
const startEditing = () => {
if (fileUrl.value && fileUrl.value !== fallbackUrl) {
// Create a temporary file from the current image URL
fetch(fileUrl.value)
.then(res => res.blob())
.then(blob => {
selectedFile.value = new File([blob], 'current-image.jpg', { type: 'image/jpeg' })
showCropper.value = true
})
.catch(error => {
console.error('Error loading image for editing:', error)
errorMessage.value = 'Une erreur est survenue lors du chargement de l\'image'
})
}
}
const applyCrop = () => {
if (!cropper.value) return
const canvas = cropper.value.getResult().canvas
canvas.toBlob((blob) => {
const croppedFile = new File([blob], selectedFile.value.name, {
type: selectedFile.value.type
})
selectedFile.value = croppedFile
fileUrl.value = canvas.toDataURL()
showCropper.value = false
}, selectedFile.value.type)
}
const client = useClient()
const publish = async () => {
if (!selectedFile.value) return
try {
const formData = new FormData()
formData.append('file', selectedFile.value)
const response = await client.post(
`/api/creators/${props.creator.id}/logo`,
formData
)
props.creator.images.logo = `${response.data.blobUrl}?t=${Date.now()}`
emits('closeRequested')
} catch (error) {
console.error(error)
errorMessage.value = 'Une erreur est survenue lors de l\'envoi de l\'image'
}
}
const cancel = () => {
showCropper.value = false
// Reset to original state if we were editing
if (props.creator.images.logo) {
fileUrl.value = props.creator.images.logo
selectedFile.value = null
} else {
fileUrl.value = fallbackUrl
selectedFile.value = null
}
emits('closeRequested')
}
</script>
<style scoped>
.card-text {
@apply font-sans text-lg;
}
.image-preview-container {
@apply mb-5;
@apply w-full;
@apply flex;
@apply justify-center;
@apply items-center;
}
.circular-preview {
@apply w-[200px];
@apply h-[200px];
@apply rounded-full;
@apply overflow-hidden;
@apply border-2;
@apply border-gray-200;
@apply relative;
@apply cursor-pointer;
}
.preview-image {
@apply w-full;
@apply h-full;
@apply object-cover;
}
.edit-overlay {
@apply absolute;
@apply inset-0;
@apply flex;
@apply items-center;
@apply justify-center;
@apply bg-black;
@apply bg-opacity-0;
@apply transition-opacity;
@apply duration-200;
}
.circular-preview:hover .edit-overlay {
@apply bg-opacity-30;
}
.edit-text {
@apply text-white;
@apply font-medium;
@apply opacity-0;
@apply transition-opacity;
@apply duration-200;
}
.circular-preview:hover .edit-text {
@apply opacity-100;
}
.cropper-wrapper {
@apply mb-5;
@apply w-full;
@apply h-[400px];
@apply flex;
@apply justify-center;
@apply items-center;
@apply overflow-hidden;
}
.file-input-container {
@apply flex;
@apply justify-center;
@apply items-center;
@apply w-full;
}
.choose-file-button {
@apply px-4;
@apply py-2;
@apply primary;
@apply rounded-lg;
@apply cursor-pointer;
}
.error-message {
@apply text-red-500;
@apply mt-2;
@apply text-center;
@apply font-medium;
}
:deep(.circle-stencil) {
@apply border-2;
@apply border-white;
@apply rounded-full;
}
:deep(.cropper) {
@apply max-h-full;
}
:deep(.cropper__stencil) {
@apply rounded-full;
}
</style>