From c4bdd052db16425522b3d9f67b60fbec1f558f2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?In=C3=AAs=20Costa?= <94355111+inesiscosta@users.noreply.github.com> Date: Tue, 6 Jan 2026 17:00:10 +0000 Subject: [PATCH 1/5] feat: remove invalid state from emails Refs: #461 --- backend/src/models/contact.go | 8 +++----- backend/src/router/contacts_test.go | 4 ---- frontend/src/components/ContactCard.vue | 16 ---------------- .../src/components/companies/ContactForm.vue | 12 ++++++------ .../components/companies/CreateCompanyForm.vue | 2 -- .../components/companies/RepresentativeForm.vue | 8 ++++---- frontend/src/dto/contacts.ts | 2 -- frontend/src/views/Dashboard/SettingsView.vue | 14 -------------- 8 files changed, 13 insertions(+), 53 deletions(-) diff --git a/backend/src/models/contact.go b/backend/src/models/contact.go index 0ab56825..3d904c91 100644 --- a/backend/src/models/contact.go +++ b/backend/src/models/contact.go @@ -8,7 +8,6 @@ import ( type ContactPhone struct { Phone string `json:"phone" bson:"phone"` - Valid bool `json:"valid" bson:"valid"` } type ContactSocials struct { @@ -22,7 +21,6 @@ type ContactSocials struct { type ContactMail struct { Mail string `json:"mail" bson:"mail"` Personal bool `json:"personal" bson:"personal"` - Valid bool `json:"valid" bson:"valid"` } type Gender string @@ -51,11 +49,11 @@ type Contact struct { Mails []ContactMail `json:"mails" bson:"mails"` } -// HasPhone (phone) returns true if contact has a valid phone +// HasPhone (phone) returns true if contact has a phone // number that is a case insensitive partial match to `phone` func (c *Contact) HasPhone(p string) bool { for _, s := range c.Phones { - if strings.Contains(strings.ToLower(s.Phone), strings.ToLower(p)) && s.Valid { + if strings.Contains(strings.ToLower(s.Phone), strings.ToLower(p)) { return true } } @@ -66,7 +64,7 @@ func (c *Contact) HasPhone(p string) bool { // that is a case insensitive partial match to `mail` func (c *Contact) HasMail(m string) bool { for _, s := range c.Mails { - if strings.Contains(strings.ToLower(s.Mail), strings.ToLower(m)) && s.Valid { + if strings.Contains(strings.ToLower(s.Mail), strings.ToLower(m)) { return true } } diff --git a/backend/src/router/contacts_test.go b/backend/src/router/contacts_test.go index 4ee0df7c..5731fe03 100644 --- a/backend/src/router/contacts_test.go +++ b/backend/src/router/contacts_test.go @@ -17,7 +17,6 @@ var ( Contact1 *models.Contact Contact1Phone = models.ContactPhone{ Phone: "1", - Valid: true, } Contact1Socials = models.ContactSocials{ Facebook: "facebook", @@ -29,7 +28,6 @@ var ( Contact1Mail = models.ContactMail{ Mail: "2", Personal: true, - Valid: true, } Contact1Data = mongodb.CreateContactData{ Phones: append(make([]models.ContactPhone, 0), Contact1Phone), @@ -39,7 +37,6 @@ var ( Contact2 *models.Contact Contact2Phone = models.ContactPhone{ Phone: "3", - Valid: true, } Contact2Socials = models.ContactSocials{ Facebook: "facebook2", @@ -51,7 +48,6 @@ var ( Contact2Mail = models.ContactMail{ Mail: "4", Personal: true, - Valid: true, } Contact2Data = mongodb.CreateContactData{ Phones: append(make([]models.ContactPhone, 0), Contact2Phone), diff --git a/frontend/src/components/ContactCard.vue b/frontend/src/components/ContactCard.vue index f4cae330..b362d573 100644 --- a/frontend/src/components/ContactCard.vue +++ b/frontend/src/components/ContactCard.vue @@ -133,13 +133,6 @@ > Personal - - Invalid -
@@ -168,15 +161,6 @@ > {{ phone.phone }} -
- - Invalid - -
diff --git a/frontend/src/components/companies/ContactForm.vue b/frontend/src/components/companies/ContactForm.vue index 44311468..67d7a877 100644 --- a/frontend/src/components/companies/ContactForm.vue +++ b/frontend/src/components/companies/ContactForm.vue @@ -316,8 +316,8 @@ const formData = reactive>({ contact: { gender: undefined, language: undefined, - mails: [{ mail: "", personal: false, valid: true }], - phones: [{ phone: "", valid: true }], + mails: [{ mail: "", personal: false }], + phones: [{ phone: "" }], socials: { linkedin: "", twitter: "", @@ -339,11 +339,11 @@ watch( formData.contact.mails = newData.contact.mails.length > 0 ? [...newData.contact.mails] - : [{ mail: "", personal: false, valid: true }]; + : [{ mail: "", personal: false }]; formData.contact.phones = newData.contact.phones.length > 0 ? [...newData.contact.phones] - : [{ phone: "", valid: true }]; + : [{ phone: "" }]; formData.contact.socials = { ...newData.contact.socials }; } }, @@ -370,7 +370,7 @@ const validationMessage = computed(() => { }); const addEmail = () => { - formData.contact.mails.push({ mail: "", personal: false, valid: true }); + formData.contact.mails.push({ mail: "", personal: false }); }; const removeEmail = (index: number) => { @@ -378,7 +378,7 @@ const removeEmail = (index: number) => { }; const addPhone = () => { - formData.contact.phones.push({ phone: "", valid: true }); + formData.contact.phones.push({ phone: "" }); }; const removePhone = (index: number) => { diff --git a/frontend/src/components/companies/CreateCompanyForm.vue b/frontend/src/components/companies/CreateCompanyForm.vue index 64529b73..952d61d6 100644 --- a/frontend/src/components/companies/CreateCompanyForm.vue +++ b/frontend/src/components/companies/CreateCompanyForm.vue @@ -437,7 +437,6 @@ const addEmail = (repIndex: number) => { representatives.value[repIndex].contact!.mails.push({ mail: "", personal: false, - valid: true, }); }; @@ -448,7 +447,6 @@ const removeEmail = (repIndex: number, emailIndex: number) => { const addPhone = (repIndex: number) => { representatives.value[repIndex].contact!.phones.push({ phone: "", - valid: true, }); }; diff --git a/frontend/src/components/companies/RepresentativeForm.vue b/frontend/src/components/companies/RepresentativeForm.vue index 8adbf2f6..106e6740 100644 --- a/frontend/src/components/companies/RepresentativeForm.vue +++ b/frontend/src/components/companies/RepresentativeForm.vue @@ -240,8 +240,8 @@ const showMoreSocials = ref(false); const formData = reactive>({ name: "", contact: { - mails: [{ mail: "", personal: false, valid: true }], - phones: [{ phone: "", valid: true }], + mails: [{ mail: "", personal: false }], + phones: [{ phone: "" }], socials: { linkedin: "", twitter: "", @@ -266,7 +266,7 @@ const validationMessage = computed(() => { }); const addEmail = () => { - formData.contact.mails.push({ mail: "", personal: false, valid: true }); + formData.contact.mails.push({ mail: "", personal: false }); }; const removeEmail = (index: number) => { @@ -274,7 +274,7 @@ const removeEmail = (index: number) => { }; const addPhone = () => { - formData.contact.phones.push({ phone: "", valid: true }); + formData.contact.phones.push({ phone: "" }); }; const removePhone = (index: number) => { diff --git a/frontend/src/dto/contacts.ts b/frontend/src/dto/contacts.ts index 19172ac9..baf32306 100644 --- a/frontend/src/dto/contacts.ts +++ b/frontend/src/dto/contacts.ts @@ -2,7 +2,6 @@ import type { ObjectID } from "."; export interface ContactPhone { phone: string; - valid: boolean; } export interface ContactSocials { @@ -16,7 +15,6 @@ export interface ContactSocials { export interface ContactMail { mail: string; personal: boolean; - valid: boolean; } export enum Gender { diff --git a/frontend/src/views/Dashboard/SettingsView.vue b/frontend/src/views/Dashboard/SettingsView.vue index 6a0ae7be..fce849e5 100644 --- a/frontend/src/views/Dashboard/SettingsView.vue +++ b/frontend/src/views/Dashboard/SettingsView.vue @@ -130,13 +130,6 @@ > Personal - - Invalid - @@ -162,13 +155,6 @@ > {{ phone.phone }} - - Invalid - From 7afa05fe3f5eba07303fc3470536169900a0893d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?In=C3=AAs=20Costa?= <94355111+inesiscosta@users.noreply.github.com> Date: Tue, 6 Jan 2026 17:11:00 +0000 Subject: [PATCH 2/5] fix: remove contactCard's invalid property reference --- frontend/src/components/ContactCard.vue | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/frontend/src/components/ContactCard.vue b/frontend/src/components/ContactCard.vue index b362d573..606a7084 100644 --- a/frontend/src/components/ContactCard.vue +++ b/frontend/src/components/ContactCard.vue @@ -122,10 +122,7 @@ -
+
Date: Tue, 6 Jan 2026 17:23:50 +0000 Subject: [PATCH 3/5] feat: participation already open on current edition Refs: #509 --- .../src/components/ParticipationsCard.vue | 110 ++++++++---------- 1 file changed, 47 insertions(+), 63 deletions(-) diff --git a/frontend/src/components/ParticipationsCard.vue b/frontend/src/components/ParticipationsCard.vue index 8d143a6a..e9fefb20 100644 --- a/frontend/src/components/ParticipationsCard.vue +++ b/frontend/src/components/ParticipationsCard.vue @@ -1,63 +1,44 @@ From 31aeb7e86f3b6b1a58030a3f413862b0c137f8f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?In=C3=AAs=20Costa?= <94355111+inesiscosta@users.noreply.github.com> Date: Tue, 6 Jan 2026 18:13:14 +0000 Subject: [PATCH 4/5] feat: disable stepper when speaker/company is given up Refs: #498 --- .../src/components/cards/WorkflowCard.vue | 100 +++++++++--------- 1 file changed, 52 insertions(+), 48 deletions(-) diff --git a/frontend/src/components/cards/WorkflowCard.vue b/frontend/src/components/cards/WorkflowCard.vue index 0a82461f..628e1ed4 100644 --- a/frontend/src/components/cards/WorkflowCard.vue +++ b/frontend/src/components/cards/WorkflowCard.vue @@ -78,65 +78,69 @@ watch( From af5163e7812107df58f9a454cf3918fb7b62a06d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?In=C3=AAs=20Costa?= <94355111+inesiscosta@users.noreply.github.com> Date: Tue, 6 Jan 2026 20:10:05 +0000 Subject: [PATCH 5/5] feat: make company notes private and add company linkedin Refs: #530 --- backend/src/models/company.go | 4 +- backend/src/mongodb/company.go | 47 +++++++++++++++---- backend/src/mongodb/companyReps.go | 33 +++++++------ backend/src/mongodb/contact.go | 45 ++++++++++++++++-- backend/src/mongodb/speaker.go | 2 +- backend/swagger/companies-id.json | 3 ++ backend/swagger/companies.json | 3 ++ backend/swagger/models/company.json | 3 ++ frontend/src/components/cards/CompanyCard.vue | 25 +++++++++- .../components/companies/CompanyInfoForm.vue | 28 +++++++++-- .../companies/CreateCompanyForm.vue | 15 ++++++ frontend/src/dto/companies.ts | 4 +- frontend/src/mutations/companies.ts | 5 +- 13 files changed, 177 insertions(+), 40 deletions(-) diff --git a/backend/src/models/company.go b/backend/src/models/company.go index bbda4947..f05d393c 100644 --- a/backend/src/models/company.go +++ b/backend/src/models/company.go @@ -104,6 +104,8 @@ type Company struct { Site string `json:"site" bson:"site"` + LinkedIn string `json:"linkedin,omitempty" bson:"linkedin,omitempty"` + // Company's contacts is an array of CompanyRep _id (see models.CompanyRep). Employers []primitive.ObjectID `json:"employers,omitempty" bson:"employers,omitempty"` @@ -140,8 +142,6 @@ type CompanyPublic struct { Name string `json:"name"` - Description string `json:"description"` - // Company's image (public). Image string `json:"img,omitempty"` diff --git a/backend/src/mongodb/company.go b/backend/src/mongodb/company.go index 8a792eb0..0d936479 100644 --- a/backend/src/mongodb/company.go +++ b/backend/src/mongodb/company.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "log" + "strings" "time" "go.mongodb.org/mongo-driver/bson/primitive" @@ -31,11 +32,26 @@ func ResetCurrentPublicCompanies() { currentPublicCompanies = nil } +func formatLinkedInURL(url *string) *string { + if url == nil || *url == "" { + return url + } + + trimmed := strings.TrimSpace(*url) + if strings.HasPrefix(strings.ToLower(trimmed), "http://") || strings.HasPrefix(strings.ToLower(trimmed), "https://") { + return &trimmed + } + + formatted := "https://linkedin.com/company/" + strings.TrimPrefix(trimmed, "@") + return &formatted +} + // CreateCompanyData holds data needed to create a company type CreateCompanyData struct { Name *string `json:"name"` Description *string `json:"description"` Site *string `json:"site"` + LinkedIn *string `json:"linkedin"` } // ParseBody fills the CreateCompanyData from a body @@ -80,10 +96,17 @@ func (c *CompaniesType) CreateCompany(data CreateCompanyData) (*models.Company, return nil, err } + var linkedinValue interface{} = nil + formattedLinkedin := formatLinkedInURL(data.LinkedIn) + if formattedLinkedin != nil { + linkedinValue = *formattedLinkedin + } + insertResult, err := c.Collection.InsertOne(ctx, bson.M{ "name": data.Name, "description": data.Description, "site": data.Site, + "linkedin": linkedinValue, "employers": []primitive.ObjectID{}, "participations": []models.CompanyParticipation{}, "contact": createdContact.InsertedID.(primitive.ObjectID), @@ -325,11 +348,10 @@ func (c *CompaniesType) GetCompaniesByMembers(memberIDs []primitive.ObjectID, ev func companyToPublic(company models.Company, eventID *int) (*models.CompanyPublic, error) { public := models.CompanyPublic{ - ID: company.ID, - Name: company.Name, - Image: company.Images.Public, - Site: company.Site, - Description: company.Description, + ID: company.ID, + Name: company.Name, + Image: company.Images.Public, + Site: company.Site, } var participation *models.CompanyParticipation @@ -860,10 +882,11 @@ func (c *CompaniesType) UpdateCompanyParticipationStatus(companyID primitive.Obj // UpdateCompanyData is the data used to update a company, using the method UpdateCompany. type UpdateCompanyData struct { - Name *string - Description *string - Site *string - BillingInfo *models.CompanyBillingInfo + Name *string `json:"name"` + Description *string `json:"description"` + Site *string `json:"site"` + LinkedIn *string `json:"linkedin"` + BillingInfo *models.CompanyBillingInfo `json:"billingInfo"` } // ParseBody fills the UpdateCompanyData from a body @@ -893,6 +916,12 @@ func (c *CompaniesType) UpdateCompany(companyID primitive.ObjectID, data UpdateC if data.Site != nil { updateFields["site"] = *data.Site } + if data.LinkedIn != nil { + formattedLinkedin := formatLinkedInURL(data.LinkedIn) + if formattedLinkedin != nil { + updateFields["linkedin"] = *formattedLinkedin + } + } if data.BillingInfo != nil { billingInfo := *data.BillingInfo updateFields["billingInfo.name"] = billingInfo.Name diff --git a/backend/src/mongodb/companyReps.go b/backend/src/mongodb/companyReps.go index 2f1738b3..9efff979 100644 --- a/backend/src/mongodb/companyReps.go +++ b/backend/src/mongodb/companyReps.go @@ -105,23 +105,18 @@ func (c *CompanyRepsType) CreateCompanyRep(data CreateCompanyRepData) (*models.C dataQuery["contact"] = contact.ID } else { - contact, err := Contacts.Collection.InsertOne(ctx, bson.M{ - "phones": []models.ContactPhone{}, - "socials": bson.M{ - "facebook": "", - "skype": "", - "github": "", - "twitter": "", - "linkedin": "", - }, - "mails": []models.ContactMail{}, - }) - + // Create empty contact using CreateContact to ensure proper initialization + emptyContact := CreateContactData{ + Phones: []models.ContactPhone{}, + Socials: models.ContactSocials{}, + Mails: []models.ContactMail{}, + } + contact, err := Contacts.CreateContact(emptyContact) if err != nil { return nil, err } - dataQuery["contact"] = contact.InsertedID.(primitive.ObjectID) + dataQuery["contact"] = contact.ID } insertResult, err := c.Collection.InsertOne(ctx, dataQuery) @@ -140,7 +135,7 @@ func (c *CompanyRepsType) CreateCompanyRep(data CreateCompanyRepData) (*models.C return newRep, nil } -// UpdateCompanyRep creates a contact and adds it to a companyRep +// UpdateCompanyRep updates a company rep and its contact func (c *CompanyRepsType) UpdateCompanyRep(id primitive.ObjectID, data CreateCompanyRepData) (*models.CompanyRep, error) { ctx := context.Background() @@ -149,13 +144,17 @@ func (c *CompanyRepsType) UpdateCompanyRep(id primitive.ObjectID, data CreateCom } if data.Contact != nil { - contact, err := Contacts.CreateContact(*data.Contact) + // Get the existing rep to find its contact ID + existingRep, err := c.GetCompanyRep(id) if err != nil { return nil, err } - // add contact id to the $set document - setDoc["contact"] = contact.ID + // Update the existing contact instead of creating a new one + _, err = Contacts.UpdateContact(existingRep.Contact, *data.Contact) + if err != nil { + return nil, err + } } var updateQuery = bson.M{"$set": setDoc} diff --git a/backend/src/mongodb/contact.go b/backend/src/mongodb/contact.go index 79f579d5..3cd433a5 100644 --- a/backend/src/mongodb/contact.go +++ b/backend/src/mongodb/contact.go @@ -16,6 +16,41 @@ import ( "go.mongodb.org/mongo-driver/bson" ) +func formatSocialURL(platform, url string) string { + if url == "" { + return "" + } + + trimmed := strings.TrimSpace(url) + if strings.HasPrefix(strings.ToLower(trimmed), "http://") || strings.HasPrefix(strings.ToLower(trimmed), "https://") { + return trimmed + } + + switch strings.ToLower(platform) { + case "linkedin": + return "https://linkedin.com/in/" + strings.TrimPrefix(trimmed, "@") + case "twitter": + return "https://twitter.com/" + strings.TrimPrefix(trimmed, "@") + case "facebook": + return "https://facebook.com/" + strings.TrimPrefix(trimmed, "@") + case "github": + return "https://github.com/" + strings.TrimPrefix(trimmed, "@") + default: + return trimmed + } +} + +// formatContactSocials formats all social media links in a contact +func formatContactSocials(socials models.ContactSocials) models.ContactSocials { + return models.ContactSocials{ + LinkedIn: formatSocialURL("linkedin", socials.LinkedIn), + Twitter: formatSocialURL("twitter", socials.Twitter), + Facebook: formatSocialURL("facebook", socials.Facebook), + Github: formatSocialURL("github", socials.Github), + Skype: socials.Skype, + } +} + // ContactsType stores importat db information on contacts type ContactsType struct { Collection *mongo.Collection @@ -127,7 +162,7 @@ func (c *ContactsType) CreateContact(data CreateContactData) (*models.Contact, e ctx := context.Background() var insertData = bson.M{} insertData["phones"] = data.Phones - insertData["socials"] = data.Socials + insertData["socials"] = formatContactSocials(data.Socials) insertData["mails"] = data.Mails insertData["gender"] = data.Gender insertData["language"] = data.Language @@ -150,10 +185,10 @@ func (c *ContactsType) UpdateContact(contactID primitive.ObjectID, data CreateCo ctx := context.Background() var updateQuery = bson.M{ "$set": bson.M{ - "phones": data.Phones, - "socials": data.Socials, - "mails": data.Mails, - "gender": data.Gender, + "phones": data.Phones, + "socials": formatContactSocials(data.Socials), + "mails": data.Mails, + "gender": data.Gender, "language": data.Language, }, } diff --git a/backend/src/mongodb/speaker.go b/backend/src/mongodb/speaker.go index f7c6e77b..9465258c 100644 --- a/backend/src/mongodb/speaker.go +++ b/backend/src/mongodb/speaker.go @@ -141,7 +141,7 @@ func (s *SpeakersType) CreateSpeaker(data CreateSpeakerData) (*models.Speaker, e if data.Contact != nil { contact["phones"] = data.Contact.Phones contact["mails"] = data.Contact.Mails - contact["socials"] = data.Contact.Socials + contact["socials"] = formatContactSocials(data.Contact.Socials) } createdContact, err := Contacts.Collection.InsertOne(ctx, contact) diff --git a/backend/swagger/companies-id.json b/backend/swagger/companies-id.json index 001e2a89..48b435ac 100644 --- a/backend/swagger/companies-id.json +++ b/backend/swagger/companies-id.json @@ -87,6 +87,9 @@ "site": { "type": "string" }, + "linkedin": { + "type": "string" + }, "billingInfo": { "type": "object", "properties": { diff --git a/backend/swagger/companies.json b/backend/swagger/companies.json index dd1ad1fe..54bdd8eb 100644 --- a/backend/swagger/companies.json +++ b/backend/swagger/companies.json @@ -99,6 +99,9 @@ }, "site": { "type": "string" + }, + "linkedin": { + "type": "string" } } } diff --git a/backend/swagger/models/company.json b/backend/swagger/models/company.json index eccbad08..b54db7a7 100644 --- a/backend/swagger/models/company.json +++ b/backend/swagger/models/company.json @@ -24,6 +24,9 @@ "site": { "type": "string" }, + "linkedin": { + "type": "string" + }, "employers": { "type": "array", "items": { diff --git a/frontend/src/components/cards/CompanyCard.vue b/frontend/src/components/cards/CompanyCard.vue index 571a0715..93de3c2e 100644 --- a/frontend/src/components/cards/CompanyCard.vue +++ b/frontend/src/components/cards/CompanyCard.vue @@ -51,6 +51,7 @@ name: company.name, description: company.description, site: company.site, + linkedin: company.linkedin, }" :is-loading="isUpdating || isUploadingImage" mode="edit" @@ -112,6 +113,18 @@ {{ formatWebsite(company.site) }}
+ +
@@ -197,7 +210,7 @@ const handleImageSelected = (file: File) => { }; const handleSubmit = async ( - data: Pick, + data: Pick, ) => { if (!props.company?.id) return; @@ -247,6 +260,16 @@ const formatWebsite = (url: string): string => { } }; +const formatLinkedIn = (url: string): string => { + try { + const urlObj = new URL(url); + const path = urlObj.pathname.replace(/^\//, ""); + return `${urlObj.hostname}/${path}`; + } catch { + return url; + } +}; + const canDelete = computed(() => { if (!authStore.decoded) return false; const role = (authStore.decoded as { role?: string }).role; diff --git a/frontend/src/components/companies/CompanyInfoForm.vue b/frontend/src/components/companies/CompanyInfoForm.vue index 97487d6c..e56d2a04 100644 --- a/frontend/src/components/companies/CompanyInfoForm.vue +++ b/frontend/src/components/companies/CompanyInfoForm.vue @@ -43,6 +43,20 @@ /> + +
+ + +
+
+
+ + + {{ + errors.linkedin + }} +
+