diff --git a/apps/api/internal/httpapi/company_handlers.go b/apps/api/internal/httpapi/company_handlers.go index 2d9419f..2e49c39 100644 --- a/apps/api/internal/httpapi/company_handlers.go +++ b/apps/api/internal/httpapi/company_handlers.go @@ -217,34 +217,18 @@ func (s *Server) handleCreateInvite(w http.ResponseWriter, r *http.Request) { ClientOrLog(w, http.StatusBadRequest, "could not create invite", err, auth.ClientError) return } - companyName, _ := s.Auth.CompanyName(r.Context(), cid) - smtpOn := s.Mail != nil && s.Mail.Enabled() - sendOK := false - if s.Mail != nil { - msg := mail.InviteMessage(s.Config.WebOrigin, inv.Email, token, companyName) - if err := s.Mail.Send(msg); err == nil { - sendOK = true - } - } - // noop/disabled mailers return nil from Send; only count real SMTP as delivered. - mailSent, includeToken := inviteMailResult(smtpOn, sendOK) - resp := map[string]any{ - "id": inv.ID, "email": inv.Email, "role": inv.Role, - "expires_at": inv.ExpiresAt, "mail_sent": mailSent, "smtp_enabled": smtpOn, - } - // Token returned when email was not delivered so operators can share the accept link. - if includeToken { - resp["token"] = token - resp["accept_url"] = mail.AcceptInviteURL(s.Config.WebOrigin, token) - } - JSON(w, http.StatusCreated, resp) -} - -// inviteMailResult decides mail_sent and whether the accept token must be returned to the client. -func inviteMailResult(smtpEnabled, sendOK bool) (mailSent bool, includeToken bool) { - mailSent = smtpEnabled && sendOK - includeToken = !mailSent - return + // Invites are link-based: always return the one-time accept URL for the admin to share. + // Email delivery is not used for team invites (SMTP may still be used for password reset, etc.). + JSON(w, http.StatusCreated, map[string]any{ + "id": inv.ID, + "email": inv.Email, + "role": inv.Role, + "expires_at": inv.ExpiresAt, + "mail_sent": false, + "smtp_enabled": s.Mail != nil && s.Mail.Enabled(), + "token": token, + "accept_url": mail.AcceptInviteURL(s.Config.WebOrigin, token), + }) } func (s *Server) handleRemoveMember(w http.ResponseWriter, r *http.Request) { diff --git a/apps/api/internal/httpapi/company_invite_test.go b/apps/api/internal/httpapi/company_invite_test.go index c3e402b..761db9e 100644 --- a/apps/api/internal/httpapi/company_invite_test.go +++ b/apps/api/internal/httpapi/company_invite_test.go @@ -1,31 +1,20 @@ package httpapi -import "testing" +import ( + "strings" + "testing" -func TestInviteMailResult(t *testing.T) { + "github.com/descrybe/descrybe-v2/apps/api/internal/mail" +) + +func TestInviteAcceptURLAlwaysShareable(t *testing.T) { t.Parallel() - cases := []struct { - name string - smtpEnabled bool - sendOK bool - wantMailSent bool - wantToken bool - }{ - {name: "smtp_ok", smtpEnabled: true, sendOK: true, wantMailSent: true, wantToken: false}, - {name: "smtp_send_fail", smtpEnabled: true, sendOK: false, wantMailSent: false, wantToken: true}, - {name: "noop_mailer_send_ok", smtpEnabled: false, sendOK: true, wantMailSent: false, wantToken: true}, - {name: "disabled_no_send", smtpEnabled: false, sendOK: false, wantMailSent: false, wantToken: true}, + url := mail.AcceptInviteURL("https://app.example.com/", "tok_abc") + want := "https://app.example.com/accept-invite?token=tok_abc" + if url != want { + t.Fatalf("AcceptInviteURL = %q want %q", url, want) } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - mailSent, includeToken := inviteMailResult(tc.smtpEnabled, tc.sendOK) - if mailSent != tc.wantMailSent { - t.Fatalf("mailSent=%v want %v", mailSent, tc.wantMailSent) - } - if includeToken != tc.wantToken { - t.Fatalf("includeToken=%v want %v", includeToken, tc.wantToken) - } - }) + if strings.Contains(url, "mailto:") { + t.Fatal("invite URL must not be mailto") } } diff --git a/apps/web/src/lib/i18n/messages/de.ts b/apps/web/src/lib/i18n/messages/de.ts index 2a2c66f..55a9950 100644 --- a/apps/web/src/lib/i18n/messages/de.ts +++ b/apps/web/src/lib/i18n/messages/de.ts @@ -202,7 +202,7 @@ export const de: MessageDict = { "settings.inviteUser": "Benutzer einladen", "settings.teamAdminOnly": "Nur Unternehmens-Admins können Teammitglieder einladen, befördern, herabstufen oder entfernen.", "settings.shareAcceptLink": "Annahmelink teilen", - "settings.shareAcceptLinkHelp": "Ausgehende E-Mail ist nicht konfiguriert. Kopieren Sie diesen Einmal-Link und senden Sie ihn an den Eingeladenen. Er legt ein Passwort fest (mindestens 8 Zeichen) und tritt mit der von Ihnen gewählten Rolle bei.", + "settings.shareAcceptLinkHelp": "Copy this one-time link and send it to the invitee (chat, SMS, etc.). They'll set a password (at least 8 characters) and join with the role you chose.", "settings.acceptLinkLabel": "Einmaliger Einladungs-Annahmelink", "settings.copyLink": "Link kopieren", "settings.linkCopied": "Annahmelink kopiert.", @@ -223,9 +223,9 @@ export const de: MessageDict = { "settings.needOneAdmin": "Unternehmen benötigen mindestens einen Admin", "settings.expires": "Läuft ab am {date}", "settings.invalidEmail": "Geben Sie eine gültige E-Mail-Adresse ein.", - "settings.inviteCreatedNoMail": "Einladung für {email} als {role} erstellt. Kopieren Sie den Annahmelink unten und teilen Sie ihn — ausgehende E-Mail ist nicht konfiguriert.", - "settings.inviteSent": "Einladung an {email} als {role} gesendet. Die Person sollte die E-Mail öffnen und vor Ablauf annehmen.", - "settings.inviteFailed": "Einladung konnte nicht gesendet werden", + "settings.inviteCreatedNoMail": "Invite created for {email} as {role}. Copy the accept link below and share it with them.", + "settings.inviteSent": "Invite created for {email} as {role}. Share the accept link before it expires.", + "settings.inviteFailed": "Could not create invite", "settings.revokeConfirm": "Diese Einladung widerrufen?", "settings.revoked": "Einladung widerrufen.", "settings.revokeFailed": "Einladung konnte nicht widerrufen werden", @@ -238,12 +238,12 @@ export const de: MessageDict = { "settings.promote": "Befördern", "settings.demote": "Herabstufen", "settings.inviteTitle": "Teammitglied einladen", - "settings.inviteDescription": "Sie erhalten einen Link, um ein Passwort festzulegen (mindestens 8 Zeichen) und diesem Unternehmen beizutreten.", + "settings.inviteDescription": "Enter their email, then copy the one-time link and share it. They'll set a password (at least 8 characters) and join this company.", "settings.inviteEmail": "E-Mail", "settings.inviteEmailPlaceholder": "kollege@beispiel.com", "settings.inviteRole": "Rolle", "settings.inviteRoleHint": "Mitglieder verwalten Produkte und Feeds. Admins können auch Teammitglieder einladen und Unternehmenseinstellungen ändern.", - "settings.sendInvite": "Einladung senden", + "settings.sendInvite": "Create invite link", "settings.companyHeading": "Unternehmenseinstellungen", "settings.activeCompany": "Aktives Unternehmen", "settings.creditsOverview": "Credit-Übersicht", diff --git a/apps/web/src/lib/i18n/messages/en.ts b/apps/web/src/lib/i18n/messages/en.ts index 21ce8de..8a72d9c 100644 --- a/apps/web/src/lib/i18n/messages/en.ts +++ b/apps/web/src/lib/i18n/messages/en.ts @@ -222,7 +222,7 @@ export const en: MessageDict = { "settings.inviteUser": "Invite user", "settings.teamAdminOnly": "Only company admins can invite, promote, demote, or remove teammates.", "settings.shareAcceptLink": "Share accept link", - "settings.shareAcceptLinkHelp": "Outbound email is not configured. Copy this one-time link and send it to the invitee. They'll set a password (at least 8 characters) and join with the role you chose.", + "settings.shareAcceptLinkHelp": "Copy this one-time link and send it to the invitee (chat, SMS, etc.). They'll set a password (at least 8 characters) and join with the role you chose.", "settings.acceptLinkLabel": "One-time accept invite link", "settings.copyLink": "Copy link", "settings.linkCopied": "Accept link copied.", @@ -243,9 +243,9 @@ export const en: MessageDict = { "settings.needOneAdmin": "Companies need at least one admin", "settings.expires": "Expires {date}", "settings.invalidEmail": "Enter a valid email address.", - "settings.inviteCreatedNoMail": "Invite created for {email} as {role}. Copy the accept link below and share it — outbound email is not configured.", - "settings.inviteSent": "Invite sent to {email} as {role}. They should open the email and accept before it expires.", - "settings.inviteFailed": "Could not send invite", + "settings.inviteCreatedNoMail": "Invite created for {email} as {role}. Copy the accept link below and share it with them.", + "settings.inviteSent": "Invite created for {email} as {role}. Share the accept link before it expires.", + "settings.inviteFailed": "Could not create invite", "settings.revokeConfirm": "Revoke this invitation?", "settings.revoked": "Invitation revoked.", "settings.revokeFailed": "Could not revoke invite", @@ -258,12 +258,12 @@ export const en: MessageDict = { "settings.promote": "Promote", "settings.demote": "Demote", "settings.inviteTitle": "Invite teammate", - "settings.inviteDescription": "They'll get a link to set a password (at least 8 characters) and join this company.", + "settings.inviteDescription": "Enter their email, then copy the one-time link and share it. They'll set a password (at least 8 characters) and join this company.", "settings.inviteEmail": "Email", "settings.inviteEmailPlaceholder": "colleague@example.com", "settings.inviteRole": "Role", "settings.inviteRoleHint": "Members manage products and feeds. Admins can also invite teammates and change company settings.", - "settings.sendInvite": "Send invite", + "settings.sendInvite": "Create invite link", "settings.companyHeading": "Company Settings", "settings.activeCompany": "Active company", "settings.creditsOverview": "Credits overview", @@ -2095,7 +2095,7 @@ export const en: MessageDict = { "settings.companyNameHelp": "This name appears in invitations and organization settings", "settings.contentSettingsHelp": "Configure language and product matching for generated content", "settings.mergeProductsHelp": "When enabled, CSV imports combine rows that share a GTIN into one product. Turn off to keep each row independent.", - "settings.emailIntegrationHelp": "Set up Resend or SMTP for invites, campaigns, and transactional mail.", + "settings.emailIntegrationHelp": "Set up Resend or SMTP for campaigns and transactional mail.", "settings.configureEmail": "Configure email", "settings.aiIntegrationsHelp": "Platform credits or your own key, plus editable prompts for titles, descriptions, SEO, and campaigns. Encrypted keys at rest.", "settings.openAiIntegrations": "Open AI integrations", diff --git a/apps/web/src/lib/i18n/messages/es.ts b/apps/web/src/lib/i18n/messages/es.ts index e23688b..46c9f21 100644 --- a/apps/web/src/lib/i18n/messages/es.ts +++ b/apps/web/src/lib/i18n/messages/es.ts @@ -202,7 +202,7 @@ export const es: MessageDict = { "settings.inviteUser": "Invitar usuario", "settings.teamAdminOnly": "Solo los administradores de la empresa pueden invitar, ascender, degradar o eliminar compañeros.", "settings.shareAcceptLink": "Compartir enlace de aceptación", - "settings.shareAcceptLinkHelp": "El correo saliente no está configurado. Copia este enlace de un solo uso y envíaselo al invitado. Establecerá una contraseña (al menos 8 caracteres) y se unirá con el rol que elegiste.", + "settings.shareAcceptLinkHelp": "Copy this one-time link and send it to the invitee (chat, SMS, etc.). They'll set a password (at least 8 characters) and join with the role you chose.", "settings.acceptLinkLabel": "Enlace de aceptación de invitación de un solo uso", "settings.copyLink": "Copiar enlace", "settings.linkCopied": "Enlace de aceptación copiado.", @@ -223,9 +223,9 @@ export const es: MessageDict = { "settings.needOneAdmin": "Las empresas necesitan al menos un administrador", "settings.expires": "Caduca el {date}", "settings.invalidEmail": "Introduce una dirección de correo válida.", - "settings.inviteCreatedNoMail": "Invitación creada para {email} como {role}. Copia el enlace de aceptación abajo y compártelo — el correo saliente no está configurado.", - "settings.inviteSent": "Invitación enviada a {email} como {role}. Debe abrir el correo y aceptar antes de que caduque.", - "settings.inviteFailed": "No se pudo enviar la invitación", + "settings.inviteCreatedNoMail": "Invite created for {email} as {role}. Copy the accept link below and share it with them.", + "settings.inviteSent": "Invite created for {email} as {role}. Share the accept link before it expires.", + "settings.inviteFailed": "Could not create invite", "settings.revokeConfirm": "¿Revocar esta invitación?", "settings.revoked": "Invitación revocada.", "settings.revokeFailed": "No se pudo revocar la invitación", @@ -238,12 +238,12 @@ export const es: MessageDict = { "settings.promote": "Ascender", "settings.demote": "Degradar", "settings.inviteTitle": "Invitar compañero", - "settings.inviteDescription": "Recibirá un enlace para establecer una contraseña (al menos 8 caracteres) y unirse a esta empresa.", + "settings.inviteDescription": "Enter their email, then copy the one-time link and share it. They'll set a password (at least 8 characters) and join this company.", "settings.inviteEmail": "Correo electrónico", "settings.inviteEmailPlaceholder": "colega@ejemplo.com", "settings.inviteRole": "Rol", "settings.inviteRoleHint": "Los miembros gestionan productos y feeds. Los administradores también pueden invitar compañeros y cambiar la configuración de la empresa.", - "settings.sendInvite": "Enviar invitación", + "settings.sendInvite": "Create invite link", "settings.companyHeading": "Configuración de la empresa", "settings.activeCompany": "Empresa activa", "settings.creditsOverview": "Resumen de créditos", diff --git a/apps/web/src/lib/i18n/messages/fr.ts b/apps/web/src/lib/i18n/messages/fr.ts index dc8ac4c..a48c704 100644 --- a/apps/web/src/lib/i18n/messages/fr.ts +++ b/apps/web/src/lib/i18n/messages/fr.ts @@ -202,7 +202,7 @@ export const fr: MessageDict = { "settings.inviteUser": "Inviter un utilisateur", "settings.teamAdminOnly": "Seuls les administrateurs de l'entreprise peuvent inviter, promouvoir, rétrograder ou retirer des coéquipiers.", "settings.shareAcceptLink": "Partager le lien d'acceptation", - "settings.shareAcceptLinkHelp": "L'e-mail sortant n'est pas configuré. Copiez ce lien à usage unique et envoyez-le à l'invité. Il définira un mot de passe (au moins 8 caractères) et rejoindra avec le rôle que vous avez choisi.", + "settings.shareAcceptLinkHelp": "Copy this one-time link and send it to the invitee (chat, SMS, etc.). They'll set a password (at least 8 characters) and join with the role you chose.", "settings.acceptLinkLabel": "Lien d'acceptation d'invitation à usage unique", "settings.copyLink": "Copier le lien", "settings.linkCopied": "Lien d'acceptation copié.", @@ -223,9 +223,9 @@ export const fr: MessageDict = { "settings.needOneAdmin": "Les entreprises ont besoin d'au moins un administrateur", "settings.expires": "Expire le {date}", "settings.invalidEmail": "Saisissez une adresse e-mail valide.", - "settings.inviteCreatedNoMail": "Invitation créée pour {email} en tant que {role}. Copiez le lien d'acceptation ci-dessous et partagez-le — l'e-mail sortant n'est pas configuré.", - "settings.inviteSent": "Invitation envoyée à {email} en tant que {role}. La personne doit ouvrir l'e-mail et accepter avant expiration.", - "settings.inviteFailed": "Impossible d'envoyer l'invitation", + "settings.inviteCreatedNoMail": "Invite created for {email} as {role}. Copy the accept link below and share it with them.", + "settings.inviteSent": "Invite created for {email} as {role}. Share the accept link before it expires.", + "settings.inviteFailed": "Could not create invite", "settings.revokeConfirm": "Révoquer cette invitation ?", "settings.revoked": "Invitation révoquée.", "settings.revokeFailed": "Impossible de révoquer l'invitation", @@ -238,12 +238,12 @@ export const fr: MessageDict = { "settings.promote": "Promouvoir", "settings.demote": "Rétrograder", "settings.inviteTitle": "Inviter un coéquipier", - "settings.inviteDescription": "Ils recevront un lien pour définir un mot de passe (au moins 8 caractères) et rejoindre cette entreprise.", + "settings.inviteDescription": "Enter their email, then copy the one-time link and share it. They'll set a password (at least 8 characters) and join this company.", "settings.inviteEmail": "E-mail", "settings.inviteEmailPlaceholder": "collegue@exemple.com", "settings.inviteRole": "Rôle", "settings.inviteRoleHint": "Les membres gèrent les produits et les flux. Les administrateurs peuvent aussi inviter des coéquipiers et modifier les paramètres de l'entreprise.", - "settings.sendInvite": "Envoyer l'invitation", + "settings.sendInvite": "Create invite link", "settings.companyHeading": "Paramètres de l'entreprise", "settings.activeCompany": "Entreprise active", "settings.creditsOverview": "Aperçu des crédits", diff --git a/apps/web/src/lib/i18n/messages/it.ts b/apps/web/src/lib/i18n/messages/it.ts index 3c1d8d8..f3b2810 100644 --- a/apps/web/src/lib/i18n/messages/it.ts +++ b/apps/web/src/lib/i18n/messages/it.ts @@ -202,7 +202,7 @@ export const it: MessageDict = { "settings.inviteUser": "Invita utente", "settings.teamAdminOnly": "Solo gli amministratori dell'azienda possono invitare, promuovere, degradare o rimuovere compagni di team.", "settings.shareAcceptLink": "Condividi link di accettazione", - "settings.shareAcceptLinkHelp": "L'email in uscita non è configurata. Copia questo link monouso e invialo all'invitato. Imposterà una password (almeno 8 caratteri) e si unirà con il ruolo che hai scelto.", + "settings.shareAcceptLinkHelp": "Copy this one-time link and send it to the invitee (chat, SMS, etc.). They'll set a password (at least 8 characters) and join with the role you chose.", "settings.acceptLinkLabel": "Link monouso di accettazione invito", "settings.copyLink": "Copia link", "settings.linkCopied": "Link di accettazione copiato.", @@ -223,9 +223,9 @@ export const it: MessageDict = { "settings.needOneAdmin": "Le aziende necessitano di almeno un amministratore", "settings.expires": "Scade il {date}", "settings.invalidEmail": "Inserisci un indirizzo email valido.", - "settings.inviteCreatedNoMail": "Invito creato per {email} come {role}. Copia il link di accettazione qui sotto e condividilo — l'email in uscita non è configurata.", - "settings.inviteSent": "Invito inviato a {email} come {role}. Deve aprire l'email e accettare prima della scadenza.", - "settings.inviteFailed": "Impossibile inviare l'invito", + "settings.inviteCreatedNoMail": "Invite created for {email} as {role}. Copy the accept link below and share it with them.", + "settings.inviteSent": "Invite created for {email} as {role}. Share the accept link before it expires.", + "settings.inviteFailed": "Could not create invite", "settings.revokeConfirm": "Revocare questo invito?", "settings.revoked": "Invito revocato.", "settings.revokeFailed": "Impossibile revocare l'invito", @@ -238,12 +238,12 @@ export const it: MessageDict = { "settings.promote": "Promuovi", "settings.demote": "Degrada", "settings.inviteTitle": "Invita un collega", - "settings.inviteDescription": "Riceveranno un link per impostare una password (almeno 8 caratteri) e unirsi a questa azienda.", + "settings.inviteDescription": "Enter their email, then copy the one-time link and share it. They'll set a password (at least 8 characters) and join this company.", "settings.inviteEmail": "Posta", "settings.inviteEmailPlaceholder": "collega@esempio.com", "settings.inviteRole": "Ruolo", "settings.inviteRoleHint": "I membri gestiscono prodotti e feed. Gli amministratori possono anche invitare colleghi e modificare le impostazioni dell'azienda.", - "settings.sendInvite": "Invia invito", + "settings.sendInvite": "Create invite link", "settings.companyHeading": "Impostazioni azienda", "settings.activeCompany": "Azienda attiva", "settings.creditsOverview": "Panoramica crediti", diff --git a/apps/web/src/lib/i18n/messages/ja.ts b/apps/web/src/lib/i18n/messages/ja.ts index db59e4c..497fca9 100644 --- a/apps/web/src/lib/i18n/messages/ja.ts +++ b/apps/web/src/lib/i18n/messages/ja.ts @@ -202,7 +202,7 @@ export const ja: MessageDict = { "settings.inviteUser": "ユーザーを招待", "settings.teamAdminOnly": "同僚の招待、昇格、降格、削除ができるのは会社の管理者のみです。", "settings.shareAcceptLink": "承認リンクを共有", - "settings.shareAcceptLinkHelp": "送信メールが設定されていません。この一回限りのリンクをコピーして招待者に送ってください。パスワード(8文字以上)を設定し、選択したロールで参加します。", + "settings.shareAcceptLinkHelp": "Copy this one-time link and send it to the invitee (chat, SMS, etc.). They'll set a password (at least 8 characters) and join with the role you chose.", "settings.acceptLinkLabel": "一回限りの招待承認リンク", "settings.copyLink": "リンクをコピー", "settings.linkCopied": "承認リンクをコピーしました。", @@ -223,9 +223,9 @@ export const ja: MessageDict = { "settings.needOneAdmin": "会社には少なくとも1人の管理者が必要です", "settings.expires": "{date} に期限切れ", "settings.invalidEmail": "有効なメールアドレスを入力してください。", - "settings.inviteCreatedNoMail": "{email} ã‚’ {role} として招待を作成しました。下の承認リンクをコピーして共有してください — 送信メールは設定されていません。", - "settings.inviteSent": "{email} に {role} として招待を送信しました。期限前にメールを開いて承認してください。", - "settings.inviteFailed": "招待を送信できませんでした", + "settings.inviteCreatedNoMail": "Invite created for {email} as {role}. Copy the accept link below and share it with them.", + "settings.inviteSent": "Invite created for {email} as {role}. Share the accept link before it expires.", + "settings.inviteFailed": "Could not create invite", "settings.revokeConfirm": "この招待を取り消しますか?", "settings.revoked": "招待を取り消しました。", "settings.revokeFailed": "招待を取り消せませんでした", @@ -238,12 +238,12 @@ export const ja: MessageDict = { "settings.promote": "昇格", "settings.demote": "降格", "settings.inviteTitle": "チームメイトを招待", - "settings.inviteDescription": "パスワード(8文字以上)を設定してこの会社に参加するためのリンクが届きます。", + "settings.inviteDescription": "Enter their email, then copy the one-time link and share it. They'll set a password (at least 8 characters) and join this company.", "settings.inviteEmail": "メール", "settings.inviteEmailPlaceholder": "taro@example.com", "settings.inviteRole": "ロール", "settings.inviteRoleHint": "メンバーは商品とフィードを管理します。管理者は同僚の招待と会社設定の変更もできます。", - "settings.sendInvite": "招待を送信", + "settings.sendInvite": "Create invite link", "settings.companyHeading": "会社の設定", "settings.activeCompany": "アクティブな会社", "settings.creditsOverview": "クレジット概要", diff --git a/apps/web/src/lib/i18n/messages/nl.ts b/apps/web/src/lib/i18n/messages/nl.ts index b433ac1..73b4cde 100644 --- a/apps/web/src/lib/i18n/messages/nl.ts +++ b/apps/web/src/lib/i18n/messages/nl.ts @@ -202,7 +202,7 @@ export const nl: MessageDict = { "settings.inviteUser": "Gebruiker uitnodigen", "settings.teamAdminOnly": "Alleen bedrijfsbeheerders kunnen teamleden uitnodigen, promoveren, degraderen of verwijderen.", "settings.shareAcceptLink": "Acceptatielink delen", - "settings.shareAcceptLinkHelp": "Uitgaande e-mail is niet geconfigureerd. Kopieer deze eenmalige link en stuur hem naar de genodigde. Die stelt een wachtwoord in (minimaal 8 tekens) en treedt toe met de rol die u koos.", + "settings.shareAcceptLinkHelp": "Copy this one-time link and send it to the invitee (chat, SMS, etc.). They'll set a password (at least 8 characters) and join with the role you chose.", "settings.acceptLinkLabel": "Eenmalige acceptatie-uitnodigingslink", "settings.copyLink": "Link kopiëren", "settings.linkCopied": "Acceptatielink gekopieerd.", @@ -223,9 +223,9 @@ export const nl: MessageDict = { "settings.needOneAdmin": "Bedrijven hebben minstens één beheerder nodig", "settings.expires": "Verloopt op {date}", "settings.invalidEmail": "Voer een geldig e-mailadres in.", - "settings.inviteCreatedNoMail": "Uitnodiging aangemaakt voor {email} als {role}. Kopieer de acceptatielink hieronder en deel hem — uitgaande e-mail is niet geconfigureerd.", - "settings.inviteSent": "Uitnodiging verzonden naar {email} als {role}. Die moet de e-mail openen en accepteren vóór de vervaldatum.", - "settings.inviteFailed": "Uitnodiging kon niet worden verzonden", + "settings.inviteCreatedNoMail": "Invite created for {email} as {role}. Copy the accept link below and share it with them.", + "settings.inviteSent": "Invite created for {email} as {role}. Share the accept link before it expires.", + "settings.inviteFailed": "Could not create invite", "settings.revokeConfirm": "Deze uitnodiging intrekken?", "settings.revoked": "Uitnodiging ingetrokken.", "settings.revokeFailed": "Uitnodiging kon niet worden ingetrokken", @@ -238,12 +238,12 @@ export const nl: MessageDict = { "settings.promote": "Promoveren", "settings.demote": "Degraderen", "settings.inviteTitle": "Teamlid uitnodigen", - "settings.inviteDescription": "Ze krijgen een link om een wachtwoord in te stellen (minimaal 8 tekens) en toe te treden tot dit bedrijf.", + "settings.inviteDescription": "Enter their email, then copy the one-time link and share it. They'll set a password (at least 8 characters) and join this company.", "settings.inviteEmail": "E-mail", "settings.inviteEmailPlaceholder": "collega@voorbeeld.com", "settings.inviteRole": "Rol", "settings.inviteRoleHint": "Leden beheren producten en feeds. Beheerders kunnen ook teamleden uitnodigen en bedrijfsinstellingen wijzigen.", - "settings.sendInvite": "Uitnodiging verzenden", + "settings.sendInvite": "Create invite link", "settings.companyHeading": "Bedrijfsinstellingen", "settings.activeCompany": "Actief bedrijf", "settings.creditsOverview": "Credits-overzicht", diff --git a/apps/web/src/lib/i18n/messages/pl.ts b/apps/web/src/lib/i18n/messages/pl.ts index 5bc3058..365c09e 100644 --- a/apps/web/src/lib/i18n/messages/pl.ts +++ b/apps/web/src/lib/i18n/messages/pl.ts @@ -202,7 +202,7 @@ export const pl: MessageDict = { "settings.inviteUser": "ZaproÅ› użytkownika", "settings.teamAdminOnly": "Tylko administratorzy firmy mogÄ… zapraszać, awansować, degradować lub usuwać czÅ‚onków zespoÅ‚u.", "settings.shareAcceptLink": "UdostÄ™pnij link akceptacji", - "settings.shareAcceptLinkHelp": "WychodzÄ…cy e-mail nie jest skonfigurowany. Skopiuj ten jednorazowy link i wyÅ›lij go zaproszonemu. Ustawi hasÅ‚o (co najmniej 8 znaków) i dołączy z wybranÄ… przez Ciebie rolÄ….", + "settings.shareAcceptLinkHelp": "Copy this one-time link and send it to the invitee (chat, SMS, etc.). They'll set a password (at least 8 characters) and join with the role you chose.", "settings.acceptLinkLabel": "Jednorazowy link akceptacji zaproszenia", "settings.copyLink": "Kopiuj link", "settings.linkCopied": "Skopiowano link akceptacji.", @@ -223,9 +223,9 @@ export const pl: MessageDict = { "settings.needOneAdmin": "Firmy potrzebujÄ… co najmniej jednego administratora", "settings.expires": "Wygasa {date}", "settings.invalidEmail": "Wprowadź prawidÅ‚owy adres e-mail.", - "settings.inviteCreatedNoMail": "Utworzono zaproszenie dla {email} jako {role}. Skopiuj link akceptacji poniżej i udostÄ™pnij go — wychodzÄ…cy e-mail nie jest skonfigurowany.", - "settings.inviteSent": "WysÅ‚ano zaproszenie do {email} jako {role}. Osoba powinna otworzyć e-mail i zaakceptować przed wygaÅ›niÄ™ciem.", - "settings.inviteFailed": "Nie można wysÅ‚ać zaproszenia", + "settings.inviteCreatedNoMail": "Invite created for {email} as {role}. Copy the accept link below and share it with them.", + "settings.inviteSent": "Invite created for {email} as {role}. Share the accept link before it expires.", + "settings.inviteFailed": "Could not create invite", "settings.revokeConfirm": "Unieważnić to zaproszenie?", "settings.revoked": "Zaproszenie unieważnione.", "settings.revokeFailed": "Nie można unieważnić zaproszenia", @@ -238,12 +238,12 @@ export const pl: MessageDict = { "settings.promote": "Awansuj", "settings.demote": "Degraduj", "settings.inviteTitle": "ZaproÅ› czÅ‚onka zespoÅ‚u", - "settings.inviteDescription": "OtrzymajÄ… link do ustawienia hasÅ‚a (co najmniej 8 znaków) i dołączenia do tej firmy.", + "settings.inviteDescription": "Enter their email, then copy the one-time link and share it. They'll set a password (at least 8 characters) and join this company.", "settings.inviteEmail": "E-mail", "settings.inviteEmailPlaceholder": "kolega@przyklad.com", "settings.inviteRole": "Rola", "settings.inviteRoleHint": "CzÅ‚onkowie zarzÄ…dzajÄ… produktami i feedami. Administratorzy mogÄ… też zapraszać współpracowników i zmieniać ustawienia firmy.", - "settings.sendInvite": "WyÅ›lij zaproszenie", + "settings.sendInvite": "Create invite link", "settings.companyHeading": "Ustawienia firmy", "settings.activeCompany": "Aktywna firma", "settings.creditsOverview": "PrzeglÄ…d kredytów", diff --git a/apps/web/src/lib/i18n/messages/pt.ts b/apps/web/src/lib/i18n/messages/pt.ts index 997dd2d..bbd6eff 100644 --- a/apps/web/src/lib/i18n/messages/pt.ts +++ b/apps/web/src/lib/i18n/messages/pt.ts @@ -202,7 +202,7 @@ export const pt: MessageDict = { "settings.inviteUser": "Convidar utilizador", "settings.teamAdminOnly": "Apenas administradores da empresa podem convidar, promover, despromover ou remover colegas.", "settings.shareAcceptLink": "Partilhar link de aceitação", - "settings.shareAcceptLinkHelp": "O e-mail de saída não está configurado. Copie este link de utilização única e envie-o ao convidado. Definirá uma palavra-passe (pelo menos 8 caracteres) e aderirá com o papel que escolheu.", + "settings.shareAcceptLinkHelp": "Copy this one-time link and send it to the invitee (chat, SMS, etc.). They'll set a password (at least 8 characters) and join with the role you chose.", "settings.acceptLinkLabel": "Link de aceitação de convite de utilização única", "settings.copyLink": "Copiar link", "settings.linkCopied": "Link de aceitação copiado.", @@ -223,9 +223,9 @@ export const pt: MessageDict = { "settings.needOneAdmin": "As empresas precisam de pelo menos um administrador", "settings.expires": "Expira a {date}", "settings.invalidEmail": "Introduza um endereço de e-mail válido.", - "settings.inviteCreatedNoMail": "Convite criado para {email} como {role}. Copie o link de aceitação abaixo e partilhe-o — o e-mail de saída não está configurado.", - "settings.inviteSent": "Convite enviado para {email} como {role}. Deve abrir o e-mail e aceitar antes de expirar.", - "settings.inviteFailed": "Não foi possível enviar o convite", + "settings.inviteCreatedNoMail": "Invite created for {email} as {role}. Copy the accept link below and share it with them.", + "settings.inviteSent": "Invite created for {email} as {role}. Share the accept link before it expires.", + "settings.inviteFailed": "Could not create invite", "settings.revokeConfirm": "Revogar este convite?", "settings.revoked": "Convite revogado.", "settings.revokeFailed": "Não foi possível revogar o convite", @@ -238,12 +238,12 @@ export const pt: MessageDict = { "settings.promote": "Promover", "settings.demote": "Despromover", "settings.inviteTitle": "Convidar colega", - "settings.inviteDescription": "Receberão um link para definir uma palavra-passe (pelo menos 8 caracteres) e aderir a esta empresa.", + "settings.inviteDescription": "Enter their email, then copy the one-time link and share it. They'll set a password (at least 8 characters) and join this company.", "settings.inviteEmail": "E-mail", "settings.inviteEmailPlaceholder": "colega@exemplo.com", "settings.inviteRole": "Papel", "settings.inviteRoleHint": "Os membros gerem produtos e feeds. Os administradores também podem convidar colegas e alterar as definições da empresa.", - "settings.sendInvite": "Enviar convite", + "settings.sendInvite": "Create invite link", "settings.companyHeading": "Definições da empresa", "settings.activeCompany": "Empresa ativa", "settings.creditsOverview": "Resumo de créditos", diff --git a/apps/web/src/routes/settings/+page.svelte b/apps/web/src/routes/settings/+page.svelte index 5ca7513..5b36f36 100644 --- a/apps/web/src/routes/settings/+page.svelte +++ b/apps/web/src/routes/settings/+page.svelte @@ -589,19 +589,24 @@ invites: [] as PendingInvite[] })); pendingInvites = invites.invites ?? []; + const link = + created.accept_url?.trim() || + (created.token + ? `${window.location.origin}/accept-invite?token=${encodeURIComponent(created.token)}` + : ""); trackEvent("invite_sent", { role: roleForEvent, - mail_sent: Boolean(created.mail_sent) + mail_sent: false, + has_link: Boolean(link) }); - if (created.token) { - inviteAcceptLink = created.accept_url?.trim() || `${window.location.origin}/accept-invite?token=${encodeURIComponent(created.token)}`; - inviteOpen = false; - success = i18n.t("settings.inviteCreatedNoMail", { email: emailed, role: roleName }); - } else { - inviteOpen = false; - inviteAcceptLink = null; - success = i18n.t("settings.inviteSent", { email: emailed, role: roleName }); + inviteOpen = false; + if (!link) { + error = i18n.t("settings.inviteFailed"); + notifyError(error); + return; } + inviteAcceptLink = link; + success = i18n.t("settings.inviteCreatedNoMail", { email: emailed, role: roleName }); notifySuccess(success); } catch (err) { error = applyApiFormError(err, i18n.t("settings.inviteFailed"), "invite teammates");