fix(album): removing last album image was not working
This commit is contained in:
@@ -1,40 +1,27 @@
|
||||
<template>
|
||||
<div class="p-4 relative"
|
||||
@mouseenter="showEditButtons = isLoggedIn && creatorProfileStore.creator?.id === brandingStore.value.id"
|
||||
@mouseleave="showEditButtons = false">
|
||||
<div class="relative p-4"
|
||||
@mouseenter="showEditButtons = isLoggedIn && creatorProfileStore.creator?.id === brandingStore.value.id"
|
||||
@mouseleave="showEditButtons = false">
|
||||
|
||||
<!-- Edit buttons with absolute positioning -->
|
||||
<div v-if="showEditButtons || isEditMode"
|
||||
class="absolute top-4 right-4 flex gap-2">
|
||||
<div v-if="showEditButtons || isEditMode" class="absolute right-4 top-4 flex gap-2">
|
||||
|
||||
<!-- Edit button with pencil icon -->
|
||||
<button
|
||||
v-if="!isEditMode"
|
||||
:title="t('edit')"
|
||||
class="w-12 h-12 bg-hutopyPrimary rounded-full flex items-center justify-center shadow-lg"
|
||||
@click="toggleEditMode()"
|
||||
>
|
||||
<button v-if="!isEditMode" :title="t('edit')"
|
||||
class="flex size-12 items-center justify-center rounded-full bg-hutopyPrimary shadow-lg"
|
||||
@click="toggleEditMode()">
|
||||
<v-icon large>mdi-pencil</v-icon>
|
||||
</button>
|
||||
|
||||
<!-- Save button -->
|
||||
<button
|
||||
v-if="isEditMode"
|
||||
:disabled="!canSave"
|
||||
:title="t('save')"
|
||||
class="w-12 h-12 bg-hutopyPrimary rounded-full flex items-center justify-center shadow-lg"
|
||||
@click="saveChanges()"
|
||||
>
|
||||
<button v-if="isEditMode" :disabled="!canSave" :title="t('save')"
|
||||
class="flex size-12 items-center justify-center rounded-full bg-hutopyPrimary shadow-lg" @click="saveChanges()">
|
||||
<v-icon large>mdi-check</v-icon>
|
||||
</button>
|
||||
|
||||
<!-- Cancel button -->
|
||||
<button
|
||||
v-if="isEditMode"
|
||||
:title="t('cancel')"
|
||||
class="w-12 h-12 bg-red-500 rounded-full flex items-center justify-center shadow-lg"
|
||||
@click="cancelEdit"
|
||||
>
|
||||
<button v-if="isEditMode" :title="t('cancel')"
|
||||
class="flex size-12 items-center justify-center rounded-full bg-red-500 shadow-lg" @click="cancelEdit">
|
||||
<v-icon large>mdi-close</v-icon>
|
||||
</button>
|
||||
</div>
|
||||
@@ -42,7 +29,7 @@
|
||||
<!-- MainPage -->
|
||||
<div class="flex flex-col">
|
||||
|
||||
<h1 class="flex justify-start text-2xl font-bold text-center mb-4">
|
||||
<h1 class="mb-4 flex justify-start text-center text-2xl font-bold">
|
||||
{{ t('creator.sections.about.title') }}
|
||||
</h1>
|
||||
|
||||
@@ -50,72 +37,47 @@
|
||||
<!-- Description Section -->
|
||||
<div>
|
||||
<div v-if="!isEditMode">
|
||||
<p v-if="description" class="text-lg text-justify mb-6 whitespace-pre-line">
|
||||
<p v-if="description" class="mb-6 whitespace-pre-line text-justify text-lg">
|
||||
{{ description }}
|
||||
</p>
|
||||
</div>
|
||||
<v-textarea v-if="isEditMode"
|
||||
v-model="editableDescription"
|
||||
:counter="2000"
|
||||
:error-messages="descriptionError"
|
||||
:label="t('creator.sections.about.description')"
|
||||
:rules="[
|
||||
v => !!v || t('creator.validation.descriptionRequired'),
|
||||
v => v.length <= 2000 || t('creator.validation.descriptionTooLong')
|
||||
]"
|
||||
auto-grow
|
||||
class="w-full p-2 py-6"
|
||||
rows="5"
|
||||
variant="outlined"></v-textarea>
|
||||
<v-textarea v-if="isEditMode" v-model="editableDescription" :counter="2000" :error-messages="descriptionError"
|
||||
:label="t('creator.sections.about.description')" :rules="[
|
||||
v => !!v || t('creator.validation.descriptionRequired'),
|
||||
v => v.length <= 2000 || t('creator.validation.descriptionTooLong')
|
||||
]" auto-grow class="w-full p-2 py-6" rows="5" variant="outlined"></v-textarea>
|
||||
</div>
|
||||
|
||||
<!-- Video Section -->
|
||||
<div v-if="videoUrl || isEditMode"
|
||||
:class="['content-section', {
|
||||
'rounded-t-xl': hasImages && !isEditMode,
|
||||
'rounded-xl': !hasImages && !isEditMode
|
||||
}]">
|
||||
<div v-if="videoUrl || isEditMode" :class="['content-section', {
|
||||
'rounded-t-xl': hasImages && !isEditMode,
|
||||
'rounded-xl': !hasImages && !isEditMode
|
||||
}]">
|
||||
<div v-if="!isEditMode && videoUrl" class="video-container">
|
||||
<iframe
|
||||
:src="youtubeEmbedUrl"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowfullscreen
|
||||
class="video-frame"
|
||||
title="YouTube video player">
|
||||
<iframe :src="youtubeEmbedUrl"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowfullscreen class="video-frame" title="YouTube video player">
|
||||
</iframe>
|
||||
</div>
|
||||
|
||||
<div v-if="isEditMode">
|
||||
<v-text-field
|
||||
v-model="editableVideoUrl"
|
||||
:error-messages="videoUrlError"
|
||||
:label="t('creator.fields.videoUrl')"
|
||||
class="w-full p-2"
|
||||
type="text"
|
||||
variant="outlined"
|
||||
/>
|
||||
<v-text-field v-model="editableVideoUrl" :error-messages="videoUrlError"
|
||||
:label="t('creator.fields.videoUrl')" class="w-full p-2" type="text" variant="outlined" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Photos Section using Album component -->
|
||||
<div>
|
||||
<!-- Use AlbumView for display mode -->
|
||||
<AlbumView v-if="!isEditMode && hasImages"
|
||||
:class="['content-section', {
|
||||
'rounded-b-xl': videoUrl && !isEditMode,
|
||||
'rounded-xl': !videoUrl && !isEditMode
|
||||
}]"
|
||||
:images="thumbnailUrls"
|
||||
@photo-click="handlePhotoClick"/>
|
||||
<AlbumView v-if="!isEditMode && hasImages" :class="['content-section', {
|
||||
'rounded-b-xl': videoUrl && !isEditMode,
|
||||
'rounded-xl': !videoUrl && !isEditMode
|
||||
}]" :images="thumbnailUrls" @photo-click="handlePhotoClick" />
|
||||
|
||||
<AlbumViewer v-model="showAlbumViewer"
|
||||
:images="originalUrls"
|
||||
:start-index="selectedPhotoIndex"/>
|
||||
<AlbumViewer v-model="showAlbumViewer" :images="originalUrls" :start-index="selectedPhotoIndex" />
|
||||
|
||||
<!-- Use AlbumEditor for edit mode -->
|
||||
<AlbumEditor v-if="isEditMode"
|
||||
:images="thumbnailUrls"
|
||||
@update:images="updateImages"/>
|
||||
<AlbumEditor v-if="isEditMode" :images="photos" @update:images="updateImages" />
|
||||
</div>
|
||||
|
||||
<!-- Contact Information Section -->
|
||||
@@ -136,18 +98,17 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {onMounted, ref, computed, watch} from "vue";
|
||||
import {useClient} from "@/plugins/api.js";
|
||||
import {useBrandingStore} from "@/stores/brandingStore.js";
|
||||
import {useCreatorProfileStore} from "@/stores/creatorProfileStore.js";
|
||||
import {useI18n} from 'vue-i18n';
|
||||
import {buildEmbedUrl, isValidYouTubeUrlOrId, extractVideoId} from '@/utils/youtube';
|
||||
import { onMounted, ref, computed, watch } from "vue";
|
||||
import { useClient } from "@/plugins/api.js";
|
||||
import { useBrandingStore } from "@/stores/brandingStore.js";
|
||||
import { useCreatorProfileStore } from "@/stores/creatorProfileStore.js";
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { buildEmbedUrl, isValidYouTubeUrlOrId, extractVideoId } from '@/utils/youtube';
|
||||
import AlbumEditor from "@/views/creators/AlbumEditor.vue";
|
||||
import AlbumView from "@/views/creators/AlbumView.vue";
|
||||
// Add these imports at the top with your other imports
|
||||
import AlbumViewer from './AlbumViewer.vue';
|
||||
|
||||
const {t} = useI18n();
|
||||
const { t } = useI18n();
|
||||
const creatorProfileStore = useCreatorProfileStore();
|
||||
const brandingStore = useBrandingStore();
|
||||
const client = useClient();
|
||||
@@ -162,7 +123,7 @@ const description = ref("");
|
||||
const videoUrl = ref("");
|
||||
const phoneNumber = ref("");
|
||||
const email = ref("");
|
||||
const thumbnailUrls = ref([]);
|
||||
const photos = ref([]); //before was thumbnailUrls
|
||||
const albumId = ref(null);
|
||||
const originalPhotos = ref([]);
|
||||
// Add these refs with your other refs
|
||||
@@ -195,10 +156,19 @@ const canSave = computed(() => {
|
||||
return true;
|
||||
});
|
||||
|
||||
const thumbnailUrls = computed(() => {
|
||||
return photos.value.map(photo => photo.thumbnailUrl)
|
||||
})
|
||||
|
||||
// Add this computed property to get the original image URLs
|
||||
const originalUrls = computed(() => {
|
||||
return photos.value.map(photo => photo.originalUrl);
|
||||
});
|
||||
|
||||
// Computed property to check if there are images
|
||||
const hasImages = computed(() => {
|
||||
// Only consider it has images if there are actual image URLs (not empty strings)
|
||||
return thumbnailUrls.value.length > 0 && thumbnailUrls.value.some(img => img && img.trim() !== "");
|
||||
return photos.value.length > 0;
|
||||
});
|
||||
|
||||
// Computed property for YouTube embed URL
|
||||
@@ -253,17 +223,17 @@ async function fetchAlbumData() {
|
||||
// Store original photos for comparison
|
||||
originalPhotos.value = response.data.photos;
|
||||
// Extract photo URLs from the album photos
|
||||
thumbnailUrls.value = response.data.photos.map(photo => photo.thumbnailUrl);
|
||||
photos.value = response.data.photos;
|
||||
} else {
|
||||
// Initialize with empty array instead of empty slots
|
||||
thumbnailUrls.value = [];
|
||||
photos.value = [];
|
||||
originalPhotos.value = [];
|
||||
}
|
||||
} catch (error) {
|
||||
// Album might not exist yet, which is fine
|
||||
console.log("Album might not exist yet:", error);
|
||||
// Initialize with empty array instead of empty slots
|
||||
thumbnailUrls.value = [];
|
||||
photos.value = [];
|
||||
originalPhotos.value = [];
|
||||
}
|
||||
}
|
||||
@@ -283,7 +253,7 @@ onMounted(async () => {
|
||||
|
||||
// Update images from Album component
|
||||
function updateImages(newImages) {
|
||||
thumbnailUrls.value = newImages;
|
||||
photos.value = newImages;
|
||||
}
|
||||
|
||||
async function saveChanges() {
|
||||
@@ -314,19 +284,26 @@ async function saveChanges() {
|
||||
|
||||
// Save presentation info
|
||||
const presentationResponse = await client.post(
|
||||
`/api/creators/${brandingStore.value.id}/presentation-infos`,
|
||||
{
|
||||
description: editableDescription.value || "",
|
||||
videoUrl: editableVideoUrl.value || null
|
||||
}
|
||||
`/api/creators/${brandingStore.value.id}/presentation-infos`,
|
||||
{
|
||||
description: editableDescription.value || "",
|
||||
videoUrl: editableVideoUrl.value || null
|
||||
}
|
||||
);
|
||||
|
||||
// Mettre à jour les valeurs locales pour refléter les changements
|
||||
description.value = editableDescription.value;
|
||||
videoUrl.value = extractVideoId(editableVideoUrl.value) || "";
|
||||
|
||||
|
||||
// Check for deleted photos
|
||||
const deletedPhotos = originalPhotos.value.filter(originalPhoto => {
|
||||
// If the photo URL is not in the current images array, it was deleted
|
||||
return !photos.value.includes(originalPhoto.thumbnailUrl);
|
||||
});
|
||||
|
||||
// Save album photos if they've changed
|
||||
if (thumbnailUrls.value.length > 0) {
|
||||
if (photos.value.length > 0 || deletedPhotos.length > 0) {
|
||||
// Create or update the album
|
||||
const albumId = brandingStore.value.id;
|
||||
|
||||
@@ -342,12 +319,6 @@ async function saveChanges() {
|
||||
console.log("Album might already exist:", error);
|
||||
}
|
||||
|
||||
// Check for deleted photos
|
||||
const deletedPhotos = originalPhotos.value.filter(originalPhoto => {
|
||||
// If the photo URL is not in the current images array, it was deleted
|
||||
return !thumbnailUrls.value.includes(originalPhoto.thumbnailUrl);
|
||||
});
|
||||
|
||||
// Delete removed photos
|
||||
for (const photo of deletedPhotos) {
|
||||
try {
|
||||
@@ -358,17 +329,30 @@ async function saveChanges() {
|
||||
}
|
||||
|
||||
// Now add or update photos
|
||||
for (let i = 0; i < thumbnailUrls.value.length; i++) {
|
||||
const imageUrl = thumbnailUrls.value[i];
|
||||
if (imageUrl && imageUrl.startsWith('data:')) {
|
||||
for (let i = 0; i < photos.value.length; i++) {
|
||||
const imageData = photos.value[i];
|
||||
console.log('Image Data to be uploaded:', imageData);
|
||||
if (imageData && imageData.image && imageData.image.originalUrl.startsWith('data:')) {
|
||||
// This is a new image that needs to be uploaded
|
||||
const photoId = crypto.randomUUID();
|
||||
const formData = new FormData();
|
||||
|
||||
// Extract MIME type from data URL
|
||||
const mimeMatch = imageData.image.originalUrl.match(/^data:(.*?);base64,/);
|
||||
if (!mimeMatch) {
|
||||
console.warn(`Invalid data URL at index ${i}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const mimeType = mimeMatch[1];
|
||||
|
||||
// Determine file extension from MIME type
|
||||
const extension = mimeType.split('/')[1]; // e.g., "jpeg", "png", "webp", etc.
|
||||
|
||||
// Convert data URL to file
|
||||
const response = await fetch(imageUrl);
|
||||
const response = await fetch(imageData.image.originalUrl);
|
||||
const blob = await response.blob();
|
||||
const file = new File([blob], `photo-${i}.jpg`, {type: 'image/jpeg'});
|
||||
const file = new File([blob], `photo-${i}.${extension}`, { type: mimeType });
|
||||
|
||||
formData.append('file', file);
|
||||
|
||||
@@ -404,16 +388,10 @@ function cancelEdit() {
|
||||
// Désactiver le mode édition
|
||||
isEditMode.value = false;
|
||||
}
|
||||
|
||||
// Add this computed property to get the original image URLs
|
||||
const originalUrls = computed(() => {
|
||||
return originalPhotos.value.map(photo => photo.originalUrl);
|
||||
});
|
||||
|
||||
// Add this function to handle photo clicks
|
||||
function handlePhotoClick(index) {
|
||||
selectedPhotoIndex.value = index;
|
||||
showAlbumViewer.value = true;
|
||||
selectedPhotoIndex.value = index;
|
||||
showAlbumViewer.value = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -425,7 +403,8 @@ function handlePhotoClick(index) {
|
||||
.video-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding-top: 31.25%; /* Reduced from 56.25% to make it shorter while maintaining aspect ratio */
|
||||
padding-top: 31.25%;
|
||||
/* Reduced from 56.25% to make it shorter while maintaining aspect ratio */
|
||||
max-height: 40vh;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user