Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 10 additions & 15 deletions frontend/public/templates/speakers/33-pt.html
Original file line number Diff line number Diff line change
Expand Up @@ -236,20 +236,16 @@
"
>
<p>
<strong
>Car<b style="color: red">o/a/e</b> {{.Speaker}},</strong
>
<strong>Car{{.SpeakerArticle}} {{.Speaker}},</strong>
</p>
<p>
Sou <b style="color: red">o/a/e</b> {{.Member}}, um
orgulhoso membro da SINFO. Em nome da equipa organizadora da
SINFO {{.Edition}}, convidamo-l<b style="color: red"
>o/a/e</b
>
a participar como orador<b style="color: red">(a)</b> de uma
palestra no nosso evento. Ficaríamos muito gratos pela
oportunidade de recebê-l<b style="color: red">o/a/e</b> se
aceitar este convite.
Sou {{.MemberArticle}} {{.Member}}, um{{.MemberSuffix}}
orgulhos{{.MemberArticle}} membro da SINFO. Em nome da
equipa organizadora da SINFO {{.Edition}},
convidamo-l{{.SpeakerArticle}} a participar como
orador{{.SpeakerSuffix}} de uma palestra no nosso evento.
Ficaríamos muito gratos pela oportunidade de
recebê-l{{.SpeakerArticle}} se aceitar este convite.
</p>

<p>
Expand Down Expand Up @@ -409,9 +405,8 @@
{{if .Paragraph}} {{.Paragraph}} {{else}} A maioria dos
nossos visitantes são estudantes de Informática ansiosos por
começar as suas carreiras e seria com muito prazer que iam
ouvi-l<b style="color: red">o/a</b> falar sobre o seu
percurso profissional e aprender com a sua experiência!
{{end}}
ouvi-l{{.SpeakerArticle}} falar sobre o seu percurso
profissional e aprender com a sua experiência! {{end}}
</p>

<br />
Expand Down
13 changes: 11 additions & 2 deletions frontend/src/components/speakers/SpeakerCommunications.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

<script setup lang="ts">
import { getSpeakerCommunications } from "@/api/speakers";
import type { SpeakerWithParticipation } from "@/dto/speakers";
import type { SpeakerWithContactObject } from "@/dto/speakers";
import { useEventStore } from "@/stores/event";
import { usePostSpeakerThreadMutation } from "@/mutations/speakers";
import Communications from "../Communications.vue";
Expand All @@ -22,7 +22,7 @@ import { computed } from "vue";
import { useAuthStore } from "@/stores/auth";

const props = defineProps<{
speaker: SpeakerWithParticipation;
speaker: SpeakerWithContactObject;
}>();

const eventStore = useEventStore();
Expand All @@ -41,9 +41,18 @@ const templates = computed(() =>

const createSpeakerTemplateVariables = () => {
const endDate = new Date(eventStore.selectedEvent?.end || 0);
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),
createEmailVariable.member(authStore.member!),
createEmailVariable.speakerArticle(speakerGender),
createEmailVariable.speakerSuffix(speakerGender),
createEmailVariable.speaker(props.speaker),
createEmailVariable.edition(eventStore.selectedEvent?.id || 0),
createEmailVariable.editionOrdinal(eventStore.selectedEvent?.id || 0),
Expand Down
90 changes: 85 additions & 5 deletions frontend/src/lib/templates.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type Speaker } from "@/dto/speakers";
import type { SpeakerWithContactObject } from "@/dto/speakers";
import type { Company } from "../dto/companies";
import { ordinalSuffix } from "./utils";
import {
Expand Down Expand Up @@ -91,7 +91,11 @@ export enum EmailVariableKey {

Company = "Company",

SpeakerArticle = "SpeakerArticle", // speaker article (e.g., o/a/e)
SpeakerSuffix = "SpeakerSuffix", // speaker suffix (e.g., (a))
Speaker = "Speaker", // speaker name
MemberArticle = "MemberArticle", // member article (e.g., o/a/e)
MemberSuffix = "MemberSuffix", // member suffix (e.g., (a))
Member = "Member", // member name
MemberEmail = "MemberEmail", // member email, has default value
MemberPhoneNumber = "MemberPhoneNumber", // member phone number, has default value
Expand All @@ -108,7 +112,11 @@ export interface EmailVariableValueMap {
[EmailVariableKey.EventEndDay]: Date;
[EmailVariableKey.EventEndMonth]: Date;
[EmailVariableKey.EventEndYear]: Date;
[EmailVariableKey.Speaker]: Speaker;
[EmailVariableKey.SpeakerArticle]: string;
[EmailVariableKey.SpeakerSuffix]: string;
[EmailVariableKey.Speaker]: SpeakerWithContactObject;
[EmailVariableKey.MemberArticle]: string;
[EmailVariableKey.MemberSuffix]: string;
[EmailVariableKey.Member]: MemberWithContact;
[EmailVariableKey.MemberEmail]: MemberWithContact;
[EmailVariableKey.MemberPhoneNumber]: MemberWithContact;
Expand All @@ -122,7 +130,7 @@ export interface VariablesInput {
member: MemberWithContact;
}
export interface SpeakerVariablesInput extends VariablesInput {
speaker: Speaker;
speaker: SpeakerWithContactObject;
paragraph?: string; // Optional paragraph for speaker emails
}
const isSpeakerVariablesInput = (
Expand Down Expand Up @@ -150,6 +158,8 @@ export const getVariablesFromType = <T extends VariablesInput>(
createEmailVariable.eventEndDay(end),
createEmailVariable.eventEndMonth(end),
createEmailVariable.eventEndYear(end),
createEmailVariable.memberArticle(input.member.contactObject.gender),
createEmailVariable.memberSuffix(input.member.contactObject.gender),
createEmailVariable.member(input.member),
createEmailVariable.memberEmail(input.member),
createEmailVariable.memberPhoneNumber(input.member),
Expand All @@ -159,7 +169,12 @@ export const getVariablesFromType = <T extends VariablesInput>(
if (input.paragraph)
vars.push(createEmailVariable.paragraph(input.paragraph));

return [...vars, createEmailVariable.speaker(input.speaker)];
return [
...vars,
createEmailVariable.speaker(input.speaker),
createEmailVariable.speakerArticle(input.speaker.contactObject.gender),
createEmailVariable.speakerSuffix(input.speaker.contactObject.gender),
];
}

if (isCompanyVariablesInput(input)) {
Expand Down Expand Up @@ -287,10 +302,26 @@ const getValueFromVariable = (
return variable.value.getFullYear().toString();
}

case EmailVariableKey.SpeakerArticle: {
return genderToArticleSuffix(variable.value as string).article;
}

case EmailVariableKey.SpeakerSuffix: {
return genderToArticleSuffix(variable.value as string).suffix;
}

case EmailVariableKey.Speaker: {
return variable.value.name;
}

case EmailVariableKey.MemberArticle: {
return genderToArticleSuffix(variable.value as string).article;
}

case EmailVariableKey.MemberSuffix: {
return genderToArticleSuffix(variable.value as string).suffix;
}

case EmailVariableKey.Member: {
return variable.value.name;
}
Expand Down Expand Up @@ -319,6 +350,21 @@ const getValueFromVariable = (
}
};

const genderToArticleSuffix = (
gender: string,
): { article: string; suffix: string } => {
switch (gender) {
case "MALE":
return { article: "o", suffix: "" };
case "FEMALE":
return { article: "a", suffix: "a" };
case "OTHER":
return { article: "e", suffix: "e" };
default:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it misses the "OTHER" gender case

Copy link
Author

@JoaoVitorLobo JoaoVitorLobo Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wouldn't the default case and the "OTHER" case be the same?

return { article: "o/a/e", suffix: "(a)" };
}
};

const languageFromTemplate = (template: EmailTemplate): Language => {
return template.endsWith("_EN") ? Language.ENGLISH : Language.PORTUGUESE;
};
Expand Down Expand Up @@ -413,7 +459,11 @@ export type AnyEmailVariableInput =
| EmailVariableInput<EmailVariableKey.EventEndDay>
| EmailVariableInput<EmailVariableKey.EventEndMonth>
| EmailVariableInput<EmailVariableKey.EventEndYear>
| EmailVariableInput<EmailVariableKey.SpeakerArticle>
| EmailVariableInput<EmailVariableKey.SpeakerSuffix>
| EmailVariableInput<EmailVariableKey.Speaker>
| EmailVariableInput<EmailVariableKey.MemberArticle>
| EmailVariableInput<EmailVariableKey.MemberSuffix>
| EmailVariableInput<EmailVariableKey.Member>
| EmailVariableInput<EmailVariableKey.MemberEmail>
| EmailVariableInput<EmailVariableKey.MemberPhoneNumber>
Expand Down Expand Up @@ -467,11 +517,41 @@ export const createEmailVariable = {
value,
}),

speaker: (value: Speaker): EmailVariableInput<EmailVariableKey.Speaker> => ({
speakerArticle: (
value: string,
): EmailVariableInput<EmailVariableKey.SpeakerArticle> => ({
key: EmailVariableKey.SpeakerArticle,
value,
}),

speakerSuffix: (
value: string,
): EmailVariableInput<EmailVariableKey.SpeakerSuffix> => ({
key: EmailVariableKey.SpeakerSuffix,
value,
}),

speaker: (
value: SpeakerWithContactObject,
): EmailVariableInput<EmailVariableKey.Speaker> => ({
key: EmailVariableKey.Speaker,
value,
}),

memberArticle: (
value: string,
): EmailVariableInput<EmailVariableKey.MemberArticle> => ({
key: EmailVariableKey.MemberArticle,
value,
}),

memberSuffix: (
value: string,
): EmailVariableInput<EmailVariableKey.MemberSuffix> => ({
key: EmailVariableKey.MemberSuffix,
value,
}),

member: (
value: MemberWithContact,
): EmailVariableInput<EmailVariableKey.Member> => ({
Expand Down
Loading