From 4bf5b2ffa63521d74726c8ae2a9d7d1a9993144c Mon Sep 17 00:00:00 2001 From: joao vitor lobo Date: Sat, 24 Jan 2026 23:44:22 +0000 Subject: [PATCH 1/5] fix: 3 issues at once --- backend/src/mongodb/speaker.go | 4 +- frontend/public/templates/speakers/33-pt.html | 1226 +++++++---------- .../src/components/BulkEmailDialogTrigger.vue | 8 +- frontend/src/components/Communications.vue | 11 +- .../src/components/companies/ContactForm.vue | 4 +- .../components/speakers/CreateSpeakerForm.vue | 14 +- .../speakers/SpeakerCommunications.vue | 13 +- frontend/src/composables/useBulkEmails.ts | 16 +- frontend/src/composables/useDirectEmail.ts | 9 +- frontend/src/dto/speakers.ts | 3 + frontend/src/lib/templates.ts | 94 +- .../views/Dashboard/ResponsibilitiesView.vue | 4 +- 12 files changed, 662 insertions(+), 744 deletions(-) diff --git a/backend/src/mongodb/speaker.go b/backend/src/mongodb/speaker.go index f7c6e77b..667fa2f6 100644 --- a/backend/src/mongodb/speaker.go +++ b/backend/src/mongodb/speaker.go @@ -127,7 +127,9 @@ func (s *SpeakersType) CreateSpeaker(data CreateSpeakerData) (*models.Speaker, e ctx := context.Background() contact := bson.M{ - "phones": []models.ContactPhone{}, + "gender": data.Contact.Gender, + "language": data.Contact.Language, + "phones": []models.ContactPhone{}, "socials": bson.M{ "facebook": "", "skype": "", diff --git a/frontend/public/templates/speakers/33-pt.html b/frontend/public/templates/speakers/33-pt.html index 6a195f66..fdf85e8b 100644 --- a/frontend/public/templates/speakers/33-pt.html +++ b/frontend/public/templates/speakers/33-pt.html @@ -1,169 +1,164 @@ - - - - - - - - SINFO {{.Edition}} - Convite para participação como orador! - - - - - - + + + - - - - - - - - - + +
-
- -
+ + + + +
+
+ +
- Em nome da equipa que organiza a SINFO, ficaríamos muito felizes - se aceitasse o nosso convite para fazer uma palestra. A SINFO é - uma organização de estudantes universitários sem fins lucrativos - responsável pelo planeamento de um dos maiores eventos de - tecnologia em Portugal. Trata-se de uma conferência anual que - ocorre em Lisboa. -
- - - - - - - - - - + + +
+
+ + + + \ No newline at end of file diff --git a/frontend/src/components/BulkEmailDialogTrigger.vue b/frontend/src/components/BulkEmailDialogTrigger.vue index 31adcccb..d02edcff 100644 --- a/frontend/src/components/BulkEmailDialogTrigger.vue +++ b/frontend/src/components/BulkEmailDialogTrigger.vue @@ -450,7 +450,7 @@ import { import { humanReadableParticipationStatus } from "@/dto/index"; import type { ParticipationStatus } from "@/dto/index"; import type { CompanyWithParticipation } from "@/dto/companies"; -import type { SpeakerWithParticipation } from "@/dto/speakers"; +import type { SpeakerWithContactAndParticipation } from "@/dto/speakers"; import { useBulkCompanyEmails, useBulkSpeakerEmails, @@ -462,7 +462,7 @@ interface Props { size?: "sm" | "default" | "lg" | "icon"; buttonClass?: string; companies?: CompanyWithParticipation[]; - speakers?: SpeakerWithParticipation[]; + speakers?: SpeakerWithContactAndParticipation[]; entityType: "companies" | "speakers"; } @@ -499,7 +499,7 @@ const speakerBulk = useBulkSpeakerEmails(); const processBulkEmails = async ( templateCategory: EmailTemplateCategory, statuses: ParticipationStatus[], - entities: CompanyWithParticipation[] | SpeakerWithParticipation[], + entities: CompanyWithParticipation[] | SpeakerWithContactAndParticipation[], ) => { if (props.entityType === "companies") { return companyBulk.processBulkEmails( @@ -511,7 +511,7 @@ const processBulkEmails = async ( return speakerBulk.processBulkEmails( templateCategory, statuses, - entities as SpeakerWithParticipation[], + entities as SpeakerWithContactAndParticipation[], ); }; diff --git a/frontend/src/components/Communications.vue b/frontend/src/components/Communications.vue index e238268f..91ec5706 100644 --- a/frontend/src/components/Communications.vue +++ b/frontend/src/components/Communications.vue @@ -154,7 +154,8 @@ {{ getKindLabel(thread.kind) }}
-
+ + {{ formatDate(thread.posted) }}
@@ -350,7 +351,7 @@ import { ref, computed, watch, nextTick } from "vue"; import { useQuery } from "@pinia/colada"; import { getAllEvents } from "@/api/events"; import { getAllMembers } from "@/api/members"; -import { ThreadKind, ThreadStatus } from "@/dto/threads"; +import { ThreadKind } from "@/dto/threads"; import type { ParticipationCommunications, ThreadWithEntry, @@ -704,7 +705,7 @@ const getKindColor = (kind: ThreadKind): string => { } }; -const getStatusLabel = (status: ThreadStatus): string => { +/*const getStatusLabel = (status: ThreadStatus): string => { switch (status) { case ThreadStatus.ThreadStatusApproved: return "Approved"; @@ -728,7 +729,7 @@ const getStatusColor = (status: ThreadStatus): string => { default: return "bg-gray-400"; } -}; +};*/ const formatDate = (dateString: string): string => { const date = new Date(dateString); diff --git a/frontend/src/components/companies/ContactForm.vue b/frontend/src/components/companies/ContactForm.vue index 44311468..a90a8555 100644 --- a/frontend/src/components/companies/ContactForm.vue +++ b/frontend/src/components/companies/ContactForm.vue @@ -16,7 +16,7 @@
- +
- +
-
@@ -266,6 +269,13 @@ const isStep1Valid = computed(() => { ); }); +const isStep3Valid = computed(() => { + return ( + contactData.value.gender != undefined && + contactData.value.language != undefined + ); +}); + // Step navigation const nextStep = () => { if (currentStep.value === 1 && validateStep1()) { @@ -352,6 +362,8 @@ const createSpeakerAndFinish = async () => { (phone) => phone.phone && phone.phone.trim().length > 0, ), socials: contactData.value.socials || {}, + gender: contactData.value.gender, + language: contactData.value.language, }; const createData: CreateSpeakerData = { diff --git a/frontend/src/components/speakers/SpeakerCommunications.vue b/frontend/src/components/speakers/SpeakerCommunications.vue index 1d8fb89d..4737af5c 100644 --- a/frontend/src/components/speakers/SpeakerCommunications.vue +++ b/frontend/src/components/speakers/SpeakerCommunications.vue @@ -13,7 +13,7 @@ - - - \ No newline at end of file + " + class="center-on-narrow" + > + João Rosário
+ Apple
+
+
+ + + + + + + + + + + + + + + + + + + + +
+
+ + + From 21b68557212dd1df1fd46086f88dfa6387c4c09f Mon Sep 17 00:00:00 2001 From: joao vitor lobo Date: Tue, 27 Jan 2026 22:44:52 +0000 Subject: [PATCH 3/5] fix: comments and template number --- frontend/src/components/Communications.vue | 38 ------------------- .../speakers/SpeakerCommunications.vue | 3 -- frontend/src/lib/templates.ts | 8 ++-- 3 files changed, 3 insertions(+), 46 deletions(-) diff --git a/frontend/src/components/Communications.vue b/frontend/src/components/Communications.vue index 91ec5706..fe5e75f7 100644 --- a/frontend/src/components/Communications.vue +++ b/frontend/src/components/Communications.vue @@ -153,18 +153,6 @@ > {{ getKindLabel(thread.kind) }} - - - - {{ formatDate(thread.posted) }} @@ -705,32 +693,6 @@ const getKindColor = (kind: ThreadKind): string => { } }; -/*const getStatusLabel = (status: ThreadStatus): string => { - switch (status) { - case ThreadStatus.ThreadStatusApproved: - return "Approved"; - case ThreadStatus.ThreadStatusReviewed: - return "Reviewed"; - case ThreadStatus.ThreadStatusPending: - return "Pending"; - default: - return "Unknown"; - } -}; - -const getStatusColor = (status: ThreadStatus): string => { - switch (status) { - case ThreadStatus.ThreadStatusApproved: - return "bg-green-500"; - case ThreadStatus.ThreadStatusReviewed: - return "bg-yellow-500"; - case ThreadStatus.ThreadStatusPending: - return "bg-red-500"; - default: - return "bg-gray-400"; - } -};*/ - const formatDate = (dateString: string): string => { const date = new Date(dateString); const now = new Date(); diff --git a/frontend/src/components/speakers/SpeakerCommunications.vue b/frontend/src/components/speakers/SpeakerCommunications.vue index 4737af5c..3dfe9b17 100644 --- a/frontend/src/components/speakers/SpeakerCommunications.vue +++ b/frontend/src/components/speakers/SpeakerCommunications.vue @@ -44,9 +44,6 @@ const createSpeakerTemplateVariables = () => { const memberGender = authStore.member!.contactObject.gender; const speakerGender = props.speaker.contactObject.gender; - // Use contactObject which contains the full Contact (including gender) - // Fallback to empty string when gender is unavailable to avoid runtime errors - return [ createEmailVariable.memberArticle(memberGender), createEmailVariable.memberSuffix(memberGender), diff --git a/frontend/src/lib/templates.ts b/frontend/src/lib/templates.ts index ce13de6a..1b7ea8c9 100644 --- a/frontend/src/lib/templates.ts +++ b/frontend/src/lib/templates.ts @@ -27,8 +27,8 @@ export const templateHumanReadableNames: Record = { // 3. Set the template paths here export const templatePaths: Record = { - [EmailTemplate.COMPANIES_EN]: "/companies/32-en.html", - [EmailTemplate.COMPANIES_PT]: "/companies/32-pt.html", + [EmailTemplate.COMPANIES_EN]: "/companies/33-en.html", + [EmailTemplate.COMPANIES_PT]: "/companies/33-pt.html", [EmailTemplate.SPEAKERS_EN]: "/speakers/33-en.html", [EmailTemplate.SPEAKERS_PT]: "/speakers/33-pt.html", }; @@ -358,10 +358,8 @@ const genderToArticleSuffix = ( return { article: "o", suffix: "" }; case "FEMALE": return { article: "a", suffix: "a" }; - case "OTHER": - return { article: "e", suffix: "e" }; default: - return { article: "o/a/e", suffix: "(a)" }; + return { article: "e", suffix: "e" }; } }; From 76add31664e0a237e7ee1ac3ddfe012e6c3d39ae Mon Sep 17 00:00:00 2001 From: joao vitor lobo Date: Wed, 28 Jan 2026 16:09:57 +0000 Subject: [PATCH 4/5] fix: validation in ContactForm --- .../src/components/companies/ContactForm.vue | 36 +++++++++++++++---- .../components/speakers/CreateSpeakerForm.vue | 6 +++- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/companies/ContactForm.vue b/frontend/src/components/companies/ContactForm.vue index a90a8555..a2bdbb88 100644 --- a/frontend/src/components/companies/ContactForm.vue +++ b/frontend/src/components/companies/ContactForm.vue @@ -237,6 +237,18 @@ {{ showMoreSocials ? "- Less" : "+ More" }} social platforms + + +
+

+ {{ validationMessage }} +

+
@@ -350,20 +362,29 @@ watch( { immediate: true }, ); -watch( - () => formData, - (newData) => emit("updated", newData), - { deep: true, immediate: true }, -); - const isValid = computed(() => { const hasName = props.withoutName || formData.name?.trim(); const hasEmail = formData.contact.mails.some((mail) => mail.mail.trim()); - return hasName && hasEmail; + const hasGender = !!formData.contact.gender; + const hasLanguage = !!formData.contact.language; + + return hasName && hasEmail && hasGender && hasLanguage; }); +watch( + () => formData, + (newData) => { + if (isValid.value) { + emit("updated", newData); + } + }, + { deep: true, immediate: true }, +); + const validationMessage = computed(() => { if (!props.withoutName && !formData.name?.trim()) return "Name is required"; + if (!formData.contact.gender) return "Gender is required"; + if (!formData.contact.language) return "Language is required"; if (!formData.contact.mails.some((mail) => mail.mail.trim())) return "At least one email is required"; return ""; @@ -386,6 +407,7 @@ const removePhone = (index: number) => { }; const handleSubmit = () => { + console.log("isValid", isValid.value); if (!isValid.value) return; // Clean up empty fields diff --git a/frontend/src/components/speakers/CreateSpeakerForm.vue b/frontend/src/components/speakers/CreateSpeakerForm.vue index 0e3513a1..9807911d 100644 --- a/frontend/src/components/speakers/CreateSpeakerForm.vue +++ b/frontend/src/components/speakers/CreateSpeakerForm.vue @@ -272,7 +272,11 @@ const isStep1Valid = computed(() => { const isStep3Valid = computed(() => { return ( contactData.value.gender != undefined && - contactData.value.language != undefined + contactData.value.language != undefined && + contactData.value.mails && + contactData.value.mails.some( + (mail) => mail.mail && mail.mail.trim().length > 0, + ) ); }); From 9a0e2e8fca7ba96f219649cc6afe2b824bb6a20c Mon Sep 17 00:00:00 2001 From: joao vitor lobo Date: Wed, 28 Jan 2026 16:23:20 +0000 Subject: [PATCH 5/5] fix: validation in ContactForm --- frontend/src/components/companies/ContactForm.vue | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/components/companies/ContactForm.vue b/frontend/src/components/companies/ContactForm.vue index a2bdbb88..38057c61 100644 --- a/frontend/src/components/companies/ContactForm.vue +++ b/frontend/src/components/companies/ContactForm.vue @@ -407,7 +407,6 @@ const removePhone = (index: number) => { }; const handleSubmit = () => { - console.log("isValid", isValid.value); if (!isValid.value) return; // Clean up empty fields