feat(creator): allow text for description

This commit is contained in:
2025-04-25 01:22:11 -04:00
parent dcd4ec3b75
commit 57fbbf17a5
5 changed files with 47 additions and 26 deletions

View File

@@ -42,7 +42,7 @@ public class Socials
public class Presentation public class Presentation
{ {
[MaxLength(2000)] public string Description { get; set; } = null!; public string Description { get; set; } = null!;
[MaxLength(2048)] public string? VideoUrl { get; set; } [MaxLength(2048)] public string? VideoUrl { get; set; }
[MaxLength(255)] public string? PhoneNumber { get; set; } [MaxLength(255)] public string? PhoneNumber { get; set; }
[MaxLength(255)] public string? Email { get; set; } [MaxLength(255)] public string? Email { get; set; }

View File

@@ -311,8 +311,7 @@ namespace Hutopy.Web.Features.Contents.Data.Migrations
b1.Property<string>("Description") b1.Property<string>("Description")
.IsRequired() .IsRequired()
.HasMaxLength(2000) .HasColumnType("text");
.HasColumnType("character varying(2000)");
b1.Property<string>("Email") b1.Property<string>("Email")
.HasMaxLength(255) .HasMaxLength(255)

View File

@@ -20,7 +20,9 @@ public sealed class ChangePresentationInfosRequestValidator : Validator<ChangePr
RuleFor(x => x.Description) RuleFor(x => x.Description)
.NotEmpty() .NotEmpty()
.WithMessage("Description is required"); .WithMessage("Description is required")
.MaximumLength(2000)
.WithMessage("Description cannot exceed 2000 characters");
RuleFor(x => x.VideoUrl) RuleFor(x => x.VideoUrl)
.Must(url => url == null || YouTubeUrlHelper.IsValidYouTubeUrlOrId(url)) .Must(url => url == null || YouTubeUrlHelper.IsValidYouTubeUrlOrId(url))

View File

@@ -23,6 +23,7 @@
class="w-12 h-12 bg-hutopyPrimary rounded-full flex items-center justify-center shadow-lg" class="w-12 h-12 bg-hutopyPrimary rounded-full flex items-center justify-center shadow-lg"
@click="saveChanges()" @click="saveChanges()"
:title="t('save')" :title="t('save')"
:disabled="editableDescription.length > 2000"
> >
<v-icon large>mdi-check</v-icon> <v-icon large>mdi-check</v-icon>
</button> </button>
@@ -49,7 +50,7 @@
<!-- Description Section --> <!-- Description Section -->
<div> <div>
<div v-if="!isEditMode"> <div v-if="!isEditMode">
<p v-if="description" class="text-lg text-justify mb-6"> <p v-if="description" class="text-lg text-justify mb-6 whitespace-pre-line">
{{ description }} {{ description }}
</p> </p>
</div> </div>
@@ -57,6 +58,11 @@
v-model="editableDescription" v-model="editableDescription"
class="w-full p-2 py-6" class="w-full p-2 py-6"
:label="t('creator.sections.about.description')" :label="t('creator.sections.about.description')"
:error-messages="descriptionError"
:counter="2000"
:rules="[v => v.length <= 2000 || t('creator.validation.descriptionTooLong')]"
auto-grow
rows="5"
variant="outlined"></v-textarea> variant="outlined"></v-textarea>
</div> </div>
@@ -140,8 +146,6 @@ const showEditButtons = ref(false);
// Variables réactives pour les données // Variables réactives pour les données
const description = ref(""); const description = ref("");
const videoUrl = ref(""); const videoUrl = ref("");
const phoneNumber = ref("");
const email = ref("");
const imageUrls = ref([]); const imageUrls = ref([]);
const albumId = ref(null); const albumId = ref(null);
const originalPhotos = ref([]); const originalPhotos = ref([]);
@@ -149,9 +153,8 @@ const originalPhotos = ref([]);
// Editable fields // Editable fields
const editableDescription = ref(""); const editableDescription = ref("");
const editableVideoUrl = ref(""); const editableVideoUrl = ref("");
const editablePhoneNumber = ref("");
const editableEmail = ref("");
const videoUrlError = ref(""); const videoUrlError = ref("");
const descriptionError = ref("");
// Computed property to check if there are images // Computed property to check if there are images
const hasImages = computed(() => { const hasImages = computed(() => {
@@ -193,8 +196,6 @@ function toggleEditMode() {
// Charger les valeurs pour l'édition // Charger les valeurs pour l'édition
editableDescription.value = description.value; editableDescription.value = description.value;
editableVideoUrl.value = videoUrl.value; editableVideoUrl.value = videoUrl.value;
editablePhoneNumber.value = phoneNumber.value;
editableEmail.value = email.value;
videoUrlError.value = ""; videoUrlError.value = "";
} }
} }
@@ -252,6 +253,12 @@ async function saveChanges() {
return; return;
} }
// Validate description length
if (editableDescription.value.length > 2000) {
descriptionError.value = t('creator.validation.descriptionTooLong');
return;
}
// Validate video URL before saving // Validate video URL before saving
if (!validateVideoUrl(editableVideoUrl.value)) { if (!validateVideoUrl(editableVideoUrl.value)) {
return; return;
@@ -265,17 +272,13 @@ async function saveChanges() {
`/api/creators/${brandingStore.value.id}/presentation-infos`, `/api/creators/${brandingStore.value.id}/presentation-infos`,
{ {
description: editableDescription.value || "", description: editableDescription.value || "",
videoUrl: editableVideoUrl.value || "", videoUrl: editableVideoUrl.value || null
phoneNumber: editablePhoneNumber.value || "",
email: editableEmail.value || ""
} }
); );
// Mettre à jour les valeurs locales pour refléter les changements // Mettre à jour les valeurs locales pour refléter les changements
description.value = editableDescription.value; description.value = editableDescription.value;
videoUrl.value = extractVideoId(editableVideoUrl.value) || ""; videoUrl.value = extractVideoId(editableVideoUrl.value) || "";
phoneNumber.value = editablePhoneNumber.value;
email.value = editableEmail.value;
// Save album photos if they've changed // Save album photos if they've changed
if (imageUrls.value.length > 0) { if (imageUrls.value.length > 0) {
@@ -354,8 +357,6 @@ function cancelEdit() {
// Restaurer les valeurs d'origine // Restaurer les valeurs d'origine
editableDescription.value = description.value; editableDescription.value = description.value;
editableVideoUrl.value = videoUrl.value; editableVideoUrl.value = videoUrl.value;
editablePhoneNumber.value = phoneNumber.value;
editableEmail.value = email.value;
// Désactiver le mode édition // Désactiver le mode édition
isEditMode.value = false; isEditMode.value = false;
@@ -406,6 +407,16 @@ function cancelEdit() {
.contact-item { .contact-item {
@apply text-lg text-center mb-2 font-semibold; @apply text-lg text-center mb-2 font-semibold;
} }
/* Formatting styles for description */
.text-justify {
line-height: 1.6;
}
/* Add some spacing between paragraphs */
.text-justify p {
margin-bottom: 1rem;
}
</style> </style>
<i18n> <i18n>
@@ -419,7 +430,9 @@ function cancelEdit() {
"about": { "about": {
"title": "About", "title": "About",
"description": "Description", "description": "Description",
"contactInfo": "Contact Information" "contactInfo": "Contact Information",
"characters": "characters",
"formattingHint": "Tip: Use line breaks and emojis to make your description more engaging!"
}, },
"photos": { "photos": {
"title": "Photos", "title": "Photos",
@@ -432,7 +445,8 @@ function cancelEdit() {
"email": "Email" "email": "Email"
}, },
"validation": { "validation": {
"invalidYoutubeUrl": "Please enter a valid YouTube URL or video ID" "invalidYoutubeUrl": "Please enter a valid YouTube URL or video ID",
"descriptionTooLong": "Description cannot exceed 2000 characters"
} }
} }
}, },
@@ -445,7 +459,9 @@ function cancelEdit() {
"about": { "about": {
"title": "À propos", "title": "À propos",
"description": "Description", "description": "Description",
"contactInfo": "Informations de contact" "contactInfo": "Informations de contact",
"characters": "caractères",
"formattingHint": "Astuce : Utilisez des sauts de ligne et des émojis pour rendre votre description plus attrayante !"
}, },
"photos": { "photos": {
"title": "Photos", "title": "Photos",
@@ -458,7 +474,8 @@ function cancelEdit() {
"email": "Email" "email": "Email"
}, },
"validation": { "validation": {
"invalidYoutubeUrl": "Veuillez entrer une URL YouTube ou un ID de vidéo valide" "invalidYoutubeUrl": "Veuillez entrer une URL YouTube ou un ID de vidéo valide",
"descriptionTooLong": "La description ne peut pas dépasser 2000 caractères"
} }
} }
}, },
@@ -471,7 +488,9 @@ function cancelEdit() {
"about": { "about": {
"title": "Acerca de", "title": "Acerca de",
"description": "Descripción", "description": "Descripción",
"contactInfo": "Información de contacto" "contactInfo": "Información de contacto",
"characters": "caracteres",
"formattingHint": "Consejo: ¡Usa saltos de línea y emojis para hacer tu descripción más atractiva!"
}, },
"photos": { "photos": {
"title": "Fotos", "title": "Fotos",
@@ -484,7 +503,8 @@ function cancelEdit() {
"email": "Correo electrónico" "email": "Correo electrónico"
}, },
"validation": { "validation": {
"invalidYoutubeUrl": "Por favor, introduce una URL de YouTube o un ID de video válido" "invalidYoutubeUrl": "Por favor, introduce una URL de YouTube o un ID de video válido",
"descriptionTooLong": "La descripción no puede exceder los 2000 caracteres"
} }
} }
} }

View File

@@ -260,7 +260,7 @@ async function deconfigureStripe() {
</div> </div>
<div class="content"> <div class="content">
<button class="action" @click="openDialog('ChangeSlugDialog')"> <div class="action" @click="openDialog('ChangeSlugDialog')">
<span class="label">{{ t('handle') }}</span> <span class="label">{{ t('handle') }}</span>
<span class="value">{{ baseURL }}/@{{ creatorProfileStore.creator.slug }}</span> <span class="value">{{ baseURL }}/@{{ creatorProfileStore.creator.slug }}</span>
<button <button
@@ -272,7 +272,7 @@ async function deconfigureStripe() {
<v-icon>{{ copySuccess ? 'mdi-check' : 'mdi-content-copy' }}</v-icon> <v-icon>{{ copySuccess ? 'mdi-check' : 'mdi-content-copy' }}</v-icon>
</button> </button>
<span class="chevron"><v-icon>mdi-chevron-right</v-icon></span> <span class="chevron"><v-icon>mdi-chevron-right</v-icon></span>
</button> </div>
<!-- NAME --> <!-- NAME -->
<button class="action" @click="openDialog('ChangeNameDialog')"> <button class="action" @click="openDialog('ChangeNameDialog')">